27 Commits

Author SHA1 Message Date
Bel LaPointe
bf10c00708 add cli -g to merge in a file 2022-01-02 20:07:52 -05:00
Bel LaPointe
cd5ce0f0df add root MergeIn 2022-01-01 17:43:20 -05:00
Bel LaPointe
eb41f065a5 todo 2022-01-01 17:33:59 -05:00
Bel LaPointe
96bfb96ee3 consts for what to print, default to just the todos 2022-01-01 17:30:40 -05:00
Bel LaPointe
770f2719d2 ezdate for yyyy-mm-dd for schedule 2022-01-01 17:21:38 -05:00
Bel LaPointe
3554bf8ba4 rm test code 2022-01-01 17:14:03 -05:00
Bel LaPointe
b4aa4ad310 ts shouldnt yield zero ever, yield now if so 2022-01-01 17:13:50 -05:00
Bel LaPointe
0dddf26265 fix getting ts for completed tasks 2022-01-01 17:04:21 -05:00
Bel LaPointe
5afdeed3b5 todo 2021-12-31 23:27:21 -05:00
Bel LaPointe
af0f094a65 support tag, simple case insensitve search when recursing 2021-12-31 23:20:27 -05:00
Bel LaPointe
031db8788b tag saerch on todo 2021-12-31 23:14:31 -05:00
Bel LaPointe
b8efdbfa52 when writing output file, dont recurse 2021-12-31 23:03:08 -05:00
Bel LaPointe
a54ccae4c2 update todo 2021-12-31 23:00:16 -05:00
Bel LaPointe
a27d5c38d7 times are now unix dates over ints 2021-12-31 22:59:13 -05:00
Bel LaPointe
cb4886992a remove unused stub 2021-12-31 22:49:51 -05:00
Bel LaPointe
15c5f03ccf fix syntax highlight by using tempfile.yaml over tempfile 2021-12-31 22:46:49 -05:00
Bel LaPointe
b82f11c248 dont try to find vimrc 2021-12-31 22:45:29 -05:00
Bel LaPointe
3c9b34202b use $EDITOR, default to vim, use $HOME/.vimrc if exists 2021-12-31 22:44:33 -05:00
Bel LaPointe
967a02c90a updated todo because i oofed 2021-12-31 22:37:47 -05:00
Bel LaPointe
3ed7d8cd9e update install for gomo 2021-12-31 22:32:47 -05:00
Bel LaPointe
492e0af993 if optional positional arg is todo/scheduled/done, then resolve first level 2021-12-31 22:32:07 -05:00
Bel LaPointe
10b95672e3 up install script for location 2021-12-31 22:28:12 -05:00
Bel LaPointe
183f39bd2a set ts to currenttime if should display 2021-12-31 22:27:48 -05:00
Bel LaPointe
24154df995 cli has dry mode and install script 2021-12-31 22:27:33 -05:00
Bel LaPointe
f7dac79233 pttodo-cli default file via env 2021-12-31 22:07:16 -05:00
Bel LaPointe
8c45d4a7df pttodo-cli works even if file does not initially exist 2021-12-31 22:05:47 -05:00
Bel LaPointe
1e3f24b4d5 rename gomod, root project 2021-12-31 21:58:02 -05:00
52 changed files with 411 additions and 5502 deletions

BIN
.DS_Store vendored

Binary file not shown.

4
.gitignore vendored
View File

@@ -1,9 +1,5 @@
**/*.sw* **/*.sw*
**/target/
todo-server-yaml todo-server-yaml
cmd/cmd cmd/cmd
cmd/cli cmd/cli
cmd/pttodo/pttodo cmd/pttodo/pttodo
cmd/pttodo-cli/pttodo-cli
cmd/pttodo-cli
pttodoer/target

View File

@@ -1,36 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/google/uuid"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)
func add(config *config) error {
if config.add == "" {
return nil
}
v := pttodo.Todo{
Todo: config.add,
Schedule: pttodo.Schedule(config.addSchedule),
Tags: config.addTags,
}
return _add(config.Targets()[0], v)
}
func _add(filepath string, todo pttodo.Todo) error {
target := fmt.Sprintf("%s.todo.%s", filepath, uuid.New().String())
c, err := yaml.Marshal([]pttodo.Todo{todo})
if err != nil {
return err
} else if err := ioutil.WriteFile(target, c, os.ModePerm); err != nil {
return err
}
return nil
}

View File

@@ -1,106 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strings"
)
type config struct {
target string
target2 string
root string
tags []string
search string
edit bool
add string
addSchedule string
addTags string
dryRun bool
archive string
dedupe bool
}
func getConfig() config {
defaultFilepath := os.Getenv("PTTODO_FILE")
if defaultFilepath == "" {
defaultFilepath = "./todo.yaml"
}
var config config
flag.StringVar(&config.target, "f", defaultFilepath, "($PTTODO_FILE) path to yaml file or dir (starting with root then alphabetical for dir)")
flag.StringVar(&config.target2, "g", "", "path to yaml file to merge into root of -f")
flag.StringVar(&config.archive, "archive", "", "path to yaml file to migrate done")
flag.StringVar(&config.root, "root", DUMP_TODO, "path to pretty print ("+fmt.Sprint([]string{DUMP_ALL, DUMP_TODO, DUMP_SCHEDULED, DUMP_DONE})+")")
var tagss string
flag.StringVar(&tagss, "tags", "", "csv of all tags to find, -x to invert")
flag.StringVar(&config.search, "search", "", "fts case insensitive")
flag.BoolVar(&config.edit, "e", false, "edit file")
flag.BoolVar(&config.dedupe, "dedupe", false, "dedupe file")
flag.BoolVar(&config.dryRun, "dry-run", false, "dry run")
flag.StringVar(&config.add, "add", "", "todo to add")
flag.StringVar(&config.addSchedule, "add-schedule", "", "todo to add schedule")
flag.StringVar(&config.addTags, "add-tags", "", "todo to add csv tags")
flag.Parse()
config.tags = strings.Split(tagss, ",")
config.Targets()
return config
}
func (config config) Targets() []string {
result, err := config.targets()
if err != nil {
panic(err)
}
sort.Strings(result)
for i := range result {
if path.Base(result[i]) == "root.yaml" {
newresult := append([]string{result[i]}, result[:i]...)
newresult = append(newresult, result[i+1:]...)
result = newresult
break
}
}
return result
}
func (config config) targets() ([]string, error) {
patterns := []string{config.target, fmt.Sprintf("%s.*", config.target)}
isDir := false
if stat, err := os.Stat(config.target); err == nil {
isDir = stat.IsDir()
}
if isDir {
patterns = []string{fmt.Sprintf("%s/*", config.target)}
}
result := make([]string, 0, 1)
for _, pattern := range patterns {
results, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
for i := range results {
if stat, err := os.Stat(results[i]); err != nil {
return nil, err
} else if !stat.IsDir() {
result = append(result, results[i])
}
}
}
if len(result) == 0 {
if isDir {
return []string{path.Join(config.target, "root.yaml")}, nil
}
return []string{config.target}, nil
}
return result, nil
}

View File

@@ -1,87 +0,0 @@
package main
import (
"os"
"path"
"testing"
)
func TestConfigTargets(t *testing.T) {
touch := func(t *testing.T, p string) {
if err := os.WriteFile(p, nil, os.ModePerm); err != nil {
t.Fatal(err)
}
}
cases := map[string]struct {
setup func(*testing.T, string)
given string
want []string
}{
"does not exist": {
given: "x",
want: []string{"x"},
},
"one file": {
setup: func(t *testing.T, d string) {
touch(t, path.Join(d, "x"))
},
given: "x",
want: []string{"x"},
},
"two files": {
setup: func(t *testing.T, d string) {
touch(t, path.Join(d, "a"))
touch(t, path.Join(d, "root.yaml"))
touch(t, path.Join(d, "z"))
},
given: "",
want: []string{"root.yaml", "a", "z"},
},
"empty dir": {
setup: func(t *testing.T, d string) {
os.Mkdir(path.Join(d, "x"), os.ModePerm)
},
given: "x",
want: []string{"root.yaml"},
},
"dir": {
setup: func(t *testing.T, d string) {
os.Mkdir(path.Join(d, "x"), os.ModePerm)
touch(t, path.Join(d, "x", "a"))
touch(t, path.Join(d, "x", "a.todo.xyz"))
touch(t, path.Join(d, "x", "b"))
},
given: "x",
want: []string{"a", "a.todo.xyz", "b"},
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
d := t.TempDir()
if c.setup != nil {
c.setup(t, d)
}
config := config{target: path.Join(d, c.given)}
got := config.Targets()
for i := range got {
got[i] = path.Base(got[i])
}
for i := range c.want {
c.want[i] = path.Base(c.want[i])
}
t.Logf("want\n\t%+v, got \n\t%+v", c.want, got)
if len(got) != len(c.want) {
t.Error(c.want, got)
}
for i := range got {
if got[i] != c.want[i] {
t.Errorf("[%d] wanted %s, got %s", i, c.want[i], got[i])
}
}
})
}
}

