This results in a lot of flexibility, because the new "Makefile" is scripted in a full-featured language - Python.
#+end_src
To hook up bash completion for =cook=, add to your file:~/.bashrc: #+begin_src sh . ~/.local/cook/bash-completion.sh
#+end_src
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 res
def last_commit(recipe): return ["git rev-parse HEAD"] #+end_src
Runing e.g.: #+begin_src sh cook files_in_dir #+end_src
will call: #+begin_src python from Cookbook import * bash(files_in_dir(42)) #+end_src
As example of a recipe without args, this will show you your current IP address: #+begin_src sh cook :net ip #+end_src
Here'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_src
Both commands have the same length, but:
All 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.
Example, =~/.cook.d/foo.py=:
#+begin_src python def bar(recipe): print("Hello, World!") #+end_src
You can run it like this: #+begin_src sh cook :foo bar #+end_src
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_src
Here, the inital ="*"= is used to select all books. It's possible to customize each book separately by using the file name as a key.
The main advantage is that Emacs will find the appropriate Cookbook.py from anywhere in the project.
The secondary advantages are:
M-x cook will:
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_src
Thanks 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).
You can call them like this: #+begin_src sh cook :dpkg file_to_package /usr/bin/python #+end_src
While 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_src
The 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_compile
def lint(recipe): return ["pylint pycook/"] #+end_src
** Generate recipes using function-writing functions #+begin_src python import shlex
def open_in_firefox(fname): def result(recipe): return ["firefox " + shlex.quote(fname)] return result
open_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): # ... return
del dont_need_it_this_month #+end_src
** Alternatives