start off with an in-memory page hierarchy

This commit is contained in:
David 2022-05-22 21:20:46 -04:00
parent 0676da9e00
commit 51e27879c3
4 changed files with 288 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
*~

1
main.go Normal file
View File

@ -0,0 +1 @@
package main

85
model.go Normal file
View File

@ -0,0 +1,85 @@
package main
import (
"fmt"
"strings"
)
var ErrPageNotFound = fmt.Errorf("page not found")
// A Page is a single unit of content on the site.
type Page struct {
Title string
Contents []byte
}
// Index is a set of pages that exist at this level,
// as well as a set of "folders" that contain sub-indexes.
// The 'index' key in Pages is special, and will be returned
// if no key is provided.
type Index struct {
Children map[string]Index
Pages map[string]Page
}
// Page returns the requested page from the index, recursively
// key is assumed to be a `/`-separated string; Page will split on the slashes,
// descending into an index to find the page, if possible. If no match is found,
// return an empty page and `ErrPageNotFound`.
// `foo/` will return a page named `foo` in the current index, if it exists.
// Otherwise, if a child index named "foo" exists, page will attempt to return its index page.
// Page strips leading / from keys.
func (i *Index) Page(key string) (*Page, error) {
if key == "" || key == "/" {
key = "index"
}
if key[0] == '/' { // strip leading slash
key = key[1:]
}
curr, rest, found := strings.Cut(key, "/")
page, pageok := i.Pages[curr]
child, childok := i.Children[curr]
if !found && !pageok {
return &Page{}, ErrPageNotFound
}
if rest == "" && pageok {
return &page, nil
}
if !childok {
return &Page{}, ErrPageNotFound
}
return (&child).Page(rest)
}
// Save stores a page in the index, recursively,
// overwriting any that may have existed before.
// `foo/` is stored as a page named 'foo' in the current index;
// default 'index' files should be explicitly passed as such.
// The empty key or `/` are invalid and result in an error.
// Leading slashes are stripped.
func (i *Index) Save(key string, page *Page) error {
if key == "" || key == "/" {
return fmt.Errorf("invalid page key")
}
if key[0] == '/' { // strip leading slash
key = key[1:]
}
if i.Pages == nil {
i.Pages = map[string]Page{}
}
if i.Children == nil {
i.Children = map[string]Index{}
}
curr, rest, _ := strings.Cut(key, "/")
if rest == "" {
i.Pages[curr] = *page
} else {
children := i.Children[curr]
err := (&children).Save(rest, page)
if err != nil {
return err
}
i.Children[curr] = children
}
return nil
}

200
model_test.go Normal file
View File

@ -0,0 +1,200 @@
package main
import (
"bytes"
"testing"
)
func TestPage(t *testing.T) {
i := &Index{
Children: map[string]Index{
"foo": {
Children: map[string]Index{},
Pages: map[string]Page{
"bar": {
Contents: []byte("bar"),
},
"index": {
Contents: []byte("quuz"),
},
},
},
"bar": {
Children: map[string]Index{},
Pages: map[string]Page{
"index": {
Contents: []byte("quuz"),
},
"goof": {
Contents: []byte("quuz2"),
},
},
},
},
Pages: map[string]Page{
"index": {
Contents: []byte("indextest"),
},
"foo": {
Contents: []byte("rootfoo"),
},
},
}
p, err := i.Page("")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("indextest")) {
t.Logf("expected contents to be 'indextest', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("/")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("indextest")) {
t.Logf("expected contents to be 'indextest', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("foo/bar")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("bar")) {
t.Logf("expected contents to be 'bar', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("/foo/bar")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("bar")) {
t.Logf("expected contents to be 'bar', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("foo")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("rootfoo")) {
t.Logf("expected contents to be 'rootfoo', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("foo/")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("rootfoo")) {
t.Logf("expected contents to be 'rootfoo', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("bar/")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("quuz")) {
t.Logf("expected contents to be 'quuz', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("bar/goof")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("quuz2")) {
t.Logf("expected contents to be 'quuz2', received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("foo/quuz")
if err == nil {
t.Logf("expected err, received nil err")
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("")) {
t.Logf("expected no content, received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("quuz/bar")
if err == nil {
t.Logf("expected err, received nil err")
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("")) {
t.Logf("expected no content, received '%v'", p.Contents)
t.Fail()
}
p, err = i.Page("//////////")
if err == nil {
t.Logf("expected err, received nil err")
t.Fail()
}
if !bytes.Equal(p.Contents, []byte("")) {
t.Logf("expected no content, received '%v'", p.Contents)
t.Fail()
}
}
func TestSave(t *testing.T) {
i := &Index{}
err := i.Save("foo", &Page{Title: "fooroot"})
if err != nil {
t.Logf("expected no err, received %v", err)
}
p, err := i.Page("foo")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if p.Title != "fooroot" {
t.Logf("expected title to be 'fooroot', received '%v'", p.Title)
t.Fail()
}
err = i.Save("foo/", &Page{Title: "fooroot2"})
if err != nil {
t.Logf("expected no err, received %v", err)
}
p, err = i.Page("foo")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Fail()
}
if p.Title != "fooroot2" {
t.Logf("expected title to be 'fooroot2', received '%v'", p.Title)
t.Fail()
}
err = i.Save("bar/baz", &Page{Title: "quuz"})
if err != nil {
t.Logf("expected no err, received %v", err)
}
p, err = i.Page("bar/baz")
if err != nil {
t.Logf("expected no err, received err: %v", err)
t.Logf("%+v", i)
t.Fail()
}
if p.Title != "quuz" {
t.Logf("expected title to be 'quuz', received '%v'", p.Title)
t.Fail()
}
}