View File

@@ -1,44 +0,0 @@
package main
import (
"io"
"os"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)
func dump(config config) error {
return _dump(os.Stdout, config.Targets(), config.tags, config.search, config.root)
}
func _dump(writer io.Writer, filepaths []string, tags []string, search, rootDisplay string) error {
root, err := pttodo.NewRootFromFiles(filepaths...)
if err != nil {
return err
}
for _, x := range []*[]pttodo.Todo{
&root.Todo,
&root.Scheduled,
&root.Done,
} {
y := pttodo.Todos(*x)
y = y.LikeTags(tags)
y = y.LikeSearch(search)
*x = y
}
var v interface{} = root
switch rootDisplay {
case DUMP_TODO:
v = root.Todo
case DUMP_SCHEDULED:
v = root.Scheduled
case DUMP_DONE:
v = root.Done
default:
}
return yaml.NewEncoder(writer).Encode(v)
}

View File

@@ -1,262 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"sort"
"strings"
"syscall"
"time"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"github.com/google/uuid"
"gopkg.in/yaml.v2"
)
func edit(config *config) error {
if !config.edit {
return nil
}
return _edit(config.Targets())
}
func _edit(filepaths []string) error {
editableDir, err := inEditableDirAsTodos(filepaths)
if err != nil {
return err
}
if err := modifyEditableDir(editableDir, func() error {
files, err := listDir(editableDir)
if err != nil {
return err
}
for _, p := range files {
if _, err := pttodo.NewTodosFromFile(p); err != nil {
return err
}
}
return nil
}); err != nil {
return err
}
editedFiles, err := listDir(editableDir)
if err != nil {
return err
}
edits := map[string]string{}
for _, editedFile := range editedFiles {
edits[path.Base(editedFile)] = editedFile
edited, err := pttodo.NewTodosFromFile(editedFile)
if err != nil {
return err
}
original, err := pttodo.NewRootFromFile(func() string {
for _, f := range filepaths {
if path.Base(f) == path.Base(editedFile) {
return f
}
}
return "/dev/null"
}())
if err != nil {
return err
}
editedIDs := map[string]int{}
for i := range edited {
editedIDs[edited[i].ID()] = 1
}
for i := range original.Todo {
if _, ok := editedIDs[original.Todo[i].ID()]; ok {
continue
}
original.Todo[i].TS = pttodo.TS(time.Now().Unix())
if string(original.Todo[i].Schedule) != "" && !original.Todo[i].Triggered() {
original.Scheduled = append(original.Scheduled, original.Todo[i])
}
original.Done = append(original.Done, original.Todo[i])
}
original.Todo = edited
original.AutoMove()
if err := func() error {
f, err := os.Create(editedFile)
if err != nil {
return err
}
defer f.Close()
return yaml.NewEncoder(f).Encode(original)
}(); err != nil {
return err
}
}
dir := ""
for _, f := range filepaths {
if edited, ok := edits[path.Base(f)]; ok {
if err := rename(edited, f); err != nil {
return err
}
delete(edits, path.Base(f))
} else if err := os.Remove(f); err != nil {
return err
}
dir = path.Dir(f)
}
for base, editedFile := range edits {
f := path.Join(dir, base)
if _, err := os.Stat(f); err == nil {
f = fmt.Sprintf("%s.todo.%s", f, uuid.New().String())
}
if err := rename(editedFile, f); err != nil {
return err
}
}
return nil
}
func inEditableDirAsTodos(filepaths []string) (string, error) {
tempDir, err := ioutil.TempDir(os.TempDir(), "edit-pttodo-*")
if err != nil {
return "", err
}
return tempDir, copyTodoToDir(tempDir, filepaths)
}
func copyTodoToDir(d string, filepaths []string) error {
inboxes := map[string][]string{}
for _, target := range filepaths {
p := path.Join(d, path.Base(target))
if strings.Contains(path.Base(target), ".todo.") {
p := path.Join(d, strings.Split(path.Base(p), ".todo")[0])
inboxes[p] = append(inboxes[p], target)
continue
}
if root, err := pttodo.NewRootFromFile(target); err != nil {
return err
} else if b, err := yaml.Marshal(root.Todo); err != nil {
return err
} else if err := os.WriteFile(p, b, os.ModePerm); err != nil {
return err
}
}
for p, inboxes := range inboxes {
inboxRoot := pttodo.Root{}
for _, inbox := range inboxes {
subInboxRoot, err := pttodo.NewRootFromFile(inbox)
if err != nil {
return err
}
inboxRoot.MergeIn(pttodo.Root{Todo: subInboxRoot.Todo})
inboxRoot.MergeIn(pttodo.Root{Todo: subInboxRoot.Scheduled})
}
root, err := pttodo.NewRootFromFile(p)
if err != nil {
return err
}
root.MergeIn(pttodo.Root{Todo: inboxRoot.Todo})
root.MergeIn(pttodo.Root{Todo: inboxRoot.Scheduled})
if b, err := yaml.Marshal(root.Todo); err != nil {
return err
} else if err := os.WriteFile(p, b, os.ModePerm); err != nil {
return err
}
}
return nil
}
func modifyEditableDir(d string, check func() error) error {
for {
if err := vimd(d); err != nil {
return err
}
err := check()
if err == nil {
return nil
}
log.Printf("%s, press <Enter> to resume editing", err)
b := make([]byte, 1)
if _, err := os.Stdin.Read(b); err != nil {
return err
}
}
}
func vimd(d string) error {
bin := "vim"
editorbin, err := exec.LookPath(bin)
if err != nil {
editorbin, err = exec.LookPath("vi")
}
if err != nil {
return err
}
args := []string{editorbin, "-p"}
files, err := listDir(d)
if err != nil {
return err
}
args = append(args, files...)
cpid, err := syscall.ForkExec(
editorbin,
args,
&syscall.ProcAttr{
Dir: d,
Env: os.Environ(),
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
Sys: nil,
},
)
if err != nil {
return err
}
proc, err := os.FindProcess(cpid)
if err != nil {
return err
}
state, err := proc.Wait()
if err != nil {
return err
}
if exitCode := state.ExitCode(); exitCode != 0 {
return fmt.Errorf("bad exit code on vim: %d, state: %+v", exitCode, state)
}
return nil
}
func listDir(dname string) ([]string, error) {
entries, err := os.ReadDir(dname)
if err != nil {
return nil, err
}
paths := make([]string, 0, len(entries))
for i := range entries {
if entries[i].IsDir() {
continue
}
if strings.HasPrefix(path.Base(entries[i].Name()), ".") {
continue
}
paths = append(paths, path.Join(dname, entries[i].Name()))
}
sort.Slice(paths, func(i, j int) bool {
if path.Base(paths[i]) == "root.yaml" {
return true
}
return paths[i] < paths[j]
})
return paths, nil
}

View File

@@ -1,29 +0,0 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"testing"
)
func TestListDir(t *testing.T) {
d := t.TempDir()
want := []string{}
for i := 0; i < 3; i++ {
p := path.Join(d, strconv.Itoa(i))
ioutil.WriteFile(p, []byte{}, os.ModePerm)
want = append(want, p)
}
os.Mkdir(path.Join(d, "d"), os.ModePerm)
got, err := listDir(d)
if err != nil {
t.Fatal(err)
}
if fmt.Sprint(want) != fmt.Sprint(got) {
t.Fatal(want, got)
}
}

View File

@@ -1,13 +0,0 @@
module pttodo-cli
go 1.21
require (
gitea.inhome.blapointe.com/gogs/pttodo v0.0.0-20231109151914-5d74b458d542
github.com/google/uuid v1.3.0
gopkg.in/yaml.v2 v2.4.0
)
require github.com/robfig/cron/v3 v3.0.1 // indirect
replace gogs.inhome.blapointe.com/gogs/pttodo => ./..

View File

