Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ _docs:
uvx --with-requirements=./docs/requirements.txt mkdocs $(WHAT)
docs_build: WHAT = build
docs_build: _docs
docs_serve: WHAT = serve
docs_serve: WHAT = serve --livereload --dirtyreload
docs_serve: _docs
docs_serve2:
uvx --with-requirements=./docs/requirements.txt --with-editable=../mkdocstrings-sh/ mkdocs serve
uvx --with-requirements=./docs/requirements.txt --with-editable=../mkdocstrings-sh/ mkdocs serve --livereload --dirtyreload
docs_docker:
docker build --target doc --output type=local,dest=./public .

2 changes: 1 addition & 1 deletion bin/L_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ L_func_usage() {
fi
done <<<"$v"
if [[ -n "${usage:=${short:+ [-$short]}$long$args}" ]]; then
echo "${FUNCNAME[up]}: usage: ${FUNCNAME[up]}$usage" >&2
echo "${BASH_SOURCE[up]}: usage: ${FUNCNAME[up]}$usage" >&2
fi
fi
}
Expand Down
4 changes: 3 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mkdocs==1.6.1
mkdocstrings==1.0.2
mkdocstrings-sh==0.1.0
mkdocs-autorefs==1.4.3
mkdocs-section-index==0.3.10
mkdocs-material==9.7.0
mkdocs-git-revision-date-localized-plugin==1.5.0
mkdocs-git-revision-date-localized-plugin==1.5.1
117 changes: 102 additions & 15 deletions docs/section/func.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,109 @@
Functions that are useful for writing utility functions that use getopts or similar and want to print simple error message in the terminal.
Functions that are useful for writing utility functions that use `getopts` or similar and want to print simple error messages in the terminal.

The idea is to print usable message to the user and spent no time creating it.
The idea is to print a usable message to the user while spending no time creating it manually.

How it works:
## Usage Guide

- Bash function has a help message stored in the comment preceeding the function.
- We can extract the comment by finding the function definition and parsing the file.
- The comment becomes the help message.
- Usage is extracted from comment by parsing lines that match the format of https://github.com/Kamilcuk/mkdocstrings-sh .
- `# @option -o <var> description` - describes a short option taking an argument
- `# @option -o description` - describes a short option not taking an argument
- `# @arg name description` - describes an argument called name.
- `# @usage description` - allows to specify custom usage line
### Self-Documenting Functions

How to use:
The core feature of this module is extracting documentation from comments directly above the function definition. This allows you to maintain the help message and the code in one place.

- Use `L_func_help` to print the help message.
- Use `L_func_error "error message" || return 2` to print the error message with usage of the function, and then return from your function.
- Use `L_func_assert "not enough arguments" test "$#" -lt 1 || return 2` to print the error message with usage of the function and then return from your function when the command `test "$#" -lt 1` fails, which effectively checks if there are enough arguments.
The comments should follow a specific format compatible with `mkdocstrings-sh`:

- `# @option -o <var> description`: Describes a short option taking an argument.
- `# @option -o description`: Describes a short option not taking an argument.
- `# @arg name description`: Describes a positional argument.
- `# @usage description`: (Optional) Specifies a custom usage line.

### Example Implementation

Here is a complete example of a function using `L_func` utilities:

```bash
# @description Deploys an artifact to a server.
# @option -v Enable verbose mode.
# @option -u <user> User to deploy as. Default: current user.
# @option -h Print this help and return 0.
# @arg file The file to deploy.
# @arg [dest] The destination path. Default: /tmp.
deploy_artifact() {
local OPTIND OPTARG OPTERR opt verbose=0 user="$USER"
while getopts vu:h opt; do
case "$opt" in
v) verbose=1 ;;
u) user="$OPTARG" ;;
h) L_func_help; return 0 ;;
*) L_func_usage; return 2 ;;
esac
done
shift "$((OPTIND-1))"

# Assertions simplify error checking
L_func_assert "File argument is required" test "$#" -ge 1 || return 2
L_func_assert "File does not exist: $1" test -f "$1" || return 2

local file="$1" dest="${2:-/tmp}"

# ... logic ...
}
```

### Printing Help (`L_func_help`)

Calling `L_func_help` inside your function parses the comments above the function and prints a formatted help message to stderr.

```bash
deploy_artifact -h
# Output:
# your_script.sh: deploy_artifact: Deploys an artifact to a server.
# @option -v Enable verbose mode.
# @option -u <user> User to deploy as. Default: current user.
# @option -h Print this help and return 0.
# @arg file The file to deploy.
# @arg [dest] The destination path. Default: /tmp.
```

