Skip to content

Commit 681f788

Browse files
author
Daniel Ruthardt
authored
✨ Make package sources flexible; support GH releases (#8)
* ✨ Add support for Basher installation Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * ✨ Make package sources flexible; support ghreleases Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🚸 Add binaries so Basher picks up the right files Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 📝 Remove bin directory I assumed installation option #2, but even then, it is safe to assume that someone would install the binaries or add the `bin` path to the PATH. * 🚚 Rename ghrelease:// to gh:// * 🚚 Rename ghrelease:// to gh:// * 🚚 Rename ghrelease:// to gh:// * 🚚 Rename ghrelease:// to gh:// * 🚸 Be XDG Base Directory Specification compliant * 🚸 Be XDG Base Directory Specification compliant * ♻️ Use another, "non-recursive" but common pattern Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🩹 Add missing file Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 📝 gh has been replaced with curl * 📝 Remove removed installation method Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🩹 Forward another relevant variable Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 📝 Update documentation blocks Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🔥 Remove accidentally committed files Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🩹 Make users and linters happy Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🐛 Ensure output isn't accidentally used as code Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🩹 Properly check for required env vars Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🧑‍💻 Exit early on failure Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🐛 Properly parse the URL of the correct asset Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> * 🐛 Create missing directory structures Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org> --------- Signed-off-by: Daniel Ruthardt <druthardt@linuxfoundation.org>
1 parent 5951c8d commit 681f788

File tree

5 files changed

+175
-47
lines changed

5 files changed

+175
-47
lines changed

.github/workflows/release.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ jobs:
2222
bashp
2323
bashpc
2424
bashpp
25+
bashpp-gh
26+
bashpp-oci

README.md

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,25 @@ functionalities with ease.
1313

1414
- **Modular Design**: Easily include and manage external libraries and packages in your Bash
1515
scripts.
16-
- **Package Management**: Leverage reusable components and packages, potentially hosted in external
17-
repositories or available as Docker images.
16+
- **Package Management**: Leverage reusable components and packages from multiple sources:
17+
- OCI registries (Docker/other OCI clients)
18+
- GitHub releases
19+
- Local directories
1820
- **Enhanced Scripting Capabilities**: Make your Bash scripts more powerful and maintainable with
1921
advanced features.
2022
- **Preprocessing**: A preprocessing step that allows for dynamic inclusion of scripts and
21-
packages. Supports including functions from local directories or Docker repositories, enhancing
22-
modularity and ease of use.
23-
- **Git Integration**: Automatically adds included functions to `.gitignore` when pulling from OCI
24-
registries, preventing unintended commits of external functions.
23+
packages, supporting multiple package sources and enhancing modularity.
24+
- **Git Integration**: Automatically adds included functions to `.gitignore` when pulling from
25+
external sources, preventing unintended commits.
2526

2627
## Getting Started
2728

2829
You can install Bash+ in two ways:
2930

3031
### Option 1: Using Basher (Recommended)
3132

32-
The easiest way to install Bash+ is using [Basher](https://www.basher.it/), a package manager for Bash scripts:
33+
The easiest way to install Bash+ is using [Basher](https://www.basher.it/), a package manager for
34+
Bash scripts:
3335

3436
```bash
3537
# Install basher if you haven't already
@@ -41,7 +43,10 @@ basher install lf-certification/bashp
4143

4244
### Option 2: Manual Installation
4345

44-
Alternatively, you can clone this repository and ensure you have Docker installed, as it's required for fetching some of the packages. Ensure Git is configured correctly if you're using Bash+ in a Git-managed project. Bash+ intelligently adds dynamically included functions to `.gitignore`, keeping your repository clean.
46+
Alternatively, you can clone this repository and ensure you have Docker and `curl` installed, as
47+
they are required for fetching some of the packages. Ensure Git is configured correctly if you're
48+
using Bash+ in a Git-managed project. Bash+ intelligently adds dynamically included functions to
49+
`.gitignore`, keeping your repository clean.
4550

4651
### Usage
4752

@@ -70,7 +75,8 @@ test::hello
7075
test::hello_world
7176
```
7277

73-
This script demonstrates two ways to include and use functions from external libraries or packages in your Bash+ scripts.
78+
This script demonstrates two ways to include and use functions from external libraries or packages
79+
in your Bash+ scripts.
7480

7581
## Contributing
7682

@@ -79,4 +85,5 @@ features.
7985

8086
## License
8187

82-
This project is licensed under the BSD-3-Clause license - see the [LICENSE](LICENSE) file for details.
88+
This project is licensed under the BSD-3-Clause license - see the [LICENSE](LICENSE) file for
89+
details.

bashpp

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ set -e
66
#
77
# Description:
88
# bashpp is a Bash script preprocessor that allows for the inclusion of external functions into
9-
# Bash scripts. It supports including functions from local directories specified by
10-
# $BASHP_INCLUDE_DIRS or from Docker repositories listed in $BASHP_INCLUDE_REPOS. This enables
11-
# modular script development by allowing scripts to dynamically include and use functions from
12-
# external packages. The script processes input files line by line, identifying and handling
13-
# include directives for external functions. If a function is not already included, bashpp
14-
# attempts to find it locally or pull it from a Docker repository. Each included function is then
15-
# preprocessed for further include directives.
9+
# Bash scripts. It supports including functions from:
10+
# - Local directories specified by $BASHP_INCLUDE_DIRS
11+
# - External sources via extension handlers (e.g., bashpp-oci, bashpp-gh)
12+
# This enables modular script development by allowing scripts to dynamically include and use
13+
# functions from external packages.
1614
#
1715
# Usage:
1816
# bashpp [file]
@@ -25,20 +23,33 @@ set -e
2523
# BASHP_INCLUDE_DIRS - Colon-separated list of directories to search for functions. If not set,
2624
# defaults to the value of $INCLUDE_DIR or 'libs' if $INCLUDE_DIR is also
2725
# unset.
28-
# BASHP_INCLUDE_REPOS - Colon-separated list of Docker repositories to search for functions.
26+
# BASHP_INCLUDE_REPOS - Comma-separated list of repositories to search for functions.
27+
# Supports the following formats:
28+
# - OCI registries (default prefix or oci://)
29+
# - GitHub releases (gh://)
2930
# Defaults to 'ghcr.io/lf-certification' if unset.
3031
# INCLUDE_DIR - Directory to search for functions. Used if BASHP_INCLUDE_DIRS is not set.
32+
# Defaults to 'libs' if unset.
33+
# OCI_CLIENT - OCI client to use for pulling packages (defaults to 'docker')
34+
# BASHP_CACHE_DIR - Cache directory for GitHub release assets (defaults to XDG_CACHE_HOME/bashp
35+
# or ~/.cache/bashp)
36+
# GH_TOKEN/GITHUB_TOKEN - GitHub authentication token for accessing releases (optional)
3137
#
3238
# Dependencies:
33-
# - Docker must be installed and available in the PATH if external functions are to be pulled
34-
# from Docker repositories.
39+
# - Docker (or alternative OCI client specified by OCI_CLIENT) for pulling from OCI registries
40+
# - curl for pulling from GitHub releases
3541
#
3642
# Notes:
37-
# - Functions to be included should be placed in directories or Docker repositories following the
38-
# structure <package>/<function>, where <package> is the name of the package containing the
39-
# function, and <function> is the name of the function file.
40-
# - The script sets the 'set -e' option to exit immediately if a command exits with a non-zero
41-
# status, ensuring that errors in function inclusion or preprocessing are not silently ignored.
43+
# - Functions to be included should be placed in directories or repositories following the
44+
# structure <package>/<function>
45+
# - For OCI registries, packages should be named 'bashp-<package>:latest'
46+
# - For GitHub releases, assets should be named 'bashp-<package>.tar.gz'
47+
# - The script automatically adds included functions to .gitignore when pulled from external
48+
# sources to prevent accidental commits
49+
# - The script sets 'set -e' to exit immediately on errors
50+
# - GitHub releases are cached in $BASHP_CACHE_DIR to avoid repeated downloads
51+
# - Repository URLs can include explicit protocol handlers (oci://, gh://)
52+
# - If no protocol is specified in repository URL, OCI is assumed
4253

4354
# include
4455
# Attempts to include a specified function from a given package.
@@ -68,26 +79,12 @@ function include() {
6879
[[ -f "$path" ]] && break
6980
done
7081

71-
if [[ ! -f "$path" ]] && command -v docker &>/dev/null; then
72-
local repository
73-
IFS=:; for repository in $BASHP_INCLUDE_REPOS; do
74-
local package=${include%%/*}
75-
local image="$repository/bashp-$package:latest"
76-
if docker manifest inspect "$image" &>/dev/null; then
77-
local id
78-
id=$(docker create "$image" none)
79-
mkdir -p "$INCLUDE_DIR/$package"
80-
docker cp "$id:/$include" "$INCLUDE_DIR/$include"
81-
docker rm "$id"
82-
83-
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
84-
local function=${include##*/}
85-
local gitignore="$INCLUDE_DIR/$package/.gitignore"
86-
[[ -f "$gitignore" ]] && grep -q "^$function\$" "$gitignore" || \
87-
echo "$function" >> "$gitignore"
88-
fi
89-
break
90-
fi
82+
if [[ ! -f "$path" ]]; then
83+
IFS=,; for repository in $BASHP_INCLUDE_REPOS; do
84+
local handler=${repository%%://*}
85+
[[ "$handler" != "$repository" ]] || handler=oci
86+
INCLUDE_DIR=$INCLUDE_DIR BASHP_CACHE_DIR="$BASHP_CACHE_DIR" \
87+
"bashpp-$handler" "${repository#*://}" "$include" >/dev/null
9188
done
9289
fi
9390

@@ -130,13 +127,13 @@ function include_once() {
130127
# preprocess <file>
131128
#
132129
# Parameters:
133-
# file - The path to the file to be processed.
130+
# file - The path to the file to be processed. If not specified, reads from /dev/stdin.
134131
#
135132
# Description:
136133
# This function reads the specified file line by line. For lines that contain a "::" or
137134
# "#include" directive, it calls the include function to handle the specified function. Each line
138135
# is echoed back, with modifications applied to lines containing directives for function
139-
# inclusion.
136+
# inclusion. A newline is added at the end of the file if the last line is not empty.
140137
function preprocess() {
141138
local file=$1
142139

bashpp-gh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# shellcheck shell=bash
2+
3+
# Script: bashpp-gh
4+
# Extension handler for downloading packages from GitHub releases.
5+
#
6+
# Description:
7+
# This script serves as an extension handler for bashpp to pull packages from GitHub releases.
8+
# When bashpp encounters a GitHub repository URL (gh:// prefix), it delegates the package
9+
# retrieval to this handler. The handler downloads the latest release asset, extracts the
10+
# specified include file, and installs it to the appropriate directory.
11+
#
12+
# Usage:
13+
# bashpp-gh <repository> <include>
14+
# Called automatically by bashpp when processing GitHub repository URLs
15+
#
16+
# Parameters:
17+
# repository - The GitHub repository in the format "owner/repo"
18+
# include - The path to the file to include, in the format "package/function"
19+
#
20+
# Outputs:
21+
# No direct output, but creates files in the cache directory and include directory.
22+
# Updates .gitignore if running within a git repository.
23+
#
24+
# Note:
25+
# Requires curl to be installed.
26+
# Requires either GH_TOKEN or GITHUB_TOKEN environment variable for GitHub API access.
27+
# Will create cache directories as needed at BASHP_CACHE_DIR, XDG_CACHE_HOME/bashp or
28+
# ~/.cache/bashp.
29+
30+
declare -r INCLUDE_DIR=${INCLUDE_DIR:-${INCLUDE_DIR:?must be set}}
31+
declare -r repository=${1:-${repository:?must be set}}
32+
declare -r include=${2:-${include:?must be set}}
33+
34+
command -v curl &>/dev/null || exit 1
35+
36+
declare -r package=${include%%/*}
37+
declare -r cache="${BASHP_CACHE_DIR:-${XDG_CACHE_HOME:-${HOME}/.cache}/bashp}/$repository/$package"
38+
39+
if [[ ! -f "$cache/$include" ]]; then
40+
declare -r asset="bashp-$package.tar.gz"
41+
42+
response=$(curl -H "Authorization: Bearer ${GH_TOKEN:-${GITHUB_TOKEN}}" -sSf \
43+
"https://api.github.com/repos/$repository/releases/latest" | tr -d "\n")
44+
declare -r response
45+
[[ -z "$response" ]] && exit 1
46+
47+
url=$(echo "$response" | grep -Eo '"url":\s*"[^"]*"[^}]*"name":\s*"'"$asset"'"' |
48+
grep -Eo '"url":\s*"[^"]*"' | cut -d'"' -f4)
49+
declare -r url
50+
[[ -z "$url" ]] && exit 1
51+
52+
mkdir -p "$cache"
53+
curl -H "Authorization: Bearer ${GH_TOKEN:-${GITHUB_TOKEN}}" \
54+
-H "Accept: application/octet-stream" -sSfL "$url" -o "$cache/$asset" || exit 1
55+
tar -xzf "$cache/$asset" -C "$cache"
56+
fi
57+
58+
mkdir -p "$INCLUDE_DIR/$package"
59+
cp "$cache/$include" "$INCLUDE_DIR/$include"
60+
61+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
62+
declare -r function=${include##*/}
63+
declare -r gitignore="$INCLUDE_DIR/$package/.gitignore"
64+
grep -q "^$function$" "$gitignore" 2>/dev/null || echo "$function" >> "$gitignore"
65+
fi

bashpp-oci

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# shellcheck shell=bash
2+
3+
# bashpp-oci
4+
# Extension handler for downloading packages from OCI container images.
5+
#
6+
# Description:
7+
# This script serves as an extension handler for bashpp to pull packages from OCI container
8+
# images.
9+
# When bashpp encounters an OCI repository URL (default or oci:// prefix), it delegates the
10+
# package retrieval to this handler. The handler pulls the package, extracts the specified
11+
# include file, and installs it to the appropriate directory.
12+
#
13+
# Usage:
14+
# bashpp-oci <repository> <include>
15+
# Called automatically by bashpp when processing OCI repository URLs
16+
#
17+
# Parameters:
18+
# repository - The OCI repository containing the package image
19+
# include - The path to the file to include, in the format "package/function"
20+
#
21+
# Outputs:
22+
# No direct output, but creates files in the include directory and updates .gitignore
23+
# if running within a git repository.
24+
#
25+
# Note:
26+
# Requires an OCI client (docker by default) to be installed.
27+
# The OCI client can be configured using the OCI_CLIENT environment variable.
28+
# The image is expected to be tagged as 'latest' and follow the naming convention
29+
# 'repository/bashp-package:latest'.
30+
31+
declare -r INCLUDE_DIR=${INCLUDE_DIR:-${INCLUDE_DIR:?must be set}}
32+
declare -r repository=${1:-${repository:?must be set}}
33+
declare -r include=${2:-${include:?must be set}}
34+
35+
declare -r ociClient=${OCI_CLIENT:-docker}
36+
command -v "$ociClient" &>/dev/null || exit 1
37+
38+
declare -r package=${include%%/*}
39+
declare -r image="$repository/bashp-$package:latest"
40+
if "$ociClient" manifest inspect "$image" &>/dev/null; then
41+
mkdir -p "$INCLUDE_DIR/$package"
42+
43+
id=$("$ociClient" create "$image" none)
44+
declare -r id
45+
46+
# shellcheck disable=SC2064
47+
trap "'$ociClient' rm '$id' >/dev/null" EXIT
48+
49+
"$ociClient" cp "$id:/$include" "$INCLUDE_DIR/$include"
50+
51+
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
52+
declare -r function=${include##*/}
53+
declare -r gitignore="$INCLUDE_DIR/$package/.gitignore"
54+
grep -q "^$function$" "$INCLUDE_DIR/$package/.gitignore" 2>/dev/null ||
55+
echo "$function" >> "$gitignore"
56+
fi
57+
fi

0 commit comments

Comments
 (0)