I'm so glad you've found this project interesting and useful enough that you'd like to contribute to its development.
Please take time to review the policies and procedures in this document prior to making and submitting any changes.
This guide was drafted with tips from Wrangling Web Contributions: How to Build a CONTRIBUTING.md and with some inspiration from the Atom project's CONTRIBUTING.md file.
- Quick links
- Contributor License Agreement
- Code of conduct
- Reporting issues
- Updating documentation
- Environment setup
- Workflow
- Testing
- Coding conventions
- Open Source License
- README
- Code of conduct
- License information
- Original repository
- Issues
- Pull requests
- Milestones
- Projects
Per the GitHub Terms of Service, be aware that by making a contribution to this project, you agree:
- to license your contribution under the same terms as this project's license, and
- that you have the right to license your contribution under those terms.
See also: "Does my project need an additional contributor agreement? Probably not."
Harrassment or rudeness of any kind will not be tolerated, period. For specifics, see the CODE_OF_CONDUCT file.
Before reporting an issue, please use the search feature on the issues page to see if an issue matching the one you've observed has already been filed.
If you do find one...
If you find an issue that interests you, but you have nothing material to contribute to the thread, use the Subscribe button on the right side of the page to receive notifications of further conversations or a resolution. Comments consisting only of "+1" or the like tend to clutter the thread and make it more painful to follow the discussion.
If you do have something to add to the conversation, or don't find a matching issue...
Try to be as specific as possible about your environment and the problem you're observing. At a minimum, include:
- The output from
./go goinfo
- Command line steps or code snippets that reproduce the issue
- Any apparently relevant information from the Bash changelog
Also consider using:
- bash's
time
builtin to collect running times - a regression test to add to the suite
- memory usage as reported by a tool such as memusg
If you've a passion for writing clear, accessible documentation, please don't be
shy about sending pull requests! The documentation is just as important as the
code, especially in this project, since the goal is to make the functionality as
discoverable as possible through the ./go help
command.
Also: no typo is too small to fix! Really. Of course, batches of fixes are preferred, but even one nit is one nit too many.
Make sure you have Bash installed per the Environment setup in the README.
You will also need Git installed on your system. If you are not familiar with Git, you may wish to reference the Git documentation.
The basic workflow for submitting changes resembles that of the GitHub Git Flow, except that you will be working with your own fork of the repository and issuing pull requests to the original.
- Fork the repo on GitHub (look for the "Fork" button)
- Clone your forked repo to your local machine
- Create your feature branch (
git checkout -b my-new-feature
) - Develop and test your changes as necessary.
- Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new GitHub pull request for your feature branch based
against the original repository's
master
branch - If your request is accepted, you can delete your feature branch
and pull the updated
master
branch from the original repository into your fork. You may even delete your fork if you don't anticipate making further changes.
- Continuous integration status for Linux and macOS:
- Continuous integration status for Windows:
- Coverage status:
No bug fixes or new features will be accepted without accompanying tests. Period.
Any changes that break the continuous integration build must be fixed or rolled back immediately.
This project uses the Bash Automated Testing System (Bats) to write and
run tests. All tests and helper scripts are in the tests/
directory and are
run using the ./go test
command. This command has a very flexible syntax for
running a subset of test suites (i.e. a set of test cases within individual
.bats
files or directories). Enabling tab completion via ./go env
is highly
encouraged.
Before sending your code for review, make sure to run the entire test suite via
./go test
. If you're on a Linux system that uses the apt-get
package manager
(e.g. Ubuntu, Debian), run ./go test --coverage
to make sure your changes are
adequately covered by new and existing tests. This will install (within the
project working directory) and run the kcov tool, which is only available on
Linux for now.
- Formatting
- Naming
- Variable and parameter declarations
- Command substitution
- Conditions and loops
- Output
- Gotchas
- Keep all files 80 characters wide. (Yes, the maintainer is a dinosaur who likes viewing files side-by-side in a 161-column terminal window.)
- Indent using two spaces.
- Enclose all variables in double quotes when used to avoid having them
interpreted as glob patterns (unless the variable contains a glob pattern)
and to avoid word splitting when the value contains spaces. Both scenarios
can introduce errors that often prove difficult to diagnose.
- This is especially important when the variable is used to generate a glob pattern, since spaces may appear in a path value.
- If the variable itself contains a glob pattern, make sure to set
IFS=$'\n'
before using it so that the pattern itself and any matching file names containing spaces are not split apart. - Exceptions: Quotes are not required within math contexts, i.e.
(( ))
or$(( ))
, and must not be used for variables on the right side of the=~
operator.
- Enclose all string literals in single quotes.
- Exception: If the string contains an apostrophe, use double quotes.
- Use quotes around variables and literals even inside of
[[ ]]
conditions.- This is because strings that contain '[' or ']' characters may fail to compare equally when they should.
- Exception: Do not quote variables that contain regular expression patterns
appearing on the right side of the
=~
operator.
- Only quote arguments to the right of
=~
if the expression is a literal match without any metacharacters.
The following are intended to prevent too-compact code:
- Declare only one item per
declare
,local
,export
, orreadonly
call.- Note: This also helps avoid subtle bugs, as trying to initialize one variable using the value of another declared in the same statement will not do what you may expect. The initialization of the first variable will not yet be complete when the second variable is declared, so the first variable will have an empty value.
- Do not use one-line
if
,for
,while
,until
,case
, orselect
statements. - Do not use
&&
or||
to avoid writingif
statements. - Do not write functions entirely on one line.
- For
case
statements: put each pattern on a line by itself; put each command on a line by itself; put the;;
terminator on a line by itself.
Confession: I have used one-liners like crazy in the past. Looking back at my
own code, I've found them difficult to understand. Spreading out declarations,
statements, and functions makes the code easier to follow, as the behavior is
more explicit. It also makes it more grep
-pable, as "one thing per line" makes
it easier to find, count, and possibly transform things.
- Use
snake_case
for all identifiers. - Constants and globals should be in
ALL_CAPS
, prefixed with_GO_
.- Exception: a global variable used for initialization that isn't used
anywhere else or intended for export should be all-lowercase, prefixed with
__go_
, andunset
after use, as seen at the top oflibexec/builtins
:declare __go_builtin_cmds=() function __go_glob_builtin_scripts { local c for c in "$_GO_CORE_DIR/libexec/"*; do if [[ -f "$c" && -x "$c" ]]; then __go_builtin_cmds+=("${c##*/}") fi done } __go_glob_builtin_scripts unset __go_glob_builtin_scripts declare -r _GO_BUILTIN_CMDS=("${__go_builtin_cmds[@]}") unset __go_builtin_cmds
- Exception: a global variable used for initialization that isn't used
anywhere else or intended for export should be all-lowercase, prefixed with
- Prefix API functions with
@go.
. - Prefix internal functions with
_@go.
. - Prefix variables used to return values to the caller with
__go_
.
- If the file is a pure library with no executable behavior of its own, put it
in
lib/
. - If the file is executable, put it in
libexec/
. - All logic that is not a constant or global declaration or initializer should be contained within a function declaration.
libexec/
files should execute a function that represents its main logic and which usesreturn 1
(or some other value) to indicate an error, e.g. fromlibexec/help
:_@go.help() { if [[ "$#" -eq '0' ]]; then _@go.usage else _@go.help_message_for_command "$@" fi } _@go.help "$@"
- This makes it easier for other scripts to source the executable script and take action on error.
- Declare functions without the
function
keyword. - Strive to always use
return
, neverexit
, unless an error condition is severe enough to warrant it.- Calling
exit
makes it difficult for the caller to recover from an error, or to compose new commands from existing ones.
- Calling
- Declare all constants near the top of the file using
declare -r
.- Exception:
declare
is not available in test files run usingbats
. - Exception: In module code (i.e. code imported via
. "$_GO_USE_MODULES"
), usereadonly
instead. Otherwise if a module is imported into a function, variables declared withdeclare
will go out of scope after the first function call, and will not be redefined for subsequent calls, causing errors.
- Exception:
- Avoid globals; but if you must, declare all globals near the top of the file,
outside of any function, using
declare
.- Exception:
declare
is not available in test files run usingbats
. - Exception: In module code (i.e. code imported via
. "$_GO_USE_MODULES"
), useexport
instead. See the note above regardingdeclare -r
andreadonly
for details. - Gotcha: Never initialize an array on the same line as an
export
ordeclare -g
statement. See the Gotchas section below for more details.
- Exception:
- Declare all variables inside functions using
local
.- Exception: If an internal function needs to return more than one distinct
result value, or an array of values, it should use undeclared variables
prefixed with
__go_
, and all callers should declare these variables as local variables. - Exception: If a function needs to set a one-time initialization flag, it
may declare that value using
readonly
.
- Exception: If an internal function needs to return more than one distinct
result value, or an array of values, it should use undeclared variables
prefixed with
- Declare temporary file-level variables using
declare
. Useunset
to remove them when finished. - Don't use
local -r
, as a readonly local variable in one scope can cause a conflict when it calls a function that declares alocal
variable of the same name. - Don't use type flags with
declare
orlocal
. Assignments to integer variables in particular may behave differently, and it has no effect on array variables. - For most functions, the first lines should use
local
declarations to assign the original positional parameters to more meaningful names, e.g.:For very short functions, this may not be necessary, e.g.:_@go.format_summary() { local cmd_name="$1" local summary="$2" local longest_name_len="$3"
_@go.has_spaces() { [[ "$1" != "${1//[[:space:]]/}" ]] }
- Use
$()
instead of backticks.
- Use wherever possible, such as when piping input into a
while
loop (which avoids having the loop body execute in a subshell) or running a command taking multiple filename arguments based on output from a function or pipeline (e.g.diff
). - Warning: It is impossible to directly determine the exit status of a process substitution; emitting an exit status as the last line of output is a possible workaround.
- Always use
[[
and]]
for evaluating variables. Per the guideline under Formatting, quote variables and strings within the brackets, but not regular expressions (or variables containing regular expressions) appearing on the right side of the=~
operator.
- Use
@go.printf
for most console output to ensure that the text fits the terminal width. - Use
@go.print_stack_trace
to provide a detailed error message as appropriate, usually before callingexit 1
.
- If you wish to use command substitution to initialize a
local
variable, and then check the exit status of the command substitution, you must declare the variable on one line and perform the substitution on another. If you don't, the exit status will always indicate success, as it is the status of thelocal
declaration, not the command substitution. - To work around a bug in some versions of Bash whereby arrays declared with
declare -g
orexport
and initialized in the same statement eventually go out of scope, alwaysexport
the array name on one line and initialize it the next line. See:- https://lists.gnu.org/archive/html/bug-bash/2012-06/msg00068.html
- ftp://ftp.gnu.org/gnu/bash/bash-4.2-patches/bash42-025
- http://lists.gnu.org/archive/html/help-bash/2012-03/msg00078.html
This software is made available as Open Source software under the ISC License. For the text of the license, see the LICENSE file.