@@ -1,302 +0,0 @@
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"slices"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)
const (
DUMP_ALL = "all"
DUMP_TODO = "todo"
DUMP_SCHEDULED = "scheduled"
DUMP_DONE = "done"
)
func main() {
if err := _main(); err != nil {
panic(err)
}
}
func _main() error {
config := getConfig()
if config.target2 != "" {
return merge(&config)
} else if config.archive != "" {
return archive(&config)
} else if config.dedupe {
return dedupe(&config)
} else {
if err := add(&config); err != nil {
return err
}
if err := edit(&config); err != nil {
return err
}
return dump(config)
}
}
func dedupe(config *config) error {
baseReader, err := filePathReader(config.target)
if err != nil {
return err
}
baseB, err := ioutil.ReadAll(baseReader)
if err != nil {
return err
}
var base pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", config.target, err)
}
do := func(todos []pttodo.Todo) []pttodo.Todo {
i := len(todos) - 1
for i >= 0 {
j := slices.IndexFunc(todos, func(todo pttodo.Todo) bool {
return todo.Equals(todos[i])
})
if j >= 0 && j != i {
todos[j] = todos[i]
base.Done = append(base.Done, todos[j])
todos = todos[:i]
}
i -= 1
}
return todos
}
base.Todo = do(base.Todo)
base.Scheduled = do(base.Scheduled)
if tmppath, err := marshalRootToTempFile(base); err != nil {
return err
} else if config.dryRun {
log.Println("===before===")
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
log.Println("===after===")
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
} else if err := rename(tmppath, config.target); err != nil {
return err
}
return nil
}
func archive(config *config) error {
if config.archive == "" {
return nil
}
baseReader, err := filePathReader(config.target)
if err != nil {
return err
}
baseB, err := ioutil.ReadAll(baseReader)
if err != nil {
return err
}
archiveReader, err := filePathReader(config.archive)
if err != nil {
return err
}
archiveB, err := ioutil.ReadAll(archiveReader)
if err != nil {
return err
}
var base, archive pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", config.target, err)
}
if err := yaml.Unmarshal(archiveB, &archive); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", config.archive, err)
}
archive.Done = append(archive.Done, base.Done...)
if tmppath2, err := marshalRootToTempFile(archive); err != nil {
return err
} else if config.dryRun {
} else if err := rename(tmppath2, config.archive); err != nil {
return err
}
base.Done = base.Done[:0]
if tmppath, err := marshalRootToTempFile(base); err != nil {
return err
} else if config.dryRun {
log.Println("===before===")
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
log.Println("===after===")
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
} else if err := rename(tmppath, config.target); err != nil {
return err
}
return nil
}
func merge(config *config) error {
if config.target2 == "" {
return nil
}
baseReader, err := filePathReader(config.target)
if err != nil {
return err
}
baseB, err := ioutil.ReadAll(baseReader)
if err != nil {
return err
}
mergingReader, err := filePathReader(config.target2)
if err != nil {
return err
}
mergingB, err := ioutil.ReadAll(mergingReader)
if err != nil {
return err
}
var base, merging pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", config.target, err)
}
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", config.target2, err)
}
slurp := func(dst, src *[]pttodo.Todo) {
_src := *src
for i := range _src {
found := false
for j := range *dst {
found = found || _src[i].Equals((*dst)[j])
}
if !found {
*dst = append(*dst, _src[i])
}
}
}
slurp(&base.Todo, &merging.Todo)
slurp(&base.Scheduled, &merging.Scheduled)
slurp(&base.Done, &merging.Done)
if tmppath, err := marshalRootToTempFile(base); err != nil {
return err
} else if config.dryRun {
log.Println("===before===")
_dump(os.Stdout, []string{config.target}, config.tags, config.search, config.root)
log.Println("===after===")
_dump(os.Stdout, []string{tmppath}, config.tags, config.search, config.root)
} else if err := rename(tmppath, config.target); err != nil {
return err
}
merging.Done = append(merging.Done, merging.Todo...)
merging.Todo = merging.Todo[:0]
merging.Scheduled = append(merging.Done, merging.Scheduled...)
merging.Scheduled = merging.Scheduled[:0]
if tmppath2, err := marshalRootToTempFile(merging); err != nil {
return err
} else if config.dryRun {
} else if err := rename(tmppath2, config.target2); err != nil {
return err
}
return nil
}
func _merge(filepath string, mergeTargetFilePath string) error {
baseReader, err := filePathReader(filepath)
if err != nil {
return err
}
baseB, err := ioutil.ReadAll(baseReader)
if err != nil {
return err
}
mergingReader, err := filePathReader(mergeTargetFilePath)
if err != nil {
return err
}
mergingB, err := ioutil.ReadAll(mergingReader)
if err != nil {
return err
}
var base, merging pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", filepath, err)
}
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
return fmt.Errorf("failed to parse yaml in %s: %w", mergeTargetFilePath, err)
}
base.MergeIn(merging)
tmppath, err := marshalRootToTempFile(base)
if err != nil {
return err
}
return rename(tmppath, filepath)
}
func rename(oldpath, newpath string) error {
b, err := os.ReadFile(oldpath)
if err != nil {
return err
}
if err := os.WriteFile(newpath, b, os.ModePerm); err != nil {
return err
}
return os.Remove(oldpath)
}
func marshalRootToTempFile(root pttodo.Root) (string, error) {
f, err := ioutil.TempFile(os.TempDir(), "tmp")
if err != nil {
return "", err
}
f.Close()
os.Remove(f.Name())
b, err := yaml.Marshal(root)
if err != nil {
return "", err
}
filepath := f.Name() + ".yaml"
err = ioutil.WriteFile(filepath, b, os.ModePerm)
return filepath, err
}
func filePathReader(path string) (io.Reader, error) {
if path == "-" {
return os.Stdin, nil
}
b, err := ioutil.ReadFile(path)
if os.IsNotExist(err) {
return bytes.NewReader([]byte("{}")), nil
}
if err != nil {
return nil, err
}
return bytes.NewReader(b), nil
}

308
cmd/pttodo-cli/cli.go Normal file
View File

