to id type

master
bel 2022-02-15 20:21:33 -07:00
parent 95c560cd23
commit 04defa3999
6 changed files with 105 additions and 149 deletions

View File

@ -0,0 +1,51 @@
package main
import (
"net/url"
"os"
"path"
"strings"
)
type ID string
func NewID(s string) ID {
return ID(path.Clean(s)).withClean()
}
func (id ID) Push(child string) ID {
return ID(path.Join(id.String(), child)).withClean()
}
func (id ID) Pop() ID {
pid := path.Clean(ID(path.Dir(id.String())).withClean().String())
if strings.HasPrefix(pid, ".") {
return ""
}
return NewID(pid)
}
func (id ID) URLSafeString() string {
splits := strings.Split(string(id), "/")
for i := range splits {
splits[i] = url.PathEscape(splits[i])
}
return strings.Join(splits, "/")
}
func (id ID) String() string {
return string(id)
}
func (id ID) withClean() ID {
splits := strings.Split(id.String(), string([]rune{os.PathSeparator}))
for i := range splits {
splits[i] = strings.Trim(splits[i], string([]rune{os.PathSeparator}))
splits[i] = strings.Trim(splits[i], "/")
t, err := url.PathUnescape(splits[i])
if err == nil {
splits[i] = t
}
}
return ID(path.Join(splits...))
}

View File

