-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbashpp
executable file
·164 lines (151 loc) · 6.45 KB
/
bashpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env bash
set -e
# bashpp
# A preprocessor for Bash scripts with support for including external functions.
#
# Description:
# bashpp is a Bash script preprocessor that allows for the inclusion of external functions into
# Bash scripts. It supports including functions from:
# - Local directories specified by $BASHP_INCLUDE_DIRS
# - External sources via extension handlers (e.g., bashpp-oci, bashpp-gh)
# This enables modular script development by allowing scripts to dynamically include and use
# functions from external packages.
#
# Usage:
# bashpp [file]
#
# Parameters:
# file - The path to the Bash script file to be preprocessed. If not specified, bashpp reads from
# /dev/stdin.
#
# Environment Variables:
# BASHP_INCLUDE_DIRS - Colon-separated list of directories to search for functions. If not set,
# defaults to the value of $INCLUDE_DIR or 'libs' if $INCLUDE_DIR is also
# unset.
# BASHP_INCLUDE_REPOS - Comma-separated list of repositories to search for functions.
# Supports the following formats:
# - OCI registries (default prefix or oci://)
# - GitHub releases (gh://)
# Defaults to 'ghcr.io/lf-certification' if unset.
# INCLUDE_DIR - Directory to search for functions. Used if BASHP_INCLUDE_DIRS is not set.
# Defaults to 'libs' if unset.
# OCI_CLIENT - OCI client to use for pulling packages (defaults to 'docker')
# BASHP_CACHE_DIR - Cache directory for GitHub release assets (defaults to XDG_CACHE_HOME/bashp
# or ~/.cache/bashp)
# GH_TOKEN/GITHUB_TOKEN - GitHub authentication token for accessing releases (optional)
#
# Dependencies:
# - Docker (or alternative OCI client specified by OCI_CLIENT) for pulling from OCI registries
# - curl for pulling from GitHub releases
#
# Notes:
# - Functions to be included should be placed in directories or repositories following the
# structure <package>/<function>
# - For OCI registries, packages should be named 'bashp-<package>:latest'
# - For GitHub releases, assets should be named 'bashp-<package>.tar.gz'
# - The script automatically adds included functions to .gitignore when pulled from external
# sources to prevent accidental commits
# - The script sets 'set -e' to exit immediately on errors
# - GitHub releases are cached in $BASHP_CACHE_DIR to avoid repeated downloads
# - Repository URLs can include explicit protocol handlers (oci://, gh://)
# - If no protocol is specified in repository URL, OCI is assumed
# include
# Attempts to include a specified function from a given package.
#
# Usage:
# include <package>/<function>
#
# Parameters:
# package/function - The combined name of the package and function to include, formatted as
# "<package>/<function>".
#
# Description:
# This function checks if the specified function is already included. If not, it searches for the
# function file within directories specified by $BASHP_INCLUDE_DIRS. Failing to find the file locally,
# it attempts to pull the package using Docker from repositories listed in $BASHP_INCLUDE_REPOS
# (defaults to ghcr.io/lf-certification if unset). If the function file is still missing after
# the pull attempt, an error is printed. Otherwise, the function is marked as included and its
# file is sent for preprocessing. Additionally, if the function is pulled from an OCI registry
# and Git is available, the function's name is added to a `.gitignore` file within the package
# directory to prevent it from being accidentally committed to version control.
function include() {
local include=$1
local path
IFS=:; for directory in $BASHP_INCLUDE_DIRS; do
path="$directory/$include"
[[ -f "$path" ]] && break
done
if [[ ! -f "$path" ]]; then
IFS=,; for repository in $BASHP_INCLUDE_REPOS; do
local handler=${repository%%://*}
[[ "$handler" != "$repository" ]] || handler=oci
INCLUDE_DIR=$INCLUDE_DIR BASHP_CACHE_DIR="$BASHP_CACHE_DIR" \
"bashpp-$handler" "${repository#*://}" "$include" >/dev/null
done
fi
[[ ! -f "$path" ]] && echo "bashpp: failed to include $include" >&2 && return 1
preprocess "$path"
}
# include_once
# Ensures a function is included only once.
#
# Usage:
# include_once <package>/<function>
#
# Parameters:
# package/function - The combined name of the package and function to include, formatted as
# "<package>/<function>".
#
# Description:
# This function checks if the specified function, identified by a combination of its package and
# function name, has already been included. If it has not been included, it marks the function as
# included and then calls the 'include' function to include the function from either local
# directories specified by $BASHP_INCLUDE_DIRS or Docker repositories listed in $BASHP_INCLUDE_REPOS.
# This prevents multiple inclusions of the same function, ensuring that each function is included
# only once, thereby avoiding redundancy and potential conflicts.
declare -A INCLUDED=()
function include_once() {
local include=$1
if [[ -z "${INCLUDED[$include]}" ]]; then
INCLUDED[$include]=1
include "$include"
fi
}
# preprocess
# Processes a given file line by line.
#
# Usage:
# preprocess <file>
#
# Parameters:
# file - The path to the file to be processed. If not specified, reads from /dev/stdin.
#
# Description:
# This function reads the specified file line by line. For lines that contain a "::" or
# "#include" directive, it calls the include function to handle the specified function. Each line
# is echoed back, with modifications applied to lines containing directives for function
# inclusion. A newline is added at the end of the file if the last line is not empty.
function preprocess() {
local file=$1
local line
while IFS= read -r line; do
case "$line" in
*::*)
local include
grep -Eo '[a-zA-Z0-9_]+::[a-zA-Z0-9_]+' <<< "$line" |
while read include; do include_once ${include//::/\/}; done
echo "$line"
;;
"#include "*)
include_once ${line#*#include }
;;
*) echo "$line" ;;
esac
done < "$file"
if [[ "$line" != "" ]]; then echo ""; fi
}
INCLUDE_DIR=${INCLUDE_DIR:-libs}
BASHP_INCLUDE_DIRS=${BASHP_INCLUDE_DIRS:-$INCLUDE_DIR}
BASHP_INCLUDE_REPOS=${BASHP_INCLUDE_REPOS:-ghcr.io/lf-certification}
mkdir -p "$INCLUDE_DIR"
preprocess "${1:-/dev/stdin}" "$file"