@@ -0,0 +1,308 @@
package main
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"local/pt-todo-server/pttodo"
"log"
"os"
"os/exec"
"path"
"strings"
"syscall"
"gopkg.in/yaml.v2"
)
const (
DUMP_ALL = "all"
DUMP_TODO = "todo"
DUMP_SCHEDULED = "scheduled"
DUMP_DONE = "done"
)
func main() {
if err := _main(); err != nil {
panic(err)
}
}
func _main() error {
defaultFilepath, ok := os.LookupEnv("PTTODO_FILE")
if !ok {
defaultFilepath = "-"
}
filepath := flag.String("f", defaultFilepath, "($PTTODO_FILE) path to yaml file")
filepathToMergeIn := flag.String("g", "", "path to yaml file to merge into -f")
root := flag.String("root", DUMP_TODO, "path to pretty print ("+fmt.Sprint([]string{DUMP_ALL, DUMP_TODO, DUMP_SCHEDULED, DUMP_DONE})+")")
tags := flag.String("tags", "", "csv of all tags to find")
search := flag.String("search", "", "fts case insensitive")
e := flag.Bool("e", false, "edit file")
dry := flag.Bool("dry", false, "dry run")
flag.Parse()
if *filepathToMergeIn != "" {
if err := merge(*dry, *filepath, *filepathToMergeIn); err != nil {
return err
}
}
if *e {
if err := edit(*dry, *filepath); err != nil {
return err
}
}
var tagslist []string
if *tags != "" {
tagslist = strings.Split(*tags, ",")
}
return dump(*dry, os.Stdout, *filepath, tagslist, *search, *root)
}
func verifyRoot(root pttodo.Root) error {
f, err := ioutil.TempFile(os.TempDir(), "tmp")
if err != nil {
return err
}
f.Close()
tempFile := f.Name()
b, err := yaml.Marshal(root)
if err != nil {
return err
}
if err := ioutil.WriteFile(tempFile, b, os.ModePerm); err != nil {
return err
}
defer os.Remove(tempFile)
return verifyFile(tempFile)
}
func verifyFile(path string) error {
return dump(true, io.Discard, path, nil, "", DUMP_ALL)
}
func edit(dry bool, filepath string) error {
var tempFile string
cp := func() error {
f, err := ioutil.TempFile(os.TempDir(), path.Base(filepath))
if err != nil {
return err
}
if _, err := os.Stat(filepath); err == nil {
g, err := os.Open(filepath)
if err != nil {
return err
}
if _, err := io.Copy(f, g); err != nil {
return err
}
g.Close()
}
f.Close()
tempFile = f.Name() + ".yaml"
return os.Rename(f.Name(), tempFile)
}
vi := func() error {
bin := "vim"
if editor := os.Getenv("EDITOR"); editor != "" {
bin = editor
}
editorbin, err := exec.LookPath(bin)
if err != nil {
editorbin, err = exec.LookPath("vi")
}
if err != nil {
return err
}
args := []string{editorbin, tempFile}
cpid, err := syscall.ForkExec(
editorbin,
args,
&syscall.ProcAttr{
Dir: "",
Env: os.Environ(),
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
Sys: nil,
},
)
if err != nil {
return err
}
proc, err := os.FindProcess(cpid)
if err != nil {
return err
}
state, err := proc.Wait()
if err != nil {
return err
}
if exitCode := state.ExitCode(); exitCode != 0 {
return fmt.Errorf("bad exit code on vim: %d, state: %+v", exitCode, state)
}
return nil
}
verify := func() error {
return verifyFile(tempFile)
}
save := func() error {
if dry {
log.Printf("would've saved %s as %s", tempFile, filepath)
return nil
}
return os.Rename(tempFile, filepath)
}
for _, foo := range []func() error{cp, vi, verify, save} {
if err := foo(); err != nil {
return err
}
}
if !dry {
os.Remove(tempFile)
}
return nil
}
func merge(dry bool, filepath string, mergeTargetFilePath string) error {
baseReader, err := filePathReader(filepath)
if err != nil {
return err
}
baseB, err := ioutil.ReadAll(baseReader)
if err != nil {
return err
}
mergingReader, err := filePathReader(mergeTargetFilePath)
if err != nil {
return err
}
mergingB, err := ioutil.ReadAll(mergingReader)
if err != nil {
return err
}
var base, merging pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return err
}
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
return err
}
base.MergeIn(merging)
if err := verifyRoot(base); err != nil {
return err
}
tmppath, err := marshalRootToTempFile(base)
if err != nil {
return err
}
if dry {
log.Printf("would've moved %s to %s when adding %s", tmppath, filepath, mergeTargetFilePath)
return nil
}
return os.Rename(tmppath, filepath)
}
func marshalRootToTempFile(root pttodo.Root) (string, error) {
f, err := ioutil.TempFile(os.TempDir(), "tmp")
if err != nil {
return "", err
}
f.Close()
os.Remove(f.Name())
b, err := yaml.Marshal(root)
if err != nil {
return "", err
}
filepath := f.Name() + ".yaml"
err = ioutil.WriteFile(filepath, b, os.ModePerm)
return filepath, err
}
func dump(dry bool, writer io.Writer, filepath string, tags []string, search, rootDisplay string) error {
reader, err := filePathReader(filepath)
if err != nil {
return err
}
b, err := ioutil.ReadAll(reader)
if err != nil {
return err
}
var root pttodo.Root
if err := yaml.Unmarshal(b, &root); err != nil {
return err
}
root.MoveScheduledToTodo()
var v interface{} = root
switch rootDisplay {
case DUMP_ALL:
case DUMP_TODO:
v = root.Todo
case DUMP_SCHEDULED:
v = root.Scheduled
case DUMP_DONE:
v = root.Done
}
if todos, ok := v.([]pttodo.Todo); ok {
if len(tags) > 0 {
result := make([]pttodo.Todo, 0, len(todos))
for _, todo := range todos {
want := len(todo.Tags) > 0
for _, tag := range tags {
want = want && strings.Contains(todo.Tags, tag)
}
if want {
result = append(result, todo)
}
}
todos = result
}
if len(search) > 0 {
result := make([]pttodo.Todo, 0, len(todos))
for _, todo := range todos {
if strings.Contains(strings.ToLower(fmt.Sprint(todo)), strings.ToLower(search)) {
result = append(result, todo)
}
}
todos = result
}
v = todos
}
b2, err := yaml.Marshal(v)
if err != nil {
return err
}
fmt.Fprintf(writer, "%s\n", b2)
if dry {
return nil
}
b3, err := yaml.Marshal(root)
if err != nil {
return err
}
return os.WriteFile(filepath, b3, os.ModePerm)
}
func filePathReader(path string) (io.Reader, error) {
var reader io.Reader
if path == "-" {
reader = os.Stdin
} else {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
reader = bytes.NewReader(b)
}
return reader, nil
}

12
cmd/pttodo-cli/go.mod Normal file
View File

@@ -0,0 +1,12 @@
module pttodo-cli
go 1.17
require (
gopkg.in/yaml.v2 v2.4.0
local/pt-todo-server v0.0.0-00010101000000-000000000000
)
require github.com/robfig/cron/v3 v3.0.1 // indirect
replace local/pt-todo-server => ../../

View File

@@ -1,7 +1,3 @@
gitea.inhome.blapointe.com/gogs/pttodo v0.0.0-20231109151914-5d74b458d542 h1:eOWrA2hEQoyu413vbdXbEXHLXEX2TVBXjWawlWndFhg=
gitea.inhome.blapointe.com/gogs/pttodo v0.0.0-20231109151914-5d74b458d542/go.mod h1:9CFZf/SSod0Z/8WvbmOI4gEzy6xGpBQAq8coVNncNvk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

20
cmd/testdata/1.yaml vendored
View File

@@ -1,20 +0,0 @@
todo:
- "1"
- "2"
- "1"
- "2"
- "2"
scheduled:
- todo: "10"
schedule: 0 0 0 * *
ts: Sat Aug 10 02:42:48 MDT 2024
- todo: "20"
- todo: "10"
- todo: "20"
- todo: "20"
done:
- todo: "100"
- todo: "200"
- todo: "100"
- todo: "200"
- todo: "200"

4
cmd/testdata/2.yaml vendored
View File

@@ -1,4 +0,0 @@
todo:
- "2"
scheduled: []
done: []

4
cmd/testdata/3.yaml vendored
View File

@@ -1,4 +0,0 @@
todo:
- "3"
scheduled: []
done: []

View File

@@ -1,17 +0,0 @@
todo:
- root
- stub
scheduled:
- todo: abc
schedule: "2024-01-01"
ts: Thu Nov 9 07:49:59 MST 2023
- todo: "1"
schedule: "2024-01-02"
ts: Thu Nov 9 07:51:13 MST 2023
- todo: "1"
schedule: "2024-01-02"
ts: Thu Nov 9 07:51:28 MST 2023
- todo: "1"
schedule: "2024-01-02"
ts: Thu Nov 9 07:51:36 MST 2023
done: []

View File

@@ -1,10 +0,0 @@
todo:
- hi20890
- hi
- b
- todo: loop
schedule: 10s
- todo: past
schedule: "2000-01-02"
scheduled: []
done: []

View File

@@ -0,0 +1,47 @@
#! /bin/bash
TODO_SERVER_URL="${TODO_SERVER_URL:-"https://todo-server.remote.blapointe.com"}"
TODO_SERVER_HEADERS="${TODO_SERVER_HEADERS:-"Cookie: BOAuthZ=$TODO_SERVER_BOAUTHZ"}"
TODO_SERVER_LIST="${TODO_SERVER_LIST:-"2548023766"}"
main() {
set -e
set -o pipefail
local tasks_in_todo="$(fetch_tasks_in_list | format_tasks_in_list)"
local schedule_tasks_in_flight="$(COMPL=1 LOOPING=1 fetch_tasks_in_list | format_tasks_in_list)"
echo "{\"todo\": $tasks_in_todo, \"scheduled\": $schedule_tasks_in_flight, \"done\": []}" | yq -P eval -
}
format_tasks_in_list() {
jq -c .list[] | while read -r line; do
echo "$line" \
| jq '{
todo: .title,
details:.note,
ts: (if .compl == 1 then (.dateCompleted | strptime("%d %b %Y %I:%M %p") | mktime) else .dateEditedInt end),
subtasks: [],
tags: .tags,
schedule: (if (.cron != "") then (.cron) else (.loop) end)
}' \
| jq -c .
done | jq -sc | yq -P eval - | grep -v -E ' (""|\[]|0s)$' | yq -j eval - | jq -c .
}
fetch_tasks_in_list() {
local csv_headers="$TODO_SERVER_HEADERS"
local headers=()
while [ "$csv_headers" != "" ]; do
header="${csv_headers%%,*}"
headers+=("-H" "${header%%:*}: ${header#*:}")
if echo "$csv_headers" | grep -q ,; then
csv_headers="${csv_headers#*,}"
else
csv_headers=""
fi
done
curl -sS "${headers[@]}" "$TODO_SERVER_URL/ajax.php?loadTasks=&list=$TODO_SERVER_LIST&compl=${COMPL:-0}&looping=${LOOPING:-0}"
}
if [ "$0" == "$BASH_SOURCE" ]; then
main "$@"
fi

4
go.mod
View File

