Skip to content

Commit cbd9e85

Browse files
committed
add ci jobs
1 parent 4f1c034 commit cbd9e85

5 files changed

Lines changed: 359 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
schedule:
10+
- cron: "0 8 * * 1"
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
metadata-validate:
17+
name: Metadata Validate
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
- name: Validate README metadata
22+
run: |
23+
chmod +x scripts/validate-readme-metadata.sh
24+
./scripts/validate-readme-metadata.sh
25+
26+
readme-sync-check:
27+
name: README Sync Check
28+
runs-on: ubuntu-latest
29+
needs: metadata-validate
30+
steps:
31+
- uses: actions/checkout@v4
32+
- name: Regenerate README
33+
run: |
34+
chmod +x scripts/update-readme.sh
35+
./scripts/update-readme.sh
36+
- name: Ensure README is committed
37+
run: |
38+
git diff --exit-code -- README.md
39+
40+
duplicate-problem-detector:
41+
name: Duplicate Problem Detector
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
- name: Detect duplicate problem names
46+
run: |
47+
chmod +x scripts/check-duplicate-problems.sh
48+
./scripts/check-duplicate-problems.sh
49+
50+
swift-build-test:
51+
name: Swift Build And Test
52+
runs-on: macos-latest
53+
steps:
54+
- uses: actions/checkout@v4
55+
- name: Build and test project
56+
shell: bash
57+
run: |
58+
set -euo pipefail
59+
project="Algorithm Solutions In Swift.xcodeproj"
60+
list_output="$(xcodebuild -list -project "$project")"
61+
echo "$list_output"
62+
63+
if echo "$list_output" | sed -n '/Schemes:/,$p' | grep -Fq "Algorithm Solutions In Swift"; then
64+
xcodebuild \
65+
-project "$project" \
66+
-scheme "Algorithm Solutions In Swift" \
67+
-destination "platform=macOS" \
68+
build test
69+
else
70+
xcodebuild -project "$project" -target "Algorithm Solutions In Swift" build
71+
xcodebuild -project "$project" -target "Algorithm Solutions In Swift Tests" build
72+
fi
73+
74+
swift-format-lint-check:
75+
name: Swift Format And Lint Check
76+
runs-on: macos-latest
77+
continue-on-error: true
78+
steps:
79+
- uses: actions/checkout@v4
80+
- name: Run style checks
81+
run: |
82+
chmod +x scripts/check-swift-style.sh
83+
./scripts/check-swift-style.sh
84+
85+
link-health:
86+
name: Link Health
87+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
88+
runs-on: ubuntu-latest
89+
continue-on-error: true
90+
steps:
91+
- uses: actions/checkout@v4
92+
- name: Check README links
93+
uses: lycheeverse/lychee-action@v2
94+
with:
95+
fail: true
96+
args: --no-progress --max-concurrency 8 --timeout 20 --accept 200,429,403 README.md
97+
env:
98+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
99+
100+
codeql:
101+
name: CodeQL
102+
runs-on: macos-latest
103+
permissions:
104+
actions: read
105+
contents: read
106+
security-events: write
107+
strategy:
108+
fail-fast: false
109+
matrix:
110+
language:
111+
- swift
112+
steps:
113+
- uses: actions/checkout@v4
114+
- name: Initialize CodeQL
115+
uses: github/codeql-action/init@v3
116+
with:
117+
languages: ${{ matrix.language }}
118+
- name: Build for CodeQL
119+
shell: bash
120+
run: |
121+
set -euo pipefail
122+
project="Algorithm Solutions In Swift.xcodeproj"
123+
list_output="$(xcodebuild -list -project "$project")"
124+
125+
if echo "$list_output" | sed -n '/Schemes:/,$p' | grep -Fq "Algorithm Solutions In Swift"; then
126+
xcodebuild \
127+
-project "$project" \
128+
-scheme "Algorithm Solutions In Swift" \
129+
-destination "platform=macOS" \
130+
build
131+
else
132+
xcodebuild -project "$project" -target "Algorithm Solutions In Swift" build
133+
fi
134+
- name: Analyze with CodeQL
135+
uses: github/codeql-action/analyze@v3
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
allowlist_path="$repo_root/scripts/duplicate-problem-allowlist.tsv"
6+
entries_file="$(mktemp)"
7+
trap 'rm -f "$entries_file"' EXIT
8+
9+
normalize_problem() {
10+
printf '%s' "$1" \
11+
| tr '[:upper:]' '[:lower:]' \
12+
| sed -E 's/&/ and /g; s/[^a-z0-9]+/ /g; s/^ +| +$//g; s/ +//g'
13+
}
14+
15+
for category in HackerRank LeetCode AlgoExpert GeekForGeeks Pramp; do
16+
while IFS= read -r dir; do
17+
name="$(basename "$dir")"
18+
key="$(normalize_problem "$name")"
19+
[[ -z "$key" ]] && continue
20+
printf '%s\t%s\t%s\n' "$category" "$key" "$name" >> "$entries_file"
21+
done < <(find "$repo_root/$category" -mindepth 1 -maxdepth 1 -type d | LC_ALL=C sort)
22+
done
23+
24+
awk -F '\t' -v allowlist="$allowlist_path" '
25+
BEGIN {
26+
if ((getline line < allowlist) >= 0) {
27+
close(allowlist)
28+
while ((getline line < allowlist) > 0) {
29+
if (line ~ /^#/ || line == "") {
30+
continue
31+
}
32+
split(line, parts, "\t")
33+
if (length(parts[1]) > 0 && length(parts[2]) > 0) {
34+
allow[parts[1] "|" parts[2]] = 1
35+
}
36+
}
37+
close(allowlist)
38+
}
39+
}
40+
{
41+
id = $1 "|" $2
42+
if (!(id in first_name)) {
43+
first_name[id] = $3
44+
next
45+
}
46+
47+
if ($3 != first_name[id] && !(id in allow)) {
48+
printf "Potential duplicate in %s: '\''%s'\'' and '\''%s'\'' (normalized key: %s).\n", $1, first_name[id], $3, $2 > "/dev/stderr"
49+
has_error = 1
50+
}
51+
}
52+
END {
53+
if (has_error) {
54+
print "Duplicate problem check failed." > "/dev/stderr"
55+
exit 1
56+
}
57+
print "Duplicate problem check passed."
58+
}
59+
' "$entries_file"

