package signpost_test import ( "context" "fmt" "strings" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/signpost" ) // Article is the example's stand-in for a content plugin's record. type Article struct { ID string Slug string Title string Published bool } // NewsPlugin is an example content plugin whose record type describes its // articles to consumers such as sitemaps. type NewsPlugin struct{ articles []Article } func (NewsPlugin) ID() string { return "acme.news" } // MenuItemTypes registers acme-article with a content descriptor: the // controller that edits articles, their morph type, Describe and Match. func (p NewsPlugin) MenuItemTypes() []pact.MenuItemType { return []pact.MenuItemType{{ Key: "acme-article", Label: "acme.news::lang.menuitem.article", DefaultPattern: "/news/:slug", Placeholders: []string{":slug", ":id"}, Resolve: p.resolve, Content: &pact.ContentDescriptor{ Controller: "acme.news.articles", Model: "acme_news_articles", Describe: p.describe, Match: p.match, }, }} } func (p NewsPlugin) resolve(_ context.Context, in pact.MenuItemResolve) (*pact.ResolvedMenuItem, error) { return nil, nil } // describe answers one article per locale; the URL is built from the // layered pattern it receives. func (p NewsPlugin) describe(_ context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) { for _, a := range p.articles { if a.ID != in.ID { continue } url := strings.NewReplacer(":slug", a.Slug, ":id", a.ID).Replace(in.Pattern) return &pact.ContentRecord{ ID: a.ID, Published: a.Published, Locales: []pact.ContentLocale{{Locale: "en", URL: url, Title: a.Title}}, }, nil } return nil, nil } // match reads the path's values with signpost.MatchPattern and checks every // one against the record: a slug finds the article, and an :id that names // another article makes the match inexact. Drafts never match. func (p NewsPlugin) match(_ context.Context, in pact.ContentMatch) (*pact.ContentMatchResult, error) { values, ok := signpost.MatchPattern(in.Pattern, in.Path) if !ok { return nil, nil } for _, a := range p.articles { if a.Slug != values["slug"] || !a.Published { continue } id, hasID := values["id"] return &pact.ContentMatchResult{ID: a.ID, Locale: "en", Exact: !hasID || id == a.ID}, nil } return nil, nil } func ExampleRegistry_MatchPath() { app := exampleApp() // its menu_items.yaml overrides acme-post of BlogPlugin news := NewsPlugin{articles: []Article{{ID: "7", Slug: "launch", Title: "Launch", Published: true}}} plugins := []interface{ ID() string }{BlogPlugin{}, news} if err := signpost.Activate(app, plugins); err != nil { fmt.Println(err) return } reg, _ := app.Lookup[*signpost.Registry]() // A record event names the model; the registry finds its type. typ, _ := reg.ContentTypeForModel("acme_news_articles") rec, _ := reg.Describe(context.Background(), typ.Key, "7", "") fmt.Println(typ.Key, rec.Locales[0].URL) // A visitor's path maps back to the record. key, res, _ := reg.MatchPath(context.Background(), "/news/launch", "") fmt.Println(key, res.ID, res.Exact) // Output: // acme-article /news/launch // acme-article 7 true }