Skip to content

Commit 847965e

Browse files
committed
refactor: enhance adapter and configuration management
- Updated `lib/adapters.sh` to improve documentation and structure for editor and AI tool registries, clarifying the format and loading process. - Introduced new helper functions in `lib/config.sh` for deduplicating and formatting configuration entries, enhancing the `cfg_list` functionality. - Added tests for adapter and configuration functionalities in `tests/adapters.bats` and `tests/config.bats`, ensuring robust validation of registry lookups and configuration mappings. - Improved error handling and output formatting in the configuration listing process, streamlining user interaction and enhancing clarity.
1 parent 8a8d7be commit 847965e

File tree

7 files changed

+509
-223
lines changed

7 files changed

+509
-223
lines changed

lib/adapters.sh

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@
22
# Adapter loading infrastructure — registries, builders, generic fallbacks, and loaders
33
# shellcheck disable=SC2329 # Functions defined inside adapter builders are invoked indirectly
44

5-
# Adapter registries — declarative definitions for standard adapters
6-
# Custom adapters (nano, claude, cursor-ai) remain as override files in adapters/
7-
# Format — editor: name|cmd|type|err_msg|flags (comma-separated: workspace, background)
8-
# Format — ai: name|cmd|err_msg|info_lines (semicolon-separated)
5+
# ── Editor Registry ────────────────────────────────────────────────────
6+
# Declarative definitions for standard editor adapters.
7+
# Custom adapters (nano) remain as override files in adapters/editor/.
8+
#
9+
# Format: name|cmd|type|err_msg|flags
10+
# name — adapter name (used in --editor flag, config, completions)
11+
# cmd — executable to check/invoke (must be in PATH)
12+
# type — "standard" (GUI, opens directory) or "terminal" (runs in current tty)
13+
# err_msg — user-facing message when cmd is not found
14+
# flags — comma-separated modifiers (optional):
15+
# "workspace" — pass .code-workspace file instead of directory
16+
# "background" — launch with & (for terminal editors that fork)
17+
#
18+
# Loading: file override (adapters/editor/<name>.sh) → registry → generic PATH fallback
919
_EDITOR_REGISTRY="
1020
atom|atom|standard|Atom not found. Install from https://atom.io|
1121
cursor|cursor|standard|Cursor not found. Install from https://cursor.com or enable the shell command.|workspace
@@ -20,6 +30,17 @@ webstorm|webstorm|standard|WebStorm 'webstorm' command not found. Enable shell l
2030
zed|zed|standard|Zed not found. Install from https://zed.dev|
2131
"
2232

33+
# ── AI Tool Registry ──────────────────────────────────────────────────
34+
# Declarative definitions for standard AI coding tool adapters.
35+
# Custom adapters (claude, cursor) remain as override files in adapters/ai/.
36+
#
37+
# Format: name|cmd|err_msg|info_lines
38+
# name — adapter name (used in --ai flag, config, completions)
39+
# cmd — executable to check/invoke (must be in PATH)
40+
# err_msg — user-facing message when cmd is not found
41+
# info_lines — semicolon-separated additional help lines shown on error
42+
#
43+
# Loading: file override (adapters/ai/<name>.sh) → registry → generic PATH fallback
2344
_AI_REGISTRY="
2445
aider|aider|Aider not found. Install with: pip install aider-chat|See https://aider.chat for more information
2546
auggie|auggie|Auggie CLI not found. Install with: npm install -g @augmentcode/auggie|See https://www.augmentcode.com/product/CLI for more information

lib/config.sh

Lines changed: 78 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,79 @@ cfg_unset() {
239239
git config $flag --unset-all "$1" 2>/dev/null || true
240240
}
241241

242+
# ── cfg_list helpers ──────────────────────────────────────────────────
243+
# Module-level state for cfg_list auto-mode deduplication.
244+
# Reset by cfg_list() at the start of each "auto" invocation.
245+
_cfg_list_seen=""
246+
_cfg_list_result=""
247+
248+
# Add a config entry, deduplicating by key+value combo.
249+
# Uses Unit Separator ($'\x1f') as delimiter to avoid collision with any value content.
250+
# Usage: _cfg_list_add_entry <origin> <key> <value>
251+
_cfg_list_add_entry() {
252+
local origin="$1" entry_key="$2" entry_value="$3"
253+
local id=$'\x1f'"${entry_key}=${entry_value}"$'\x1f'
254+
255+
# Use [[ ]] for literal string matching (no glob interpretation)
256+
if [[ "$_cfg_list_seen" == *"$id"* ]]; then
257+
return 0
258+
fi
259+
260+
_cfg_list_seen="${_cfg_list_seen}${id}"
261+
_cfg_list_result="${_cfg_list_result}${entry_key}"$'\x1f'"${entry_value}"$'\x1f'"${origin}"$'\n'
262+
}
263+
264+
# Parse git config --get-regexp output and add each entry with an origin label.
265+
# Usage: _cfg_list_parse_entries <origin> <get-regexp-output>
266+
_cfg_list_parse_entries() {
267+
local origin="$1" entries="$2"
268+
local line key value
269+
while IFS= read -r line; do
270+
[ -z "$line" ] && continue
271+
key="${line%% *}"
272+
if [[ "$line" == *" "* ]]; then
273+
value="${line#* }"
274+
else
275+
value=""
276+
fi
277+
_cfg_list_add_entry "$origin" "$key" "$value"
278+
done <<< "$entries"
279+
}
280+
281+
# Format cfg_list output with alignment.
282+
# Detects auto-mode (Unit Separator delimited with origin) vs scoped (space delimited).
283+
# Usage: _cfg_list_format <output>
284+
_cfg_list_format() {
285+
local output="$1"
286+
if [ -z "$output" ]; then
287+
echo "No gtr configuration found"
288+
return 0
289+
fi
290+
291+
printf '%s\n' "$output" | while IFS= read -r line; do
292+
[ -z "$line" ] && continue
293+
294+
local key value origin rest
295+
if [[ "$line" == *$'\x1f'* ]]; then
296+
# Auto-mode format: key<US>value<US>origin
297+
key="${line%%$'\x1f'*}"
298+
rest="${line#*$'\x1f'}"
299+
value="${rest%%$'\x1f'*}"
300+
origin="${rest#*$'\x1f'}"
301+
printf "%-35s = %-25s [%s]\n" "$key" "$value" "$origin"
302+
else
303+
# Scoped format: key value (no origin)
304+
key="${line%% *}"
305+
if [[ "$line" == *" "* ]]; then
306+
value="${line#* }"
307+
else
308+
value=""
309+
fi
310+
printf "%-35s = %s\n" "$key" "$value"
311+
fi
312+
done
313+
}
314+
242315
# List all gtr.* config values
243316
# Usage: cfg_list [scope]
244317
# scope: auto (default), local, global, system
@@ -262,52 +335,11 @@ cfg_list() {
262335
output=$(git config --system --get-regexp '^gtr\.' 2>/dev/null || true)
263336
;;
264337
auto)
265-
# Merge all sources with origin labels
266-
# Deduplicates by key+value combo, preserving all multi-values from highest priority source
267-
local seen_keys=""
268-
local result=""
338+
# Reset module-level state for this invocation
339+
_cfg_list_seen=""
340+
_cfg_list_result=""
269341
local key value line
270342