### Printing Usage (`L_func_usage`)

`L_func_usage` parses the comments to automatically generate a standard usage line. This is useful for `getopts` `*)` case.

```bash
deploy_artifact -x
# Output:
# your_script.sh: illegal option -- x
# your_script.sh: usage: deploy_artifact [-vh] [-u user] file [dest]
```

### Handling Errors (`L_func_error`, `L_func_usage_error`)

- `L_func_error "message"`: Prints an error message prefixed with the function name.
- `L_func_usage_error "message"`: Prints the error message followed by the usage line.

```bash
L_func_error "Connection failed"
# Output: your_script.sh: deploy_artifact: error: Connection failed

L_func_usage_error "Invalid argument"
# Output:
# your_script.sh: deploy_artifact: error: Invalid argument
# your_script.sh: usage: deploy_artifact [-vh] [-u user] file [dest]
```

### Assertions (`L_func_assert`)

`L_func_assert` allows you to write concise checks. It runs a command (usually `test` or `[[ ... ]]`). If the command fails, it prints an error message and returns 2.

```bash
# Explicit check
if [[ ! -f "$file" ]]; then
L_func_error "File not found: $file"
return 2
fi

# Equivalent using L_func_assert
L_func_assert "File not found: $file" test -f "$file" || return 2
```

## API Reference

::: bin/L_lib.sh func
189 changes: 116 additions & 73 deletions docs/section/log.md
Original file line number Diff line number Diff line change
@@ -1,102 +1,145 @@
## L_log
Log library that provides functions for logging messages, similar to the Python logging module. It supports different log levels, custom formatting, filtering, and output redirection.

Log library that provides functions for logging messages.
## Usage Guide

## Usage
### Basic Logging

The `L_log` module is initialized with level INFO on startup.

To log a message you can use several functions functions.
Each of these function takes a message to print.
The most common way to log is using the level-specific functions. They accept a single string or a `printf` format followed by arguments.

```bash
L_info "This is an info message"
L_error "Something went wrong with file: %s" "$filename"
L_debug "Variable x is: %d" "$x"
```
L_trace "Tracing message"
L_debug "Debugging message"
L_info "Informational message"
L_notice "Notice message"
L_warning "Warning message"
L_error "Error message"
L_critical "Critical message"

Available levels (from highest to lowest severity):
- `L_critical`
- `L_error`
- `L_warning`
- `L_notice`
- `L_info`
- `L_debug`
- `L_trace`

### L_run and L_dryrun

`L_run` is a powerful wrapper for executing commands while logging them. It respects the `L_dryrun` variable.

```bash
L_dryrun=1
L_run rm -rf /important/dir
# Outputs: DRYRUN: +rm -rf /important/dir
# Command is NOT executed.

L_dryrun=0
L_run touch new_file
# Outputs: +touch new_file
# Command is executed.
```

By default, if one argument is given to the function, it is outputted as-is.
If more arguments are given, they are parsed as a `printf` formatting string.
### Log Configuration

```
L_info "hello %s" # logs 'hello %s'
L_info "hello %s" "world" # logs 'hello world'
```
Use `L_log_configure` to change the behavior of the logging system.

