diff --git a/cmd/testdata/graph/pages/tag-brackets.md b/cmd/testdata/graph/pages/tag-brackets.md new file mode 100644 index 0000000..c135c47 --- /dev/null +++ b/cmd/testdata/graph/pages/tag-brackets.md @@ -0,0 +1,3 @@ +- here is some #[[tag]] + - and here is one #[[tag with spaces]] + - text before #[[some-tag]] then after diff --git a/cmd/testdata/graph/pages/tag-brackets.md.golden b/cmd/testdata/graph/pages/tag-brackets.md.golden new file mode 100644 index 0000000..cd93cae --- /dev/null +++ b/cmd/testdata/graph/pages/tag-brackets.md.golden @@ -0,0 +1,3 @@ +- here is some #tag + - and here is one #[[tag with spaces]] + - text before #some-tag then after diff --git a/cmd/tidy_up.go b/cmd/tidy_up.go index b7f8f2e..778352a 100644 --- a/cmd/tidy_up.go +++ b/cmd/tidy_up.go @@ -37,13 +37,48 @@ var tidyUpCmd = &cobra.Command{ //nolint:exhaustruct,gochecknoglobals } transaction := graph.NewTransaction() - shouldSave := false + commit := false exitCode := 0 for _, path := range args { if !isValidMarkdownFile(path) { fmt.Printf("%s: skipping, not a Markdown file\n", path) } else { + // Some fixes still need modifications directly on the file contents. + // We will do them first, and apply each function on top of the previously modified contents. + bytes, err := os.ReadFile(path) + if err != nil { + log.Fatalf("%s: error reading file contents: %s\n", path, err) + } + currentFileContents := string(bytes) + + fileInfo, err := os.Stat(path) + if err != nil { + log.Fatalf("%s: error getting file info: %s\n", path, err) + } + + messages := make([]string, 0) + + write := false + for _, f := range []func(string) changedContents{ + removeUnnecessaryBracketsFromTags, + } { + result := f(currentFileContents) + if result.msg != "" { + messages = append(messages, result.msg) + // Pass the new contents to the next function. + currentFileContents = result.newContents + write = true + } + } + if write { + err := os.WriteFile(path, []byte(currentFileContents), fileInfo.Mode()) + if err != nil { + log.Fatalf("%s: error writing file contents: %s\n", path, err) + } + } + + // Now we will apply the functions that modify the Markdown through a Page and a transaction. page, err := transaction.OpenViaPath(path) if err != nil { log.Fatalf("%s: error opening file via path: %s\n", path, err) @@ -52,17 +87,15 @@ var tidyUpCmd = &cobra.Command{ //nolint:exhaustruct,gochecknoglobals log.Fatalf("%s: error opening file via path: page is nil\n", path) } - messages := make([]string, 0) - - functions := []func(logseq.Page) tidyInfo{ - checkForbiddenReferences, checkRunningTasks, removeDoubleSpaces} - for _, f := range functions { - info := f(page) - if info.msg != "" { - messages = append(messages, info.msg) + for _, f := range []func(logseq.Page) changedPage{ + checkForbiddenReferences, checkRunningTasks, removeDoubleSpaces, + } { + result := f(page) + if result.msg != "" { + messages = append(messages, result.msg) } - if info.changed { - shouldSave = true + if result.changed { + commit = true } } @@ -74,7 +107,7 @@ var tidyUpCmd = &cobra.Command{ //nolint:exhaustruct,gochecknoglobals } } } - if shouldSave { + if commit { err = transaction.Save() if err != nil { log.Fatalf("error saving transaction: %s\n", err) @@ -106,13 +139,20 @@ func isValidMarkdownFile(filePath string) bool { return !info.IsDir() } -type tidyInfo struct { +// changedContents is the result of a check function that modifies file contents directly without a transaction. +type changedContents struct { + msg string + newContents string +} + +// changedPage is the result of a check function that modifies Markdown through a Page and a transaction. +type changedPage struct { msg string changed bool } // checkForbiddenReferences checks if a page has forbidden references to other pages or tags. -func checkForbiddenReferences(page logseq.Page) tidyInfo { +func checkForbiddenReferences(page logseq.Page) changedPage { all := make([]string, 0) for _, block := range page.Blocks() { @@ -145,11 +185,11 @@ func checkForbiddenReferences(page logseq.Page) tidyInfo { if count := len(all); count > 0 { unique := sortAndRemoveDuplicates(all) - return tidyInfo{fmt.Sprintf("remove %d forbidden references to pages/tags: %s", + return changedPage{fmt.Sprintf("remove %d forbidden references to pages/tags: %s", count, strings.Join(unique, ", ")), false} } - return tidyInfo{"", false} + return changedPage{"", false} } func sortAndRemoveDuplicates(elements []string) []string { @@ -170,7 +210,7 @@ func sortAndRemoveDuplicates(elements []string) []string { } // checkRunningTasks checks if a page has running tasks (DOING, etc.). -func checkRunningTasks(page logseq.Page) tidyInfo { +func checkRunningTasks(page logseq.Page) changedPage { all := make([]string, 0) for _, block := range page.Blocks() { @@ -194,13 +234,13 @@ func checkRunningTasks(page logseq.Page) tidyInfo { if count := len(all); count > 0 { unique := sortAndRemoveDuplicates(all) - return tidyInfo{fmt.Sprintf("stop %d running task(s): %s", count, strings.Join(unique, ", ")), false} + return changedPage{fmt.Sprintf("stop %d running task(s): %s", count, strings.Join(unique, ", ")), false} } - return tidyInfo{"", false} + return changedPage{"", false} } -func removeDoubleSpaces(page logseq.Page) tidyInfo { +func removeDoubleSpaces(page logseq.Page) changedPage { all := make([]string, 0) doubleSpaceRegex := regexp.MustCompile(`\s{2,}`) fixed := false @@ -238,8 +278,25 @@ func removeDoubleSpaces(page logseq.Page) tidyInfo { if count := len(all); count > 0 { unique := sortAndRemoveDuplicates(all) - return tidyInfo{fmt.Sprintf("fixed %d double spaces: %s", count, strings.Join(unique, ", ")), fixed} + return changedPage{fmt.Sprintf("fixed %d double spaces: %s", count, strings.Join(unique, ", ")), fixed} + } + + return changedPage{"", fixed} +} + +// removeUnnecessaryBracketsFromTags removes unnecessary brackets from hashtags. +// logseq-go rewrites tags correctly when saving the transaction, removing unnecessary brackets. +// But, when reading the file, the AST doesn't provide the information if a tag has brackets or not. +// So I would have to rewrite the file to fix them, and I don't want to do it every time there is a tag without spaces. +// Also, as of 2024-12-30, logseq-go has a bug when reading properties with spaces in values, +// which causes them to be partially removed from the file, destroying data. I will report it soon. +func removeUnnecessaryBracketsFromTags(oldContents string) changedContents { + re := regexp.MustCompile(`#\[\[([^ ]*?)]]`) + + newContents := re.ReplaceAllString(oldContents, "#$1") + if newContents != oldContents { + return changedContents{"removed unnecessary brackets from tags", newContents} } - return tidyInfo{"", fixed} + return changedContents{"", ""} } diff --git a/cmd/tidy_up_test.go b/cmd/tidy_up_test.go index 320b921..95bb322 100644 --- a/cmd/tidy_up_test.go +++ b/cmd/tidy_up_test.go @@ -9,6 +9,7 @@ import ( "github.com/andreoliwa/logseq-go" "github.com/stretchr/testify/assert" + "gotest.tools/v3/golden" ) func TestSortAndRemoveDuplicates(t *testing.T) { @@ -91,27 +92,46 @@ func setupPage(t *testing.T, name string) logseq.Page { return page } +type resultSetupFileContents struct { + oldContents string + goldenPath string +} + +func setupFileContents(t *testing.T, name string) resultSetupFileContents { + t.Helper() + + subdir := filepath.Join("graph", "pages", name+".md") + path := filepath.Join("testdata", subdir) + + bytes, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + + return resultSetupFileContents{string(bytes), subdir + ".golden"} +} + func TestCheckForbiddenReferences(t *testing.T) { invalid := setupPage(t, "forbidden") - assert.Equal(t, tidyInfo{"remove 4 forbidden references to pages/tags: Inbox, quick capture", false}, + assert.Equal(t, changedPage{"remove 4 forbidden references to pages/tags: Inbox, quick capture", false}, checkForbiddenReferences(invalid)) valid := setupPage(t, "valid") - assert.Equal(t, tidyInfo{"", false}, checkForbiddenReferences(valid)) + assert.Equal(t, changedPage{"", false}, checkForbiddenReferences(valid)) } func TestCheckRunningTasks(t *testing.T) { invalid := setupPage(t, "running") - assert.Equal(t, tidyInfo{"stop 2 running task(s): DOING, IN-PROGRESS", false}, checkRunningTasks(invalid)) + assert.Equal(t, changedPage{"stop 2 running task(s): DOING, IN-PROGRESS", false}, checkRunningTasks(invalid)) valid := setupPage(t, "valid") - assert.Equal(t, tidyInfo{"", false}, checkRunningTasks(valid)) + assert.Equal(t, changedPage{"", false}, checkRunningTasks(valid)) } func TestRemoveDoubleSpaces(t *testing.T) { invalid := setupPage(t, "spaces") assert.Equal(t, - tidyInfo{"fixed 4 double spaces: 'Link With Spaces ', 'Regular text with spaces'," + + changedPage{"fixed 4 double spaces: 'Link With Spaces ', 'Regular text with spaces'," + " 'some page title with spaces', 'some tag with spaces'", true}, removeDoubleSpaces(invalid)) @@ -122,5 +142,15 @@ func TestRemoveDoubleSpaces(t *testing.T) { // assert.Equal(t, expected, actual) valid := setupPage(t, "valid") - assert.Equal(t, tidyInfo{"", false}, removeDoubleSpaces(valid)) + assert.Equal(t, changedPage{"", false}, removeDoubleSpaces(valid)) +} + +func TestRemoveUnnecessaryBracketsFromTags(t *testing.T) { + invalid := setupFileContents(t, "tag-brackets") + changed := removeUnnecessaryBracketsFromTags(invalid.oldContents) + assert.Equal(t, "removed unnecessary brackets from tags", changed.msg) + golden.Assert(t, changed.newContents, invalid.goldenPath) + + valid := setupFileContents(t, "valid") + assert.Equal(t, changedContents{"", ""}, removeUnnecessaryBracketsFromTags(valid.oldContents)) } diff --git a/go.mod b/go.mod index 3b25f0d..224756b 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/andreoliwa/logseq-go v0.0.0-20241228161444-32088a5a9491 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 + gotest.tools/v3 v3.5.1 ) require ( @@ -28,6 +29,7 @@ require ( github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kamstrup/intmap v0.5.1 // indirect diff --git a/go.sum b/go.sum index 69e207b..e0fc13a 100644 --- a/go.sum +++ b/go.sum @@ -159,6 +159,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/python/logseq_doctor/cli.py b/python/logseq_doctor/cli.py index d0ad4b8..62d3a9c 100644 --- a/python/logseq_doctor/cli.py +++ b/python/logseq_doctor/cli.py @@ -124,9 +124,6 @@ def tidy_up( changed.append("empty bullets") each_file.write_text(rm_empty_bullets) - if rust_ext.tidy_up(each_file): - changed.append("brackets") - if changed: if not exit_code: exit_code = 1