271-
# Set up cleanup trap for helper functions (protects against early exit/return)
272-
trap 'unset -f _cfg_list_add_entry _cfg_list_parse_entries 2>/dev/null' RETURN
273-
274-
# Helper function to add entries with origin (inline to avoid Bash 3.2 nameref issues)
275-
# Uses Unit Separator ($'\x1f') as delimiter to avoid conflicts with any values
276-
_cfg_list_add_entry() {
277-
local origin="$1"
278-
local entry_key="$2"
279-
local entry_value="$3"
280-
281-
# For multi-valued keys: check if key+value combo already seen
282-
# This allows multiple values for the same key from the same source
283-
# Use Unit Separator as delimiter in seen_keys to avoid collision with any value content
284-
local id=$'\x1f'"${entry_key}=${entry_value}"$'\x1f'
285-
# Use [[ ]] for literal string matching (no glob interpretation)
286-
if [[ "$seen_keys" == *"$id"* ]]; then
287-
return 0
288-
fi
289-
290-
seen_keys="${seen_keys}${id}"
291-
# Use Unit Separator ($'\x1f') as delimiter - won't appear in normal values
292-
result="${result}${entry_key}"$'\x1f'"${entry_value}"$'\x1f'"${origin}"$'\n'
293-
}
294-
295-
# Parse get-regexp output and add each entry with an origin label
296-
_cfg_list_parse_entries() {
297-
local origin="$1"
298-
local entries="$2"
299-
while IFS= read -r line; do
300-
[ -z "$line" ] && continue
301-
key="${line%% *}"
302-
if [[ "$line" == *" "* ]]; then
303-
value="${line#* }"
304-
else
305-
value=""
306-
fi
307-
_cfg_list_add_entry "$origin" "$key" "$value"
308-
done <<< "$entries"
309-
}
310-
311343
# Process in priority order: local > .gtrconfig > global > system
312344
_cfg_list_parse_entries "local" \
313345
"$(git config --local --get-regexp '^gtr\.' 2>/dev/null || true)"
@@ -334,52 +366,16 @@ cfg_list() {
334366
_cfg_list_parse_entries "system" \
335367
"$(git config --system --get-regexp '^gtr\.' 2>/dev/null || true)"
336368

337-
# Clean up helper functions and clear trap (trap handles early exit cases)
338-
unset -f _cfg_list_add_entry _cfg_list_parse_entries
339-
trap - RETURN
340-
341-
output="$result"
369+
output="$_cfg_list_result"
342370
;;
343371
*)
344-
# Unknown scope - warn and fall back to auto
345372
log_warn "Unknown scope '$scope', using 'auto'"
346373
cfg_list "auto"
347374
return $?
348375
;;
349376
esac
350377

351-
# Format and display output
352-
if [ -z "$output" ]; then
353-
echo "No gtr configuration found"
354-
return 0
355-
fi
356-
357-
# Format output with alignment
358-
# Use printf '%s\n' instead of echo for safety with special characters
359-
printf '%s\n' "$output" | while IFS= read -r line; do
360-
[ -z "$line" ] && continue
361-
362-
local key value origin rest
363-
# Check if line uses Unit Separator delimiter (auto mode with origin)
364-
if [[ "$line" == *$'\x1f'* ]]; then
365-
# Format: key<US>value<US>origin
366-
key="${line%%$'\x1f'*}"
367-
rest="${line#*$'\x1f'}"
368-
value="${rest%%$'\x1f'*}"
369-
origin="${rest#*$'\x1f'}"
370-
printf "%-35s = %-25s [%s]\n" "$key" "$value" "$origin"
371-
else
372-
# Format: key value (no origin, for scoped queries)
373-
key="${line%% *}"
374-
# Handle empty values (no space in line means value is empty)
375-
if [[ "$line" == *" "* ]]; then
376-
value="${line#* }"
377-
else
378-
value=""
379-
fi
380-
printf "%-35s = %s\n" "$key" "$value"
381-
fi
382-
done
378+
_cfg_list_format "$output"
383379
}
384380

385381
# Get config value with environment variable fallback

0 commit comments

Comments
 (0)