mirror of
https://github.com/veggiemonk/awesome-docker.git
synced 2026-05-16 04:47:41 +02:00
feat(linter): add SortFile for sort-only README fixing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,3 +145,84 @@ func FixFile(path string) (int, error) {
|
|||||||
w.WriteString("\n")
|
w.WriteString("\n")
|
||||||
return fixCount, w.Flush()
|
return fixCount, w.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SortFile reads the README, sorts entries alphabetically within each section,
|
||||||
|
// and writes the result back. Unlike FixFile, it does not modify descriptions
|
||||||
|
// (no capitalization, period, or attribution changes).
|
||||||
|
func SortFile(path string) (int, error) {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
lines = append(lines, scanner.Text())
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fixCount := 0
|
||||||
|
|
||||||
|
var headingLines []int
|
||||||
|
for i, line := range lines {
|
||||||
|
if sectionHeadingRe.MatchString(line) {
|
||||||
|
headingLines = append(headingLines, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, headingIdx := range headingLines {
|
||||||
|
start := headingIdx + 1
|
||||||
|
end := len(lines)
|
||||||
|
if i+1 < len(headingLines) {
|
||||||
|
end = headingLines[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var entryPositions []int
|
||||||
|
var entries []parser.Entry
|
||||||
|
for lineIdx := start; lineIdx < end; lineIdx++ {
|
||||||
|
entry, err := parser.ParseEntry(lines[lineIdx], lineIdx+1)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entryPositions = append(entryPositions, lineIdx)
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted := SortEntries(entries)
|
||||||
|
for j, e := range sorted {
|
||||||
|
lineIdx := entryPositions[j]
|
||||||
|
// Use the original Raw line from the sorted entry to preserve formatting
|
||||||
|
if lines[lineIdx] != e.Raw {
|
||||||
|
fixCount++
|
||||||
|
lines[lineIdx] = e.Raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixCount == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
w := bufio.NewWriter(out)
|
||||||
|
for i, line := range lines {
|
||||||
|
w.WriteString(line)
|
||||||
|
if i < len(lines)-1 {
|
||||||
|
w.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteString("\n")
|
||||||
|
return fixCount, w.Flush()
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,6 +139,94 @@ Some text here.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSortFile(t *testing.T) {
|
||||||
|
content := `# Awesome Docker
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- [Zebra](https://example.com/zebra) - a tool by [@author](https://github.com/author)
|
||||||
|
- [Alpha](https://example.com/alpha) - another tool
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
Some text here.
|
||||||
|
`
|
||||||
|
tmp, err := os.CreateTemp("", "readme-sort-*.md")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
|
||||||
|
if _, err := tmp.WriteString(content); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tmp.Close()
|
||||||
|
|
||||||
|
count, err := SortFile(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
t.Fatal("expected fixes, got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result := string(data)
|
||||||
|
|
||||||
|
// Check sorting: Alpha should come before Zebra
|
||||||
|
alphaIdx := strings.Index(result, "[Alpha]")
|
||||||
|
zebraIdx := strings.Index(result, "[Zebra]")
|
||||||
|
if alphaIdx > zebraIdx {
|
||||||
|
t.Error("expected Alpha before Zebra after sort")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFile must NOT capitalize descriptions
|
||||||
|
if strings.Contains(result, "- A tool") {
|
||||||
|
t.Errorf("SortFile should not capitalize descriptions, got:\n%s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFile must NOT remove attribution
|
||||||
|
if !strings.Contains(result, "@author") {
|
||||||
|
t.Errorf("SortFile should preserve attribution, got:\n%s", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFile must NOT add periods
|
||||||
|
if strings.Contains(result, "another tool.") {
|
||||||
|
t.Errorf("SortFile should not add periods, got:\n%s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSortFileIdempotent(t *testing.T) {
|
||||||
|
content := `# Awesome Docker
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
- [Alpha](https://example.com/alpha) - A tool.
|
||||||
|
- [Bravo](https://example.com/bravo) - B tool.
|
||||||
|
`
|
||||||
|
tmp, err := os.CreateTemp("", "readme-sort-idem-*.md")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
|
||||||
|
if _, err := tmp.WriteString(content); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tmp.Close()
|
||||||
|
|
||||||
|
count, err := SortFile(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if count != 0 {
|
||||||
|
t.Fatalf("expected no changes on already-sorted file, got %d", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFixFileSortsAcrossBlankLinesAndIsIdempotent(t *testing.T) {
|
func TestFixFileSortsAcrossBlankLinesAndIsIdempotent(t *testing.T) {
|
||||||
content := `# Awesome Docker
|
content := `# Awesome Docker
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user