@@ -1,6 +1,6 @@
module gitea.inhome.blapointe.com/gogs/pttodo module pttodo
go 1.21 go 1.17
require ( require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1

View File

@@ -1,91 +1,11 @@
package pttodo package pttodo
import (
"bytes"
"fmt"
"os"
yaml "gopkg.in/yaml.v2"
)
type Root struct { type Root struct {
Todo []Todo Todo []Todo
Scheduled []Todo Scheduled []Todo
Done []Todo Done []Todo
} }
func NewRootFromFiles(p ...string) (Root, error) {
var result Root
for _, p := range p {
subroot, err := NewRootFromFile(p)
if err != nil {
return Root{}, err
}
result.MergeIn(subroot)
}
result.AutoMove()
return result, nil
}
func NewRootFromFile(p string) (Root, error) {
if b, err := os.ReadFile(p); err == nil && len(bytes.TrimSpace(b)) == 0 {
return Root{}, nil
}
f, err := os.Open(p)
if os.IsNotExist(err) {
return Root{}, nil
}
if err != nil {
return Root{}, err
}
defer f.Close()
var result Root
if err := yaml.NewDecoder(f).Decode(&result); err != nil {
todos, err2 := NewTodosFromFile(p)
if err2 != nil {
return Root{}, fmt.Errorf("failed to parse yaml from %s: %w", p, err)
}
result.Todo = todos
}
result.AutoMove()
return result, nil
}
func (root Root) Equals(other Root) bool {
for i, slice := range [][2][]Todo{
[2][]Todo{root.Todo, other.Todo},
[2][]Todo{root.Scheduled, other.Scheduled},
[2][]Todo{root.Done, other.Done},
} {
_ = i
if !equalTodoSlices(slice[0], slice[1]) {
return false
}
}
return true
}
func (root *Root) AutoMove() {
root.MoveScheduledToTodo()
root.MoveTodoToScheduled()
}
func (root *Root) MoveTodoToScheduled() {
for i := len(root.Todo) - 1; i >= 0; i-- {
if !root.Todo[i].Schedule.isFixedFuture() {
continue
}
root.Scheduled = append(root.Scheduled, root.Todo[i])
for j := i; j < len(root.Todo)-1; j++ {
root.Todo[j] = root.Todo[j+1]
}
root.Todo = root.Todo[:len(root.Todo)-1]
}
}
func (root *Root) MoveScheduledToTodo() { func (root *Root) MoveScheduledToTodo() {
for i := len(root.Scheduled) - 1; i >= 0; i-- { for i := len(root.Scheduled) - 1; i >= 0; i-- {
if root.Scheduled[i].Triggered() { if root.Scheduled[i].Triggered() {
@@ -116,17 +36,3 @@ func (root *Root) MergeIn(root2 Root) {
} }
} }
} }
func (root Root) MarshalYAML() (interface{}, error) {
for i := range root.Todo {
root.Todo[i].writeTS = false
}
for i := range root.Scheduled {
root.Scheduled[i].writeTS = true
}
for i := range root.Done {
root.Done[i].writeTS = true
}
type Alt Root
return (Alt)(root), nil
}

View File

@@ -1,13 +1,9 @@
package pttodo package pttodo
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path"
"strconv" "strconv"
"strings"
"testing" "testing"
"time" "time"
@@ -27,8 +23,6 @@ func TestJSONRoot(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} else if fmt.Sprint(root) != fmt.Sprint(root2) { } else if fmt.Sprint(root) != fmt.Sprint(root2) {
t.Fatal(root, root2) t.Fatal(root, root2)
} else if !root.Equals(root2) {
t.Fatal(root2)
} }
} }
@@ -100,8 +94,6 @@ func TestRootMoveScheduledToTodo(t *testing.T) {
got.MoveScheduledToTodo() got.MoveScheduledToTodo()
if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", want) { if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", want) {
t.Fatalf("want \n\t%+v, got \n\t%+v", want, got) t.Fatalf("want \n\t%+v, got \n\t%+v", want, got)
} else if !got.Equals(want) {
t.Fatalf("want \n\t%+v, got \n\t%+v", want, got)
} }
}) })
} }
@@ -151,109 +143,5 @@ todo:
root0.MergeIn(root1) root0.MergeIn(root1)
if fmt.Sprintf("%+v", root0) != fmt.Sprintf("%+v", rootWant) { if fmt.Sprintf("%+v", root0) != fmt.Sprintf("%+v", rootWant) {
t.Fatalf("want \n\t%+v, got \n\t%+v", rootWant, root0) t.Fatalf("want \n\t%+v, got \n\t%+v", rootWant, root0)
} else if !root0.Equals(rootWant) {
t.Fatalf("want \n\t%+v, got \n\t%+v", rootWant, root0)
}
}
func TestRootMarshalYAMLWriteTS(t *testing.T) {
root := Root{
Todo: []Todo{Todo{Todo: "todo", TS: 1, writeTS: true}},
Scheduled: []Todo{Todo{Todo: "sched", TS: 2, writeTS: false, Schedule: "2099-01-01"}},
Done: []Todo{Todo{Todo: "done", TS: 3, writeTS: false}},
}
got, err := yaml.Marshal(root)
if err != nil {
t.Fatal(err)
}
got = bytes.TrimSpace(got)
want := strings.TrimSpace(`
todo:
- todo
scheduled:
- todo: sched
schedule: "2099-01-01"
ts: Wed Dec 31 17:00:02 MST 1969
done:
- todo: done
ts: Wed Dec 31 17:00:03 MST 1969
`)
if string(got) != want {
t.Fatalf("want\n\t%q, got\n\t%q", want, string(got))
}
}
func TestRootFromFile(t *testing.T) {
cases := map[string]struct {
given string
want Root
}{
"empty": {},
"happy": {
given: `{"todo":["a", "b"],"scheduled":["c"], "done":[{"todo": "d"}]}`,
want: Root{
Todo: []Todo{
Todo{Todo: "a"},
Todo{Todo: "b"},
},
Scheduled: []Todo{
Todo{Todo: "c"},
},
Done: []Todo{
Todo{Todo: "d"},
},
},
},
"todos": {
given: `["a", {"todo": "b"}]`,
want: Root{
Todo: []Todo{
{Todo: "a"},
{Todo: "b"},
},
},
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
d := t.TempDir()
p := path.Join(d, "input.yaml")
if err := os.WriteFile(p, []byte(c.given), os.ModePerm); err != nil {
t.Fatal(err)
}
got, err := NewRootFromFile(p)
if err != nil {
t.Fatal(err)
}
if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", c.want) {
t.Errorf("want\n\t%+v, got\n\t%+v", c.want, got)
}
})
}
}
func TestRootFromFiles(t *testing.T) {
d := t.TempDir()
ps := []string{
path.Join(d, "a"),
path.Join(d, "b"),
}
os.WriteFile(ps[0], []byte(`["a"]`), os.ModePerm)
os.WriteFile(ps[1], []byte(`["b"]`), os.ModePerm)
got, err := NewRootFromFiles(ps...)
if err != nil {
t.Fatal(err)
}
want := Root{
Todo: []Todo{
{Todo: "a"},
{Todo: "b"},
},
}
if fmt.Sprintf("%+v", got) != fmt.Sprintf("%+v", want) {
t.Errorf("want\n\t%+v, got \n\t%+v", want, got)
} }
} }

View File

@@ -2,7 +2,6 @@ package pttodo
import ( import (
"encoding/json" "encoding/json"
"fmt"
"math" "math"
"regexp" "regexp"
"strconv" "strconv"
@@ -18,10 +17,7 @@ func (schedule Schedule) MarshalJSON() ([]byte, error) {
} }
func (schedule *Schedule) UnmarshalJSON(b []byte) error { func (schedule *Schedule) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, (*string)(schedule)); err != nil { return json.Unmarshal(b, (*string)(schedule))
return fmt.Errorf("failed to parse json from %q: %w", b, err)
}
return nil
} }
func (schedule Schedule) Next(t time.Time) (time.Time, error) { func (schedule Schedule) Next(t time.Time) (time.Time, error) {
@@ -29,42 +25,6 @@ func (schedule Schedule) Next(t time.Time) (time.Time, error) {
return scheduler.next(t) return scheduler.next(t)
} }
func (schedule Schedule) isFixedFuture() bool {
if !schedule.isFixed() {
return false
}
return schedule.isFuture()
}
func (schedule Schedule) isFixed() bool {
if schedule.empty() {
return false
}
scheduler := schedulerFactory(string(schedule))
switch scheduler.(type) {
case scheduleEZDate, scheduleDue:
default:
return false
}
return true
}
func (schedule Schedule) isFuture() bool {
if schedule.empty() {
return false
}
scheduler := schedulerFactory(string(schedule))
t, err := scheduler.next(time.Now())
if err != nil {
return false
}
return time.Now().Before(t)
}
func (schedule Schedule) empty() bool {
return string(schedule) == ""
}
type scheduler interface { type scheduler interface {
next(time.Time) (time.Time, error) next(time.Time) (time.Time, error)
} }
@@ -134,11 +94,8 @@ func (due scheduleDue) next(time.Time) (time.Time, error) {
// 2022-01-01 // 2022-01-01
type scheduleEZDate string type scheduleEZDate string
var scheduleEZDatePattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}(T[0-9]{2})?$`) var scheduleEZDatePattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}$`)
func (ezdate scheduleEZDate) next(time.Time) (time.Time, error) { func (ezdate scheduleEZDate) next(time.Time) (time.Time, error) {
if len(ezdate) == len("20xx-xx-xxTxx") { return time.Parse("2006-01-02", string(ezdate))
return time.ParseInLocation("2006-01-02T15", string(ezdate), time.Local)
}
return time.ParseInLocation("2006-01-02", string(ezdate), time.Local)
} }

