199 lines
8 KiB
Org Mode
199 lines
8 KiB
Org Mode
#+Title: Flesh as a Shell
|
|
#+Author: Ava Affine
|
|
|
|
Note: this document is best read using a dedicated ORG mode editor
|
|
|
|
* Description
|
|
This document outlines the ways that Flesh can be used as a shell for daily administration or scripting purposes. Readers should have already read through the [[file:Readme.org][general Flesh documentation]]. With the exception of the ~circuit~ function, all facilities introduced in this document apply only to flesh interpreters compiled with the [[file:/src/stl/posix.rs][POSIX module]] enabled and ~CFG_FLESH_POSIX~ set at load time.
|
|
|
|
** Launch a command
|
|
For the most common uses (executing shell commands) a function is provided to find and load binaries from entries in the ~PATH~ variable. This function may be called either with ~load~ or ~l~.
|
|
#+BEGIN_SRC lisp
|
|
(load htop) ;; executes the htop binary, if htop is found on the users PATH
|
|
(l emacs) ;; executes the emacs binary, if emacs is found on the users PATH
|
|
#+END_SRC
|
|
|
|
The load command takes an infinite number of arguments and uses the following rules to construct a shell command from them:
|
|
+ symbols that are not set are taken as strings
|
|
#+BEGIN_SRC lisp
|
|
(l emacs -nw) ;; 'emacs' and '-nw' not defined
|
|
#+END_SRC
|
|
+ symbols that are set are replaced by their values
|
|
#+BEGIN_SRC lisp
|
|
(l ls -la HOME)
|
|
(let ((ping-count 4)
|
|
(domain "sunnypup.io"))
|
|
(l ping -c ping-count domain)
|
|
#+END_SRC
|
|
+ nested forms are evaluated
|
|
#+BEGIN_SRC lisp
|
|
(l cat (concat HOME "/notes.txt"))
|
|
#+END_SRC
|
|
|
|
Shell command evaluation rules apply to the following functions:
|
|
+ ~load~ / ~l~
|
|
+ ~pipe~
|
|
+ ~load-to-string~
|
|
+ ~load-with~
|
|
+ ~bg~
|
|
|
|
With the exception of the ~load-to-string~ function and the ~bg~ function, each of the aforementioned functions returns the exit code of a new process as an integer.
|
|
|
|
Symbols set in the Flesh REPL are converted to strings and placed in the environment as well, so ~def~ can be used to define environment variables for a process (but not ~let~, which only creates form-local symbols).
|
|
|
|
** Special command forms
|
|
A number of forms are provided to offer a first class experience when running Flesh as a shell.
|
|
|
|
*** Command short circuiting
|
|
In a shell such as Bash or Zsh, commands can be chained with the ~&&~ operator:
|
|
#+BEGIN_EXAMPLE shell
|
|
$ apt update && apt upgrade && echo "Success!"
|
|
#+END_EXAMPLE
|
|
|
|
In these chains, if one command fails the next one(s) are not run. Colloquially, the command short-circuits. A similar construct is offered in Flesh called ~circuit~. Circuit will evaluate one or more forms (all expected to evaluate to either an integer (shell command) or a boolean (more general form). If a form returns false (or non-zero) no other forms are evaluated. The printed error message will identify where in the sequence evaluation was halted.
|
|
#+BEGIN_EXAMPLE lisp
|
|
(circuit
|
|
(l apt update) ;; if this fails, no upgrade is made
|
|
(l apt upgrade) ;; if this fails, "Success!" is not printed
|
|
(l echo "Success!"))
|
|
#+END_EXAMPLE
|
|
|
|
*** Command piping
|
|
In a shell such as Bash or Zsh, the output of one command may be automatically looped into the input of another command. Below is an example of three shell commands piped together. On execution this example counts the number of running Flesh processes:
|
|
#+BEGIN_EXAMPLE shell
|
|
$ ps aux | grep flesh | wc -l
|
|
#+END_EXAMPLE
|
|
|
|
In order to provide such a facility in Flesh, the ~pipe~ function is provided.
|
|
#+BEGIN_EXAMPLE lisp
|
|
(pipe
|
|
(ps aux)
|
|
(grep flesh)
|
|
(wc -l))
|
|
#+END_EXAMPLE
|
|
|
|
*** Processing command output
|
|
There will be many times a user will want to directly process command output other than the process exit code. For this a function ~load-to-string~ is included. Below is an example of a series of commands that leverage this function to print a symbolic token based on the presence and status of a Git repository.
|
|
#+BEGIN_EXAMPLE shell
|
|
(def in-a-git-repo?
|
|
'returns true or false depending on if currently in a git repo'
|
|
() (eq? (load-to-string git rev-parse --is-inside-work-tree) "true"))
|
|
|
|
(def git-repo-is-dirty?
|
|
'returns true or false depending on if current dir is a dirty git repo'
|
|
() (not (eq? (load-to-string git diff '--stat') "")))
|
|
|
|
(def git-status 'returns "(git:<branch>{!,})" if dir is in a git repository'
|
|
()
|
|
(if (in-a-git-repo?)
|
|
(concat
|
|
"(git:"
|
|
(load-to-string git rev-parse --abbrev-ref HEAD)
|
|
(if (git-repo-is-dirty?)
|
|
"!"
|
|
"")
|
|
")")
|
|
''))
|
|
|
|
(git-status)
|
|
#+END_EXAMPLE
|
|
|
|
(Example is from [[file:snippets/avas-laptop-prompt.f][Ava's Laptop Prompt]])
|
|
|
|
*** Redirecting command output to or from files
|
|
Another common shell feature is the redirection of input/output to/from files. For example:
|
|
#+BEGIN_EXAMPLE shell
|
|
$ find / -iname "needle.haystack" 2> /dev/null
|
|
#+END_EXAMPLE
|
|
|
|
Flesh can redirect "stdin", "stdout", or "stderr" of a shell command using the ~load-with~ function.
|
|
#+BEGIN_EXAMPLE lisp
|
|
(load-with (("stderr" "/dev/null"))
|
|
(find / -iname "needle.haystack"))
|
|
#+END_EXAMPLE
|
|
|
|
Or, a more comprehensive example using hypothetical commands and data:
|
|
#+BEGIN_EXAMPLE lisp
|
|
(load-with (("stdin" "img.png")
|
|
("stdout" "img.jpg")
|
|
("stderr" "/dev/null"))
|
|
(my-img-convert))
|
|
#+END_EXAMPLE
|
|
|
|
** Control background and foreground processes
|
|
Flesh implements fully interactive job control.
|
|
To launch a background process use the ~bg~ function:
|
|
#+BEGIN_EXAMPLE lisp
|
|
(bg emacs -nw)
|
|
#+END_EXAMPLE
|
|
|
|
To get all jobs in your shell use the system ~ps~ binary:
|
|
#+BEGIN_EXAMPLE lisp
|
|
(l ps)
|
|
#+END_EXAMPLE
|
|
|
|
To foreground a background process use the ~fg~ function:
|
|
#+BEGIN_EXAMPLE
|
|
(fg <pid>)
|
|
#+END_EXAMPLE
|
|
|
|
** Changing directories
|
|
Flesh also provides a ~cd~ utility to change current working directory:
|
|
#+BEGIN_EXAMPLE lisp
|
|
(cd (concat HOME '/repositories')) ;; $ cd ~/repositories
|
|
#+END_EXAMPLE
|
|
|
|
This ~cd~ routine will keep the ~PWD~ variable up to date.
|
|
Every invokation of ~cd~ will call whatever lambda or function is stored in ~CFG_FLESH_CD_CB~ with no arguments. A function stored in this variable can then perform on the fly reconfiguration or set additional variables based on the directory that the user has entered.
|
|
|
|
** Creating bindings for shell commands
|
|
Daily Flesh users will long for first class shell commands that are accounted for in autocomplete.
|
|
|
|
A simple solution:
|
|
#+BEGIN_EXAMPLE lisp
|
|
(def lis 'shortcut for ls -la'
|
|
(lambda (dir) (l ls -la dir)))
|
|
|
|
(lis HOME)
|
|
#+END_EXAMPLE
|
|
|
|
The reader may also wish for a utility that allows them to create first-class shortcuts to shell subcommands. It is for this purpose that [[file:snippets/genbind.f][genbind]] was written. Genbind allows the user to create globally defined functions that reference shell commands (more specifically, subcommands) from their shell. The reader is advised to refer to the documentation in Genbind for more information.
|
|
#+BEGIN_EXAMPLE lisp
|
|
(def g-add 'shortcut for git add'
|
|
(gen-binding "git" "add"))
|
|
|
|
(g-add ("src" "docs"))
|
|
#+END_EXAMPLE
|
|
|
|
Or:
|
|
#+BEGIN_EXAMPLE lisp
|
|
;; REQUIRES USERLIB FOR PREPEND (or write your own)
|
|
(def pacman-search 'shortcut for sudo pacman -Ss'
|
|
(lambda (many-dirs) ((gen-binding "sudo" "pacman") (prepend "-Ss" many-dirs))))
|
|
|
|
(pacman-search "xfce4")
|
|
#+END_EXAMPLE
|
|
|
|
* Implicit Load
|
|
Typically, any call to an undefined function, lambda, or symbol will result in an undefined symbol error. For example, here is a call to GCC without explicitly prepending the ~load~ function:
|
|
#+BEGIN_EXAMPLE
|
|
> (gcc myfile.c -o myfile)
|
|
|
|
** ERROR TRACEBACK
|
|
> gdb: (is an undefined symbol)
|
|
Please refactor forms and try again...
|
|
#+END_EXAMPLE
|
|
|
|
With the ~implicit load~ feature any call to an undefined function will trigger the ~load~ function as if it had been there the whole time. The above example would yield a new subprocess running GCC (assuming it is installed and accessible). This allows users to quickly type shell commands at the prompt as if they were using a standard shell. Any reference to an undefined symbol outside of where a function would be will still cause an error.
|
|
|
|
#+BEGIN_EXAMPLE
|
|
> ls -la .config
|
|
|
|
...... (redacted directory list) .....
|
|
#+END_EXAMPLE
|
|
|
|
Implicit load can be used by building flesh with the following command:
|
|
|
|
#+BEGIN_EXAMPLE
|
|
cargo build -F implicit-load
|
|
#+END_EXAMPLE
|