bash/.local/share/blesh/doc/CONTRIBUTING.md
2024-08-18 03:40:38 +02:00

20 KiB

Contribution Guide

How to contribute

Issues

You can freely create an issue using the following links:

  • Report and fixes for bugs and performance issues [Here]
  • Questions on usage [Here]
  • Feature request [Here]
  • Others (suggestions, projects, discussion, complaints, news, information or anything) [Here]

Pull requests

We always welcome the following types of pull requests. Any changes will be considered to be provided under the BSD 3-Clause License. If you do not know whether your changes would be appropriate for merge, please feel free to create a pull request and let us talk with each other!

  • Better translation to English, typo fixes
  • Fixes, optimization, test cases
  • New features
  • New color themes ... We accept new themes in contrib repository.
  • Others

Wiki

You can freely edit wiki pages.

  • Translations
  • Typo fixes
  • Create new pages

For package maintainers

If you are a package maintainer for a repository of Linux distribution, etc. you may provide a package-specific setting by preparing a file /path/to/blesh/lib/_package.sh (e.g. /usr/share/blesh/lib/_package.sh) which will be sourced after the load of ble.sh just before sourcing user's configuration (~/.blerc).

  • In the file, the shell variable _ble_base_package_type=TYPE should be set up to have a repository-specific name (such as AUR).

  • The function named ble/base/package:TYPE/update (where TYPE matches with a value assigned to _ble_base_package_type) may be provided to define a custom updating procedure. The exit status of the function can be

    • 0 ... when the update succeeded
    • 6 ... when the update was skipped because the package was up to date.
    • 125 ... when it wants to fall back to the built-in updating procedure of ble.sh.
    • Other ... when the update failed

An example lib/_package.sh might be

_ble_base_package_type=apt

function ble/base/package:apt/update {
  sudo apt upgrade blesh
}

You can also find a real example for AUR (Arch User Repository) here.

Summary of codebase

The core script file ble.sh is generated by combining the following files:

  • ble.pp ... Basic initialiation
  • src/def.sh ... Prototype definitions
  • src/util.sh ... Basic utility functions
  • src/decode.sh ... User-input decoder and keybindings
  • src/color.sh ... Terminal graphic attributes
  • src/canvas.sh ... Terminal layout engine
    • src/canvas.emoji.sh ... Emoji database
  • src/history.sh ... Command history management
  • src/edit.sh ... Line editor
  • src/benchmark.sh ... Measure processing time
  • lib/core-completion.sh ... Prototype definition for completions
  • lib/core-syntax.sh ... Prototype definitions for syntax analyzer

Useful features are implemented in separate modules:

  • lib/keymap.vi.sh ... Vim mode
    • lib/vim-arpeggio.sh ... vim-arpeggio-like plugin
    • lib/vim-surround.sh ... vim-surround-like plugin
  • lib/keymap.emacs.sh ... Emacs mode
  • lib/core-syntax.sh ... Shell parser and syntax highlighting
    • lib/core-syntax-ctx.def ... Definition of parser states
  • lib/core-complete.sh ... Completions including menu-complete, auto-complete, menu-filter, dabbrev, sabbrev, etc.

The following files are initialization scripts:

  • lib/init-term.sh ... Initialize terminal escape sequences (host-to-terminal, i.e. control sequences)
  • lib/init-cmap.sh ... Initialize terminal escape sequences (terminal-to-host, i.e. key sequences)
  • lib/init-bind.sh ... Initialize readline attacher
  • lib/init-msys1.sh ... Workaround for MSYS1 with broken pipes
    • lib/init-msys1-helper.c ... Helper C program for the workaround of broken pipes

