forked from LF-Certification/bashp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bashpp
executable file
·167 lines (153 loc) · 6.33 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
165
166
167
#!/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 or from Docker repositories listed in $BASHP_INCLUDE_REPOS. This enables
# modular script development by allowing scripts to dynamically include and use functions from
# external packages. The script processes input files line by line, identifying and handling
# include directives for external functions. If a function is not already included, bashpp
# attempts to find it locally or pull it from a Docker repository. Each included function is then
# preprocessed for further include directives.
#
# 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 - Colon-separated list of Docker repositories to search for functions.
# Defaults to 'ghcr.io/lf-certification' if unset.
# INCLUDE_DIR - Directory to search for functions. Used if BASHP_INCLUDE_DIRS is not set.
#
# Dependencies:
# - Docker must be installed and available in the PATH if external functions are to be pulled
# from Docker repositories.
#
# Notes:
# - Functions to be included should be placed in directories or Docker repositories following the
# structure <package>/<function>, where <package> is the name of the package containing the
# function, and <function> is the name of the function file.
# - The script sets the 'set -e' option to exit immediately if a command exits with a non-zero
# status, ensuring that errors in function inclusion or preprocessing are not silently ignored.
# 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" ]] && command -v docker &>/dev/null; then
local repository
IFS=:; for repository in $BASHP_INCLUDE_REPOS; do
local package=${include%%/*}
local image="$repository/bashp-$package:latest"
if docker manifest inspect "$image" &>/dev/null; then
local id
id=$(docker create "$image" none)
mkdir -p "$INCLUDE_DIR/$package"
docker cp "$id:/$include" "$INCLUDE_DIR/$include"
docker rm "$id"
if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
local function=${include##*/}
local gitignore="$INCLUDE_DIR/$package/.gitignore"
[[ -f "$gitignore" ]] && grep -q "^$function\$" "$gitignore" || \
echo "$function" >> "$gitignore"
fi
break
fi
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.
#
# 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.
function preprocess() {
local file=$1
local line
while IFS= read -r line; do
case "$line" in
*::*)
if [[ "$line" =~ ([a-zA-Z0-9_]+)::([a-zA-Z0-9_]+) ]]; then
include_once "${BASH_REMATCH[1]}/${BASH_REMATCH[2]}"
fi
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"