diff --git a/ingestion/internal/wiki/slug.go b/ingestion/internal/wiki/slug.go new file mode 100644 index 0000000..ab39b46 --- /dev/null +++ b/ingestion/internal/wiki/slug.go @@ -0,0 +1,28 @@ +// ingestion/internal/wiki/slug.go +package wiki + +import ( + "strings" + "unicode" +) + +// Slug converts a title to a kebab-case slug suitable for wiki filenames. +// Rules: lowercase, spaces/hyphens/underscores → hyphens, strip everything else. +func Slug(title string) string { + var b strings.Builder + prevHyphen := true // start true to trim leading hyphens + for _, r := range strings.ToLower(title) { + switch { + case r == ' ' || r == '-' || r == '_': + if !prevHyphen { + b.WriteRune('-') + prevHyphen = true + } + case unicode.IsLetter(r) || unicode.IsDigit(r): + b.WriteRune(r) + prevHyphen = false + // all other characters (apostrophes, colons, dots, etc.) are dropped + } + } + return strings.TrimRight(b.String(), "-") +} diff --git a/ingestion/internal/wiki/slug_test.go b/ingestion/internal/wiki/slug_test.go new file mode 100644 index 0000000..cbed678 --- /dev/null +++ b/ingestion/internal/wiki/slug_test.go @@ -0,0 +1,29 @@ +// ingestion/internal/wiki/slug_test.go +package wiki + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlug(t *testing.T) { + tests := []struct { + input string + want string + }{ + {"Domain Driven Design", "domain-driven-design"}, + {"It's Complicated", "its-complicated"}, + {"gRPC", "grpc"}, + {"GPT-4o", "gpt-4o"}, + {"Property 1: It's Rough", "property-1-its-rough"}, + {" leading spaces ", "leading-spaces"}, + {"multiple spaces", "multiple-spaces"}, + {"already-kebab", "already-kebab"}, + } + for _, tc := range tests { + t.Run(tc.input, func(t *testing.T) { + assert.Equal(t, tc.want, Slug(tc.input)) + }) + } +} diff --git a/ingestion/internal/wiki/types.go b/ingestion/internal/wiki/types.go new file mode 100644 index 0000000..b555d91 --- /dev/null +++ b/ingestion/internal/wiki/types.go @@ -0,0 +1,24 @@ +// ingestion/internal/wiki/types.go +package wiki + +// PageType identifies the wiki subdirectory for a page. +type PageType string + +const ( + PageTypeConcept PageType = "concepts" + PageTypeEntity PageType = "entities" + PageTypeSource PageType = "sources" +) + +// Page is a wiki page to be written to disk. +type Page struct { + Path string // relative to brainDir, e.g. "wiki/sources/foo.md" + Content string // full markdown including YAML frontmatter +} + +// Entry is a summary of an existing wiki page used to build the inventory. +type Entry struct { + Slug string + Title string + Type PageType +}