Start better documentation + fix bug in g::source

This commit is contained in:
Garrett Mills 2024-07-30 01:43:01 -04:00
parent 9fe327a78a
commit c5141fd19e
2 changed files with 241 additions and 1 deletions

241
README.md
View File

@ -1,3 +1,242 @@
# g.bash # g.bash
A Bash framework. A Bash framework.
## Documentation (WIP)
### Global Helpers
- `g::eval [...command]` - Execute a command, but w/ in-framework logging
- `g::silence [...command]` - Execute a command, squashing all output
- `g::now` - Get a timestamp in ISO8061
- `g::bc [...args]` - Execute a `bc` math statement
- `g::awk [...inputs]` - Execute an `awk` print command
### Utilties (`g::util`)
- `isNumeric [value]` - Checks if a given value is a number with optional decimal and positive/negative
- `true` - A successful exit code/return value (`0`)
- `false` - A failure exit code/return value (`1`)
- `returnToEcho [...command]` - Execute a command that returns `true`/`false` and instead echo `1` on success, `0` otherwise
- `uuid` - Generate a UUID
- `uuid::underscore` - Generate a UUID (underscore-separated)
- `escape [value]` - Bash-escape a value for safe use in `eval` strings
- `realpath [path]` - A non-GNU real path resolver
- `trace [?message] [?skip = frames to exclude]` - Generate a stack trace with an optional message
### Strings (`g::str`)
- `quote [str]` - Surround a string in quotes
- `eval [...args]` - Execute a command and quote the output
- `length [str]` - Get the number of chars in a string
- `padLeft [str] [length] [pad character=' ']` - Left-pad a string to the given length
- `padRight [str] [length] [pad character=' ']` - Right-pad a string to the given length
- `padCenter [str] [length] [pad character=' ']` - Center-pad a string to the given length
- `substring [str] [startAt] [length]` - Get a substring
- `offset [str] [startAt]` - Drop the first `n` characters of a string
- `reverse [str]` - Reverse a string
- `startsWith [haystack] [needle]` - Check if the haystack starts with the needle
- `endsWith [haystack] [needle]` - Check if the haystack ends with the needle
- `trim [str]` - Trim the whitespace from either end of the string
- `indexOf [str] [substring]` - Print the character index where the `substring` appears, if any (returns `g::util::false` if no match)
- `replace [str] [find] [replace]` - Find-and-replace all occurrences in a string
- `replace::once [str] [find] [replace]` - Find-and-replace the first occurrence in a string
### Arrays (`g::arr`)
> Note: When passing an array as an argument to a function, it should be passed as `"${array[@]}"`
- `includes [value] [array]` - Checks if a given value exists in the array
- `assoc::hasKey [key] [array]` - Checks if an associative array has a given key
- `join [delimiter] [array]` - Implodes a list of values to a string using the given delimiter
### Math (`g::math`)
- Constants (`g::math::c`)
- `e` - Euler's constant
- `ln2` - Natural log of 2
- `ln10` - Natural log of 10
- `pi` - Pi
- `sqrt05` - Square root of 1/2
- `sqrt2` - Square root of 2
- `abs [num]` - Absolute value of a number
- `cubeRoot [num]` - Cube root of a number
- `squareRoot [num]` - Square root of a number
- `lessThan [left] [right]` - Check if `left < right`
- `greaterThan [left] [right]` - Check if `left > right`
- `equalTo [left] [right]` - Check if `left == right`, numerically
- `isPositive [num]` - Check if `num > 0`
- `isNegative [num]` - Check if `num < 0`
- `signOf [num]` - Prints the leading sign of a number (either `+` or `-`)
- `mod [num] [modulus]` - Compute the modulus of a number
- `ceiling [num]` - Round the number up to the nearest int
- `truncate [num]` - Truncate the number to an int
- `floor [num]` - Round the number down to the nearest int
- `exponent [num] [power]` - Compute `num^power`
- `cos [num]` - Cosine of a number
- `sine [num]` - Sine of a number
- `tan [num]` - Tangent of a number
- `ln [num]` - Natural log of a number
- `log10 [num]` - Log base 10 of a number
- `log2 [num]` - Log base 2 of a number
- `log [num] [base]` - Arbitrary base logarithm
- `random` - Get a random float
- `max [...nums]` - Get the max value of some numbers
- `min [...nums]` - Get the min value of some numbers
- `round [num] [precision]` - Round a number to the specified number of decimal points
### Files (`g::file`)
- `appendString [path] [string]` - Append the string contents to the end of a file
- `exists [path]` - Checks if a file exists
- `directoryExists [path]` - Checks if a directory exists
- `touch [path]` - Create a file if it does not exist
- `truncate [path]` - If a file exists, empty its contents
### Source Code (`g::source`)
`g::source` allows you to organize your scripts into multiple files, and perform filesystem-relative source imports. Similarly, it will prevent the same source file from being re-imported multiple times.
Example:
```shell
# index.bash
g::source src/setup
g::source src/logging
# src/setup.bash
g::source logging
# src/logging.bash
# This file is included once
```
- `g::source [path] [?up=1]` - Load a source file relative to the current script (must end in `.bash` and will only be loaded once)
- `resolve [path] [?up=1]` - Resolve the path of a source file relative to the current script
- `exists [path] [?up=1]` - Check if a source file exists relative to the current script
- `force [path] [?up=1]` - Resolve and load a source file, bypassing the cache
- `has [path] [?up=1]` - Check if the given source file has been loaded
### Configuration File (`g::config`)
Provides a simple key-value way of storing persistent configuration.
Example:
```shell
echo "Last run: $(g::config::get last-run Never)"
g::config::set 'last-run' "$(g::now)"
```
- `setDefault [name]` - Set the default name of the config file
- `getDefault` - Get the default name of the config file
- `init [?name]` - Make sure a config file exists
- `get [name] [?default value]` - Get a value from the default config file
- `get::forFile [file] [name] [?default value]` - Get a value from a non-default config file
- `set [name] [value]` - Store a value in the default config file
- `set::forFile [file] [name] [value]` - Store a value in a non-default config file
### Paths (`g::path`)
- `concat [path] [...parts]` - Combine path segments into a path, accounting for leading and trailing slashes
- `resolve [...parts]` - Concat path parts and determine the real path
- `resolveFrom [base dir] [...parts]` - Concat path parts and determine the real path relative to some base directory
- `cd [...parts]` - Resolve path parts and change directories
- `tmp` - Get a temp file path (and mark it for cleanup)
- `tmpdir` - Get a temp dir path (and mark it for cleanup)
### Errors (`g::error`)
- `throw [message]` - Throw an error
- (global) `try`
- (global) `catch`
### Logging (`g::log`)
`g::log` provides level- and target-aware logging output. The `internal` level is used by `g.bash` itself.
Available levels: `error` | `warn` | `info` | `debug` | `verbose` | `internal`
- `enableTarget [name]` - Enable log outputs for the given target
- `enableAllTargets` - Enable log outputs for ALL targets
- `all` - Shorthand to enable the highest verbosity for ALL targets
- `getLevel` - Get the current logging level
- `setLevel [name]` - Set a new logging level
- `enable [target=stdout|stderr|file] [?param]` - Enable logging to the specified target
- `disable [target=stdout|stderr|file]` - Disable logging to the specified target
- `g::error [output] [?target]` - Write a log message at the `error` level, optionally for a specific target
- Same format for `g::warn`, `g::info`, `g::debug`, `g::verbose`, and `g::internal`
- `g::log::rotate` - Archive the log file to a timestamped version and start a new one
### Locks (`g::lock`)
- `try [name]` - Try to acquire a lock, returning `g::util::true` if successful
- `acquire [name]` - Acquire a lock, sleeping until it is available
- `holds [name]` - Check if we currently hold the given lock
- `release [name]` - Release the given lock if held
- `singleton` - Throw an error if this script is being executed concurrently
- `singleton::acquire` - Wait for other instances of this script to finish
### App Framework (`g::app`)
`g::app` is a way of defining multi-directive CLI applications with argument/option parsing.
Each application is broken up into "commands," and each command can have multiple arguments/flags.
Your source code can provide multiple applications (switching between them using `g::app`) with app-scoped storage.
- `g::app [?name=app] [?description]` - Create a new top-level application
- `get [name] [?default value]` - Get the value of an app-scoped variable
- `set [name] [value]` - Set the value of an app-scoped variable
- `has [name]` - Check if the app-scope has a variable with the given name
- `usage` - Print usage information for the current application
- `invoke [command] [...args]` - Invoke a command in the current application
#### Defining Commands (`g::app::command`)
A command is an invokable sub-command function for an application, which can have multiple flags and positional arguments associated with it.
When a command is executed, its arguments are parsed and a function `app::<app name>::<command name>` is called. Basic example:
```shell
g::app myapp "An example application"
g::app::command greet "Greeting command"
g::app::command::arg name "Name of the person to greet" "World"
g::app::command::flag greeting= "Override the greeting"
function app::myapp::greet() {
local args="$1"
name="$(g::arg "$args" name)"
greeting="$(g::flag "$args" greeting)"
if [ -z "$greeting" ]; then
greeting="Hello,"
fi
echo "$greeting $name"
}
```
- `g::app::command [name] [?description]` - Start defining a new command in the current app
- `exists [name]` - Check if a given command exists in the current app
- `description [name]` - Get the description for a command in the current app
- `arg [name] [?description] [?default value]` - Add a positional argument to the current command
- `flag [name] [?description]` - Add a position-less flag option. If `name` trails with a `=`, then a value is expected
- `rest [name] [?description]` - Register a rest-argument (`[...args]`) for the current command
#### Accessing Arguments
When arguments for a command are parsed, they are stored in an "argument set." The argument set is a series of data structures containing the parsed/validated flags & positional arguments.
The argument set ID is passed to the command handler as the only parameter (`$1`) and can be used to look up the values of flags and arguments.
- `g::args [uuid]` - Get the raw arguments from a parsed set (akin to `$@`)
- `g::arg [uuid] [name]` - Get the value of a positional argument
- `g::arg::has [uuid] [name]` - Checks if a positional argument was provided
- `g::arg::rest [uuid]` - Get all the unmatched CLI arguments/flags
- `g::flag [uuid] [name]` - Get the value of a position-less flag
- `g::flag::has [uuid] [name]` - Checks if a position-less flag was specified

View File

@ -1366,6 +1366,7 @@ function g::source() {
local slug="$1" local slug="$1"
local up="${2:-1}" local up="${2:-1}"
local resolved="$(g::source::resolve "$slug" $(( up + 1 )))"
if ! g::source::has "$slug" $(( up + 1 )); then if ! g::source::has "$slug" $(( up + 1 )); then
g::source::force "$slug" $(( up + 1 )) g::source::force "$slug" $(( up + 1 ))
g__IMPORTED_FILES+=("$resolved") g__IMPORTED_FILES+=("$resolved")