Skip to content

Commit cebe937

Browse files
committed
feat: Create generic regex functions to support bash 3.x + perl and native bash 4+ better regexp
1 parent 3eae1fa commit cebe937

File tree

6 files changed

+158
-18
lines changed

6 files changed

+158
-18
lines changed

.github/workflows/integration-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
exit 1
4444
fi
4545
46-
required_commands=(tar unzip curl jq)
46+
required_commands=(tar unzip curl jq openssl)
4747
for cmd in "${required_commands[@]}"; do
4848
if ! command -v "$cmd" &> /dev/null; then
4949
echo "::error title=Pre-requisite failed::$cmd is not installed"

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ Nowadays more and more command-line tools and applications are distributed via G
2929

3030
## Requirements
3131

32-
- `bash`
32+
- `bash` (version 3 or higher)
33+
- `perl` (by default installed on most unix systems, required for bash < 4)
3334
- `jq`
3435
- `curl` or `wget`
35-
- `tar`
36-
- `unzip`
37-
- `openssl`
36+
- `tar` (by default installed on most unix systems)
37+
- `unzip` (by default installed on most unix systems)
38+
- `openssl` (by default installed on most unix systems)
3839

3940
## Installation
4041

gah

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ function cleanup() {
7171
fi
7272
}
7373