@ -12,7 +12,6 @@ import (
"local/simpleserve/simpleserve"
"log"
"net/http"
"net/url"
"os"
"path"
"regexp"
@ -226,9 +225,8 @@ func (server *Server) uiSearchHandler(w http.ResponseWriter, r *http.Request) er
}
func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) error {
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/ui/files")
id := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
if len(id) == 0 || (len(id) == 1 && id[0] == "") {
id := NewID(strings.TrimPrefix(r.URL.Path, "/ui/files"))
if id == "" {
return server.rootHandler(w, r)
}
t, err := server.uiSubTemplates()
@ -249,16 +247,16 @@ func (server *Server) uiFilesHandler(w http.ResponseWriter, r *http.Request) err
return err
}
var parent Leaf
if len(id) > 1 {
parent, err = tree.Get(id[:len(id)-1])
if id.Pop() != "" {
parent, err = tree.Get(id.Pop())
if err != nil {
return err
return fmt.Errorf("failed to get pid %q: %v", id.Pop(), err)
}
}
leaf, err := tree.Get(id)
if err != nil {
if len(id) > 1 && parent.Title == "" {
return err
if id.Pop() != "" {
return fmt.Errorf("failed to get id %q: %v", id, err)
}
leaf.Title = "My New File"
}
@ -350,20 +348,20 @@ func (server *Server) apiV0FilesPostHandler(w http.ResponseWriter, r *http.Reque
}
pid := server.fileId(r)
id := append(pid, strings.Split(uuid.New().String(), "-")[0])
id := NewID(pid).Push(strings.Split(uuid.New().String(), "-")[0])
if err := server.tree().Put(id, Leaf{Title: r.Header.Get("Title"), Content: string(b)}); err != nil {
return err
}
return json.NewEncoder(w).Encode(map[string]map[string]string{
"data": map[string]string{
"filePath": path.Join("/api/v0/files/", server.urlFileId(id)),
"filePath": path.Join("/api/v0/files/", id.URLSafeString()),
},
})
}
func (server *Server) apiV0FilesIDGetHandler(w http.ResponseWriter, r *http.Request) error {
id := server.fileId(r)
if len(id) == 0 || id[0] == "" {
id := NewID(server.fileId(r))
if id.String() == "" {
return fmt.Errorf("no id found: %+v", id)
}
@ -381,8 +379,8 @@ func (server *Server) apiV0FilesIDGetHandler(w http.ResponseWriter, r *http.Requ
}
func (server *Server) apiV0FilesIDDelHandler(w http.ResponseWriter, r *http.Request) error {
id := server.fileId(r)
if len(id) == 0 || id[0] == "" {
id := NewID(server.fileId(r))
if id.String() == "" {
return fmt.Errorf("no id found: %+v", id)
}
@ -397,32 +395,19 @@ func (server *Server) apiV0FilesIDDelHandler(w http.ResponseWriter, r *http.Requ
return server.tree().Put(id, leaf)
}
func (server *Server) urlFileId(id []string) string {
if len(id) == 0 {
return ""
}
result := id[0]
for i := 1; i < len(id); i++ {
result = strings.Join([]string{result, url.PathEscape(id[i])}, "/")
}
return result
}
func (server *Server) fileId(r *http.Request) []string {
return strings.Split(
strings.Trim(
func (server *Server) fileId(r *http.Request) string {
return strings.Trim(
strings.TrimPrefix(
strings.Trim(r.URL.Path, "/"),
"api/v0/files",
),
"/"),
"/",
)
}
func (server *Server) apiV0FilesIDPutHandler(w http.ResponseWriter, r *http.Request) error {
id := server.fileId(r)
if len(id) == 0 || id[0] == "" {
id := NewID(server.fileId(r))
if id.String() == "" {
return fmt.Errorf("no id found: %+v", id)
}
@ -445,7 +430,7 @@ func (server *Server) apiV0FilesIDPutHandler(w http.ResponseWriter, r *http.Requ
}
return json.NewEncoder(w).Encode(map[string]map[string]string{
"data": map[string]string{
"filePath": path.Join("/api/v0/files/", server.urlFileId(id)),
"filePath": path.Join("/api/v0/files/", id.URLSafeString()),
},
})
}
@ -484,21 +469,23 @@ func (server *Server) _apiV0SearchHandler(query string) ([][2]string, error) {
return nil, err
}
result := [][2]string{}
if err := tree.ForEach(func(id []string, leaf Leaf) error {
if err := tree.ForEach(func(id ID, leaf Leaf) error {
for _, pattern := range patterns {
if !pattern.MatchString(leaf.Content) && !pattern.MatchString(leaf.Title) {
return nil
}
}
title := leaf.Title
for i := len(id) - 1; i >= 1; i-- {
parent, err := server.tree().Get(id[:i])
pid := id.Pop()
for pid != "" {
parent, err := server.tree().Get(pid)
if err != nil {
return err
}
title = path.Join(parent.Title, title)
pid = pid.Pop()
}
result = append(result, [2]string{server.urlFileId(id), title})
result = append(result, [2]string{id.URLSafeString(), title})
return nil
}); err != nil {
return nil, err

View File

@ -31,11 +31,11 @@ func TestServerRoutes(t *testing.T) {
ensureAndWrite(server.diskMediaPath("delid"), []byte("hi"))
tree := server.tree()
if err := tree.Put([]string{"getfid"}, Leaf{Title: "", Content: "getfid body"}); err != nil {
if err := tree.Put(ID("getfid"), Leaf{Title: "", Content: "getfid body"}); err != nil {
t.Fatal(err)
}
tree.Put([]string{"putfid"}, Leaf{Title: "putfid title", Content: "initial putfid body"})
tree.Put([]string{"delfid"}, Leaf{Title: "delfid title", Content: "delfid body"})
tree.Put(ID("putfid"), Leaf{Title: "putfid title", Content: "initial putfid body"})
tree.Put(ID("delfid"), Leaf{Title: "delfid title", Content: "delfid body"})
t.Log(tree.GetRoot())
ensureAndWrite(path.Join(server.root, "index.html"), []byte("mom"))
@ -151,7 +151,7 @@ func TestServerPutTreeGetFile(t *testing.T) {
if err := server.Routes(); err != nil {
t.Fatal(err)
}
server.tree().Put([]string{"my pid"}, Leaf{})
server.tree().Put(ID("my pid"), Leaf{})
var id string
t.Run("put to create an id", func(t *testing.T) {
r := httptest.NewRequest(http.MethodPut, "/my%20pid/my-put-id", strings.NewReader("body"))

View File

@ -6,7 +6,6 @@ todo:
- delete button does nothing
- do not rewrite .md title vs. link cause hrefs to ./gobs.md wont work
- https://codepen.io/bisserof/pen/nrMveb
- breadcrumb; https://concisecss.com/documentation/ui
- alert box; https://concisecss.com/documentation/ui
- scrape odo
- only one scroll bar
@ -28,3 +27,4 @@ done:
- preview default via q param
- only 1 pid link in tree as title
- fix images
- breadcrumb; https://concisecss.com/documentation/ui

View File

@ -10,37 +10,23 @@ import (
type Branch struct {
Leaf Leaf
Branches map[string]Branch
Branches map[ID]Branch
}
func (branch Branch) IsZero() bool {
return branch.Leaf == (Leaf{}) && len(branch.Branches) == 0
}
func (branch Branch) Find(baseId string) ([]string, bool) {
if _, ok := branch.Branches[baseId]; ok {
return []string{baseId}, true
}
for pid, child := range branch.Branches {
if subids, ok := child.Find(baseId); ok {
return append([]string{pid}, subids...), true
}
}
return nil, false
func (branch Branch) ForEach(foo func(ID, Leaf) error) error {
return branch.forEach(NewID(""), foo)
}
func (branch Branch) ForEach(foo func([]string, Leaf) error) error {
return branch.forEach([]string{}, foo)
}
func (branch Branch) forEach(preid []string, foo func([]string, Leaf) error) error {
func (branch Branch) forEach(preid ID, foo func(ID, Leaf) error) error {
if err := foo(preid, branch.Leaf); err != nil {
return err
}
postid := append(preid, "")
for id, child := range branch.Branches {
postid[len(postid)-1] = id
if err := child.forEach(postid, foo); err != nil {
if err := child.forEach(preid.Push(string(id)), foo); err != nil {
return err
}
}
@ -80,14 +66,6 @@ func (tree Tree) WithRoot(root string) Tree {
return tree
}
func (tree Tree) Find(baseId string) ([]string, bool) {
root, err := tree.GetRoot()
if err != nil {
return nil, false
}
return root.Find(baseId)
}
func (tree Tree) GetRootMeta() (Branch, error) {
return tree.getRoot(false, false)
}
@ -104,7 +82,7 @@ func (tree Tree) GetRoot() (Branch, error) {
}
func (tree Tree) getRoot(withContent, withDeleted bool) (Branch, error) {
m := Branch{Branches: map[string]Branch{}}
m := Branch{Branches: map[ID]Branch{}}
entries, err := os.ReadDir(tree.root)
if os.IsNotExist(err) {
return m, nil
@ -130,22 +108,22 @@ func (tree Tree) getRoot(withContent, withDeleted bool) (Branch, error) {
if branch, err := subtree.getRoot(withContent, withDeleted); err != nil {
return Branch{}, err
} else if !branch.IsZero() && (!branch.Leaf.Deleted || withDeleted) {
m.Branches[entry.Name()] = branch
m.Branches[NewID(entry.Name())] = branch
}
}
}
return m, nil
}
func (tree Tree) toDir(id []string) string {
func (tree Tree) toDir(id ID) string {
return path.Dir(tree.toData(id))
}
func (tree Tree) toData(id []string) string {
return path.Join(tree.root, path.Join(id...), "data.yaml")
func (tree Tree) toData(id ID) string {
return path.Join(tree.root, string(id), "data.yaml")
}
func (tree Tree) Put(id []string, input Leaf) error {
func (tree Tree) Put(id ID, input Leaf) error {
if _, err := os.Stat(tree.toData(id)); os.IsNotExist(err) {
b, err := yaml.Marshal(Leaf{})
if err != nil {
@ -170,7 +148,7 @@ func (tree Tree) Put(id []string, input Leaf) error {
return nil
}
func (tree Tree) Del(id []string) error {
func (tree Tree) Del(id ID) error {
got, err := tree.Get(id)
if os.IsNotExist(err) {
return nil
@ -185,13 +163,13 @@ func (tree Tree) Del(id []string) error {
return tree.Put(id, got)
}
func (tree Tree) HardDel(id []string) error {
func (tree Tree) HardDel(id ID) error {
os.RemoveAll(tree.toDir(id))
tree.cachedRoot = Branch{}
return nil
}
func (tree Tree) Get(id []string) (Leaf, error) {
func (tree Tree) Get(id ID) (Leaf, error) {
f, err := os.Open(tree.toData(id))
if err != nil {
return Leaf{}, err

View File

@ -3,22 +3,21 @@ package main
import (
"encoding/json"
"fmt"
"path"
"testing"
)
func TestTreeDel(t *testing.T) {
tree := NewTree(t.TempDir())
if err := tree.Put([]string{"id"}, Leaf{}); err != nil {
if err := tree.Put(ID("id"), Leaf{}); err != nil {
t.Fatal(err)
}
if err := tree.Put([]string{"id", "subid"}, Leaf{}); err != nil {
if err := tree.Put(ID("id/subid"), Leaf{}); err != nil {
t.Fatal(err)
}
if err := tree.Del([]string{"id"}); err != nil {
if err := tree.Del(ID("id")); err != nil {
t.Fatal(err)
} else if got, err := tree.Get([]string{"id"}); err != nil {
} else if got, err := tree.Get(ID("id")); err != nil {
t.Fatal(err)
} else if !got.Deleted {
t.Fatal(got)
@ -46,7 +45,7 @@ func TestTreeCrud(t *testing.T) {
t.Fatal(m)
}
if err := tree.Del([]string{"id"}); err != nil {
if err := tree.Del(ID("id")); err != nil {
t.Fatal(err)
}
@ -55,9 +54,9 @@ func TestTreeCrud(t *testing.T) {
Deleted: false,
Content: "leaf content",
}
if err := tree.Put([]string{"id"}, want); err != nil {
if err := tree.Put(ID("id"), want); err != nil {
t.Fatal(err)
} else if l, err := tree.Get([]string{"id"}); err != nil {
} else if l, err := tree.Get(ID("id")); err != nil {
t.Fatal(err)
} else if l != want {
t.Fatal(want, l)
@ -73,62 +72,3 @@ func TestTreeCrud(t *testing.T) {
t.Fatalf("without content == with content: \n\twith=%s\n\twout=%s", with, without)
}
}
func TestBranchFind(t *testing.T) {
cases := map[string]struct {
input string
want []string
found bool
branch Branch
}{
"empty": {
input: "id",
want: nil,
found: false,
branch: Branch{},
},
"yes top level": {
input: "id",
want: []string{"id"},
found: true,
branch: Branch{
Branches: map[string]Branch{"id": Branch{}},
},
},
"yes deep level": {
input: "subsubid",
want: []string{"id", "subid", "subsubid"},
found: true,
branch: Branch{
Branches: map[string]Branch{"id": Branch{
Branches: map[string]Branch{"subid": Branch{
Branches: map[string]Branch{"subsubid": Branch{}}}},
}},
},
},
"no but has deep levels": {
input: "notsubsubid",
want: nil,
found: false,
branch: Branch{
Branches: map[string]Branch{"id": Branch{
Branches: map[string]Branch{"subid": Branch{
Branches: map[string]Branch{"subsubid": Branch{}}}},
}},
},
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
got, found := c.branch.Find(c.input)
if found != c.found {
t.Error(c.found, found)
}
if path.Join(got...) != path.Join(c.want...) {
t.Error(c.want, got)
}
})
}
}