View File

@@ -51,17 +51,11 @@ func TestJSONSchedule(t *testing.T) {
func TestSchedulerFactory(t *testing.T) { func TestSchedulerFactory(t *testing.T) {
start := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC) start := time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC)
someDay := time.Date(2001, 2, 3, 0, 0, 0, 0, time.UTC) someDay := time.Date(2001, 2, 3, 0, 0, 0, 0, time.UTC)
someHour := time.Date(2001, 2, 3, 15, 0, 0, 0, time.UTC)
cases := map[string]struct { cases := map[string]struct {
input string input string
want interface{} want interface{}
next time.Time next time.Time
}{ }{
"ezdate with hour": {
input: `2000-01-03T15`,
want: scheduleEZDate(`2000-01-03T15`),
next: someHour,
},
"long dur": { "long dur": {
input: `2h1m`, input: `2h1m`,
want: scheduleDuration("2h1m"), want: scheduleDuration("2h1m"),
@@ -130,60 +124,3 @@ func TestSchedulerFactory(t *testing.T) {
} }
}) })
} }
func TestScheduleIsFixedFuture(t *testing.T) {
cases := map[string]struct {
input string
want bool
}{
"empty": {
input: "",
want: false,
},
"cron": {
input: "* * * * *",
want: false,
},
"duration": {
input: "1m",
want: false,
},
"due past": {
input: "123",
want: false,
},
"due future": {
input: "9648154541",
want: true,
},
"ez date short past": {
input: "2000-01-02",
want: false,
},
"ez date short future": {
input: "2100-01-02",
want: true,
},
"ez date long past": {
input: "2000-01-02T01",
want: false,
},
"ez date long future": {
input: "2100-01-02T02",
want: true,
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
schedule := Schedule(c.input)
got := schedule.isFixedFuture()
if got != c.want {
gotFuture := schedule.isFuture()
gotFixed := schedule.isFixed()
t.Errorf("want %v, got %v = %v && %v", c.want, got, gotFuture, gotFixed)
}
})
}
}

View File

@@ -1,53 +1,17 @@
package pttodo package pttodo
import ( import (
"encoding/base64"
"fmt" "fmt"
"hash/crc32"
"os"
"time" "time"
yaml "gopkg.in/yaml.v2"
) )
type Todo struct { type Todo struct {
Todo string Todo string
TS TS
Details string `yaml:",omitempty"` Details string `yaml:",omitempty"`
Schedule Schedule `yaml:",omitempty"` Schedule Schedule `yaml:",omitempty"`
Tags string `yaml:",omitempty"` Tags string `yaml:",omitempty"`
Subtasks []Todo `yaml:",omitempty"` Subtasks []Todo `yaml:",omitempty"`
TS TS `yaml:",omitempty"`
writeTS bool
}
func NewTodosFromFile(p string) ([]Todo, error) {
f, err := os.Open(p)
if os.IsNotExist(err) {
return nil, nil
}
if err != nil {
return nil, err
}
defer f.Close()
var result []Todo
if err := yaml.NewDecoder(f).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to parse yaml from %s: %w", p, err)
}
return result, nil
}
func (todo Todo) ID() string {
hash := crc32.NewIEEE()
fmt.Fprintf(hash, "%d:%s", 0, todo.Todo)
fmt.Fprintf(hash, "%d:%s", 1, todo.Details)
fmt.Fprintf(hash, "%d:%s", 2, todo.Schedule)
fmt.Fprintf(hash, "%d:%s", 3, todo.Tags)
for i := range todo.Subtasks {
fmt.Fprintf(hash, "%d:%s", 4, todo.Subtasks[i].ID())
}
return base64.StdEncoding.EncodeToString(hash.Sum(nil))
} }
func (todo Todo) Triggered() bool { func (todo Todo) Triggered() bool {
@@ -57,11 +21,6 @@ func (todo Todo) Triggered() bool {
} }
func (todo Todo) MarshalYAML() (interface{}, error) { func (todo Todo) MarshalYAML() (interface{}, error) {
if !todo.writeTS {
todo.TS = 0
} else {
todo.TS = TS(todo.TS.time().Unix())
}
if fmt.Sprint(todo) == fmt.Sprint(Todo{Todo: todo.Todo}) { if fmt.Sprint(todo) == fmt.Sprint(Todo{Todo: todo.Todo}) {
return todo.Todo, nil return todo.Todo, nil
} }
@@ -78,37 +37,3 @@ func (todo *Todo) UnmarshalYAML(unmarshal func(interface{}) error) error {
alt := (*Alt)(todo) alt := (*Alt)(todo)
return unmarshal(alt) return unmarshal(alt)
} }
func (todo Todo) Equals(other Todo) bool {
if !equalTodoSlices(todo.Subtasks, other.Subtasks) {
return false
}
if todo.TS != other.TS {
return false
}
if todo.Tags != other.Tags {
return false
}
if todo.Schedule != other.Schedule {
return false
}
if todo.Details != other.Details {
return false
}
if todo.Todo != other.Todo {
return false
}
return true
}
func equalTodoSlices(a, b []Todo) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if !a[i].Equals(b[i]) {
return false
}
}
return true
}

View File

@@ -125,33 +125,3 @@ func TestMarshalTodo(t *testing.T) {
} }
}) })
} }
func TestTodoID(t *testing.T) {
cases := map[string]Todo{
"empty": Todo{},
"todo": Todo{Todo: "abc"},
"details": Todo{Details: "abc"},
"todo,details": Todo{Todo: "abc", Details: "abc"},
}
got := map[string]bool{}
for name, todod := range cases {
todo := todod
t.Run(name, func(t *testing.T) {
if _, ok := got[todo.ID()]; ok {
t.Error("dupe", todo.ID())
}
got[todo.ID()] = true
t.Logf("%s: %+v", todo.ID(), todo)
todo2 := todo
todo2.TS = 1
if todo.ID() != todo2.ID() {
t.Error("ts changed")
}
todo2.writeTS = true
if todo.ID() != todo2.ID() {
t.Error("writets changed")
}
})
}
}

View File

@@ -1,36 +0,0 @@
package pttodo
import "strings"
type Todos []Todo
func (todos Todos) LikeSearch(search string) Todos {
return todos.Like(func(todo Todo) bool {
return strings.Contains(
strings.ToLower(todo.Todo),
strings.ToLower(search),
)
})
}
func (todos Todos) LikeTags(tags []string) Todos {
return todos.Like(func(todo Todo) bool {
matches := true
for _, tag := range tags {
str := strings.TrimLeft(tag, "-")
want := !strings.HasPrefix(tag, "-")
matches = matches && strings.Contains(todo.Tags, str) == want
}
return matches
})
}
func (todos Todos) Like(like func(Todo) bool) Todos {
result := make(Todos, 0)
for i := range todos {
if like(todos[i]) {
result = append(result, todos[i])
}
}
return result
}

View File

@@ -1,17 +0,0 @@
package pttodo
import "testing"
func TestTodosLikeTags(t *testing.T) {
todos := Todos{
{Todo: "a", Tags: "x"},
{Todo: "b", Tags: "x,y"},
}
result := todos.LikeTags([]string{"x", "-y"})
if len(result) != 1 {
t.Error(result)
} else if result[0].Todo != "a" {
t.Error(result[0].Todo)
}
}

View File

