|
| 1 | +## Welcome! |
| 2 | + |
| 3 | +I'm so glad you've found this project interesting and useful enough that you'd |
| 4 | +like to contribute to its development. |
| 5 | + |
| 6 | +Please take time to review the policies and procedures in this document prior |
| 7 | +to making and submitting any changes. |
| 8 | + |
| 9 | +This guide was drafted with tips from [Wrangling Web Contributions: How to |
| 10 | +Build a CONTRIBUTING.md](https://mozillascience.github.io/working-open-workshop/contributing/) |
| 11 | +and with some inspiration from [the Atom project's CONTRIBUTING.md |
| 12 | +file](https://github.com/atom/atom/blob/master/CONTRIBUTING.md). |
| 13 | + |
| 14 | +## Table of contents |
| 15 | + |
| 16 | +- [Quick links](#quick-links) |
| 17 | +- [Code of conduct](#code-of-conduct) |
| 18 | +- [Reporting issues](#reporting-issues) |
| 19 | +- [Updating documentation](#updating-documentation) |
| 20 | +- [Environment setup](#environment-setup) |
| 21 | +- [Workflow](#workflow) |
| 22 | +- [Testing](#testing) |
| 23 | +- [Coding conventions](#coding-conventions) |
| 24 | +- [Public domain](#public-domain) |
| 25 | + |
| 26 | +## Quick links |
| 27 | + |
| 28 | +- [README](README.md) |
| 29 | +- [Code of conduct](CODE_OF_CONDUCT.md) |
| 30 | +- [License information](LICENSE.md) |
| 31 | +- [Original repository](https://github.com/mbland/go-script-bash/) |
| 32 | +- [Issues](https://github.com/mbland/go-script-bash/issues) |
| 33 | +- [Pull requests](https://github.com/mbland/go-script-bash/pulls) |
| 34 | +- [Issues](https://github.com/mbland/go-script-bash/issues) |
| 35 | + |
| 36 | +## Code of conduct |
| 37 | + |
| 38 | +Harrassment or rudeness of any kind will not be tolerated, period. For |
| 39 | +specifics, see the [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) file. |
| 40 | + |
| 41 | +## Reporting issues |
| 42 | + |
| 43 | +Before reporting an issue, please use the search feature on the [issues |
| 44 | +page](https://github.com/mbland/go-script-bash/issues) to see if an issue |
| 45 | +matching the one you've observed has already been filed. |
| 46 | + |
| 47 | +If you do find one... |
| 48 | + |
| 49 | +### Do not add a +1 comment! |
| 50 | + |
| 51 | +If you find an issue that interests you, but you have nothing material to |
| 52 | +contribute to the thread, use the *Subscribe* button on the right side of the |
| 53 | +page to receive notifications of further conversations or a resolution. Comments |
| 54 | +consisting only of "+1" or the like tend to clutter the thread and make it more |
| 55 | +painful to follow the discussion. |
| 56 | + |
| 57 | +If you _do_ have something to add to the conversation, or _don't_ find a |
| 58 | +matching issue... |
| 59 | + |
| 60 | +### File a new issue or update an existing one |
| 61 | + |
| 62 | +Try to be as specific as possible about your environment and the problem you're |
| 63 | +observing. At a minimum, include: |
| 64 | + |
| 65 | +- The version of bash you're using, from either `bash --version` or `echo |
| 66 | + $BASH_VERSION` |
| 67 | +- The version of the go-script-bash library you're using |
| 68 | +- Command line steps or code snippets that reproduce the issue |
| 69 | + |
| 70 | +Also consider using: |
| 71 | + |
| 72 | +- bash's `time` builtin to collect running times |
| 73 | +- a regression test to add to the suite |
| 74 | +- memory usage as reported by a tool such as |
| 75 | + [memusg](https://gist.github.com/netj/526585) |
| 76 | + |
| 77 | +## Updating documentation |
| 78 | + |
| 79 | +## Environment setup |
| 80 | + |
| 81 | +Make sure you have Bash installed per the [Environment setup in the |
| 82 | +README](README.md#environment-setup). |
| 83 | + |
| 84 | +You will also need [Git](https://git-scm.com/downloads) installed on your |
| 85 | +system. If you are not familiar with Git, you may wish to reference the [Git |
| 86 | +documentation](https://git-scm.com/doc). |
| 87 | + |
| 88 | +## Workflow |
| 89 | + |
| 90 | +The basic workflow for submitting changes resembles that of the [GitHub Git |
| 91 | +Flow](https://guides.github.com/introduction/flow/), except that you will be |
| 92 | +working with your own fork of the repository and issuing pull requests to the |
| 93 | +original. |
| 94 | + |
| 95 | +1. Fork the repo on GitHub (look for the "Fork" button) |
| 96 | +2. Clone your forked repo to your local machine |
| 97 | +3. Create your feature branch (`git checkout -b my-new-feature`) |
| 98 | +4. Develop _and [test](#testing)_ your changes as necessary. |
| 99 | +4. Commit your changes (`git commit -am 'Add some feature'`) |
| 100 | +5. Push to the branch (`git push origin my-new-feature`) |
| 101 | +6. Create a new [GitHub pull |
| 102 | + request](https://help.github.com/articles/using-pull-requests/) for your |
| 103 | + feature branch based against the original repository's `master` branch |
| 104 | +7. If your request is accepted, you can [delete your feature |
| 105 | + branch](https://help.github.com/articles/deleting-unused-branches/) and |
| 106 | + pull the updated `master` branch from the original repository into your |
| 107 | + fork. You may even [delete your |
| 108 | + fork](https://help.github.com/articles/deleting-a-repository/) if you don't |
| 109 | + anticipate making further changes. |
| 110 | + |
| 111 | +## Testing |
| 112 | + |
| 113 | +No bug fixes or new features will be accepted without accompanying tests. |
| 114 | + |
| 115 | +## Coding conventions |
| 116 | + |
| 117 | +- [Formatting](#formatting) |
| 118 | +- [Naming](#naming) |
| 119 | +- [Variable and parameter declarations](#variable-and-parameter-declarations) |
| 120 | +- [Command substitution](#command-substitution) |
| 121 | +- [Conditions and loops](#conditionals-and-loops) |
| 122 | +- [Output](#output) |
| 123 | +- [Gotchas](#gotchas) |
| 124 | + |
| 125 | +### Formatting |
| 126 | + |
| 127 | +- Keep all files 80 characters wide. (Yes, the maintainer is a dinosaur who |
| 128 | + likes viewing files side-by-side in a 161-column terminal window.) |
| 129 | +- Indent using two spaces. |
| 130 | +- Enclose all variables in double-quotes when used, to avoid having them |
| 131 | + interpreted as glob patterns (unless the variable contains a glob pattern). |
| 132 | + - Exception: Do not quote them within `[[` and `]]` condition tests. |
| 133 | + |
| 134 | +The following are intended to prevent too-compact code: |
| 135 | + |
| 136 | +- Declare only one variable per `declare` or `local` line. |
| 137 | + - _Note:_ This also helps avoid subtle bugs, as trying to initialize one |
| 138 | + variable using the value of another declared in the same statement will |
| 139 | + not do what you may expect. The initialization of the first variable will |
| 140 | + not yet be complete when the second variable is declared, so the first |
| 141 | + variable will have an empty value. |
| 142 | +- Do not use one-line `if`, `for`, `while`, `until`, `case`, or `select` |
| 143 | + statements. |
| 144 | +- Do not use `&&` or `||` to avoid writing `if` statements. |
| 145 | +- Do not write functions entirely on one line. |
| 146 | +- For `case` statements: put each pattern on a line by itself; put each command |
| 147 | + on a line by itself; put the `;;` terminator on a line by itself. |
| 148 | + |
| 149 | +_Confession:_ I have used one-liners like crazy in the past. Looking back at my |
| 150 | +own code, I've found them difficult to understand. Spreading out declarations, |
| 151 | +statements, and functions makes the code easier to follow, as the behavior is |
| 152 | +more explicit. It also makes it more `grep`-pable, as "one thing per line" makes |
| 153 | +it easier to find, count, and possibly transform things. |
| 154 | + |
| 155 | +### Naming |
| 156 | + |
| 157 | +- Use `snake_case` for all identifiers. |
| 158 | +- Constants and globals should be in `ALL_CAPS`, prefixed with `_GO_`. |
| 159 | + - Exception: a global variable used for initialization that isn't used |
| 160 | + anywhere else or intended for export should be prefixed with `__go_`, as |
| 161 | + seen at the top of `libexec/builtins`: |
| 162 | + ```bash |
| 163 | + declare __go_builtin_cmds=() |
| 164 | + function __go_glob_builtin_scripts { |
| 165 | + local c |
| 166 | + for c in "$_GO_CORE_DIR/libexec/"*; do |
| 167 | + if [[ -f $c && -x $c ]]; then |
| 168 | + __go_builtin_cmds+=("${c##*/}") |
| 169 | + fi |
| 170 | + done |
| 171 | + } |
| 172 | + __go_glob_builtin_scripts |
| 173 | + |
| 174 | + declare -r _GO_BUILTIN_CMDS=("${__go_builtin_cmds[@]}") |
| 175 | + ``` |
| 176 | +- Prefix API functions with `@go.`. |
| 177 | +- Prefix internal functions with `_@go.`. |
| 178 | +- Prefix variables used to return values to the caller with `__go_`. |
| 179 | + |
| 180 | +### Files |
| 181 | + |
| 182 | +- If the file is a pure library with no executable behavior of its own, put it |
| 183 | + in `lib/`. |
| 184 | +- If the file is executable, put it in `libexec/`. |
| 185 | +- All logic that is not a constant or global declaration or initializer should |
| 186 | + be contained within a function declaration. |
| 187 | +- `libexec/` files should execute a function that represents its main logic and |
| 188 | + which uses `return 1` (or some other value) to indicate an error, e.g. from |
| 189 | + `libexec/help`: |
| 190 | + ```bash |
| 191 | + |
| 192 | + if [[ $# -eq 0 ]]; then |
| 193 | + |
| 194 | + else |
| 195 | + [email protected]_message_for_command "$@" |
| 196 | + fi |
| 197 | + } |
| 198 | +
|
| 199 | + |
| 200 | + ``` |
| 201 | + - This makes it easier for other scripts to source the executable script and |
| 202 | + take action on error. |
| 203 | + |
| 204 | +### Function declarations |
| 205 | + |
| 206 | +- Declare functions without the `function` keyword. |
| 207 | +- Strive to always use `return`, never `exit`, unless an error condition is |
| 208 | + severe enough to warrant it. |
| 209 | + - Calling `exit` makes it difficult for the caller to recover from an error, |
| 210 | + or to compose new commands from existing ones. |
| 211 | + |
| 212 | +### Variable and parameter declarations |
| 213 | + |
| 214 | +- Declare all constants near the top of the file using `declare -r`. |
| 215 | +- Avoid globals; but if you must, declare all globals near the top of the file, |
| 216 | + outside of any function, using `declare`. |
| 217 | +- Declare all variables inside functions using `local`. |
| 218 | + - Exception: If an internal function needs to return more than one distinct |
| 219 | + result value, or an array of values, it should use _undeclared_ variables |
| 220 | + prefixed with `__go_`, and all callers should declare these variables as |
| 221 | +- Don't use `local -r`, as a readonly local variable in one scope can cause a |
| 222 | + conflict when it calls a function that declares a `local` variable of the same |
| 223 | + name. |
| 224 | +- Don't use type flags with `declare` or `local`. Assignments to integer |
| 225 | + variables in particular may behave differently, and it has no effect on array |
| 226 | + variables. |
| 227 | +- For most functions, the first lines should use `local` declarations to |
| 228 | + assign the original positional parameters to more meaningful names, e.g.: |
| 229 | + ```bash |
| 230 | + |
| 231 | + local cmd_name="$1" |
| 232 | + local summary="$2" |
| 233 | + local longest_name_len="$3" |
| 234 | + ``` |
| 235 | + For very short functions, this _may not_ be necessary, e.g.: |
| 236 | + ```bash |
| 237 | + |
| 238 | + [[ $1 != ${1//[[:space:]]/} ]] |
| 239 | + } |
| 240 | + ``` |
| 241 | +
|
| 242 | +### Command substitution |
| 243 | +
|
| 244 | +- Use `$()` instead of backticks. |
| 245 | +
|
| 246 | +### Process substitution |
| 247 | +
|
| 248 | +- Avoid it, since it is not available on Windows platforms (yet). |
| 249 | +
|
| 250 | +### Conditionals and loops |
| 251 | +
|
| 252 | +- Always use `[[` and `]]` for evaluating variables. Do not quote variables |
| 253 | + within the brackets. |
| 254 | +
|
| 255 | +### Output |
| 256 | +
|
| 257 | +- Use `@go.printf` for most console output to ensure that the text fits the |
| 258 | + terminal width. |
| 259 | +
|
| 260 | +### Gotchas |
| 261 | +
|
| 262 | +- If you wish to use command substitution to initialize a `local` variable, and |
| 263 | + then check the exit status of the command substitution, you _must_ declare the |
| 264 | + variable on one line and perform the substitution on another. If you don't, |
| 265 | + the exit status will always indicate success, as it is the status of the |
| 266 | + `local` declaration, not the command substitution: |
| 267 | + ```bash |
| 268 | + local defined |
| 269 | + defined=($(declare -F "${_COMMANDS[@]}")) && [[ $? -eq 0 ]] && return |
| 270 | + ``` |
| 271 | +
|
| 272 | +## Public domain |
| 273 | +
|
| 274 | +This project is in the public domain within the United States, and |
| 275 | +copyright and related rights in the work worldwide are waived through |
| 276 | +the [CC0 1.0 Universal public domain |
| 277 | +dedication](https://creativecommons.org/publicdomain/zero/1.0/). |
| 278 | +
|
| 279 | +All contributions to this project will be released under the CC0 |
| 280 | +dedication. By submitting a pull request, you are agreeing to comply |
| 281 | +with this waiver of copyright interest. |
| 282 | +
|
| 283 | +For more information, see the [LICENSE](LICENSE.md) file. |
0 commit comments