The contrib repository contains configurations built on top of the ble.sh framework

  • contrib/config/*.bash are specific configurations whose details may depend on the user's preferences. These are expected to work just by ble-import but at the same time serve as examples for the user's personal versions.
  • contrib/sample/*.bash are examples to explain interfaces for configurations. These are just explanation of the interface so would not actually function.
  • contrib/airline/*.bash are the themes for vim-airline.
  • contrib/integration/*.bash are codes needed for the adjustments with other framework.
  • contrib/*.bash are other miscellaneous configurations. Usually one needs additional configuration after ble-import. These files may be moved to another directory in the future.

Tests

Tests can be run by one of the following commands:

$ make check
$ make check-all
$ bash out/ble.sh --test

Currently, test coverage is very small partly because the testing for interactive behavior and terminal rendering results is hard. Nevertheless, the tests are defined in the following files:

  • lib/core-test.sh ... Library for tests
  • lib/test-bash.sh
  • lib/test-main.sh
  • lib/test-util.sh
  • lib/test-canvas.sh
  • lib/test-edit.sh
  • lib/test-complete.sh
  • lib/test-syntax.sh

Coding styles

These styles are not required for the initial PR as these are too complicated. The maintainer(s) will apply the styles before merging. Nevertheless, it would be useful to follow the styles when you are going to submit PRs many times.

Naming convention for functions

The function names roughly have the following structure, but the rule is ambiguous to some extent, and there are also many exceptions in the codebase.

function_name = (namespace '/')* function_basename ;

namespace = function_basename ;

function_basename = name ext?                        (* namespace function              *)
                  | class '.' name ext?              (* class method                    *)
                  | class '#' name ext?              (* instance method                 *)
                  | class '#' class '::' name ext?   (* instance method (override)      *)
                  | type ':' name ext? ;             (* customization point             *)

name = hyphenated_name                               (* public                          *)
     | '.' hyphenated_name ;                         (* private                         *)

ext = '.hook'                                        (* hook functions for blehook      *)
    | '.draw'                                        (* function operating on DRAW_BUFF *)
    | '.fib'                                         (* worker for ble/util/fiberchain  *)
    | '.proc'                                        (* callback functions              *)
    | '.impl' ;                                      (* internal implementation         *)
                                                     (* ... (and more extensions)       *)

class = name ;

type = hyphenated_name ;

hyphenated_name = (word '-')* word ;

word = /[_a-zA-Z0-9]+/ ;

The function names are basically hyphenated words (such as foo-bar-baz instead of foo_bar_baz, fooBarBaz, or FooBarBaz).

Namespace--The function names can be prefixed by namespaces of the form <namespace>/. Except for the functions intended to be called from the command line or the user configuration, all the functions should be defined in a namespace. The interface of the functions defined in a namespace is subject to change in the future. A function name can also be used as the namespace name for the helper functions to implement the function. For the functions defined in external modules, the following namespace should be used to avoid conflicts:

Types Namespace
contrib/*.bash ble/contrib/<modulename>
contrib/config/*.bash ble/contrib/config:<modulename>
contrib/integration/*.bash ble/contrib/integration:<modulename>
User's public config e.g. <user>
User's private config e.g. <user> or my
User's private config in Bash initialization file e.g. <user>, my or bashrc
User's private config in ble.sh initialization file e.g. <user>, my or blerc

Private--The function basename started with . is considered private within the namespace. All the other functions are public.

Method--The function basename of the form <class>#<name> is considered an instance method. The function basename of the form <class>.<name> is considered a class method. The difference between the two is the latter assumes a single global state while the former assumes that an instance is specified by the first argument or by other means. The way to specify the instance depends on the class. For example, ble/array#* receives the variable name as the first argument while ble/string#* directly receives the string value as the first argument. For ble/textarea#*, the target instance is selected by the shell variable _ble_textarea_panel. The function basename of the form <class1>#<class2>::<name> is used to define a virtual function called by the base class <class2> at the side of the derived class <class1>.

Customization point--The function basename of the form <type>:<name> is used to provide the customization point for dynamic values. This function is typically called as <type>:"$var" with var contains a value that depends on the context including the currently selected class, user configuration, etc.

Extension--The extension <ext> can be used to mark the specific types of functions.

Naming convention for variables

The variable names roughly have the following structure.

local_variable_name = varname
                    | '__ble_' varname
                    | '_ble_local_' varname ;

global_variable_name = ('_' namespace)* '_' varname ;

namespace = word ;
varname = (word '_')* word ;
word = /[a-zA-Z0-9]+/ ;

The variable names are basically lowercase underscored words such as foo_bar_baz (instead of fooBarBaz or FooBarBaz). Some global constants and the exported variables have all-uppercase names, but the lowercase should be basically used.

Local variables--The local variables that are only used in the declared function should be basically unprefixed by an underscore. When the function reads/returns a value through an arbitrary name of in/out variables, the local variable declared within the function should be prefixed by __ble_ or _ble_local_ to avoid the name conflicts between the local and in/out variables.

Global variables--All the global variables, except for the variables that are intended to be directly modified from the command line, should be prefixed by _. Currently, there are no variables intended to be directly modified from the command line[Note 1]. The variables defined in external modules should be namespaced by the module name. The variables in modules in the contrib repository should have the prefix _ble_contrib_<module_name>_

  • [Note 1] In the initial implementation of ble.sh, the ble.sh options are provided as shell variables bleopt_*. As remnant or for backward compatibility, the values of ble.sh options are still stored in the variables of the name bleopt_*, but these variables for the ble.sh options will be renamed in the future.

Function interface

The success/failure states should be returned by the exit status.

ret--All the other values should be basically returned through shell variables. This is because, if a function returned its result through the standard output, the caller would need to capture the results by a command substitution which involves a fork of a subshell and thus is slow. Here, efficiency is the primary interest, so there are exceptions where the standard output can be more efficient: For example, when the output needs to be saved to a file for later use or to be passed to a different process, or when the output needs to be processed by the external commands that read the standard input. The variable name to return a value should be basically ret unless the purpose of the function is to store values. When the function basename has the form get-<name>, one should consider returning the result through the shell variable named <name>. When a function returning multiple values is called from many places, the function may receive an array name as the first argument and store the result in the specified array. When the out variable names are specified in the arguments, they should come first in the list of the arguments.

opts--The argument position should be basically fixed (e.g., never shifted by optional arguments like -f or -o name). The optional arguments can be specified by colon-separated fields (like SHELLOPTS or BASHOPTS) instead of the Unix-style flags and options like -f and -o optarg. This is because the parsing of the Unix-style arguments (with or without getopts) is usually slow. The local variable that receives the colon-separated option typically has the name opts or *opts (e.g. local opts=$2). A single option has the form name or name=value, where name should be hyphenated words like foo-bar-baz. The former form without the value is called a flag option. One can test a flag option by [[ :$opts: == *:flag-name:* ]]. An option of the form name=value can be matched by ble/opts#extract-first-optarg, ble/opts#extract-last-optarg, or ble/opts#extract-all-optargs. The function ble/opts#has can be used to test if either name or name=value is contained

$*--The functions that receive a string should not allow a variable number of arguments that are joined inside the function. A variable number of arguments should be used only when each argument is really processed separately. If necessary, the joining should be performed at the caller side. This is because IFS in the caller context affects the joining $*, so the handling of IFS should be properly done at the caller side. Another reason is for the future extension, e.g., to add another optional argument.

Variable declarations

Local variables should be declared by local (instead of declare or typeset). Variables should be always initialized before referencing their values. This is because uninitialized variables can have random values when shopt -s localvar_inherit is turned on.

The declaration and the initialization of an array variable should be separated as far as the initializer values contain any expansions or spaces. There is a bug in Bash 3.0 that the values are separated by whitespaces even when the values are quoted. When the declaration and the initialization of an array variable are made in a single command, the option -a needs to be specified. Without -a, the entire list (...) including the surrounding parens will be treated as a scalar string in Bash 3.0.

local v; v=("$var")
local -a v=()

# The following may not work as expected
# local v=()         # Bash 3.0 will initialize a scalar with the value v='()'  #D0184
# local -a v=("a b") # Bash 3.0 will split it into the two elements "a" and "b" #D0525

Defining local readonly variables should be avoided because they will hide global variables from the utilities ble/variable#is-global, ble/util/print-global-definitions, etc. Global readonly variables should never be defined. The background is described in the Limitation section of README.

Conditional commands [[ ... ]]

Do not use [ ... ] or test unless it is intended to be a POSIX script. Always use [[ ... ]].

Use [[ word ]] and [[ ! word ]] instead of [[ -n word ]] and [[ -z word ]].

Use == instead of the operator = inside conditional commands.

Quoting--The right-hand sides of == and != inside conditional commands [[ ... ]] should be properly quoted. The right-hand sides of =~ inside the conditional commands [[ ... ]] should be specified by a single parameter expansion as [[ word =~ $rex ]], or one can use ble/string#match 'word' "$rex" instead. In other contexts, the arguments of the conditional command should not be quoted unless raw special characters [[:space:]|&;<>\`"$] are contained in the argument.

No -eq etc--Basically use arithmetic commands ((expr)) instead of the operators -eq, -ne, -gt, -ge, -lt, and -le of the conditional command. They might be used when the test is used as a part of complicated conditions in the conditional commands. For example, [[ -f $file || $word && word -eq 0 ]] can be used instead of [[ -f $file ]] || { [[ $word ]] && ((word == 0)); } because sticking with the arithmetic command would unnecessarily complicate the code in this case.

Other styles

Function definition--There are two forms of function definitions, the POSIX style func() and the ksh style function func, as well as the mixed form. We always use the ksh style of the function definition. The reasons are that it is easier to search by the keyword function and that it is protected by the aliases of the same name as the function (unless the user defines an alias with the same name as the keyword function). The function body is always the compound command { ... } (instead of any other compound commands such as ( ... ), for, if, etc.), and the opening brace { is placed on the first line.

function function-name {
  : do_something
}

ble/util/print--Use ble/util/print instead of echo. Use ble/util/put instead of echo -n. The control characters should be directly specified by the escape string literal $'...' instead of relying on the conversion by echo -e. This is because the builtin echo can change its behavior depending on the shell option shopt -s xpg_echo, where the backslashes in the arguments might have special meaning. ble.sh is intended to work under any possible user options.

Quote/unquote--The arguments where the word splitting and pathname expansions can take place need to be always quoted. When the subject word of the case statement and the right-hand sides of variable assignments (including those specified to assignment builtins declare, typeset, local, export, and readonly) contains $*, ${arr[*]}, or raw special characters [[:space:]|&;<>\`"$], they need to be quoted. In other contexts, the subject word of case and the right-hand sides of variable assignments should not be quoted. In these contexts, the word splitting and pathname expansions will never happen, so the quoting is unnecessary.

Array subscripts are arithmetic context, so the arithmetic expansions $((expr)) inside the array subscripts are redundant. One should simply write it as arr[expr] instead of arr[$((expr))].

ble/util/assign--Use ble/util/assign, ble/util/assign-array, and ble/util/assign-words instead of var=$(cmd), mapfile -t var <<< "$(cmd)", and var=($(cmd)), respectively. The command substitutions $(...) are slow because they involve the forks of subshells.

External commands--Implement it using built-in Bash features as much as possible. If the same result can be obtained by built-in Bash features efficiently, do not use the external commands. When the external command is used, first freeze the path by calling ble/bin#freeze-utility-path '<cmd>' and call the commands through ble/bin/<cmd>. Or save the path of the external command in a variable, and call the command through the variable as "$_saved_cmd" args.... The POSIX commands should be basically used. If non-POSIX commands are used, a POSIX fallback should be always prepared. Please combine multiple calls of external commands into a single call of the command as much as possible. Usually, a single call of sed and awk is preferred over the chain of simple commands.