@@ -3,7 +3,6 @@ package pttodo
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"time" "time"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
@@ -34,10 +33,7 @@ func (ts TS) MarshalYAML() (interface{}, error) {
} }
func (ts *TS) UnmarshalJSON(b []byte) error { func (ts *TS) UnmarshalJSON(b []byte) error {
if err := yaml.Unmarshal(b, ts); err != nil { return yaml.Unmarshal(b, ts)
return fmt.Errorf("faild to unmarshal TS from %q: %w", b, err)
}
return nil
} }
func (ts *TS) UnmarshalYAML(unmarshaller func(interface{}) error) error { func (ts *TS) UnmarshalYAML(unmarshaller func(interface{}) error) error {
@@ -48,7 +44,7 @@ func (ts *TS) UnmarshalYAML(unmarshaller func(interface{}) error) error {
} }
var s string var s string
if err := unmarshaller(&s); err == nil { if err := unmarshaller(&s); err == nil {
t, err := time.ParseInLocation(time.UnixDate, s, time.Local) t, err := time.Parse(time.UnixDate, s)
*ts = TS(t.Unix()) *ts = TS(t.Unix())
return err return err
} }

BIN
pttodoer/.DS_Store vendored

Binary file not shown.

634
pttodoer/Cargo.lock generated
View File

@@ -1,634 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cc"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets",
]
[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "croner"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "516aad5374ea0ea75a0f0f4512fb4e7ad46c5eeff9971cb8ebc8fd74f1cd16c1"
dependencies = [
"chrono",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "proc-macro2"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pttodoer"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"croner",
"regex",
"serde",
"serde_yaml",
"tempdir",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.202"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View File

@@ -1,15 +0,0 @@
[package]
name = "pttodoer"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.38"
clap = { version = "4.4.8", features = ["derive"] }
croner = "2.0.4"
regex = "1.10.4"
serde = { version = "1.0.202", features = [ "serde_derive" ] }
serde_yaml = "0.9.34"
tempdir = "0.3.7"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
- todo: a b and c
schedule: '* * * * *'
ts: 123
- d e f

View File

@@ -1,17 +0,0 @@
todo:
- a
- todo: b
schedule: 2000-01-01
scheduled:
- c
- todo: d
schedule: 2000-02-01
- todo: e
ts: Sun Dec 3 23:29:27 EST 2023
schedule: '0 0 1 1 *'
- todo: f
schedule: 2099-02-02
done:
- g
- todo: h
ts: Sun Dec 3 23:29:27 EST 2023

View File

@@ -1 +0,0 @@
plaintext

View File

@@ -1,4 +0,0 @@
todo: todo here
schedule: '* * * * *'
details: |
hello world

View File

@@ -1,2 +0,0 @@
- x
- y and z

View File

@@ -1,10 +0,0 @@
- 1do: due
schedule: 2006-04-05
- 2do: scheduled
schedule: 2099-04-05
- 3do: repeating scheduled
schedule: 0 0 1 1 *
ts: 2001-02-03T04:05:06Z
- _done:
do: done
schedule: 2006-04-05

View File

@@ -1 +0,0 @@
- file

View File

@@ -1 +0,0 @@
- a

View File

@@ -1 +0,0 @@
- b

View File

@@ -1,4 +0,0 @@
- todo
- do: scheduled
schedule: 2099-01-01
- _done: any

View File

@@ -1 +0,0 @@
{"ts":1762915830,"patch":{"op":"add","path":"/0","value":"scheduled tasks hide"}}

814
pttodoest/Cargo.lock generated
View File

@@ -1,814 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cc"
version = "1.2.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "clap"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cron-parser"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702858ce99daf23d8822fb22ec363b641b4bdcd9704182211fc113b01870f6de"
dependencies = [
"chrono",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "gethostname"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"windows-link",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "indexmap"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "json-patch"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90"
dependencies = [
"jsonptr",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "jsonptr"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pttodoest"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"cron-parser",
"gethostname",
"json-patch",
"jsonptr",
"regex",
"serde",
"serde_json",
"serde_yaml",
"tempdir",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wasm-bindgen"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"

View File

@@ -1,17 +0,0 @@
[package]
name = "pttodoest"
version = "0.1.0"
edition = "2024"
[dependencies]
chrono = "0.4.42"
clap = { version = "4.5.51", features = ["derive"] }
cron-parser = "0.11.0"
gethostname = "1.1.0"
json-patch = "4.1.0"
jsonptr = "0.7.1"
regex = "1.12.2"
serde = { version = "1.0.228", features = ["serde_derive"] }
serde_json = "1.0.145"
serde_yaml = "0.9.34"
tempdir = "0.3.7"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
{"ts":1762884455,"patch":{"op":"replace","path":"","value":["read; https://topicpartition.io/blog/postgres-pubsub-queue-benchmarks","pglogical vs ha\n\n# api.git#breel/keys-620-pglogical-always-set-cr/2-user-survives-cr\n$ mise run pulsegres-new ^logical/toggl\n","drive; VERIFY spoc posts daily summary w/ unresolved","drive; VERIFY spoc refreshes summary w/ thread comment contianing 'refresh'","637; reconcile deploy if replicas wrong; https://github.com/renderinc/api/pull/26540/files","https://linear.app/render-com/issue/KEYS-633/add-3-when-max-connections-overridden-for-3-superuser-connections","https://linear.app/render-com/issue/KEYS-637/billing-resume-should-1-unsuspend-pg-in-cloudsql-2-unsuspend-pg-in-cr","https://linear.app/render-com/issue/KEYS-638/pgoperator-generates-new-ha-patroni-cert-every-reconcile-no-matter","pg; how2partition; https://renderinc.slack.com/archives/C0319NYCSSG/p1756357545556659?thread_ts=1756357467.613369&cid=C0319NYCSSG","pitr; backup purge cronjob for PL types","pg11 pgbackup doesnt write to envsetting mucked env key","incident io; teach spocbotvr to read slacks","userdb to internal; peer packages can use internal as userdb","fcr; cannot pitr because pgbackrest doesnt know wal spans thus pgexporter and friends cant know pitr works","etcd statefulset of 1 (for no random podname, no conflict, k8s ensures pod replace)\npatroni always\n","maher; https://slab.render.com/posts/hopes-and-dreams-blegf8fx#hdsyt-valkey-bundle","maher; shadow lizhi pm loops","maher; get more interviewers","maher; get concrete career and project plans so i can get promo in 2y; no manager to advocate","read; https://trychroma.com/engineering/wal3","read; https://github.com/renderinc/dashboard/pull/8883","read; https://litestream.io/getting-started/","kr\nto del gcloud old key\nie https://console.cloud.google.com/iam-admin/serviceaccounts/details/104206017956912104938/keys?hl=en&project=render-prod\n",{"subtasks":["","pitr\nhttps://slab.render.com/posts/pitr-as-a-service-health-abvnqx11\nmore aggressive alert autotune backup cores\nmore aggressive alert on MOAR backup cores\ncreate alert autotune archive-push cores\ncreate alert MOAR archive-push cores\n","cr; frontend","cr; cli.git","cr; public-api-schema.git; https://github.com/renderinc/public-api-schema/pull/407 STILL NEED EVENTS","cr; website.git","cr; changelog","ops; pgproxy rate limits 50ps 100burst; https://github.com/renderinc/dbproxy/pull/91","2873; no conn patroni if upgradeInProgressWithoutHA; https://github.com/renderinc/api/pull/26328","2733; only EnvSettings; https://github.com/renderinc/api/pull/25322/files","pg18; after cred rotation works, re enable e2e","655; pg18; pub api sch; https://github.com/renderinc/public-api-schema/pull/421","655; pg18; go generate pub api sch; https://github.com/renderinc/api/pull/26694","663; das; show status in /info; https://github.com/renderinc/dashboard/pull/9616","664; pg18; go gen terraform; https://github.com/renderinc/api/pull/26701","664; pg18; ga; push terraform.git#breel/keys-664-pg18","656; pg18; website; https://github.com/renderinc/website/pull/985/files","663; das; note disk cannot decrease even if autoscaled; https://github.com/renderinc/dashboard/pull/9621","pulsegres; pls let me keep my test emails; https://github.com/renderinc/api/pull/26741","pgup; restore view owner; https://github.com/renderinc/api/pull/26814","pgup; resync if missing resync; https://github.com/renderinc/api/pull/26817","pgup; replicas use $RESYNC; https://github.com/renderinc/api/pull/26878"],"todo":"blocked"}]}}
{"ts":1762885026,"patch":{"op":"add","path":"/-","value":"hi"}}
{"ts":1762915959,"patch":{"op":"add","path":"/-","value":"enqueued add"}}
{"ts":1762915973,"patch":{"op":"remove","path":"/25"}}

View File

@@ -1,64 +0,0 @@
- read; https://topicpartition.io/blog/postgres-pubsub-queue-benchmarks
- |
pglogical vs ha
# api.git#breel/keys-620-pglogical-always-set-cr/2-user-survives-cr
$ mise run pulsegres-new ^logical/toggl
- drive; VERIFY spoc posts daily summary w/ unresolved
- drive; VERIFY spoc refreshes summary w/ thread comment contianing 'refresh'
- 637; reconcile deploy if replicas wrong; https://github.com/renderinc/api/pull/26540/files
- https://linear.app/render-com/issue/KEYS-633/add-3-when-max-connections-overridden-for-3-superuser-connections
- https://linear.app/render-com/issue/KEYS-637/billing-resume-should-1-unsuspend-pg-in-cloudsql-2-unsuspend-pg-in-cr
- https://linear.app/render-com/issue/KEYS-638/pgoperator-generates-new-ha-patroni-cert-every-reconcile-no-matter
- pg; how2partition; https://renderinc.slack.com/archives/C0319NYCSSG/p1756357545556659?thread_ts=1756357467.613369&cid=C0319NYCSSG
- pitr; backup purge cronjob for PL types
- pg11 pgbackup doesnt write to envsetting mucked env key
- incident io; teach spocbotvr to read slacks
- userdb to internal; peer packages can use internal as userdb
- fcr; cannot pitr because pgbackrest doesnt know wal spans thus pgexporter and friends cant know pitr works
- |
etcd statefulset of 1 (for no random podname, no conflict, k8s ensures pod replace)
patroni always
- maher; https://slab.render.com/posts/hopes-and-dreams-blegf8fx#hdsyt-valkey-bundle
- maher; shadow lizhi pm loops
- maher; get more interviewers
- maher; get concrete career and project plans so i can get promo in 2y; no manager to advocate
- read; https://trychroma.com/engineering/wal3
- read; https://github.com/renderinc/dashboard/pull/8883
- read; https://litestream.io/getting-started/
- |
kr
to del gcloud old key
ie https://console.cloud.google.com/iam-admin/serviceaccounts/details/104206017956912104938/keys?hl=en&project=render-prod
- subtasks:
- ''
- |
pitr
https://slab.render.com/posts/pitr-as-a-service-health-abvnqx11
more aggressive alert autotune backup cores
more aggressive alert on MOAR backup cores
create alert autotune archive-push cores
create alert MOAR archive-push cores
- cr; frontend
- cr; cli.git
- cr; public-api-schema.git; https://github.com/renderinc/public-api-schema/pull/407 STILL NEED EVENTS
- cr; website.git
- cr; changelog
- ops; pgproxy rate limits 50ps 100burst; https://github.com/renderinc/dbproxy/pull/91
- 2873; no conn patroni if upgradeInProgressWithoutHA; https://github.com/renderinc/api/pull/26328
- 2733; only EnvSettings; https://github.com/renderinc/api/pull/25322/files
- pg18; after cred rotation works, re enable e2e
- 655; pg18; pub api sch; https://github.com/renderinc/public-api-schema/pull/421
- 655; pg18; go generate pub api sch; https://github.com/renderinc/api/pull/26694
- 663; das; show status in /info; https://github.com/renderinc/dashboard/pull/9616
- 664; pg18; go gen terraform; https://github.com/renderinc/api/pull/26701
- 664; pg18; ga; push terraform.git#breel/keys-664-pg18
- 656; pg18; website; https://github.com/renderinc/website/pull/985/files
- 663; das; note disk cannot decrease even if autoscaled; https://github.com/renderinc/dashboard/pull/9621
- pulsegres; pls let me keep my test emails; https://github.com/renderinc/api/pull/26741
- pgup; restore view owner; https://github.com/renderinc/api/pull/26814
- pgup; resync if missing resync; https://github.com/renderinc/api/pull/26817
- pgup; replicas use $RESYNC; https://github.com/renderinc/api/pull/26878
todo: blocked
- hi

View File

@@ -1,2 +0,0 @@
- scheduled tasks hide

View File

@@ -1,12 +1,18 @@
todo: todo:
- when merging, check for complete/incomplete and cross reference todo vs scheduled - merge multi todo files for 'inbox' style with some dedupe
vs done
- click on links when dumped via cli - click on links when dumped via cli
scheduled: [] - what do about todo-now vs todo vs watch vs later... could do many files and manage
done: outside binary, but search would suck. Good would be vendor lockin, and that's UI problem
- xactions emails extend ledger,todo-server with pttodo - schedule merge inbox style - add tag anti-search, like -tag=-notMe
- xactions emails extend ledger,todo-server with pttodo - todo: when to run scheduled modifier? like, syncthing could have conflicts if I
modify only file on remote
ts: Fri Dec 31 22:33:12 EST 2021
details: |
- if it's a web ui hook or somethin, then it'd only have file conflict if I modify without waiting
- but thats a step back from current todo solution
tags: stuffToTry,secondTag
- todo: ez edit on many platforms, even offline and mobile - todo: ez edit on many platforms, even offline and mobile
ts: Fri Dec 31 22:33:12 EST 2021
details: | details: |
mobile view + complete method mobile view + complete method
collab editing of file prob resolves mobile and other stuff... collab editing of file prob resolves mobile and other stuff...
@@ -14,69 +20,38 @@ done:
web server access to ops web server access to ops
is a web ui for mobile best solution? is a web ui for mobile best solution?
let git be smart-ish and keep latest? would provide versioning OOTB without touching raw let git be smart-ish and keep latest? would provide versioning OOTB without touching raw
- todo: when to run scheduled modifier? like, syncthing could have conflicts if I scheduled: []
modify only file on remote done:
details: | - more schedule formats, like just 2022-01-15, for deferring
- if it's a web ui hook or somethin, then it'd only have file conflict if I modify without waiting - from todo-server to pttodo format
- but thats a step back from current todo solution - add -tag to search via cli
tags: stuffToTry,secondTag - ts to human readable
- what do about todo-now vs todo vs watch vs later... could do many files and manage - here is my really here is my really here is my really here is my really here is
outside binary, but search would suck. Good would be vendor lockin, and that's UI my really here is my really here is my really here is my really here is my really
problem here is my really here is my really here is my really here is my really here is
- merge multi todo files for 'inbox' style with some dedupe (probably best to just my really here is my really long string
do this via calling pttodo-cli from script or somethin) (probably at an off hour - vim doesnt source vimrc, so stuff like tab width and tab characters, also syntax
so no collisions from live editing) highlight :(
- how to defer a scheduled task? - crontab -e style editing to ensure good syntax
- todo: add tag anti-search, like -tag=-notMe
ts: Tue Jan 4 06:56:03 EST 2022
- todo: if in todo, then omit ts field
ts: Sun Jan 2 20:44:27 EST 2022
- todo: merge full files to import from todo-server
ts: Sun Jan 2 20:44:27 EST 2022
- todo: more schedule formats, like just 2022-01-15, for deferring
ts: Sun Jan 2 20:44:27 EST 2022
- todo: from todo-server to pttodo format
ts: Sun Jan 2 20:44:27 EST 2022
- todo: add -tag to search via cli
ts: Sun Jan 2 20:44:27 EST 2022
- todo: ts to human readable
ts: Sun Jan 2 20:44:27 EST 2022
- todo: here is my really here is my really here is my really here is my really here
is my really here is my really here is my really here is my really here is my
really here is my really here is my really here is my really here is my really
here is my really here is my really long string
ts: Sun Jan 2 20:44:27 EST 2022
- todo: vim doesnt source vimrc, so stuff like tab width and tab characters, also
syntax highlight :(
ts: Sun Jan 2 20:44:27 EST 2022
- todo: crontab -e style editing to ensure good syntax
ts: Sun Jan 2 20:44:27 EST 2022
- todo: YAML based todo - todo: YAML based todo
ts: Fri Dec 31 22:33:12 EST 2021
details: because goddamnit a year of this shit isn't significant on disk or in RAM details: because goddamnit a year of this shit isn't significant on disk or in RAM
for vim for vim
ts: Fri Dec 31 22:33:12 EST 2021
- todo: yaml based todo for plaintext - todo: yaml based todo for plaintext
details: a year isnt even a mb
ts: Fri Dec 31 22:33:12 EST 2021 ts: Fri Dec 31 22:33:12 EST 2021
- todo: ez edit, start on many platforms details: a year isnt even a mb
ts: Sun Jan 2 20:44:27 EST 2022 - ez edit, start on many platforms
- todo: defer - defer
ts: Sun Jan 2 20:44:27 EST 2022 - schedule
- todo: schedule - looping
ts: Sun Jan 2 20:44:27 EST 2022 - details
- todo: looping - let UI be UI for whatever platform
ts: Sun Jan 2 20:44:27 EST 2022 - tags
- todo: details
ts: Sun Jan 2 20:44:27 EST 2022
- todo: let UI be UI for whatever platform
ts: Sun Jan 2 20:44:27 EST 2022
- todo: tags
ts: Sun Jan 2 20:44:27 EST 2022
- todo: sub tasks - todo: sub tasks
ts: Fri Dec 31 22:33:12 EST 2021
subtasks: subtasks:
- a - a
ts: Fri Dec 31 22:33:12 EST 2021
- todo: crap losing on a bad edit hurts - todo: crap losing on a bad edit hurts
ts: Fri Dec 31 22:37:58 EST 2021
details: | details: |
? ?
ts: Fri Dec 31 22:37:58 EST 2021