Skip to content

Commit cf3111a

Browse files
locus313Copilot
andauthored
refactor: extract csv_escape helper to lib/github-common.sh (#57)
Deduplicates the 'sed s/"/""/g' CSV double-quote escaping pattern that was hand-rolled 8 times across github-dockerfile-discovery.sh (6 call sites), github-get-repo-list.sh, and github-archive-old-repos.sh into a single shared csv_escape helper, documented in the shared library docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9de57d0 commit cf3111a

8 files changed

Lines changed: 35 additions & 9 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Provides reusable helpers sourced by scripts:
3333
| `validate_github_token [bearer]` | Verify GITHUB_TOKEN via /user endpoint |
3434
| `validate_slug <value> <label>` | Reject values with non-alphanumeric/hyphen/underscore chars |
3535
| `is_bsd_date` | Returns 0 if `date` is BSD-style (macOS), 1 for GNU (Linux); branch between `date -v` and `date -d` syntax |
36+
| `csv_escape <value>` | Doubles embedded double-quotes for a quoted CSV field |
3637
| `gh_api <path> [--api-version V] [curl args...]` | Bearer-auth REST helper with 5-retry rate-limit handling; optional `--api-version` overrides the default `2022-11-28` header; returns literal `__404__` or `__422__` for those HTTP statuses — callers must check for these sentinels |
3738
| `gh_api_paginate <path> [filter] [version]` | Paginated REST helper, follows Link headers, streams items; returns silently with empty output on 404/422 |
3839
| `get_enterprise_orgs` | Three-tier enterprise org resolver (REST → GraphQL → /user/orgs) |

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ bats tests/test_common.bats
9393

9494
| File | What it covers |
9595
|------|----------------|
96-
| `tests/test_common.bats` | `lib/github-common.sh` — pure-logic functions (`validate_slug`, `require_env_var`, `require_command`, `err`, `configure_gh_auth`, `validate_token`, `get_repo_page_count`, `is_bsd_date`) and API helpers (`gh_api` sentinels, `gh_api_paginate`) |
96+
| `tests/test_common.bats` | `lib/github-common.sh` — pure-logic functions (`validate_slug`, `require_env_var`, `require_command`, `err`, `configure_gh_auth`, `validate_token`, `get_repo_page_count`, `is_bsd_date`, `csv_escape`) and API helpers (`gh_api` sentinels, `gh_api_paginate`) |
9797
| `tests/test_script_validation.bats` | Every script — missing required env vars exit 1, invalid CLI args exit 1, `--help` exits 0, script-specific enum/allowlist validation |
9898
| `tests/mock_curl.sh` | Universal drop-in curl mock (used by both test files); response data via env vars `MOCK_CURL_CODE`, `MOCK_CURL_BODY`, `MOCK_CURL_LINK` |
9999

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ All scripts can leverage a shared utility library for common operations like val
948948
- `validate_github_token [bearer]` — Convenience wrapper for `GITHUB_TOKEN` validation
949949
- `validate_slug <value> <label>` — Exit if value contains characters other than alphanumeric, hyphen, or underscore
950950
- `is_bsd_date` — Return 0 if the system `date` is BSD-style (macOS), 1 for GNU (Linux); use to branch between `date -v` and `date -d` syntax
951+
- `csv_escape <value>` — Double embedded double-quotes so a value is safe to wrap in a quoted CSV field
951952

952953
**Auth helpers:**
953954
- `configure_gh_auth [scope_hint]` — Bridge `GITHUB_TOKEN→GH_TOKEN` for scripts that use the `gh` CLI, or verify an active `gh` auth session if no token is set

enterprise/github-dockerfile-discovery/github-dockerfile-discovery.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,13 +188,13 @@ parse_dockerfile_content() {
188188

189189
# Sanitize fields for CSV (escape double-quotes, wrap in quotes)
190190
local safe_path safe_url
191-
safe_path=$(echo "${dockerfile_path}" | sed 's/"/""/g')
192-
safe_url=$(echo "${html_url}" | sed 's/"/""/g')
191+
safe_path=$(csv_escape "${dockerfile_path}")
192+
safe_url=$(csv_escape "${html_url}")
193193
local safe_image safe_tag safe_digest safe_base
194-
safe_image=$(echo "${image}" | sed 's/"/""/g')
195-
safe_tag=$(echo "${tag}" | sed 's/"/""/g')
196-
safe_digest=$(echo "${digest}" | sed 's/"/""/g')
197-
safe_base=$(echo "${base_image}" | sed 's/"/""/g')
194+
safe_image=$(csv_escape "${image}")
195+
safe_tag=$(csv_escape "${tag}")
196+
safe_digest=$(csv_escape "${digest}")
197+
safe_base=$(csv_escape "${base_image}")
198198
local repo_name
199199
repo_name=$(echo "${repo_full_name}" | cut -d/ -f2)
200200

lib/github-common.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
# validate_token <VAR_NAME> — verify a secondary token variable
2020
# validate_slug <value> [label] — exit if value contains unsafe chars
2121
# is_bsd_date — 0 if `date` is BSD-style (macOS), 1 for GNU (Linux)
22+
# csv_escape <value> — doubles embedded double-quotes for a quoted CSV field
2223
# gh_api <path|url> [--api-version V] [curl args…] — Bearer-auth REST helper with retry;
2324
# returns "__404__"/"__422__" (exit 0) for those codes
2425
# gh_api_paginate <path> [filter] [version] — paginated REST, follows Link headers;
@@ -163,6 +164,15 @@ is_bsd_date() {
163164
date -v-1d > /dev/null 2>&1
164165
}
165166

167+
###
168+
## csv_escape <value>
169+
## Doubles embedded double-quotes so a value is safe to wrap in a quoted
170+
## CSV field (e.g. printf '"%s"' "$(csv_escape "$value")").
171+
###
172+
csv_escape() {
173+
echo "$1" | sed 's/"/""/g'
174+
}
175+
166176
###
167177
## validate_slug <value> <label>
168178
## Exits with status 1 if the value contains characters other than

org-admin/github-archive-old-repos/github-archive-old-repos.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ fetch_old_repos() {
113113
REPO_HTMLURL=$(echo "$REPO_PAYLOAD" | jq -r .html_url)
114114
REPO_DESCRIPTION=$(echo "$REPO_PAYLOAD" | jq -r .description)
115115
REPO_FORK=$(echo "$REPO_PAYLOAD" | jq -r .fork)
116-
ESCAPED_DESC=$(echo "$REPO_DESCRIPTION" | sed 's/"/""/g')
116+
ESCAPED_DESC=$(csv_escape "$REPO_DESCRIPTION")
117117
echo "${REPO_NAME},${REPO_FULLNAME},${REPO_PRIVATE},${REPO_ARCHIVED},${REPO_HTMLURL},\"${ESCAPED_DESC}\",${REPO_FORK},${REPO_UPDATEDAT},${DAYS_SINCE}" >> "$REPORT_FILE"
118118

119119
total_old_repos=$((total_old_repos + 1))

org-admin/github-get-repo-list/github-get-repo-list.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ process_repos () {
7171
REPO_PUSHEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .pushed_at)
7272
REPO_CREATEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .created_at)
7373
REPO_UPDATEDAT=$(echo "${REPO_PAYLOAD}" | jq -r .updated_at)
74-
ESCAPED_DESCRIPTION=$(echo "${REPO_DESCRIPTION}" | sed 's/"/""/g')
74+
ESCAPED_DESCRIPTION=$(csv_escape "${REPO_DESCRIPTION}")
7575

7676
printf '%s,%s,%s,%s,%s,"%s",%s,%s,%s,%s\n' \
7777
"${i}" "${REPO_FULLNAME}" "${REPO_OWNER}" "${REPO_PRIVATE}" "${REPO_HTMLURL}" \

tests/test_common.bats

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ _mock_curl() {
102102
fi
103103
}
104104

105+
# ─── csv_escape ───────────────────────────────────────────────────────────────
106+
107+
@test "csv_escape: leaves plain values unchanged" {
108+
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; csv_escape 'plain value'"
109+
[ "$status" -eq 0 ]
110+
[ "$output" = "plain value" ]
111+
}
112+
113+
@test "csv_escape: doubles embedded double-quotes" {
114+
run bash -c "GITHUB_TOKEN=x source '${LIB_PATH}' 2>/dev/null; csv_escape 'she said \"hi\"'"
115+
[ "$status" -eq 0 ]
116+
[ "$output" = 'she said ""hi""' ]
117+
}
118+
105119
# ─── require_env_var ──────────────────────────────────────────────────────────
106120

107121
@test "require_env_var: exits 1 when variable is unset" {

0 commit comments

Comments
 (0)