Coding style ============ This is the official coding style for Shellfu project. Basic principles ---------------- Even though different language, the style is inspired by Python community, more specifically by PEP20 and PEP8. Especially the main principles (PEP20) are honored throughout the Shellfu project, so it may be easier for you to understand recommendations here if you already know PEP20. So **go read it now**---as the name hints, it's **only 20 lines**. You can read it offline: echo "import this" | python PEP8 is official coding style, being a Python community way of achieving some of PEP20's principles. So although many things won't make sense for Bash, I recommend to read it or run through it at the very least. Code layout ----------- ### Indentation ### Indentation is done exclusively by *space*. Indentation level is *almost exclusively* 4 spaces. for i in $(seq 1 5); do something_with "$i" done One exception is *vertical pipelines*: cat list \ | while read line; do something_with "$line" ... done \ | sort \ | uniq where each pipe is indented by 2 spaces and followed by 1 space. This makes the pipe *float* in the indentation space, preserving visual aspect of indentation inside any loops etc (which also may help your editor auto-indent correctly--when inside the loop). ### Line length ### 79 characters. For docstrings, it's 72 characters *including* mandatory *she-space* prefix (`# `). Add 4 in case of function docstrings. ### Imports ### Imports come *after* module docstring, *before* variable declarations. #!/bin/bash # license text # # A modest module # shellfu import config shellfu import exit shellfu import pretty # # Some var # SOME_VAR=159 Imports should be grouped in this order: 1. standard library modules, 2. related third party modules, 3. local application/library specific imports, with empty line between the groups, and each group being sorted alphabetically. Comments -------- First, don't overuse comments. Don't use comments to state the same what the code states. The question the comment is answering must be **why**, not **what**. If you need too many comments, it may mean that something else is wrong with your code--more comments won't fix it. ### Comment formatting ### Any comment must start with at least single space. # hello Exceptions from this rule are TODO/FIXME comments--see below. ### Block comments ### ### Inline comments ### Inline comment should be aligned to 4 spaces, and must be separated from the code by at least 2 spaces. Don't do this: foo # hello bar1 # world but rather this: foo # hello bar1 # world or this (easier when another line is added): foo # hello bar1 # world ### TODOs, FIXMEs, workarounds ### These must be one-liners, so that they can be easily understood when grepping. #TODO: Review the loop #FIXME: We throw away stdout due to bug in Glib # http://bugs.gnome.org/bug/12345 Exception is if the note is acompanied with URL, which probably would not be useful to reader, so can be moved to next line. Note that for both TODOs and FIXMEs, it's always better to have a tracker that is referenced in place--that's especially true if you can't fit the description into one line. Docstrings ---------- Docstrings are special kind of block comments that usually span many lines and are primary means of providing documentation for functions, modules and variables. Generic form of docstring is # # One-line summary # # More detailed explanation # written in Markdown. # # Can have multiple paragraphs etc, as long # as there is an empty comment at the end # In other words, one empty comment, one-line comment for summary, one empty comment, and the rest, followed by one more empty comment. This means that the line preceding the whole block as well as the one following *must not* be comments. Assigning docstring to the related item is done by specific juxtaposition to this item. #### Module docstring #### Module docstring is placed directly after the first empty line in the module file. Typically this will be after shebang, additional lines like Vim editor directives and possibly author and license information. For example: #!/bin/bash # # Some crazy walll of license text ... or not... # # Authors: Me # # # My cool module # # This module is so useful that I can't even start # describing it! # #### Variable docstring #### To add docstring to a global variable, prepend it directly to the variable declaration: # # My cool var # # This variable means something to me # but I'm not going to tell you! # MY_COOL_VAR=$1 i.e. an empty line, and empty comment, the docstring and immediately the variable and at least one empty line. It is possible to group related variables together by adding all assignments right after the docstring like this: # # I don't have numpad # # I prefer using words for numbers, so I will # always use these variables instead. # WORD_ONE=1 WORD_TWO=2 WORD_THREE=3 This is equivalent for copying the same docstring to each of the variables. #### Function docstring #### For functions, the docstring is part of the body, and follows the first declarative line (the one ending with `{`): fooize() { # # Fooize barrator $1 with bazates $@ # # Fooize using most recent best practices as accepted by # academic community. # local bar=$1 # barrator local baz # bazate for baz in "$@"; do something_with "$bar" "$baz" done } This also means that unlike modules and variable docstrings function docstrings: * are indented with 4 additional spaces, * are not preceded by empty line (but the declaration instead), * and may, or may not be followed by empty line---here anything that is not ` #` (i.e. normal code as well) counts as delimiter. It is considered good practice to mention positional parameters, or possible inherited variables right in the short description, using naive Bash notation (no quoting) as in above example. Naming ------ ### Module name and namespacing ### A valid Shellfu module name consists of lowercase letters, numbers and underscore, ie. `[a-z_][a-z0-9_]`. This name must be used as name of the module file, without the extension. Module must not define a global variable or function that does not start with its name. That is, a global variable or function may be: * Just module name itself. * Module name itself followed by *two underscores** (`__`) and an id consisting of one or more letters, numbers or underscores. * Any of above, prefixed by one or more underscores. ### Private names ### Names starting with **double underscore** are considered strongly private: that is, they must only be used in the same file. Names starting with **single underscore** are considered weakly private: they may be used in other modules within the same project. (This can be useful in plugin scenarios.) When in doubt, use **double underscore**. ### Capitalization ### There are three types of variables recognized in terms of this style. The distinction is based on scope: * Global variables must be `ALL_CAPS`. * Function-local variables must be `lowercase` or `snake_case`. These variables *must not* be used in inherited scope, i.e. in a child function. * Inheritable-local variables must be `CamelCase`. These variables may be used in inherited scope, i.e. in a child function. * Both kinds of local variables (inherited and non-inherited) should be defined in header of the function, that is, before actual code. It's also recommended to comment variables here. Functions should be named in `snake_case`. ### Example ### For example, a file named `greet.sh` would be a valid module named `greet` if it contained following declarations: # # Name of this planet # GREET__PLANET=${GREET__PLANET:-Earth} greet() { # # Greet persom named $1 # local name=$1 # name to greet local TimeOfDay # morning, afternoon or night TimeOfDay=$(determine_timeofday) __greet__mkgreet "$name" } __greet__mkgreet() { # # Greet user $1 based on $TimeOfDay # local name=$1 # name to greet case $TimeOfDay in morning) echo "Good $GREET__PLANET morning, $name!" ;; afternoon) echo "Nice $GREET__PLANET afternoon, $name!" ;; night) echo "Sleep well, $name!" ;; esac } Notice that while `TimeOfDay` is allowed to slip through and be referenced in `__greet__mkgreet()`, `name` is purely local to `greet()`, and then `name` happens to be declared again in `__greet__mkgreet()`. Common language constructs -------------------------- ### functions ### Preferred way of declaration is a variation of most common Bourne shell way with K&R-style brackets: myfun() { foo bar baz } That is, * multi-line; * no `function` keyword; * single space between parentheses and opening curly bracket; * closing curly bracket is alone -- *no redirection here*. ### `if`, `while`, `for` ### if foo; then bar elif baz; then quux else idk fi while foo; do bar baz quux done for foo in bar baz; do quux $foo done That is, * `then` and `do` are always on same line as opening keyword (`if`, `while`, `for`) or intermediary keyword (`elif`); * command part (i.e. condition for `if`/`elif`/`while`, or expansion in `for`) is terminated by semicolon; * commands in loop body are not terminated by semicolon. ### `case` ### case $foo in bar) baz ;; *) quux ;; esac That is, three levels: * `case` and `esac` on level 1, * patterns on level 2, * commands and terminators on level 3. Let's get idio(ma)tic --------------------- ### condensed case table ### However, a "condensed" version is possible --- and often recommended: case $foo in bar) bar_something_up ;; baz) do_bazzy_thing ;; *) quux; exit ;; esac # ^ 4 ^ 12 ^ 32 That is, * a "table" layout, where pattern, commands and terminator form a "row"; * each "column" is aligned 4 spaces after `case`; * commands aligned together, to next 4-space boundary after longest pattern; * just as terminators, after longest command; * in case of multiple commands, semicolon and space is used as delimiter. ### the new case - argument router ### This is a cross-breed of infinite *while* loop and *case* switch, a construct I call *argument router*. To understand how this is useful, I'll show you a more complete example: verbose=false file="-" item="" while true; do case $1 in -f|--file) file="$2"; shift 2 || show_usage ;; -q|--quiet) verbose=false; shift ;; -v|--verbose) verbose=true; shift ;; --help) show_help; exit ;; --) item="$2"; break ;; -*) show_usage; exit 2 ;; *) item="$1"; break ;; esac done test -n "$item" || show_usage As you can see it's a variant of *condensed case table*, wrapped in an infinite *while* loop. It may break some rules, but for some great advantages: Whole CLI is described in just few lines--you just need to read header to understand how variables may be set. As opposed to nesting *case* in *while* properly, 2 lines (which are always the same) are saved vertically, and 4 spaces per pattern are saved horizontally, which may become extremely useful for longer option/variable names. As opposed to `getopts`, you miss some validation mechanisms and option bundling, but OTOH you don't need to understand the getopts syntax--what you see here in Bash is what you get. From my experience (writing rather simple scripts with simple interfaces), this is worth.