Skip to content

Commit 9cc49f5

Browse files
committed
Add shvr update command and migrate oksh to data-driven versions
Introduces three helpers in shvr.sh: - shvr_update [<shell>...]: dispatches to shvr_update_<shell>; shells without an updater are skipped with a stderr notice. - shvr_read_versions <shell> {current|all}: reads versions/<shell>.{current,all}, ignoring blank lines and # comments. - shvr_merge_versions <shell>: unions existing + discovered, optionally filters to one entry per series via shvr_series_<shell>, sorts -V -r -u, and rewrites versions/<shell>.all atomically. Refuses to overwrite if stdin is empty so a silent upstream failure can't wipe the data file. Reports added/dropped/no-change to stderr. Discovery strategies live in common/version_sources/. The first one, github_releases.sh, uses git ls-remote --tags --refs (no GitHub API rate limit) and warns to stderr + returns 1 on network failure or zero matches. oksh is the first migrated shell: heredocs in shvr_current_oksh / shvr_targets_oksh replaced with shvr_read_versions reads from versions/oksh.{current,all}; shvr_update_oksh fetches from ibara/oksh and shvr_series_oksh emits <major>.<minor> so superseded point releases (e.g. 6.7, 6.8) are dropped in favor of the patched final (6.7.1, 6.8.1).
1 parent 89bdf50 commit 9cc49f5

5 files changed

Lines changed: 192 additions & 20 deletions

File tree

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env sh
2+
3+
# SPDX-FileCopyrightText: 2025 Alexandre Gomes Gaigalas <alganet@gmail.com>
4+
# SPDX-License-Identifier: ISC
5+
6+
# shvr_versions_from_github_tags <owner>/<repo> [tag_regex]
7+
#
8+
# Lists tags from a GitHub repository via `git ls-remote --tags --refs` to
9+
# avoid the GitHub API rate limit. Tags are filtered by an optional ERE
10+
# regex with one capture group; the captured substring is what gets emitted.
11+
# Without a regex, the bare tag name is emitted.
12+
#
13+
# Output: one version per line, sorted descending by version (newest first).
14+
# Returns 1 (and warns to stderr) on network failure or when zero tags match,
15+
# so callers can detect that the discovery silently produced nothing.
16+
shvr_versions_from_github_tags ()
17+
(
18+
repo="$1"
19+
regex="${2:-}"
20+
21+
base="${TMPDIR:-/tmp}/shvr_gh_tags.$$"
22+
raw="${base}.raw"
23+
err="${base}.err"
24+
out="${base}.out"
25+
trap 'rm -f "$raw" "$err" "$out"' EXIT INT TERM
26+
27+
if ! git ls-remote --tags --refs "https://github.com/${repo}" > "$raw" 2> "$err"
28+
then
29+
echo "shvr_versions_from_github_tags: git ls-remote failed for ${repo}" >&2
30+
sed 's/^/ /' "$err" >&2
31+
return 1
32+
fi
33+
34+
if test -n "$regex"
35+
then awk '{sub(/^refs\/tags\//, "", $2); print $2}' "$raw" | sed -nE "s/^${regex}\$/\\1/p" | sort -V -u -r > "$out"
36+
else awk '{sub(/^refs\/tags\//, "", $2); print $2}' "$raw" | sort -V -u -r > "$out"
37+
fi
38+
39+
if ! test -s "$out"
40+
then
41+
echo "shvr_versions_from_github_tags: no tags matched for ${repo} (regex: ${regex:-<none>})" >&2
42+
return 1
43+
fi
44+
45+
cat "$out"
46+
)

shvr.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,25 @@ shvr_current ()
7676
shvr_each current "${@:-}"
7777
}
7878

79+
shvr_update ()
80+
{
81+
if test -z "$*"
82+
then set -- $(printf '%s ' $(shvr_interpreters))
83+
fi
84+
85+
while test $# -gt 0
86+
do
87+
interpreter="$1"
88+
shvr_clear_versioninfo
89+
. "${SHVR_DIR_SELF}/variants/${interpreter}.sh"
90+
if command -v "shvr_update_${interpreter}" >/dev/null 2>&1
91+
then "shvr_update_${interpreter}"
92+
else echo "skip: ${interpreter} has no updater yet" >&2
93+
fi
94+
shift
95+
done
96+
}
97+
7998
shvr_interpreters ()
8099
{
81100
find "${SHVR_DIR_SELF}/variants" -type f |
@@ -132,6 +151,102 @@ shvr_untar()
132151
--mode=go-w
133152
}
134153