74+
# Check if Bash supports non-greedy quantifiers (Bash 4.0+)
75+
function supports_bash_regex() {
76+
local bash_major_version="${BASH_VERSINFO[0]}"
77+
[[ "$bash_major_version" -ge 4 ]]
78+
}
79+
80+
#endregion
81+
#--------------------------------------------------
82+
#region HTTP functions
83+
7484
function http_get() {
7585
local url="$1"
7686
# if curl is available, use it, otherwise use wget.
@@ -196,6 +206,53 @@ function get_md_url_regexp() {
196206
echo "\(https:\/\/[a-z0-9.\/]+\/$(get_filename_regexp)\)"
197207
}
198208

209+
# Generic regex matching function
210+
# Usage: regex_match "string" "pattern"
211+
# Returns: 0 if matches, 1 if doesn't match
212+
function regex_match() {
213+
local string="$1"
214+
local pattern="$2"
215+
216+
if supports_bash_regex; then
217+
[[ "$string" =~ $pattern ]]
218+
else
219+
echo "$string" | perl -ne "exit(0) if /$pattern/; exit(1)" 2>/dev/null
220+
fi
221+
}
222+
223+
# Extract capture group from regex match
224+
# Usage: regex_capture "string" "pattern" capture_group_number
225+
# Returns: captured text or empty string
226+
function regex_capture() {
227+
local string="$1"
228+
local pattern="$2"
229+
local capture_group="${3:-1}"
230+
231+
if supports_bash_regex; then
232+
if [[ "$string" =~ $pattern ]]; then
233+
echo "${BASH_REMATCH[$capture_group]}"
234+
fi
235+
else
236+
echo "$string" | perl -ne "print \$$capture_group if /$pattern/"
237+
fi
238+
}
239+
240+
# Extract full match from regex
241+
# Usage: regex_match_text "string" "pattern"
242+
# Returns: matched text or empty string
243+
function regex_match_text() {
244+
local string="$1"
245+
local pattern="$2"
246+
247+
if supports_bash_regex; then
248+
if [[ "$string" =~ $pattern ]]; then
249+
echo "${BASH_REMATCH[0]}"
250+
fi
251+
else
252+
echo "$string" | perl -ne "print \$& if /$pattern/"
253+
fi
254+
}
255+
199256
#endregion
200257
#--------------------------------------------------
201258
#region GitHub API functions
@@ -250,8 +307,9 @@ function fetch_release_info() {
250307
function get_repo_name() {
251308
local release_json="$1"
252309
local html_url=$(jq -r '.html_url' "$release_json")
253-
if [[ "$html_url" =~ $REGEXP_NAME_FROM_HTML_URL ]]; then
254-
echo "${BASH_REMATCH[1]}"
310+
local match=$(regex_capture "$html_url" "$REGEXP_NAME_FROM_HTML_URL" 1)
311+
if [[ -n "$match" ]]; then
312+
echo "$match"
255313
fi
256314
}
257315

@@ -266,8 +324,8 @@ function find_download_url() {
266324

267325
for name in $(jq -r '.assets[].name' "$release_json"); do
268326
local lower_name=$(echo "$name" | tr '[A-Z]' '[a-z]')
269-
if [[ "$lower_name" =~ $regexp ]]; then
270-
if [[ "$lower_name" =~ $REGEXP_IGNORE_PATTERN ]]; then
327+
if regex_match "$lower_name" "$regexp"; then
328+
if regex_match "$lower_name" "$REGEXP_IGNORE_PATTERN"; then
271329
print_debug " $name ... Ignored"
272330
else
273331
print_debug " $name ... MATCH!"
@@ -292,13 +350,13 @@ function find_download_url() {
292350

293351
jq -r '.body' "$release_json" | while read -r line; do
294352
lower_line=$(echo "$line" | tr '[A-Z]' '[a-z]')
295-
if [[ "$lower_line" =~ $regexp ]]; then
353+
local match=$(regex_match_text "$lower_line" "$regexp")
354+
if [[ -n "$match" ]]; then
296355
print_debug " $line ... MATCH!"
297356
found="true"
298-
line="${BASH_REMATCH[0]}"
299-
line=${line:1}
300-
line=${line%?}
301-
echo $line
357+
match=${match:1}
358+
match=${match%?}
359+
echo "$match"
302360
else
303361
print_debug " $line ... Doesn't match"
304362
fi
@@ -526,16 +584,17 @@ function command_install() {
526584
print_debug "OK, lower case file name: $lower_file_name"
527585

528586
print_debug "Checking if file should be skipped"
529-
if [[ "$lower_file_name" =~ $REGEXP_SKIP_FILES ]]; then
587+
if regex_match "$lower_file_name" "$REGEXP_SKIP_FILES"; then
530588
print_debug "File name matches skip pattern - Skipping: $file_name"
531589
continue
532590
fi
533591
print_debug "No skip pattern matched - File will be installed"
534592

535593
print_debug "Removing version/os/arch parts from file name if present"
536594
local regexp=$(get_name_regexp)
537-
if [[ "$file_name" =~ $regexp ]]; then
538-
file_name="${BASH_REMATCH[1]}"
595+
local match=$(regex_capture "$file_name" "$regexp" 1)
596+
if [[ -n "$match" ]]; then
597+
file_name="$match"
539598
print_debug "OK, cleaned file name"
540599
fi
541600

@@ -620,7 +679,7 @@ function main() {
620679
throw_error 1 "Please provide either repo in format 'owner/repo_name' or known alias.\n$HELP_STRING"
621680

622681
elif [[ "$2" == *"/"* ]]; then
623-
if [[ "$2" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$ ]]; then
682+
if regex_match "$2" "^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$"; then
624683
repo="$2"
625684
else
626685
throw_error 2 "Given string '$2' is not in format 'owner/repo_name'.\n$HELP_STRING"

test/00_utils.bats

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,15 @@ teardown() {
2424

2525
assert_output "amd64"
2626
}
27+
28+
@test "supports_bash_regex should return true for Bash 4+" {
29+
# This test will pass/fail based on actual Bash version
30+
# If running Bash 4+, it should succeed
31+
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
32+
run supports_bash_regex
33+
assert_success
34+
else
35+
run supports_bash_regex
36+
assert_failure
37+
fi
38+
}

test/01_regexp_functions.bats

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,63 @@ teardown() {
192192
assert_success
193193
assert_output '([a-z][a-z0-9_-]+?)([_-]v?[0-9.]+)?([._-](apple[._-])?(darwin|macos|osx)[._-](arm64|aarch64|universal)|[._-](arm64|aarch64|universal)[._-](apple[._-])?(darwin|macos|osx))([_-][a-z0-9_-]+)?(\.zip|\.tar\.gz|\.tgz|\.tar\.xz|\.txz|\.tar\.bz2|\.tbz)?'
194194
}
195+
196+
@test "regex_match should match simple pattern" {
197+
run regex_match "hello-world" "^hello-world$"
198+
assert_success
199+
}
200+
201+
@test "regex_match should not match wrong pattern" {
202+
run regex_match "hello-world" "^goodbye$"
203+
assert_failure
204+
}
205+
206+
@test "regex_match should match complex pattern" {
207+
run regex_match "app-v1.2.3-linux-amd64.tar.gz" "^[a-z]+-v[0-9.]+-linux-amd64"
208+
assert_success
209+
}
210+
211+
@test "regex_match should handle non-greedy quantifiers" {
212+
run regex_match "app-v1.2.3-linux-amd64" "^([a-z]+?)-.+"
213+
assert_success
214+
}
215+
216+
@test "regex_capture should extract first capture group" {
217+
result=$(regex_capture "https://github.com/owner/repo" '^https://github.com/([^/]+)/([^/]+)$' 1)
218+
assert_equal "$result" "owner"
219+
}
220+
221+
@test "regex_capture should extract second capture group" {
222+
result=$(regex_capture "https://github.com/owner/repo" '^https://github.com/([^/]+)/([^/]+)$' 2)
223+
assert_equal "$result" "repo"
224+
}
225+
226+
@test "regex_capture should extract with non-greedy quantifier" {
227+
result=$(regex_capture "app-v1.2.3-linux-amd64" '^([a-z]+?)-v' 1)
228+
assert_equal "$result" "app"
229+
}
230+
231+
@test "regex_capture should return empty string when no match" {
232+
result=$(regex_capture "hello" '^goodbye(.+)$' 1)
233+
assert_equal "$result" ""
234+
}
235+
236+
@test "regex_capture should default to first capture group" {
237+
result=$(regex_capture "app-v1.2.3" '^([a-z]+)-v([0-9.]+)$')
238+
assert_equal "$result" "app"
239+
}
240+
241+
@test "regex_match_text should extract full match" {
242+
result=$(regex_match_text "Download from https://example.com/file.tar.gz here" 'https://[^ ]+')
243+
assert_equal "$result" "https://example.com/file.tar.gz"
244+
}
245+
246+
@test "regex_match_text should return empty string when no match" {
247+
result=$(regex_match_text "hello world" 'https://[^ ]+')
248+
assert_equal "$result" ""
249+
}
250+
251+
@test "regex_match_text should handle complex pattern with non-greedy" {
252+
result=$(regex_match_text "app-v1.2.3-linux-amd64.tar.gz" '([a-z]+?)-v[0-9.]+-linux-amd64\.tar\.gz')
253+
assert_equal "$result" "app-v1.2.3-linux-amd64.tar.gz"
254+
}

tools/install.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ fi
6565
print_green "OK"
6666

6767
# Check if required commands are installed
68+
# Perl is only required for Bash 3.x (Bash 4+ has native regex support)
69+
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
70+
print_yellow "Bash 3.x detected - Perl is required for regex support"
71+
require_command perl
72+
else
73+
print_green "Bash 4+ detected - native regex support available"
74+
fi
75+
6876
require_command tar
6977
require_command unzip
7078
require_command curl wget

0 commit comments

Comments
 (0)