Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/abo-abo/cook
Makefile -> Cookbook.py
https://github.com/abo-abo/cook
emacs makefile python
Last synced: about 2 months ago
JSON representation
Makefile -> Cookbook.py
- Host: GitHub
- URL: https://github.com/abo-abo/cook
- Owner: abo-abo
- Created: 2017-10-09T21:28:37.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-06-13T17:47:31.000Z (8 months ago)
- Last Synced: 2024-10-15T17:43:35.660Z (4 months ago)
- Topics: emacs, makefile, python
- Language: Python
- Size: 305 KB
- Stars: 37
- Watchers: 7
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README
Awesome Lists containing this project
README
* Introduction -*- mode: org -*-
The traditional =Makefile= is replaced by =Cookbook.py=. Use =cook= in place
of =make=.This results in a lot of flexibility, because the new "Makefile" is
scripted in a full-featured language - Python.- Recipe :: the equivalent of a Makefile's rule
- A Python function that takes a single argument called =recipe= and
returns a vector of strings - a sequence of commands to run.* Installing
Install for user (ensure file:~/.local/bin is in your =PATH=):
#+begin_src sh
pip3 install --upgrade pycook
# or system-wide: sudo pip3 install --upgrade pycook
#+end_srcTo hook up bash completion for =cook=, add to your file:~/.bashrc:
#+begin_src sh
. ~/.local/cook/bash-completion.sh
# or system-wide: . /usr/local/cook/bash-completion.sh
#+end_src* Running recipes
You can run the recipes with =cook =.Minimal =Cookbook.py= example:
#+begin_src python
#* Recipes
def files_in_dir(recipe):
return ["ls"]def files_in_parent_dir(recipe):
res = ["cd .."]
res += ["find ."]
return resdef last_commit(recipe):
return ["git rev-parse HEAD"]
#+end_srcRuning e.g.:
#+begin_src sh
cook files_in_dir
#+end_srcwill call:
#+begin_src python
from Cookbook import *
bash(files_in_dir(42))
#+end_src* Running global recipes
You can also run "global" recipes, which are installed together with
=pycook=.As example of a recipe without args, this will show you your current
IP address:
#+begin_src sh
cook :net ip
#+end_srcHere's a recipe with args, and the equivalent =sed= command:
#+begin_src sh
echo foo:bar:baz | cook :str split :
echo foo:bar:baz | sed -e 's/:/\n/g'
#+end_srcBoth commands have the same length, but:
- =sed= is more generic, harder to remember, harder to get right off the
bat
- =cook= is less generic, but easy to remember and discoverableAll global recipes start with =cook :=. Pressing ~TAB~ after that should
show all available recipe modules, like e.g. =str= or =net= or =pip= etc.After selecting a module, pressing ~TAB~ should show all available
recipes within the module.Finally, you enter the arguments to the recipe if it has any.
* Running user recipes
Users can add to the global recipes list by placing Python files in
=~/.cook.d/=.Example, =~/.cook.d/foo.py=:
#+begin_src python
def bar(recipe):
print("Hello, World!")
#+end_srcYou can run it like this:
#+begin_src sh
cook :foo bar
#+end_src* Automatic logging
=cook= can automatically log your =stdout= to a file with a
timestamped name in a location you specify.To make use of this, create a file =~/.cook.d/__config__.py= with the following contents:
#+begin_src python
config = {
"*": {
"tee": {
"location": "/tmp/cook"
}
}
}
#+end_srcHere, the inital ="*"= is used to select all books. It's possible to
customize each book separately by using the file name as a key.* Elisp completion
It's actually much more convenient to use =cook= from Emacs.The main advantage is that Emacs will find the appropriate Cookbook.py from anywhere in
the project.The secondary advantages are:
- better completion for recipe selection
- the selected recipe is run in =compilation-mode=, which connects any
errors or warings to locations in a project.
- the selected recipe is run in a buffer named after the recipe
- works with TRAMP, so the recipes from remote cookbooks will be run
remotely.~M-x cook~ will:
- go recursively up from the current directory until a cookbook is
found
- parse the cookbook for recipes
- offer the list of recipes
- run the seleted recipe in =compilation-mode=I'm using this binding:
#+begin_src elisp
(global-set-key [f6] 'cook)
#+end_src** Elisp completion in =shell=
Thanks to [[https://github.com/szermatt/emacs-bash-completion][bash-completion.el]], the completion in =M-x shell= works nicely as well.I especially like completion for:
#+begin_src sh
cook :apt install python-
#+end_srcThanks to =ivy-mode=, I can easily select from 3805 packages in Ubuntu that with "python-".
One extra plus of =cook :apt install= over =apt-get install= is that it will not ask for =sudo=
if it's not required (i.e. when the package is already installed).* Custom recipe completion
For recipes can have extra arguments:
#+begin_src python
def file_to_package(recipe, fname):
return ["dpkg -S " + fname]
#+end_srcYou can call them like this:
#+begin_src sh
cook :dpkg file_to_package /usr/bin/python
#+end_srcWhile getting completion for the =:dpkg= and =file_to_package= parts is automatic, for the
third argument it's not, since it could be anything. However, in this case, since the
argument is named =fname=, an automatic file name completion is provided.Here's how to implement a manual file name completion:
#+begin_src python
def file_to_package(recipe, fname):
if type(recipe) is int:
return ["dpkg -S " + fname]
elif recipe[0] == "complete":
return el.sc("compgen -o filenames -A file " + recipe[1])
#+end_srcThe use of =compgen= isn't mandatory: all it does is return a string of the possible
completions separated by newlines.* Completion for variants in recipe args
#+begin_src python
def build(recipe, mode=["debug","release"]):
...
#+end_src
Invoking this recipe with el:cook, we get completion for =mode= using el:ivy-read.
TODO: implement bash completion for this.* Custom recipe config
Some commands require a PTY in order to be run correctly. Here's how the recipe can enable it:
#+begin_src python
def psql(recipe, config={"pty": True}):
return "psql $DATABASE_URL"
#+end_src
For these commands, a history file is setup in ~/.cook.d/history.
This allows, for example, to automatically have a separate history when connecting to
different databases.* Useful Python tricks for Cookbook.py
** Don't write recipes if you can import them
For example, here's this repo's Cookbook.py:
#+begin_src python
from pycook.recipes.pip import clean, sdist, reinstall, publish
from pycook.recipes.emacs import byte_compile as emacs_byte_compiledef lint(recipe):
return ["pylint pycook/"]
#+end_src** Generate recipes using function-writing functions
#+begin_src python
import shlexdef open_in_firefox(fname):
def result(recipe):
return ["firefox " + shlex.quote(fname)]
return resultopen_README = open_in_firefox("README")
open_Cookbook = open_in_firefox("Cookbook.py")
#+end_src** Remove a recipe temporarily
Obviously, you can comment it out. But a faster approach is to delete its variable.
#+begin_src python
def dont_need_it_this_month(recipe):
# ...
returndel dont_need_it_this_month
#+end_src** Alternatives
- [[https://www.fabfile.org/][Fabric]]/[[https://www.pyinvoke.org/][Invoke]]