Last active
December 25, 2025 23:22
-
-
Save alganet/99b9f2e87d66e73ba47c94ffdc60d40a to your computer and use it in GitHub Desktop.
um.sh - A simple, extensible, literate auto-documenting standard for automation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # ISC License | |
| # Copyright (c) 2025, Alexandre Gomes Gaigalas <alganet@gmail.com> | |
| # Permission to use, copy, modify, and/or distribute this software for any | |
| # purpose with or without fee is hereby granted, provided that the above | |
| # copyright notice and this permission notice appear in all copies. | |
| # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| UM_INFO="um version 0.1.0, Copyright (c) Alexandre Gomes Gaigalas" | |
| UM_MAIN="$PWD/$0" | |
| UM_PATH="${UM_PATH:-"${UM_MAIN%/*}/ums"}" | |
| # um.sh | |
| # ======= | |
| # | |
| # Welcome to um.sh! A literate program for humans and machines alike. | |
| # | |
| # It is written in portable shell script (bash, zsh, dash, etc) with comments | |
| # in concise english markdown. | |
| # | |
| # um is very simple. It's made of functions prefixed with `um_` and a tiny | |
| # dispatcher to invoke them. | |
| # | |
| # Here is `um_showinfo`: | |
| um_showinfo () { | |
| /bin/cat <<-@ | |
| version: $UM_INFO | |
| main: $UM_MAIN | |
| path: $UM_PATH | |
| @ | |
| } | |
| # You can invoke it by running `sh um.sh showinfo`. | |
| # | |
| # All ums follow a strict style: | |
| # | |
| # - use descriptive yet concise names | |
| # - the declaration MUST be exactly `um_<name> () {` | |
| # - indentation MUST be tabs to enable `<<-` style heredocs | |
| # - each line SHOULD be at most 80 characters long | |
| # - use only simple UNIX commands | |
| # | |
| # That's it! Now, with those simple style rules, we can compose some | |
| # interesting new ums. | |
| um_list () { | |
| local um_file= | |
| local um_name= | |
| um_file="${1:-"$UM_MAIN"}" | |
| while read -r um_src_line | |
| do | |
| case "$um_src_line" in "um_"*' () {') | |
| um_name="${um_src_line#"um_"}" | |
| um_name="${um_name%' () {'}" | |
| printf '%s ' "$um_name" | |
| ;; | |
| esac | |
| done < "$um_file" | |
| printf '\n' | |
| } | |
| # Cool! Now, if we run `sh um.sh list`, we can see available ums. | |
| # | |
| # The um system is extensible. It can load ums from `UM_PATH`. | |
| # Let's make a function to find files in there: | |
| um_umfiles () { | |
| ! test -d "$UM_PATH" || find "$UM_PATH" -name '*.sh' | |
| } | |
| # ums can also be private, when it doesn't make sense to invoke them | |
| # directly. Let's make a private function to load all umfiles: | |
| _um_load_umfiles () { | |
| local um_files= | |
| local um_file= | |
| um_files="$(um_umfiles)" | |
| for um_file in $um_files | |
| do . "$um_file" | |
| done | |
| } | |
| # This one will be used at the end of the script, before we run the dispatcher. | |
| # | |
| # We call these extra files "umfiles". They follow some rules too: | |
| # | |
| # - umfiles MUST be named like `foo.um.sh`, no underscores, no subdirs | |
| # - umfiles MUST follow the same style conventions as um.sh | |
| # - umfiles MUST NOT redefine existing ums | |
| # - ums inside umfiles must have their own namespace (e.g., `um_foo_`) | |
| # - um naming should be enough, umfiles MUST NOT have a special help um | |
| # | |
| # By now, you must also have noticed some additional style conventions: | |
| # | |
| # - in addition to the um prefix, private functions MUST be start with `_` | |
| # - all variables inside ums MUST be local and initialized | |
| # - extended ums can be loaded from shell files in UM_PATH | |
| # | |
| # Hey, maybe we can also have a function to list all ums in all umfiles! | |
| um_listall () { | |
| um_list | |
| um_umfiles | | |
| while read -r um_file | |
| do um_list "$um_file" | |
| done | |
| } | |
| # That seems useful! By running `sh um.sh listall`, we can see all ums, | |
| # including those loaded from UM_PATH. | |
| # | |
| # Let's reuse that to create a help function: | |
| um_help () { | |
| printf 'usage: %s <um>\n\n' "$0" | |
| printf 'ums:\n' | |
| um_listall | |
| } | |
| # Let's also add a version um for completeness: | |
| um_version () { | |
| printf '%s\n' "$UM_INFO" | |
| } | |
| # --- | |
| # Code below is needed only in the main um.sh and MUST NOT be in umfiles. | |
| set -euf | |
| IFS=' | |
| ' | |
| um_main="${1:-}" | |
| um_main="${um_main##*-}" | |
| test $# -lt 1 || shift | |
| _um_load_umfiles | |
| um_"${um_main:-help}" "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment