Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Thanks for helping keep this list short and honest. The whole point of the list
## How the list works

- `entries/<owner>--<name>.json` is one file per listed project. That is the file you add, edit or delete.
- `list.json` holds the categories, the policy thresholds and the list metadata. It changes rarely.
- `list.json` holds the categories, the policy thresholds and the list metadata. It changes rarely; `fmt` keeps categories and link sections in alphabetical order.
- `README.md`, `docs/index.html`, `metadata.json` and everything under `badges/` are generated. CI rejects pull requests that touch them.
- The generated `docs/` folder is published with GitHub Pages at [floatdrop.github.io/awesome-go](https://floatdrop.github.io/awesome-go/), with search, filtering and sorting over the same data.
- A nightly job refreshes star counts, removes repositories that were archived or deleted, and regenerates the README and badges. Nobody has to remember to clean up.
Expand Down
29 changes: 29 additions & 0 deletions internal/list/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package list

import (
"strings"
"testing"
)

func TestFormatListSortsCategoriesAndSectionsByName(t *testing.T) {
l := &List{
Categories: []Category{{ID: "web", Name: "Web Frameworks"}, {ID: "cli", Name: "Command Line"}, {ID: "audio", Name: "audio"}},
LinkSections: []LinkSection{{ID: "videos", Name: "Videos"}, {ID: "books", Name: "Books"}},
}
out, err := l.FormatList()
if err != nil {
t.Fatal(err)
}
s := string(out)
for _, pair := range [][2]string{{`"audio"`, `"Command Line"`}, {`"Command Line"`, `"Web Frameworks"`}, {`"Books"`, `"Videos"`}} {
if strings.Index(s, pair[0]) > strings.Index(s, pair[1]) {
t.Errorf("%s must come before %s:\n%s", pair[0], pair[1], s)
}
}
if strings.Index(s, `"Web Frameworks"`) > strings.Index(s, `"Books"`) {
t.Errorf("categories must stay ahead of link sections:\n%s", s)
}
if l.Categories[0].ID != "web" {
t.Errorf("FormatList must not reorder the caller's slice")
}
}
16 changes: 12 additions & 4 deletions internal/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,21 @@ func encode(v any) ([]byte, error) {
return buf.Bytes(), nil
}

// FormatList returns the canonical encoding of list.json.
// FormatList returns the canonical encoding of list.json. Categories and
// link sections are sorted by name, case-insensitively, so the README and the
// site list them alphabetically; the two groups never mix, since categories
// hold repositories and link sections hold everything else.
func (l *List) FormatList() ([]byte, error) {
c := *l
c.Schema = listSchema
if c.Categories == nil {
c.Categories = []Category{}
}
c.Categories = append([]Category{}, l.Categories...)
sort.SliceStable(c.Categories, func(i, j int) bool {
return strings.ToLower(c.Categories[i].Name) < strings.ToLower(c.Categories[j].Name)
})
c.LinkSections = append([]LinkSection{}, l.LinkSections...)
sort.SliceStable(c.LinkSections, func(i, j int) bool {
return strings.ToLower(c.LinkSections[i].Name) < strings.ToLower(c.LinkSections[j].Name)
})
return encode(&c)
}

Expand Down
134 changes: 67 additions & 67 deletions list.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,156 +22,156 @@
},
"categories": [
{
"id": "web-frameworks",
"name": "Web Frameworks & Routers",
"description": "HTTP servers, routers and full-stack web frameworks."
"id": "audio-video",
"name": "Audio & Video",
"description": "Codecs, containers and media processing."
},
{
"id": "auth",
"name": "Authentication & Authorization",
"description": "Tokens, sessions, OAuth and access control."
},
{
"id": "caching",
"name": "Caching",
"description": "In-memory caches and eviction policies."
},
{
"id": "cli",
"name": "Command Line",
"description": "Argument parsing, terminal UIs and everything else for building CLIs."
},
{
"id": "gui",
"name": "GUI & Desktop",
"description": "Desktop application frameworks and GUI toolkits."
"id": "concurrency",
"name": "Concurrency",
"description": "Goroutine pools, structured concurrency, synchronization helpers and actor frameworks."
},
{
"id": "configuration",
"name": "Configuration",
"description": "Loading settings from files, environment variables and flags."
},
{
"id": "logging",
"name": "Logging",
"description": "Structured and leveled logging."
},
{
"id": "testing",
"name": "Testing",
"description": "Assertions, mocks, fixtures and integration test helpers."
"id": "data-structures",
"name": "Data Structures",
"description": "Bitsets, compressed bitmaps and other containers beyond slices and maps."
},
{
"id": "databases",
"name": "Databases & SQL",
"description": "Drivers, query builders, ORMs, migrations, embedded and distributed stores."
},
{
"id": "serialization",
"name": "Serialization",
"description": "JSON, YAML, TOML, MessagePack and friends."
},
{
"id": "validation",
"name": "Validation",
"description": "Validating structs and inputs."
},
{
"id": "dependency-injection",
"name": "Dependency Injection",
"description": "Wiring applications together."
},
{
"id": "concurrency",
"name": "Concurrency",
"description": "Goroutine pools, structured concurrency, synchronization helpers and actor frameworks."
"id": "developer-tools",
"name": "Developer Tools",
"description": "Linters, build tools, debuggers, code generators and terminal tools for daily development work."
},
{
"id": "resilience",
"name": "Resilience",
"description": "Retries, backoff, circuit breakers and other patterns for surviving failing dependencies."
"id": "embedded",
"name": "Embedded & Hardware",
"description": "Compilers, frameworks and drivers for microcontrollers, boards and the devices attached to them."
},
{
"id": "caching",
"name": "Caching",
"description": "In-memory caches and eviction policies."
"id": "gui",
"name": "GUI & Desktop",
"description": "Desktop application frameworks and GUI toolkits."
},
{
"id": "http-clients",
"name": "HTTP Clients",
"description": "Making requests with retries, tracing and convenience."
},
{
"id": "logging",
"name": "Logging",
"description": "Structured and leveled logging."
},
{
"id": "messaging",
"name": "Messaging & Queues",
"description": "Message brokers, streaming platforms and task queues."
},
{
"id": "networking",
"name": "Networking & Protocols",
"description": "Protocol implementations, transports and low-level network plumbing."
},
{
"id": "audio-video",
"name": "Audio & Video",
"description": "Codecs, containers and media processing."
"id": "observability",
"name": "Observability",
"description": "Metrics, tracing and instrumentation."
},
{
"id": "messaging",
"name": "Messaging & Queues",
"description": "Message brokers, streaming platforms and task queues."
"id": "resilience",
"name": "Resilience",
"description": "Retries, backoff, circuit breakers and other patterns for surviving failing dependencies."
},
{
"id": "rpc-and-api",
"name": "RPC & API",
"description": "gRPC, RPC frameworks, GraphQL and OpenAPI tooling."
},
{
"id": "auth",
"name": "Authentication & Authorization",
"description": "Tokens, sessions, OAuth and access control."
"id": "serialization",
"name": "Serialization",
"description": "JSON, YAML, TOML, MessagePack and friends."
},
{
"id": "observability",
"name": "Observability",
"description": "Metrics, tracing and instrumentation."
"id": "testing",
"name": "Testing",
"description": "Assertions, mocks, fixtures and integration test helpers."
},
{
"id": "text-and-templates",
"name": "Text, HTML & Templates",
"description": "Parsing and generating markup and text."
},
{
"id": "data-structures",
"name": "Data Structures",
"description": "Bitsets, compressed bitmaps and other containers beyond slices and maps."
},
{
"id": "utilities",
"name": "Utilities",
"description": "General-purpose helpers that end up in every project."
},
{
"id": "developer-tools",
"name": "Developer Tools",
"description": "Linters, build tools, debuggers, code generators and terminal tools for daily development work."
"id": "validation",
"name": "Validation",
"description": "Validating structs and inputs."
},
{
"id": "embedded",
"name": "Embedded & Hardware",
"description": "Compilers, frameworks and drivers for microcontrollers, boards and the devices attached to them."
"id": "web-frameworks",
"name": "Web Frameworks & Routers",
"description": "HTTP servers, routers and full-stack web frameworks."
}
],
"link_sections": [
{
"id": "documentation",
"name": "Documentation",
"description": "Official guides and references for learning and using the language."
"id": "blog-posts",
"name": "Blog Posts",
"description": "Articles that changed how people write Go at work."
},
{
"id": "books",
"name": "Books",
"description": "Books worth reading cover to cover."
},
{
"id": "videos",
"name": "Videos",
"description": "Talks and courses worth watching from start to finish."
},
{
"id": "blog-posts",
"name": "Blog Posts",
"description": "Articles that changed how people write Go at work."
"id": "documentation",
"name": "Documentation",
"description": "Official guides and references for learning and using the language."
},
{
"id": "podcasts",
"name": "Podcasts",
"description": "Shows worth a spot in your queue."
},
{
"id": "videos",
"name": "Videos",
"description": "Talks and courses worth watching from start to finish."
}
]
}
Loading