The configuration of log module is done with [`L_log_configure`](#L_lib.sh--L_log_configure).
#### The "First Call Wins" Principle

By default, `L_log_configure` follows a "configure-once" design. This means the **first call** to this function sets the global configuration, and all subsequent calls are **ignored**.

This prevents libraries or sourced scripts from accidentally overriding your application's logging setup (e.g., changing your JSON format back to plain text).

#### Reconfiguring with `-r`

If you need to change the configuration later (for example, after parsing command-line arguments to change the log level), you **must** use the `-r` (reconfigure) flag.

```bash
# Initial default setup (maybe in a library)
L_log_configure -l info

# This call will be IGNORED because logging is already configured
L_log_configure -l debug

# This call will WORK because -r forces a reconfiguration
L_log_configure -r -l debug
```
declare info verbose
L_argparse -- -v --verbose action=store_1 ---- "$@"
if ((verbose)); then level=debug; else level=info; fi
L_log_configure -l "$level"
```

The logging functions accept the `-s` option to to increase logging stack information level.
#### Setting Log Level

```bash
# Set level via name
L_log_configure -l debug

# Set level via integer constant
L_log_configure -l "$L_LOGLEVEL_ERROR"
```
your_logger() {
L_info -s 1 -- "$@"
}
somefunc() {
L_info hello
your_logger world

#### Integration with Argparse

A common pattern is to set the verbosity via command-line flags.

```bash
main() {
local verbose=0
L_argparse -- \
-v --verbose action=count var=verbose help="Increase verbosity" \
---- "$@"

local level=INFO
if ((verbose >= 2)); then level=TRACE;
elif ((verbose >= 1)); then level=DEBUG; fi

L_log_configure -r -l "$level"
}
```

All these functions forward messages to `L_log` which is main entrypoint for logging.
`L_log` takes two options, `-s` for stacklevel and `-l` for loglevel.
The loglevel can be specified as a sting `info` or `INFO` or `L_LOGLEVEL_INFO` or as a number `30` or `$L_LOGLEVEL_INFO`.
#### Predefined Formats

```
L_log -s 1 -l debug -- "This is a debug message"
```
The library comes with several built-in formatters:
- **Default:** `script:LEVEL:line:message`
- **Long (`-L`):** Includes ISO8601 timestamp, source file, function name, and line number.
- **JSON (`-J`):** Outputs one JSON object per line, ideal for log aggregators.

## Configuration
```bash
# Default format
L_info "hi"
# Output: my_script:info:10:hi

The logging can be configured with `L_log_configure`.
It supports custom log line filtering, custom formatting and outputting, independent.
# Use the long format
L_log_configure -L
L_info "hi"
# Output: 2026-02-06T12:56:25+0100 my_script:main:2 info hi

# Use JSON format for structured logging
L_log_configure -J
L_info "hi"
# Output: {"timestamp":"2026-02-06T12:56:20+0100","funcname":"main","lineno":1,"source":"my_script","level":20,"levelname":"info","message":"hi","script":"my_script","pid":15653,"ppid":1170}
```
my_log_formatter() {
printf -v L_logline "%(%c)T: %s %s" -1 "${L_LOGLEVEL_NAMES[L_logline_loglevel]}" "$*"
}
my_log_ouputter() {
echo "$L_logline" | logger -t mylogmessage
echo "$L_logline" >&2
}
my_log_filter() {
# output only logs from functions starting with L_
[[ $L_logline_funcname == L_* ]]
}
L_log_configure -l debug -F my_log_formatter -o my_log_ouputter -s my_log_selector
```

There are these formatting functions available:
### Customization

You can fully customize how logs are filtered, formatted, and where they are sent.

- `L_log_format_default` - defualt log formatting function.
- `L_log_format_long` - long formatting with timestamp, source, function, line, level and message.
- `L_log_format_json` - format log as JSON lines.
```bash
my_formatter() {
# The message is in "$@", the result must be put in L_logline
printf -v L_logline "[%s] %s" "$L_logline_levelname" "$*"
}

### Available variables in filter, outputter and formatter functions:
my_outputter() {
# Print the formatted L_logline
echo "CUSTOM: $L_logline" >&2
}

There are several variables `L_logline_*` available for callback functions:
L_log_configure -F my_formatter -o my_outputter
```

- `$L_logline` - The variable should be set by the formatting function and printed by the outputting function.
- `$L_logline_level` - Numeric logging level for the message.
- `$L_logline_levelname` - Text logging level for the message. Empty if unknown.
- `$L_logline_funcname` - Name of function containing the logging call.
- `$L_logline_source` - The BASH_SOURCE where the logging call was made.
- `$L_logline_lineno` - The line number in the source file where the logging call was made.
- `$L_logline_stacklevel` - The offset in stack to where the logging call was made.
- `${L_LOGLEVEL_COLORS[L_logline_levelno]:-}` - The color for the log line.
- `$L_logline_color` - Set to 1 if line should print color. Set to empty otherwise.
- This is used in templating. `${L_logline_color:+${L_LOGLEVEL_COLORS[L_logline_levelno]:-}colored${L_logline_color:+$L_COLORRESET}`
#### Available variables in callbacks:
- `$L_logline`: The variable to be set by the formatter and read by the outputter.
- `$L_logline_levelno`: Numeric logging level.
- `$L_logline_levelname`: Text logging level.
- `$L_logline_funcname`: Function name.
- `$L_logline_source`: Source file path.
- `$L_logline_lineno`: Line number.

# Generated documentation from source:
## API Reference

::: bin/L_lib.sh log
::: bin/L_lib.sh log
Loading
Loading