diff --git a/examples/README.md b/examples/README.md index 546f0abf..8998c782 100644 --- a/examples/README.md +++ b/examples/README.md @@ -67,6 +67,7 @@ Each of these examples demonstrates one aspect or feature of bashly. - [completions](completions#readme) - adding bash completion functionality - [validations](validations#readme) - adding validation functions for arguments, flags or environment variables - [hooks](hooks#readme) - adding before/after hooks +- [stacktrace](stacktrace#readme) - adding stacktrace on error ## Real-world-like examples diff --git a/examples/stacktrace/.gitignore b/examples/stacktrace/.gitignore new file mode 100644 index 00000000..d3a4c581 --- /dev/null +++ b/examples/stacktrace/.gitignore @@ -0,0 +1 @@ +download diff --git a/examples/stacktrace/README.md b/examples/stacktrace/README.md new file mode 100644 index 00000000..63eb53e8 --- /dev/null +++ b/examples/stacktrace/README.md @@ -0,0 +1,128 @@ +# Stack Trace Example + +Demonstrates how to show stack trace on error. + +This example was generated with: + +```bash +$ bashly init --minimal +$ bashly add stacktrace +$ bashly generate +# ... now create and edit src/initialize.sh to match the example ... +# ... now edit src/root_command.sh to match the example ... +$ bashly generate +``` + + + +----- + +## `bashly.yml` + +````yaml +name: download +help: Sample minimal application without commands +version: 0.1.0 + +args: +- name: source + required: true + help: URL to download from +- name: target + help: "Target filename (default: same as source)" + +flags: +- long: --force + short: -f + help: Overwrite existing files + +examples: +- download example.com +- download example.com ./output -f +```` + +## `src/initialize.sh` + +````bash +## initialize hook +## +## Any code here will be placed inside the `initialize()` function and called +## before running anything else. +## +## You can safely delete this file if you do not need it. +trap 'stacktrace' ERR +set -o errtrace + +```` + +## `src/root_command.sh` + +````bash +## trigger an error by calling a function that does not exist +no_such_command + +```` + + +## Output + +### `$ ./download` + +````shell +missing required argument: SOURCE +usage: download SOURCE [TARGET] [OPTIONS] + + +```` + +### `$ ./download --help` + +````shell +download - Sample minimal application without commands + +Usage: + download SOURCE [TARGET] [OPTIONS] + download --help | -h + download --version | -v + +Options: + --force, -f + Overwrite existing files + + --help, -h + Show this help + + --version, -v + Show version number + +Arguments: + SOURCE + URL to download from + + TARGET + Target filename (default: same as source) + +Examples: + download example.com + download example.com ./output -f + + + +```` + +### `$ ./download something` + +````shell +./download: line 15: no_such_command: command not found +./download:15 in `root_command`: no_such_command + +Stack trace: + from ./download:15 in `root_command` + from ./download:254 in `run` + from ./download:260 in `main` + + +```` + + + diff --git a/examples/stacktrace/src/bashly.yml b/examples/stacktrace/src/bashly.yml new file mode 100644 index 00000000..58119ca0 --- /dev/null +++ b/examples/stacktrace/src/bashly.yml @@ -0,0 +1,19 @@ +name: download +help: Sample minimal application without commands +version: 0.1.0 + +args: +- name: source + required: true + help: URL to download from +- name: target + help: "Target filename (default: same as source)" + +flags: +- long: --force + short: -f + help: Overwrite existing files + +examples: +- download example.com +- download example.com ./output -f diff --git a/examples/stacktrace/src/initialize.sh b/examples/stacktrace/src/initialize.sh new file mode 100644 index 00000000..0fa0e0d6 --- /dev/null +++ b/examples/stacktrace/src/initialize.sh @@ -0,0 +1,8 @@ +## initialize hook +## +## Any code here will be placed inside the `initialize()` function and called +## before running anything else. +## +## You can safely delete this file if you do not need it. +trap 'stacktrace' ERR +set -o errtrace diff --git a/examples/stacktrace/src/lib/stacktrace.sh b/examples/stacktrace/src/lib/stacktrace.sh new file mode 100644 index 00000000..570caac3 --- /dev/null +++ b/examples/stacktrace/src/lib/stacktrace.sh @@ -0,0 +1,29 @@ +## Stack trace functions [@bashly-upgrade stacktrace] +## This file is a part of Bashly standard library +## +## Usage: +## This function is designed to be called on error. +## +## To enable this functionality, add these lines to your `src/initialize.sh` +## file (Run `bashly add hooks` to add this file): +## +## trap 'stacktrace' ERR +## set -o errtrace +## +## Note that this functionality also requires `set -e`, which is enabled by +## default in bashly generated scripts. +## +stacktrace() { + local exit_status="$?" + local i=0 + local caller_output line func file + + printf "%s:%s in \`%s\`: %s\n" "${BASH_SOURCE[0]}" "${BASH_LINENO[0]}" "${FUNCNAME[1]}" "$BASH_COMMAND" + printf "\nStack trace:\n" + while caller_output="$(caller $i)"; do + read -r line func file <<<"$caller_output" + printf "\tfrom %s:%s in \`%s\`\n" "$file" "$line" "$func" + i=$((i + 1)) + done + exit "$exit_status" +} >&2 diff --git a/examples/stacktrace/src/root_command.sh b/examples/stacktrace/src/root_command.sh new file mode 100644 index 00000000..47c99494 --- /dev/null +++ b/examples/stacktrace/src/root_command.sh @@ -0,0 +1,2 @@ +## trigger an error by calling a function that does not exist +no_such_command diff --git a/examples/stacktrace/test.sh b/examples/stacktrace/test.sh new file mode 100644 index 00000000..e5ca8732 --- /dev/null +++ b/examples/stacktrace/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -x + +bashly add stacktrace --force +bashly generate + +### Try Me ### + +./download +./download --help +./download something diff --git a/lib/bashly/libraries/libraries.yml b/lib/bashly/libraries/libraries.yml index cda16f55..c25eb1d0 100644 --- a/lib/bashly/libraries/libraries.yml +++ b/lib/bashly/libraries/libraries.yml @@ -102,6 +102,19 @@ settings: - source: "settings/settings.yml" target: "settings.yml" +stacktrace: + help: Add a function that shows stacktrace on error. + files: + - source: "stacktrace/stacktrace.sh" + target: "%{user_lib_dir}/stacktrace.%{user_ext}" + post_install_message: | + The stacktrace function is designed to be called automatically on error. + + To enable this functionality, add these lines to your `initialize.sh`: + + g`trap 'stacktrace' ERR` + g`set -o errtrace` + strings: help: Copy an additional configuration file to your project, allowing you to customize all the tips and error strings. files: diff --git a/lib/bashly/libraries/stacktrace/stacktrace.sh b/lib/bashly/libraries/stacktrace/stacktrace.sh new file mode 100644 index 00000000..570caac3 --- /dev/null +++ b/lib/bashly/libraries/stacktrace/stacktrace.sh @@ -0,0 +1,29 @@ +## Stack trace functions [@bashly-upgrade stacktrace] +## This file is a part of Bashly standard library +## +## Usage: +## This function is designed to be called on error. +## +## To enable this functionality, add these lines to your `src/initialize.sh` +## file (Run `bashly add hooks` to add this file): +## +## trap 'stacktrace' ERR +## set -o errtrace +## +## Note that this functionality also requires `set -e`, which is enabled by +## default in bashly generated scripts. +## +stacktrace() { + local exit_status="$?" + local i=0 + local caller_output line func file + + printf "%s:%s in \`%s\`: %s\n" "${BASH_SOURCE[0]}" "${BASH_LINENO[0]}" "${FUNCNAME[1]}" "$BASH_COMMAND" + printf "\nStack trace:\n" + while caller_output="$(caller $i)"; do + read -r line func file <<<"$caller_output" + printf "\tfrom %s:%s in \`%s\`\n" "$file" "$line" "$func" + i=$((i + 1)) + done + exit "$exit_status" +} >&2 diff --git a/spec/approvals/cli/add/list b/spec/approvals/cli/add/list index e7a51128..fbdd60c5 100644 --- a/spec/approvals/cli/add/list +++ b/spec/approvals/cli/add/list @@ -42,6 +42,9 @@ settings Copy a sample settings.yml file to your project, allowing you to customize some bashly options. +stacktrace + Add a function that shows stacktrace on error. + strings Copy an additional configuration file to your project, allowing you to customize all the tips and error strings. diff --git a/spec/approvals/examples/stacktrace b/spec/approvals/examples/stacktrace new file mode 100644 index 00000000..1f8ecf3d --- /dev/null +++ b/spec/approvals/examples/stacktrace @@ -0,0 +1,55 @@ ++ bashly add stacktrace --force +created src/lib/stacktrace.sh + +The stacktrace function is designed to be called automatically on error. + +To enable this functionality, add these lines to your `initialize.sh`: + + trap 'stacktrace' ERR + set -o errtrace + ++ bashly generate +creating user files in src +skipped src/root_command.sh (exists) +created ./download +run ./download --help to test your bash script ++ ./download +missing required argument: SOURCE +usage: download SOURCE [TARGET] [OPTIONS] ++ ./download --help +download - Sample minimal application without commands + +Usage: + download SOURCE [TARGET] [OPTIONS] + download --help | -h + download --version | -v + +Options: + --force, -f + Overwrite existing files + + --help, -h + Show this help + + --version, -v + Show version number + +Arguments: + SOURCE + URL to download from + + TARGET + Target filename (default: same as source) + +Examples: + download example.com + download example.com ./output -f + ++ ./download something +./download: line 15: no_such_command: command not found +./download:15 in `root_command`: no_such_command + +Stack trace: + from ./download:15 in `root_command` + from ./download:255 in `run` + from ./download:261 in `main` diff --git a/spec/bashly/library_source_spec.rb b/spec/bashly/library_source_spec.rb index 5ffe9514..fb5fff0a 100644 --- a/spec/bashly/library_source_spec.rb +++ b/spec/bashly/library_source_spec.rb @@ -101,7 +101,7 @@ it 'returns all libraries as keys' do expect(subject.libraries.keys).to match_array %i[ colors completions completions_script completions_yaml config - help hooks ini lib settings strings test validations yaml + help hooks ini lib settings stacktrace strings test validations yaml render_markdown render_mandoc ] end