154+
shvr_read_versions ()
155+
{
156+
interpreter="$1"
157+
which="$2"
158+
file="${SHVR_DIR_SELF}/versions/${interpreter}.${which}"
159+
if test -f "$file"
160+
then sed -e '/^[[:space:]]*$/d' -e '/^[[:space:]]*#/d' -e "s/^/${interpreter}_/" "$file"
161+
fi
162+
}
163+
164+
shvr_filter_latest_per_series ()
165+
{
166+
keyfn="$1"
167+
while IFS= read -r ver
168+
do
169+
if key="$("$keyfn" "$ver" 2>/dev/null)" && test -n "$key"
170+
then printf '%s\t%s\n' "$key" "$ver"
171+
fi
172+
done |
173+
sort -V -r |
174+
awk -F '\t' '!seen[$1]++ { print $2 }'
175+
}
176+
177+
shvr_merge_versions ()
178+
{
179+
interpreter="$1"
180+
file="${SHVR_DIR_SELF}/versions/${interpreter}.all"
181+
mkdir -p "$(dirname "$file")"
182+
183+
if ! test -f "$file"
184+
then : > "$file"
185+
fi
186+
187+
# Subshell so the EXIT trap is function-scoped (POSIX traps are
188+
# process-scoped otherwise).
189+
(
190+
tmp="${file}.tmp.$$"
191+
discovered="${tmp}.discovered"
192+
existing="${tmp}.existing"
193+
merged="${tmp}.merged"
194+
new_only="${tmp}.new"
195+
dropped="${tmp}.dropped"
196+
197+
trap 'rm -f "$discovered" "$existing" "$merged" "$new_only" "$dropped"' EXIT INT TERM
198+
199+
cat > "$discovered"
200+
201+
if ! test -s "$discovered"
202+
then
203+
echo "${interpreter}: no versions discovered (upstream fetch may have failed); refusing to overwrite ${file}" >&2
204+
exit 1
205+
fi
206+
207+
sed -e '/^[[:space:]]*$/d' -e '/^[[:space:]]*#/d' "$file" > "$existing"
208+
209+
if command -v "shvr_series_${interpreter}" >/dev/null 2>&1
210+
then filter_cmd="shvr_filter_latest_per_series shvr_series_${interpreter}"
211+
else filter_cmd="cat"
212+
fi
213+
214+
# Union existing + discovered, optionally filter to one entry per
215+
# series, then sort descending by version.
216+
cat "$existing" "$discovered" | $filter_cmd | sort -V -u -r > "$merged"
217+
218+
if test -s "$existing"
219+
then grep -v -F -x -f "$existing" "$merged" > "$new_only" || true
220+
else cp "$merged" "$new_only"
221+
fi
222+
223+
if test -s "$existing"
224+
then grep -v -F -x -f "$merged" "$existing" > "$dropped" || true
225+
else : > "$dropped"
226+
fi
227+
228+
cp "$merged" "$file"
229+
230+
if test -s "$new_only"
231+
then
232+
n_new=$(wc -l < "$new_only" | tr -d ' ')
233+
echo "${interpreter}: added ${n_new} new version(s):" >&2
234+
sed 's/^/ /' "$new_only" >&2
235+
fi
236+
237+
if test -s "$dropped"
238+
then
239+
n_drop=$(wc -l < "$dropped" | tr -d ' ')
240+
echo "${interpreter}: dropped ${n_drop} superseded version(s):" >&2
241+
sed 's/^/ /' "$dropped" >&2
242+
fi
243+
244+
if ! test -s "$new_only" && ! test -s "$dropped"
245+
then echo "${interpreter}: no changes" >&2
246+
fi
247+
)
248+
}
249+
135250
shvr_fetch()
136251
{
137252
url="$1"

variants/oksh.sh

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,25 @@ shvr_static_oksh ()
1212

1313
shvr_current_oksh ()
1414
{
15-
cat <<-@
16-
oksh_7.8
17-
oksh_7.7
18-
@
15+
shvr_read_versions oksh current
1916
}
2017

2118
shvr_targets_oksh ()
2219
{
23-
cat <<-@
24-
oksh_7.8
25-
oksh_7.7
26-
oksh_7.6
27-
oksh_7.5
28-
oksh_7.4
29-
oksh_7.3
30-
oksh_7.2
31-
oksh_7.1
32-
oksh_7.0
33-
oksh_6.9
34-
oksh_6.8.1
35-
oksh_6.7.1
36-
oksh_6.6
37-
oksh_6.5
38-
@
20+
shvr_read_versions oksh all
21+
}
22+
23+
shvr_update_oksh ()
24+
{
25+
. "${SHVR_DIR_SELF}/common/version_sources/github_releases.sh"
26+
shvr_versions_from_github_tags ibara/oksh 'oksh-([0-9.]+)' |
27+
shvr_merge_versions oksh
28+
}
29+
30+
shvr_series_oksh ()
31+
{
32+
shvr_versioninfo_oksh "$1" || return 1
33+
printf '%s.%s\n' "${version_major}" "${version_minor}"
3934
}
4035

4136
shvr_versioninfo_oksh ()

versions/oksh.all

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
7.8
2+
7.7
3+
7.6
4+
7.5
5+
7.4
6+
7.3
7+
7.2
8+
7.1
9+
7.0
10+
6.9
11+
6.8.1
12+
6.7.1
13+
6.6
14+
6.5

versions/oksh.current

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
7.8
2+
7.7

0 commit comments

Comments
 (0)