diff options
| author | Gab Virebent <gabriel1@virebent.art> | 2026-07-07 16:24:28 +0200 |
|---|---|---|
| committer | Gab Virebent <gabriel1@virebent.art> | 2026-07-07 16:24:28 +0200 |
| commit | 37f156c30e2a2b01c840d39f0dd2bdbf811732d6 (patch) | |
| tree | e2ce84dc7f88ce0eb312899448f91b7bb93ca8a3 /internal/cms/menu.go | |
| download | gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.tar.gz gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.tar.xz gemcms-37f156c30e2a2b01c840d39f0dd2bdbf811732d6.zip | |
Diffstat (limited to 'internal/cms/menu.go')
| -rw-r--r-- | internal/cms/menu.go | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/internal/cms/menu.go b/internal/cms/menu.go new file mode 100644 index 0000000..f430525 --- /dev/null +++ b/internal/cms/menu.go @@ -0,0 +1,193 @@ +package cms + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +func CreateMenu(root, name string) (string, error) { + name = slugify(name) + if name == "" || name == "untitled" { + return "", fmt.Errorf("menu name is required") + } + path := filepath.Join(root, "site/menus", name+".toml") + data := "name = " + formatString(name) + "\n\n" + return path, writeNewFile(path, []byte(data)) +} + +func LoadMenus(root string) (map[string]Menu, error) { + dir := filepath.Join(root, "site/menus") + menus := map[string]Menu{} + if _, err := os.Stat(dir); err != nil { + if os.IsNotExist(err) { + return menus, nil + } + return nil, err + } + err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + if entry.IsDir() || filepath.Ext(path) != ".toml" { + return nil + } + menu, err := loadMenu(path) + if err != nil { + return err + } + menus[menu.Name] = menu + return nil + }) + return menus, err +} + +func AddMenuItem(root string, opts MenuItemOptions) error { + name := slugify(opts.Menu) + if name == "" || name == "untitled" { + return fmt.Errorf("menu name is required") + } + path := filepath.Join(root, "site/menus", name+".toml") + menu, err := loadMenu(path) + if err != nil { + if os.IsNotExist(err) { + if _, createErr := CreateMenu(root, name); createErr != nil { + return createErr + } + menu = Menu{Name: name} + } else { + return err + } + } + item, err := resolveMenuItem(opts) + if err != nil { + return err + } + menu.Items = append(menu.Items, item) + return saveMenu(path, menu) +} + +func loadMenu(path string) (Menu, error) { + data, err := os.ReadFile(path) + if err != nil { + return Menu{}, err + } + menu := Menu{Name: strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))} + var current *MenuItem + + for lineNo, raw := range strings.Split(string(data), "\n") { + line := strings.TrimSpace(raw) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if line == "[[items]]" { + menu.Items = append(menu.Items, MenuItem{}) + current = &menu.Items[len(menu.Items)-1] + continue + } + key, rawValue, ok := strings.Cut(line, "=") + if !ok { + return Menu{}, fmt.Errorf("%s:%d: expected key = value", path, lineNo+1) + } + value, err := parseScalar(strings.TrimSpace(rawValue)) + if err != nil { + return Menu{}, fmt.Errorf("%s:%d: %w", path, lineNo+1, err) + } + key = strings.TrimSpace(key) + if current == nil { + if key == "name" { + menu.Name = value + } + continue + } + switch key { + case "label": + current.Label = value + case "url": + current.URL = value + } + } + if menu.Name == "" { + menu.Name = strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + } + return menu, nil +} + +func saveMenu(path string, menu Menu) error { + var b strings.Builder + b.WriteString("name = " + formatString(menu.Name) + "\n") + for _, item := range menu.Items { + b.WriteString("\n[[items]]\n") + b.WriteString("label = " + formatString(item.Label) + "\n") + b.WriteString("url = " + formatString(item.URL) + "\n") + } + return writeFile(path, []byte(b.String())) +} + +func resolveMenuItem(opts MenuItemOptions) (MenuItem, error) { + label := opts.Label + url := opts.URL + target := slugify(opts.Target) + + switch opts.Kind { + case "home": + if label == "" { + label = "Home" + } + if url == "" { + url = "/" + } + case "page": + if target == "" || target == "untitled" { + return MenuItem{}, fmt.Errorf("page menu item requires a target") + } + if label == "" { + label = titleize(target) + } + if url == "" { + if target == "index" { + url = "/" + } else { + url = "/" + target + ".gmi" + } + } + case "section": + if target == "" || target == "untitled" { + return MenuItem{}, fmt.Errorf("section menu item requires a target") + } + if label == "" { + label = titleize(target) + } + if url == "" { + url = "/" + target + "/" + } + case "article": + if target == "" || target == "untitled" { + return MenuItem{}, fmt.Errorf("article menu item requires a target") + } + if label == "" { + label = titleize(target) + } + if url == "" { + url = "/articles/" + target + ".gmi" + } + case "custom": + if label == "" { + label = titleize(target) + } + if url == "" { + return MenuItem{}, fmt.Errorf("custom menu item requires --url") + } + default: + if label == "" { + label = titleize(opts.Kind) + } + if url == "" { + url = "/" + slugify(opts.Kind) + ".gmi" + } + } + + return MenuItem{Label: label, URL: url}, nil +} |