scripts/check-swift-style.sh

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
errors=0
6+
7+
cd "$repo_root"
8+
9+
tabs_report="$(mktemp)"
10+
trailing_ws_report="$(mktemp)"
11+
trap 'rm -f "$tabs_report" "$trailing_ws_report"' EXIT
12+
13+
if command -v rg >/dev/null 2>&1; then
14+
rg -n $'\t' --glob '*.swift' --glob '!**/.git/**' --glob '!**/.build/**' . > "$tabs_report" || true
15+
if [[ -s "$tabs_report" ]]; then
16+
head -n 50 "$tabs_report"
17+
if [[ "$(wc -l < "$tabs_report")" -gt 50 ]]; then
18+
echo "...truncated (showing first 50 matches)"
19+
fi
20+
echo "Swift style check: tabs found in Swift files." >&2
21+
errors=1
22+
fi
23+
24+
rg -n "[[:blank:]]$" --glob '*.swift' --glob '!**/.git/**' --glob '!**/.build/**' . > "$trailing_ws_report" || true
25+
if [[ -s "$trailing_ws_report" ]]; then
26+
head -n 50 "$trailing_ws_report"
27+
if [[ "$(wc -l < "$trailing_ws_report")" -gt 50 ]]; then
28+
echo "...truncated (showing first 50 matches)"
29+
fi
30+
echo "Swift style check: trailing whitespace found in Swift files." >&2
31+
errors=1
32+
fi
33+
else
34+
grep -RIn --include='*.swift' $'\t' . > "$tabs_report" || true
35+
if [[ -s "$tabs_report" ]]; then
36+
head -n 50 "$tabs_report"
37+
if [[ "$(wc -l < "$tabs_report")" -gt 50 ]]; then
38+
echo "...truncated (showing first 50 matches)"
39+
fi
40+
echo "Swift style check: tabs found in Swift files." >&2
41+
errors=1
42+
fi
43+
44+
grep -RIn --include='*.swift' -E "[[:blank:]]$" . > "$trailing_ws_report" || true
45+
if [[ -s "$trailing_ws_report" ]]; then
46+
head -n 50 "$trailing_ws_report"
47+
if [[ "$(wc -l < "$trailing_ws_report")" -gt 50 ]]; then
48+
echo "...truncated (showing first 50 matches)"
49+
fi
50+
echo "Swift style check: trailing whitespace found in Swift files." >&2
51+
errors=1
52+
fi
53+
fi
54+
55+
swift_format_cmd=""
56+
if command -v swift-format >/dev/null 2>&1; then
57+
swift_format_cmd="swift-format"
58+
elif xcrun --find swift-format >/dev/null 2>&1; then
59+
swift_format_cmd="xcrun swift-format"
60+
fi
61+
62+
if [[ -n "$swift_format_cmd" && -f "$repo_root/.swift-format" ]]; then
63+
if ! eval "$swift_format_cmd lint --recursive ."; then
64+
echo "Swift style check: swift-format lint failed." >&2
65+
errors=1
66+
fi
67+
else
68+
echo "swift-format lint skipped (missing swift-format command or .swift-format config)."
69+
fi
70+
71+
if [[ "$errors" -ne 0 ]]; then
72+
exit 1
73+
fi
74+
75+
echo "Swift style checks passed."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# category<TAB>normalized_problem_key
2+
# Example:
3+
# LeetCode<TAB>mergeintervals
4+
LeetCode mergeintervals
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
metadata_path="$repo_root/scripts/readme-metadata.tsv"
6+
7+
if [[ ! -f "$metadata_path" ]]; then
8+
echo "Missing metadata file: $metadata_path" >&2
9+
exit 1
10+
fi
11+
12+
errors=0
13+
line_no=0
14+
seen_keys_file="$(mktemp)"
15+
trap 'rm -f "$seen_keys_file"' EXIT
16+
17+
is_allowed_category() {
18+
case "$1" in
19+
AlgoExpert|HackerRank|LeetCode|GeekForGeeks|Pramp) return 0 ;;
20+
*) return 1 ;;
21+
esac
22+
}
23+
24+
while IFS= read -r line || [[ -n "$line" ]]; do
25+
line_no=$((line_no + 1))
26+
27+
if [[ -z "$line" || "$line" =~ ^# ]]; then
28+
continue
29+
fi
30+
31+
tab_count="$(awk -F '\t' '{print NF-1}' <<< "$line")"
32+
if [[ "$tab_count" -ne 4 ]]; then
33+
echo "Line $line_no: expected 5 tab-separated columns, found $((tab_count + 1))." >&2
34+
errors=1
35+
continue
36+
fi
37+
38+
IFS=$'\t' read -r category problem difficulty topic problem_url <<< "$line"
39+
40+
if ! is_allowed_category "$category"; then
41+
echo "Line $line_no: invalid category '$category'." >&2
42+
errors=1
43+
fi
44+
45+
if [[ -z "$problem" ]]; then
46+
echo "Line $line_no: problem must not be empty." >&2
47+
errors=1
48+
fi
49+
50+
key="$(printf '%s|%s' "$category" "$problem" | tr '[:upper:]' '[:lower:]')"
51+
if grep -Fqx "$key" "$seen_keys_file"; then
52+
echo "Line $line_no: duplicate metadata entry for '$category / $problem'." >&2
53+
errors=1
54+
else
55+
echo "$key" >> "$seen_keys_file"
56+
fi
57+
58+
if [[ -n "$difficulty" && "$difficulty" != "_" ]]; then
59+
case "$(printf '%s' "$difficulty" | tr '[:upper:]' '[:lower:]')" in
60+
easy|medium|hard) ;;
61+
*)
62+
echo "Line $line_no: difficulty '$difficulty' should be Easy, Medium, Hard, or '_'." >&2
63+
errors=1
64+
;;
65+
esac
66+
fi
67+
68+
if [[ -n "$problem_url" && "$problem_url" != "_" ]]; then
69+
if [[ ! "$problem_url" =~ ^https?:// ]]; then
70+
echo "Line $line_no: problem_url must start with http:// or https://." >&2
71+
errors=1
72+
fi
73+
fi
74+
75+
if [[ -n "$category" && -n "$problem" && ! -d "$repo_root/$category/$problem" ]]; then
76+
echo "Line $line_no: referenced directory '$category/$problem' does not exist." >&2
77+
errors=1
78+
fi
79+
done < "$metadata_path"
80+
81+
if [[ "$errors" -ne 0 ]]; then
82+
echo "Metadata validation failed." >&2
83+
exit 1
84+
fi
85+
86+
echo "Metadata validation passed."

0 commit comments

Comments
 (0)