This document is a little all over the place--I've been working on it for a while, but I keep refactoring it as I move pieces of it into man pages and whatnot. It's mostly a brain dump, sorry. There are three parts to this; procedures, "content" guidelines and "style" guidelines. The style guidelines are less strict; as long as things are consistent at the file-level, I'm pretty happy. Contributing ============ I'd love to have your patches! Code should be hackable; if you want to modify something, but can't figure out how: 1) ping me for help, 2) it probably means the code was too complicated in the first place. Patches should be sent to <dev@lists.parabolagnulinux.org>; please put "[PATCH]" and "libretools" in the subject line. If you have commit access, but want me to look over it first, feel free to create a new branch in git, and I will notice it. Try to avoid pushing to the "master" branch unless it's a trivial change; it makes it easier to review things; though I *will* look over every commit before I do a release, so don't think you can sneak something in :) I'd love to discuss possible changes on IRC (I'm lukeshu), either on irc.freenode.net#parabola or in personal messages. My account may be online even if I'm not; I will eventually see your it, I do a search for mentions of "luke" on #parabola every time I get on. Testing ======= Please write unit tests for new things. Tests can be run with `make check`, which just runs `./testenv roundup` in the `test/` directory. Relatedly, you need the `roundup` tool to run the tests. `./testenv` can be given `--no-network` and/or `--no-sudo` to dissable tests that require those things. Make can be made to pass those things in by setting `TESTENVFLAGS`. If you don't dissable either, I *strongly* recommend setting TMPDIR to somewhere on a btrfs partition before running the tests; otherwise the chroot tests will take forever. I mean, they already take long on btrfs, but without it... _dang_. Code content ============ Be aware of the `librelib(7)` library suite, which lives `src/lib`. It is a suite of Bash libraries that will help you out. Most of the people looking at the libretools code are familiar with the `messages` part of it, which actually contains a much of utility routines, not just message printing. There is also a library for dealing with `blacklist.txt`, and one for loading configuration files and PKGBUILDs. These are common tasks, but are tricky to handle consistently--the libraries are there to make things easier. Take a look at the man pages. Message printing: All of the message printing routines, except for `term_title` and `flag`, take printf-type arguments. Take advantage of that; don't use string interpolation (don't do `"foo ${var} bar"`). The reason for this is that if you don't do string interpolation, messages can be automatically internationalized. (Internationalization is incomplete at the momement) Message printing: The in `--help`/`-h` text, use `print` to print lines that should not wrap, `echo` to print blank lines, `prose` to print paragraphs, `bullet` to print bullet points, and `flag` to print option flags. The text should follow this general format: print "Usage: %s [OPTIONS] VARS_ARE_UNDERSCORE_AND_CAPITAL" "${program_name}" print "One line description of program, no period" echo prose "More details. This is a paragraph." echo print "Options:" flag "-h" "Show this message" In the "Usage:" line, use printf `%s` and the value `"${0##*/}"` to determine the program name at runtime. There used to be guidelines for how to align the option flags and descriptions, but now the `flag` command exists takes care of it for you. Yay for things being easier! When using `set -u`, `set -e`, or `trap`, you should also use `set -E` to have the error handling be passed down to subshells. Feel free to use `set -e` (fail on error), but be careful of the caveats (there are a bunch of them); don't assume all errors are checked because of it. Use `set -u` if you can; it makes using an unset variable an error. - If a variable not being set is valid (perhaps a configuration option), use `${var:-}` when accessing it to suppress the error. - An empty array counts as unset, so if you have an array that may be empty, use `set +u` before accessing it. - The reason for this is that a normal string variable is basically an array with length=1; an unset variable looks like an array with length=0. Weird stuff. In the shebang, use `#!/usr/bin/env bash`. This allows us to not hardcode the location of bash (I'm not sure why this is useful for something distro-dependent like libretools, but fauno seems to have a use-case for it). In the shebang, don't pass flags to bash, besides breaking `env` (above), it means people will make mistakes when debugging, and running things with `bash FILENAME`. Instead, use `set` to adjust the flags inside of the program. Obey `$TMPDIR`. It's usually as easy as passing `--tmpdir` to `mktemp`. Use `trap` to clean up your temporary files. This way, even if your program terminates early, things will be cleaned up. Bash best practices =================== Basically, know what you are doing, and be safe with it. The problem is that most people don't know about safe bash scripting. A lot of people look at the "Advanced Bash Scripting" ebook--DO NOT do that, it is trash... though it contains a "reference card" page that may be useful and isn't trash. Take a look at Gentoo's Bash guidelines <http://devmanual.gentoo.org/tools-reference/bash/index.html>. They're pretty good, and cover most of the "gotcha's" about Bash syntax. It mentions but discourages the use of Bash 3 features... why? Who still uses Bash 2? Feel free to use Bash 4 features! I wrote an article on Bash arrays <https://lukeshu.com/blog/bash-arrays.html>. A lot of people think they're tricky, but they're simple once you know how they work. It's short enough that you should read the whole thing. Know the difference between `"${array[@]}"` and `"${array[*]}"`. And I'll say it again here, ALWAYS wrap those in double quotes; there is no reason I can think of that the unquoted behavior would ever be the correct thing. My brief rules of thumb: - Quote every variable. - That includes arrays: `"${array[@]}"` and `"${array[*]}"`. - In most (but not all!) cases inside of `[[ ... ]]` conditions, variables don't need to be quoted. When in doubt, quote them. - When assigning one variable to another, you don't need quotes; you don't need quotes for `foo=$bar` - Try to avoid global variables; declare all variables in functions with `local`. - Or `declare`; inside of a function, unless you pass the `-g` flag, `declare` makes the variable local. - Use `local VAR` before a `for VAR in LIST` loop--the variable is created in the current scope, not the scope of the loop. - Feeding input to `while` loops is weird because of how subshells work: # Input from a file # BAD cat file | while read line; do ... done # GOOD while read line; do ... done <file # Input from a program # BAD prog | while read line; do ... done # GOOD while read line; do ... done < <(prog) Style guidelines ================ Unless you have a good reason, use `[[ ... ]]` instead of `[ ... ]`; they work similarly, but `[[ ... ]]` is sometimes more readable (fine, rarely, but never less), and is harder to make mistakes with quoting, because it is syntactic magic, as opposed to `[ ... ]` which is an executable which just happens to be implemented as a builtin. Use a litteral tab for indent. When indenting line-wrapped text, such as that for `prose`, do it like this: (» indicates tab, · indicates space) func() { » prose "This is the first line. This paragraph is going to be » ·······wrapped." } The `; then` and `; do` should go on the same line as `if`/`elif`/`for`/`while`. Also, there is no space before the `;`. Prefer the `for VAR in LIST` syntax over the `for ((init; cond; inc))` syntax, when possible. For example (heh, `for` example): local i for (( i = 1 ; i <= 10 ; i++ )); do should be local i for i in {1..10}; do Of course, if the upper bound is a variable, the C-like syntax is the better option, as otherwise you would have to use `seq` (calling an external), or `eval` (gross, easy to mess up royally). Indent comments like you would code; don't leave them at the beginning of the line. Example: for item in "${list[@]}"; do if [[ $item == foo ]]; then # BAD foobar fi if [[ $item == bar ]]; then # GOOD barbaz fi done Fauno, I'm sorry. But I don't know how you can read your own code :P. Some people argue in favor of the useless use of cat, because data should flow from left to right. However, the input redirection doesn't have to go on the right side of a command: cat file | program # useless use of cat program < file # data flows right to left < file program # just right