From 104ee66f5930f4a37ac84538c29a291bf1d08f4f Mon Sep 17 00:00:00 2001 From: Guangxiong Lin Date: Tue, 24 Jan 2023 20:27:22 +0800 Subject: Use cobra as command selector --- Makefile | 5 ++ cmd/mv.go | 59 ++++++++++++++++++++++ cmd/mv_test.go | 39 +++++++++++++++ cmd/root.go | 19 +++++++ command.go | 32 ------------ doc.go | 154 --------------------------------------------------------- go.mod | 10 +++- go.sum | 10 ++++ main.go | 16 +++--- map.go | 8 --- pkg/doc.go | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/map.go | 8 +++ pkg/set.go | 16 ++++++ set.go | 16 ------ 14 files changed, 328 insertions(+), 218 deletions(-) create mode 100644 cmd/mv.go create mode 100644 cmd/mv_test.go create mode 100644 cmd/root.go delete mode 100644 command.go delete mode 100644 doc.go delete mode 100644 map.go create mode 100644 pkg/doc.go create mode 100644 pkg/map.go create mode 100644 pkg/set.go delete mode 100644 set.go diff --git a/Makefile b/Makefile index 45238f4..0d23689 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,14 @@ all: zk zk: main.go command.go go.mod go.sum map.go set.go doc.go go build . +.PHONY: test +test: + go test ./... + .PHONY: install install: zk @install -vm0755 zk /usr/bin/zk +.PHONY: uninstall uninstall: @rm -vrf /usr/bin/zk diff --git a/cmd/mv.go b/cmd/mv.go new file mode 100644 index 0000000..c3f4b5b --- /dev/null +++ b/cmd/mv.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "errors" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "gxlin.org/zk/pkg" +) + +func init() { + rootCmd.AddCommand(mvCmd) +} + +var mvCmd = &cobra.Command{ + Use: "mv [source] [target]", + Args: mvCmdArgs, + Run: mvCmdRun, +} + +func mvCmdArgs(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("requires 2 args") + } + + return nil +} + +func mvCmdRun(cmd *cobra.Command, args []string) { + rename(args[0], args[1]) +} + +func rename(source, target string) { + source, err := filepath.Abs(source) + if err != nil { + panic("Unable to get abspath of " + source) + } + + target, err = filepath.Abs(target) + if err != nil { + panic("Unable to get abspath of " + target) + } + + if !pkg.DocCollection.Contain(source) { + panic("Database doesn't contain " + source) + } + + if err := os.Rename(source, target); err != nil { + panic(err) + } + + doc := pkg.DocCollection[source] + for backlink := range doc.Backlinks { + pkg.DocCollection[backlink].UpdateLinks(source, target) + } + +} diff --git a/cmd/mv_test.go b/cmd/mv_test.go new file mode 100644 index 0000000..4e0544f --- /dev/null +++ b/cmd/mv_test.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "os" + "path/filepath" + "testing" + + "gxlin.org/zk/pkg" +) + +func TestRename(t *testing.T) { + tmpDir := t.TempDir() + backlinkedFilename := filepath.Join(tmpDir, "backlinked.md") + sourceFilename := filepath.Join(tmpDir, "source.md") + targetFilename := filepath.Join(tmpDir, "target.md") + + os.WriteFile(backlinkedFilename, []byte("[Title](source.md)"), 0644) + os.WriteFile(sourceFilename, []byte("Hello World"), 0644) + + pkg.DocCollection = pkg.NewDocs(tmpDir) + rename(sourceFilename, targetFilename) + + targetFileContent, err := os.ReadFile(targetFilename) + if err != nil { + t.Error(err) + } else if string(targetFileContent) != "Hello World" { + t.Errorf("File content or matched after moved") + } + + backlinkedFileContent, err := os.ReadFile(backlinkedFilename) + if err != nil { + t.Error(err) + } else if string(backlinkedFileContent) != "[Title](target.md)" { + t.Errorf( + "The link in backlinked file doesn't match. File content:\n%s", + string(backlinkedFileContent)) + + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..19bf463 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "zk", +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/command.go b/command.go deleted file mode 100644 index 803970b..0000000 --- a/command.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "os" - "path/filepath" -) - -func Rename(source, target string) { - source, err := filepath.Abs(source) - if err != nil { - panic("Unable to get abspath of " + source) - } - - target, err = filepath.Abs(target) - if err != nil { - panic("Unable to get abspath of " + target) - } - - if !DocCollection.Contain(source) { - panic("Database doesn't contain " + source) - } - - if err := os.Rename(source, target); err != nil { - panic(err) - } - - doc := DocCollection[source] - for backlink := range doc.backlinks { - DocCollection[backlink].UpdateLinks(source, target) - } - -} diff --git a/doc.go b/doc.go deleted file mode 100644 index 4971e8e..0000000 --- a/doc.go +++ /dev/null @@ -1,154 +0,0 @@ -package main - -import ( - "bytes" - "io/fs" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/yuin/goldmark" - "github.com/yuin/goldmark/ast" - "github.com/yuin/goldmark/extension" - "github.com/yuin/goldmark/parser" - "github.com/yuin/goldmark/text" -) - -var md = goldmark.New( - goldmark.WithExtensions(extension.TaskList), -) - -type Doc struct { - path string - content []byte - links Set[string] - backlinks Set[string] -} - -func (doc *Doc) UpdateLinks(source, target string) { - dirname := filepath.Dir(doc.path) - relpath, err := filepath.Rel(dirname, target) - if err != nil { - return - } - - for link := range doc.links { - if filepath.Join(dirname, link) != source { - continue - } - - doc.content = bytes.Replace(doc.content, []byte(link), []byte(relpath), -1) - // TODO: Update doc.links - } - - doc.Overwrite() -} - -func (doc *Doc) Overwrite() { - os.WriteFile(doc.path, doc.content, 0644) -} - -func NewDoc(filename string) *Doc { - // NOTE: so far, only markdown is supported - if !strings.HasSuffix(filename, ".md") { - return nil - } - - content, err := os.ReadFile(filename) - if err != nil { - return nil - } - - root := md.Parser().Parse( - text.NewReader(content), - parser.WithContext(parser.NewContext()), - ) - - links := Set[string]{} - err = ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) { - if entering { - switch link := n.(type) { - case *ast.Link: - href, err := url.PathUnescape(string(link.Destination)) - if err != nil { - return ast.WalkStop, err - } - - links.Insert(href) - } - } - return ast.WalkContinue, nil - }) - - if err != nil { - return nil - } - - return &Doc{ - path: filename, - content: content, - links: links, - backlinks: Set[string]{}, - } -} - -type Docs Map[string, *Doc] - -func (docs Docs) GetOrNew(filename string) *Doc { - if doc, ok := docs[filename]; ok { - return doc - } - - doc := NewDoc(filename) - docs[filename] = doc - return doc -} - -func NewDocs(dirname string) Docs { - docs := Docs{} - - dirname, err := filepath.Abs(dirname) - if err != nil { - return nil - } - - if err := filepath.Walk(dirname, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - - // TODO: Add skip dir config - if info.IsDir() && (info.Name() == ".git" || info.Name() == "vendor") { - return filepath.SkipDir - } - - if doc := NewDoc(path); doc != nil { - docs[path] = doc - } - - return nil - }); err != nil { - return nil - } - - for filename, doc := range docs { - for link := range doc.links { - abslink := filepath.Join(filepath.Dir(filename), link) - if _, ok := docs[abslink]; !ok { - continue - } - - docs[abslink].backlinks.Insert(filename) - } - } - - return docs -} - -func (docs Docs) Contain(filename string) bool { - _, ok := docs[filename] - return ok -} - -var DocCollection Docs diff --git a/go.mod b/go.mod index f952513..ecb1020 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,12 @@ module gxlin.org/zk go 1.19 -require github.com/yuin/goldmark v1.5.3 +require ( + github.com/spf13/cobra v1.6.1 + github.com/yuin/goldmark v1.5.3 +) + +require ( + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum index b36a911..fd883d6 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M= github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 99ae88c..c967b6e 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,17 @@ package main -import "os" +// import "os" + +import ( + "gxlin.org/zk/cmd" + "gxlin.org/zk/pkg" +) func main() { - DocCollection = NewDocs(".") - if DocCollection == nil { + pkg.DocCollection = pkg.NewDocs(".") + if pkg.DocCollection == nil { panic("Unable to initialize collection") } - if len(os.Args) > 3 && os.Args[1] == "mv" { - Rename(os.Args[2], os.Args[3]) - } - + cmd.Execute() } diff --git a/map.go b/map.go deleted file mode 100644 index 5692065..0000000 --- a/map.go +++ /dev/null @@ -1,8 +0,0 @@ -package main - -type Map[K comparable, V interface{}] map[K]V - -func (m Map[K, V]) Contain(key K) bool { - _, ok := m[key] - return ok -} diff --git a/pkg/doc.go b/pkg/doc.go new file mode 100644 index 0000000..9f85b3a --- /dev/null +++ b/pkg/doc.go @@ -0,0 +1,154 @@ +package pkg + +import ( + "bytes" + "io/fs" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" +) + +var md = goldmark.New( + goldmark.WithExtensions(extension.TaskList), +) + +type Doc struct { + path string + content []byte + links Set[string] + Backlinks Set[string] +} + +func (doc *Doc) UpdateLinks(source, target string) { + dirname := filepath.Dir(doc.path) + relpath, err := filepath.Rel(dirname, target) + if err != nil { + return + } + + for link := range doc.links { + if filepath.Join(dirname, link) != source { + continue + } + + doc.content = bytes.Replace(doc.content, []byte(link), []byte(relpath), -1) + // TODO: Update doc.links + } + + doc.Overwrite() +} + +func (doc *Doc) Overwrite() { + os.WriteFile(doc.path, doc.content, 0644) +} + +func NewDoc(filename string) *Doc { + // NOTE: so far, only markdown is supported + if !strings.HasSuffix(filename, ".md") { + return nil + } + + content, err := os.ReadFile(filename) + if err != nil { + return nil + } + + root := md.Parser().Parse( + text.NewReader(content), + parser.WithContext(parser.NewContext()), + ) + + links := Set[string]{} + err = ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) { + if entering { + switch link := n.(type) { + case *ast.Link: + href, err := url.PathUnescape(string(link.Destination)) + if err != nil { + return ast.WalkStop, err + } + + links.Insert(href) + } + } + return ast.WalkContinue, nil + }) + + if err != nil { + return nil + } + + return &Doc{ + path: filename, + content: content, + links: links, + Backlinks: Set[string]{}, + } +} + +type Docs Map[string, *Doc] + +func (docs Docs) GetOrNew(filename string) *Doc { + if doc, ok := docs[filename]; ok { + return doc + } + + doc := NewDoc(filename) + docs[filename] = doc + return doc +} + +func NewDocs(dirname string) Docs { + docs := Docs{} + + dirname, err := filepath.Abs(dirname) + if err != nil { + return nil + } + + if err := filepath.Walk(dirname, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + // TODO: Add skip dir config + if info.IsDir() && (info.Name() == ".git" || info.Name() == "vendor") { + return filepath.SkipDir + } + + if doc := NewDoc(path); doc != nil { + docs[path] = doc + } + + return nil + }); err != nil { + return nil + } + + for filename, doc := range docs { + for link := range doc.links { + abslink := filepath.Join(filepath.Dir(filename), link) + if _, ok := docs[abslink]; !ok { + continue + } + + docs[abslink].Backlinks.Insert(filename) + } + } + + return docs +} + +func (docs Docs) Contain(filename string) bool { + _, ok := docs[filename] + return ok +} + +var DocCollection Docs diff --git a/pkg/map.go b/pkg/map.go new file mode 100644 index 0000000..a9f8ccb --- /dev/null +++ b/pkg/map.go @@ -0,0 +1,8 @@ +package pkg + +type Map[K comparable, V interface{}] map[K]V + +func (m Map[K, V]) Contain(key K) bool { + _, ok := m[key] + return ok +} diff --git a/pkg/set.go b/pkg/set.go new file mode 100644 index 0000000..634bb59 --- /dev/null +++ b/pkg/set.go @@ -0,0 +1,16 @@ +package pkg + +type Set[T comparable] map[T]bool + +func (s Set[T]) Contain(val T) bool { + _, ok := s[val] + return ok +} + +func (s Set[T]) Insert(val T) { + s[val] = true +} + +func (s Set[T]) Erase(val T) { + delete(s, val) +} diff --git a/set.go b/set.go deleted file mode 100644 index 760c9ae..0000000 --- a/set.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -type Set[T comparable] map[T]bool - -func (s Set[T]) Contain(val T) bool { - _, ok := s[val] - return ok -} - -func (s Set[T]) Insert(val T) { - s[val] = true -} - -func (s Set[T]) Erase(val T) { - delete(s, val) -} -- cgit v1.2.3