117 Commits

Author SHA1 Message Date
323ddea27e f it 2026-03-08 09:47:59 -06:00
1bea4d30ef from inhome 2026-03-08 09:44:50 -06:00
Bel LaPointe
213ee60807 4 files i am a mastor 2026-02-11 17:23:12 -08:00
Bel LaPointe
1c89507497 split into 3 files by... declaring them all in main 2026-02-11 17:21:04 -08:00
Bel LaPointe
13cb4d1d9a not inline import 2026-02-11 17:17:15 -08:00
Bel LaPointe
5b50302b71 multi file technically woo 2026-02-11 17:16:01 -08:00
Bel LaPointe
d45ddb3b6d ffrom panics 2026-02-01 11:48:58 -07:00
Bel LaPointe
d6167c84bf from panic 2026-02-01 11:48:39 -07:00
Bel LaPointe
05dfbcc270 delta mod 2026-02-01 11:48:02 -07:00
Bel LaPointe
cd26b1a82a parse add 2026-02-01 11:41:14 -07:00
Bel LaPointe
cb8a173456 stub into add, reconcile, edit, dump 2026-02-01 11:37:00 -07:00
Bel LaPointe
7da42521e9 stub add, edit, dump 2026-02-01 11:35:49 -07:00
Bel LaPointe
999e37bd65 stub add 2026-02-01 11:35:09 -07:00
Bel LaPointe
df9b2823b6 stub edit 2026-02-01 11:32:39 -07:00
Bel LaPointe
2f708ef41c stub edit 2026-02-01 11:32:02 -07:00
Bel LaPointe
dcdbb8a5a1 v2 start 2026-02-01 11:28:58 -07:00
Bel LaPointe
07dc5ca5ee comment out v1 2026-02-01 10:33:47 -07:00
Bel LaPointe
d6cd806bf5 todo 2026-01-31 18:16:42 -07:00
Bel LaPointe
73121272d3 todo 2026-01-31 18:13:50 -07:00
Bel LaPointe
7efe298922 if oldpath==newpath, then skip mv and just rm oldpath 2026-01-31 18:00:12 -07:00
Bel LaPointe
060a8dfb3b new tests 2025-12-02 17:47:12 -07:00
Bel LaPointe
3d7ebcf9bc drop one debug 2025-12-02 17:42:36 -07:00
Bel LaPointe
51f10b7944 shush warn 2025-12-02 17:40:25 -07:00
Bel LaPointe
9ed6b48806 shhhh warning 2025-12-02 17:36:29 -07:00
Bel LaPointe
a867809cb8 use to shush warn 2025-12-02 17:35:57 -07:00
Bel LaPointe
053071f4be hrm an enqueu eoperator is hard because a cron fires many times but looks removed but isnt and hrm 2025-12-02 17:34:04 -07:00
Bel LaPointe
eccaa06d98 can add, add past, add future 2025-12-02 17:30:29 -07:00
Bel LaPointe
ee9377d6da reconcile on add too 2025-12-02 17:21:51 -07:00
Bel LaPointe
7da6aa8ae9 still unsure about triggered vs snapshot time... 2025-12-02 17:17:31 -07:00
Bel LaPointe
a5553d75f4 add w schedule via ts after now 2025-12-02 17:03:06 -07:00
Bel LaPointe
fe8a55b4c1 WIP test add-scheduled, snapshot, scheduled-due 2025-12-01 18:26:41 -07:00
Bel LaPointe
0a7e6873a3 can -s 2025-01-02 2025-12-01 17:39:17 -07:00
Bel LaPointe
1a61701c53 panik todo 2025-12-01 17:35:18 -07:00
Bel LaPointe
f5b47c4e74 reverse for last snapshot 2025-12-01 17:32:50 -07:00
Bel LaPointe
8728867cc3 testdata to new NOT jsonpatch 2025-12-01 17:24:41 -07:00
Bel LaPointe
bb64b87752 all tests pass woo 2025-12-01 17:23:23 -07:00
Bel LaPointe
4ad0b7d2ff no double persist snapshot 2025-12-01 17:05:07 -07:00
Bel LaPointe
700b2a22cc persist delta of last snapshot and stage 2025-12-01 17:03:56 -07:00
Bel LaPointe
97caaebc09 drop nonfuture 2025-12-01 16:51:36 -07:00
Bel LaPointe
72eb29d766 step 1 2025-11-20 14:29:01 -07:00
Bel LaPointe
56d0628ece gr 2025-11-13 16:04:26 -07:00
Bel LaPointe
4884d551e2 oh almost there 2025-11-13 09:45:40 -07:00
Bel LaPointe
b37e61a223 closer 2025-11-12 15:17:21 -07:00
Bel LaPointe
c06091d576 test schedule can pass 2025-11-12 14:44:35 -07:00
Bel LaPointe
fa7b537106 schedule herlp Due 2025-11-12 14:10:02 -07:00
Bel LaPointe
4ca5ed4d7c can find due after ts and Task w schedule 2025-11-12 14:08:32 -07:00
Bel LaPointe
89185f5016 drop debug 2025-11-12 14:00:08 -07:00
Bel LaPointe
b57618889e parse not crons 2025-11-12 13:59:46 -07:00
Bel LaPointe
c2f1255406 lint 2025-11-12 13:06:48 -07:00
Bel LaPointe
9e63b5b1dd todo: SCHEDULE 2025-11-12 13:02:05 -07:00
Bel LaPointe
1a9f052396 new tests with just add-remove and persist_stage collapses finding missed persists 2025-11-12 13:01:35 -07:00
Bel LaPointe
c1a5934215 todo 2025-11-11 20:17:48 -07:00
Bel LaPointe
7c79b20797 solution: old logs < stage mod time < logs after mod time 2025-11-11 20:02:26 -07:00
Bel LaPointe
a13fa68599 hmmm cannot enqueue adds because snapshot is source of truth but then scratch adding would make noise for laptop... 2025-11-11 19:53:27 -07:00
Bel LaPointe
b0b962277a a todo 2025-11-11 19:51:11 -07:00
Bel LaPointe
65bc42c246 vim 2025-11-11 19:49:13 -07:00
Bel LaPointe
bef9ef1a2a ooo tests pass 2025-11-11 19:34:00 -07:00
Bel LaPointe
5bfd9e52c3 some test edit 2025-11-11 19:24:21 -07:00
Bel LaPointe
2076ebd6f4 some test edit 2025-11-11 19:22:19 -07:00
Bel LaPointe
dc70c3ca7a gettin closer 2025-11-11 19:13:34 -07:00
Bel LaPointe
be632d3da3 testing reveals the stage as source of truth IS angry 2025-11-11 17:31:44 -07:00
Bel LaPointe
90de4a0cfd tests pass 2025-11-11 17:29:31 -07:00
Bel LaPointe
3c64bb27de some tests 2025-11-11 17:18:03 -07:00
Bel LaPointe
bcbcd6fbb7 test helpers 2025-11-11 17:09:42 -07:00
Bel LaPointe
b5b794817c more test 2025-11-11 16:40:38 -07:00
Bel LaPointe
c34ad15ba1 todo 2025-11-11 11:46:00 -07:00
Bel LaPointe
60ddbc673c wip 2025-11-11 11:41:07 -07:00
Bel LaPointe
7d36455bad 1 test 2025-11-11 11:29:04 -07:00
Bel LaPointe
5d2441ce0c o no more panics 2025-11-11 11:17:45 -07:00
Bel LaPointe
30d3875e09 ooo can add 2025-11-11 11:17:28 -07:00
Bel LaPointe
52fdf79d65 after add, stage 2025-11-11 11:16:58 -07:00
Bel LaPointe
06d1144c85 stage 2025-11-11 11:16:10 -07:00
Bel LaPointe
e3f0d816de oooo 2025-11-11 11:05:50 -07:00
Bel LaPointe
bcd479ceae narrow delta 2025-11-11 11:03:50 -07:00
Bel LaPointe
1dc9b6149c impl File.stage_persisted 2025-11-11 11:03:05 -07:00
Bel LaPointe
9242d41a0f wheeee 2025-11-11 10:39:50 -07:00
Bel LaPointe
a899241c28 oooo 2025-11-11 10:23:13 -07:00
Bel LaPointe
07e2750db5 rename 2025-11-11 10:21:58 -07:00
Bel LaPointe
e48338ff0e ooo 2025-11-11 10:21:09 -07:00
Bel LaPointe
c174181435 can read deltas 2025-11-11 10:12:11 -07:00
bel
001712ed1c TODO snapshot from ALL log files, reconcile delta of staging to HOSTNAME file by TS 2025-11-11 00:15:46 -07:00
bel
c9ee9d9a27 like this 2025-11-10 23:48:48 -07:00
bel
4067bd75fe can json diff 2025-11-10 23:20:01 -07:00
Bel LaPointe
56d5f59daf wheeee 2025-11-10 21:32:18 -07:00
bel
7a23fba2ae pttodoest 2025-11-09 21:21:35 -07:00
bel
263482f607 Revert "original.yaml has just todo, merge against .original.yaml so mobile edits still get a history"
This reverts commit 242ad3e127.
2025-07-13 08:51:21 -06:00
bel
871e75f8a8 schedule cron in scheduled 2025-07-13 08:38:42 -06:00
Bel LaPointe
242ad3e127 original.yaml has just todo, merge against .original.yaml so mobile edits still get a history 2025-07-03 17:44:24 -06:00
Bel LaPointe
29bc6a06b4 gr 2025-01-14 21:19:39 -07:00
Bel LaPointe
44488aec2d better err 2025-01-14 21:12:11 -07:00
bel
45b2060083 add -dedupe to move dupe TODO and SCHEDULED to DONE 2024-12-31 10:21:36 -07:00
bel
1413d02764 accept -archive to push -f Done into -archive Done 2024-12-31 10:09:55 -07:00
bel
284b57b9c4 accept -g to merge files 2024-12-31 10:03:37 -07:00
bel
9539d54145 oopoop 2024-12-20 20:39:55 -07:00
bel
c17e61a6ca oop 2024-12-20 20:39:39 -07:00
bel
def5422482 Merge branch 'master' of https://gitea.inhome.blapointe.com/gogs/pttodo 2024-12-20 20:38:21 -07:00
bel
f72cb35e79 instead of os.rename, io.WriteFile and os.Remove 2024-12-20 20:38:11 -07:00
Bel LaPointe
7344c660ed todos as failing tests 2024-08-10 09:30:06 -06:00
Bel LaPointe
301fb6046f tests yay 2024-08-09 17:22:08 -06:00
Bel LaPointe
b067d0fb8e failing test 2024-08-09 17:07:25 -06:00
Bel LaPointe
e05509f28f tests are good 2024-08-09 15:48:10 -07:00
Bel LaPointe
33d318edd4 o that swap wasnt too bd 2024-08-08 13:25:38 -07:00
Bel LaPointe
a5f5013ef6 ew 2024-08-08 13:17:32 -07:00
Bel LaPointe
52977ff0a7 insert reveal i cannot handle no src db 2024-08-08 13:14:26 -07:00
Bel LaPointe
452b692c6a wipper 2024-08-08 12:58:21 -07:00
Bel LaPointe
5c74ec0c15 todo 2024-08-08 12:53:15 -07:00
Bel LaPointe
602939b25c revert testdata 2024-08-04 13:36:08 -06:00
Bel LaPointe
7bf1d15835 need tests for all the edits--like adding a scheduled, deleting a scheduled should add a _doned 2024-08-04 13:35:48 -06:00
Bel LaPointe
e4834d8a11 gr 2024-08-04 13:35:29 -06:00
Bel LaPointe
2425f22b56 grr edit dupes 2024-08-04 11:44:22 -06:00
Bel LaPointe
50ea3258b7 edit implemented maybe 2024-08-04 11:42:09 -06:00
Bel LaPointe
6eba05895f comment 2024-06-14 09:59:49 -06:00
Bel LaPointe
5a45cd1410 comment THE PLAN 2024-06-14 09:54:46 -06:00
Bel LaPointe
7832d09fe8 ok vim runs that was ez 2024-06-14 09:49:22 -06:00
Bel LaPointe
aa9e032c31 tempdir ty 2024-06-14 09:27:01 -06:00
Bel LaPointe
b949502869 now it gets gross ew 2024-06-14 09:26:26 -06:00
Bel LaPointe
41cab74028 eprint dont err on conflict 2024-06-14 09:24:05 -06:00
31 changed files with 3052 additions and 90 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored
View File

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

View File

@@ -5,8 +5,8 @@ import (
"io/ioutil"
"os"
"gitea.bel.blue/gogs/pttodo/pttodo"
"github.com/google/uuid"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)

View File

@@ -12,6 +12,7 @@ import (
type config struct {
target string
target2 string
root string
tags []string
search string
@@ -19,6 +20,9 @@ type config struct {
add string
addSchedule string
addTags string
dryRun bool
archive string
dedupe bool
}
func getConfig() config {
@@ -29,11 +33,15 @@ func getConfig() config {
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")

View File

@@ -4,7 +4,7 @@ import (
"io"
"os"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gitea.bel.blue/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)

View File

@@ -12,7 +12,7 @@ import (
"syscall"
"time"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"gitea.bel.blue/gogs/pttodo/pttodo"
"github.com/google/uuid"
"gopkg.in/yaml.v2"
)
@@ -103,7 +103,7 @@ func _edit(filepaths []string) error {
dir := ""
for _, f := range filepaths {
if edited, ok := edits[path.Base(f)]; ok {
if err := os.Rename(edited, f); err != nil {
if err := rename(edited, f); err != nil {
return err
}
delete(edits, path.Base(f))
@@ -118,7 +118,7 @@ func _edit(filepaths []string) error {
if _, err := os.Stat(f); err == nil {
f = fmt.Sprintf("%s.todo.%s", f, uuid.New().String())
}
if err := os.Rename(editedFile, f); err != nil {
if err := rename(editedFile, f); err != nil {
return err
}
}
@@ -204,7 +204,7 @@ func vimd(d string) error {
if err != nil {
return err
}
args := []string{editorbin, "-p"}
args := []string{editorbin, "--noplugin", "-p"}
files, err := listDir(d)
if err != nil {
return err

View File

@@ -1,13 +0,0 @@
module pttodo-cli
go 1.17
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,10 +0,0 @@
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@@ -3,6 +3,7 @@
cd "$(dirname "$BASH_SOURCE")"
binary_name="$(head -n 1 go.mod | awk '{print $NF}' | sed 's/.*\///')"
binary_name=pttodo-cli
git_commit="$((
git rev-list -1 HEAD
if git diff | grep . > /dev/null; then

View File

@@ -2,11 +2,15 @@ package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"gitea.inhome.blapointe.com/gogs/pttodo/pttodo"
"slices"
"gitea.bel.blue/gogs/pttodo/pttodo"
"gopkg.in/yaml.v2"
)
@@ -26,6 +30,14 @@ func main() {
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
}
@@ -34,8 +46,183 @@ func _main() error {
}
return dump(config)
}
}
func merge(filepath string, mergeTargetFilePath string) error {
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
@@ -56,10 +243,10 @@ func merge(filepath string, mergeTargetFilePath string) error {
var base, merging pttodo.Root
if err := yaml.Unmarshal(baseB, &base); err != nil {
return err
return fmt.Errorf("failed to parse yaml in %s: %w", filepath, err)
}
if err := yaml.Unmarshal(mergingB, &merging); err != nil {
return err
return fmt.Errorf("failed to parse yaml in %s: %w", mergeTargetFilePath, err)
}
base.MergeIn(merging)
@@ -68,7 +255,34 @@ func merge(filepath string, mergeTargetFilePath string) error {
if err != nil {
return err
}
return os.Rename(tmppath, filepath)
return rename(tmppath, filepath)
}
func rename(oldpath, newpath string) error {
if same, err := func() (bool, error) {
oldb, err := os.ReadFile(oldpath)
if err != nil {
return false, err
}
newb, err := os.ReadFile(newpath)
if err != nil {
return false, err
}
return bytes.Equal(oldb, newb), nil
}(); err != nil || !same {
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) {

20
cmd/testdata/1.yaml vendored
View File

@@ -1,4 +1,20 @@
todo:
- "1"
scheduled: []
done: []
- "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"

5
go.mod
View File

@@ -1,8 +1,9 @@
module gitea.inhome.blapointe.com/gogs/pttodo
module gitea.bel.blue/gogs/pttodo
go 1.17
go 1.21
require (
github.com/google/uuid v1.6.0
github.com/robfig/cron/v3 v3.0.1
gopkg.in/yaml.v2 v2.4.0
)

2
go.sum
View File

@@ -1,3 +1,5 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.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/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -2,6 +2,7 @@ package pttodo
import (
"bytes"
"fmt"
"os"
yaml "gopkg.in/yaml.v2"
@@ -44,7 +45,7 @@ func NewRootFromFile(p string) (Root, error) {
if err := yaml.NewDecoder(f).Decode(&result); err != nil {
todos, err2 := NewTodosFromFile(p)
if err2 != nil {
return Root{}, err
return Root{}, fmt.Errorf("failed to parse yaml from %s: %w", p, err)
}
result.Todo = todos
}

View File

@@ -2,6 +2,7 @@ package pttodo
import (
"encoding/json"
"fmt"
"math"
"regexp"
"strconv"
@@ -17,7 +18,10 @@ func (schedule Schedule) MarshalJSON() ([]byte, error) {
}
func (schedule *Schedule) UnmarshalJSON(b []byte) error {
return json.Unmarshal(b, (*string)(schedule))
if err := json.Unmarshal(b, (*string)(schedule)); err != nil {
return fmt.Errorf("failed to parse json from %q: %w", b, err)
}
return nil
}
func (schedule Schedule) Next(t time.Time) (time.Time, error) {

View File

@@ -32,7 +32,7 @@ func NewTodosFromFile(p string) ([]Todo, error) {
var result []Todo
if err := yaml.NewDecoder(f).Decode(&result); err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse yaml from %s: %w", p, err)
}
return result, nil

View File

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

BIN
pttodoer/.DS_Store vendored Normal file

Binary file not shown.

85
pttodoer/Cargo.lock generated
View File

@@ -180,6 +180,12 @@ 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"
@@ -298,6 +304,7 @@ dependencies = [
"regex",
"serde",
"serde_yaml",
"tempdir",
]
[[package]]
@@ -309,6 +316,43 @@ 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"
@@ -338,6 +382,15 @@ 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"
@@ -394,6 +447,16 @@ dependencies = [
"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"
@@ -466,6 +529,28 @@ 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"

View File

@@ -12,3 +12,4 @@ 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"

View File

@@ -7,26 +7,37 @@ use regex::Regex;
use croner;
use clap::Parser;
use std::io::Write;
use tempdir::TempDir;
fn main() {
let flags = Flags::new();
let mut db = DB::new(flags.path).unwrap();
match flags.add {
match flags.add.clone() {
Some(s) => {
let t = match flags.add_schedule {
Some(sch) => Task::from_string_schedule(s, sch),
None => Task::from_string(s),
};
db.0[0].tasks.0.push(t);
db.tasks_and_metadatas[0].tasks.0.push(t);
},
_ => {},
};
if flags.edit {
db = db.edit().expect("failed to edit")
}
if flags.edit || flags.add.is_some() {
db.save(flags.dry_run).expect("failed to save");
}
if flags.list {
println!("{}", db.due().to_string());
}
}
#[derive(Debug, Parser)]
struct Flags {
#[arg(short = 'f', long = "path", default_value="$PTTODO_FILE")]
@@ -43,6 +54,9 @@ struct Flags {
#[arg(short = 'd', long = "dry-run", default_value="false")]
dry_run: bool,
#[arg(short = 'l', long = "list", default_value="true")]
list: bool,
}
impl Flags {
@@ -59,8 +73,11 @@ impl Flags {
}
}
#[derive(Debug)]
pub struct DB(Vec<TasksAndMetadata>);
#[derive(Debug, PartialEq, Clone)]
pub struct DB {
tasks_and_metadatas: Vec<TasksAndMetadata>,
cwd: String,
}
impl DB {
pub fn new(path: String) -> Result<DB, String> {
@@ -91,12 +108,22 @@ impl DB {
let item = TasksAndMetadata::new(file)?;
result.push(item);
}
Ok(DB(result))
Ok(DB{
tasks_and_metadatas: result,
cwd: match metadata.is_file() {
true => std::path::Path::new(&path).parent().expect("root?").display().to_string(),
_ => path.clone(),
},
})
}
fn new_empty(cwd: String) -> DB {
DB{tasks_and_metadatas: vec![], cwd: cwd.clone()}
}
pub fn incomplete(&self) -> Tasks {
let mut result = Tasks::new();
for set in &self.0 {
for set in &self.tasks_and_metadatas {
result.0.extend(set.tasks.incomplete().0);
}
result
@@ -104,14 +131,118 @@ impl DB {
pub fn due(&self) -> Tasks {
let mut result = Tasks::new();
for set in &self.0 {
for set in &self.tasks_and_metadatas {
result.0.extend(set.tasks.due().0);
}
result
}
pub fn edit(&self) -> Result<DB, String> {
self._edit(|cwd: &String| {
std::process::Command::new("/bin/sh")
.current_dir(cwd)
.arg("-c")
.arg("vim -p ./*")
.spawn()
.expect("failed to start vim")
.wait()
.expect("failed to vim");
})
}
fn _edit(&self, edit: impl Fn(&String)) -> Result<DB, String> {
let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir");
for set in &self.tasks_and_metadatas {
let base = set.file.split("/").last().unwrap().to_string();
let f = d.path().join(base);
set.save_due_as(f.display().to_string())?;
}
loop {
let cwd = d.path().display().to_string();
edit(&cwd);
let mut ok = true;
for f in std::fs::read_dir(&cwd).expect("failed to list edited files") {
ok = ok && TasksAndMetadata::new(f.expect("failed to list edited file").path().display().to_string()).is_ok();
}
if ok {
break;
}
}
let mut result = vec![];
for f in std::fs::read_dir(d.path()).expect("failed to list edited files") {
let f = f.expect("failed to list edited file").path();
let base = f.display().to_string().split("/").last().unwrap().to_string();
let set = match self.tasks_and_metadatas
.iter()
.filter(|tasks_and_metadata| tasks_and_metadata.file.ends_with(format!("/{}", base).as_str()))
.nth(0) {
Some(set) => set.clone(),
None => TasksAndMetadata::new_with(
format!("{}/{}",
self.cwd,
base,
),
TS::now(),
Tasks::new(),
),
};
let edited = TasksAndMetadata::new(f.display().to_string()).expect("failed to read edited tasks");
let was_due = set.due();
let mut now_due = vec![];
let mut not_due = set.not_due().tasks.0;
for task in &edited.tasks.0 {
if task.is_due() {
eprintln!("now_due.push({:?})", task.clone());
now_due.push(task.clone());
} else if !not_due.contains(task) {
eprintln!("not_due.push({:?})", task.clone());
not_due.push(task.clone());
}
}
let mut already_due = vec![];
let mut already_not_due = vec![];
for task in &was_due.tasks.0 {
let mut without_ts = task.clone();
without_ts.unset("ts".to_string());
let mut with_ts = task.clone();
with_ts.set("ts".to_string(), TS::now().to_string());
if now_due.contains(&without_ts) {
} else if not_due.contains(task) {
} else if task.is_due() {
eprintln!("already_due.push({:?})", with_ts.clone());
already_due.push(with_ts.clone());
} else {
eprintln!("already_not_due.push({:?})", task.clone());
already_not_due.push(task.clone());
}
}
let mut new_tasks = Tasks::new();
new_tasks.0.extend(now_due);
new_tasks.0.extend(already_due);
new_tasks.0.extend(not_due);
new_tasks.0.extend(already_not_due);
result.push(TasksAndMetadata::new_with(
set.file.clone(),
set.version.clone(),
new_tasks,
));
}
Ok(DB{tasks_and_metadatas: result, cwd: self.cwd.clone()})
}
pub fn save(&self, dry_run: bool) -> Result<(), String> {
for i in &self.0 {
for i in &self.tasks_and_metadatas {
i.save(dry_run)?;
}
Ok(())
@@ -119,13 +250,240 @@ impl DB {
}
#[cfg(test)]
mod test_taskss {
mod test_db {
use super::*;
#[test]
fn resolved_scheduled_recurring() {
assert!(false, "not impl")
}
#[test]
fn resolved_scheduled_non_recurring() {
assert!(false, "not impl")
}
#[test]
fn conflicting_save_handled() {
use std::ops::Add;
let d = TempDir::new(&TS::now().to_string()).expect("failed to create a temp dir");
let d = d.path().display().to_string();
let p = format!("{}/f", &d);
{
let mut f = std::fs::File::create(p.clone()).expect("failed to create a file in cwd");
f.write_all(b"- x").expect("failed to create a file");
}
let mut db = DB::new(d.clone()).expect("failed to open tempd");
assert_eq!(1, db.tasks_and_metadatas[0].tasks.len());
std::fs::File::open(p)
.expect("failed to open file to change modtime")
.set_modified(std::time::SystemTime::now().add(std::time::Duration::new(10, 0)))
.expect("failed to change modtime");
db.tasks_and_metadatas[0].tasks.0.push(Task::new());
db.save(false).expect("save as conflicting update shouldve saved as a different file");
let db = DB::new(d.clone()).expect("failed to open tempd again");
assert_eq!(2, db.tasks_and_metadatas.len());
assert_eq!(1, db.tasks_and_metadatas[0].tasks.len());
assert_eq!(2, db.tasks_and_metadatas[1].tasks.len());
}
#[test]
fn edit_append_duplicate_keeps_both() {
use std::io::Seek;
let db = new_test_db(1, 1);
let db_after = db._edit(|cwd| {
let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::<Vec<_>>();
assert_eq!(1, files_in_dir.len());
let mut f = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(files_in_dir[0].as_ref().expect("failed to get file in dir").path())
.expect("failed to open file for append");
f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end");
f.write_all(b"\n- f").expect("failed to append the file in cwd");
f.write_all(b"\n- f").expect("failed to append the file in cwd");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(1, db_after.tasks_and_metadatas.len());
assert_eq!(3, db_after.tasks_and_metadatas[0].tasks.len());
assert_eq!(db.tasks_and_metadatas[0].tasks.0[0], db_after.tasks_and_metadatas[0].tasks.0[0]);
assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[1].get("is".to_string()));
assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[2].get("is".to_string()));
}
#[test]
fn edit_schedule_future() {
let db = new_test_db(0, 0);
let db_after = db._edit(|cwd| {
let mut task = Task::new();
task.set("k".to_string(), "v".to_string());
task.set("schedule".to_string(), "2101-02-03".to_string());
let mut tasks = Tasks::new();
tasks.0.push(task);
let temp = TasksAndMetadata::new_with(
format!("{}/only", cwd),
TS::now(),
tasks,
);
temp.save(false).expect("failed to save");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(1, db_after.tasks_and_metadatas.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len());
assert_eq!(0, db_after.tasks_and_metadatas[0].due().tasks.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].not_due().tasks.len());
}
#[test]
fn edit_schedule_past() {
let db = new_test_db(0, 0);
let db_after = db._edit(|cwd| {
let mut task = Task::new();
task.set("k".to_string(), "v".to_string());
task.set("schedule".to_string(), "2001-02-03".to_string());
let mut tasks = Tasks::new();
tasks.0.push(task);
let temp = TasksAndMetadata::new_with(
format!("{}/only", cwd),
TS::now(),
tasks,
);
temp.save(false).expect("failed to save");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(1, db_after.tasks_and_metadatas.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].due().tasks.len());
assert_eq!(0, db_after.tasks_and_metadatas[0].not_due().tasks.len());
}
#[test]
fn edit_append_second_file() {
use std::io::Seek;
let db = new_test_db(2, 1);
let db_after = db._edit(|cwd| {
let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::<Vec<_>>();
assert_eq!(2, files_in_dir.len());
let mut f = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(files_in_dir[1].as_ref().expect("failed to get file in dir").path())
.expect("failed to open file for append");
f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end");
f.write_all(b"\n- f").expect("failed to append the file in cwd");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(2, db_after.tasks_and_metadatas.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len());
assert_eq!(2, db_after.tasks_and_metadatas[1].tasks.len());
assert_eq!(db.tasks_and_metadatas[1].tasks.0[0], db_after.tasks_and_metadatas[1].tasks.0[0]);
assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[1].tasks.0[1].get("is".to_string()));
}
#[test]
fn edit_append_current_file() {
use std::io::Seek;
let db = new_test_db(1, 1);
let db_after = db._edit(|cwd| {
let files_in_dir = std::fs::read_dir(cwd).expect("failed to ls cwd").collect::<Vec<_>>();
assert_eq!(1, files_in_dir.len());
let mut f = std::fs::OpenOptions::new()
.write(true)
.append(true)
.open(files_in_dir[0].as_ref().expect("failed to get file in dir").path())
.expect("failed to open file for append");
f.seek(std::io::SeekFrom::End(0)).expect("failed to seek to end");
f.write_all(b"\n- f").expect("failed to append the file in cwd");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(1, db_after.tasks_and_metadatas.len());
assert_eq!(2, db_after.tasks_and_metadatas[0].tasks.len());
assert_eq!(db.tasks_and_metadatas[0].tasks.0[0], db_after.tasks_and_metadatas[0].tasks.0[0]);
assert_eq!(Some("f".to_string()), db_after.tasks_and_metadatas[0].tasks.0[1].get("is".to_string()));
}
#[test]
fn edit_insert_new_second_file() {
let db = new_test_db(1, 1);
let db_after = db._edit(|cwd| {
let mut f = std::fs::File::create(format!("{}/new_second_file", cwd)).expect("failed to open a file in cwd");
f.write_all(b"f").expect("failed to write a file in cwd");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(2, db_after.tasks_and_metadatas.len());
assert_eq!(1, db_after.tasks_and_metadatas[1].tasks.len());
assert_eq!(1, db_after.tasks_and_metadatas[0].tasks.len());
}
#[test]
fn edit_insert_empty() {
let db = new_test_db(0, 0);
let db_after = db._edit(|cwd| {
assert_eq!(0, std::fs::read_dir(cwd).expect("failed to ls cwd").collect::<Vec<_>>().len());
assert_ne!("", cwd);
let mut f = std::fs::File::create(format!("{}/f", cwd)).expect("failed to open a file in cwd");
f.write_all(b"f").expect("failed to write a file in cwd");
let mut g = std::fs::File::create(format!("{}/g", cwd)).expect("failed to open a file in cwd");
g.write_all(b"- g: h\n- j: k").expect("failed to write a file in cwd");
}).expect("failed to not edit");
assert_ne!(db, db_after);
assert_eq!(2, db_after.tasks_and_metadatas.len());
assert_eq!(1, db_after.tasks_and_metadatas[1].tasks.len());
assert_eq!(2, db_after.tasks_and_metadatas[0].tasks.len());
}
#[test]
fn edit_noop_empty() {
let db = new_test_db(0, 0);
let db_after = db._edit(|_| {}).expect("failed to not edit");
assert_eq!(0, db.tasks_and_metadatas.len());
assert_eq!(db, db_after);
}
fn new_test_db(files: i32, tasks_per_file: i32) -> DB {
let mut db = DB::new_empty("".to_string());
for i in 0..files {
let mut tasks = Tasks::new();
for j in 0..tasks_per_file {
tasks.0.push(Task::from_string(format!("{{\"hello\": \"world[{}]\"}}", j)));
}
db.tasks_and_metadatas.push(TasksAndMetadata::new_with(
format!("{}.yaml", i),
TS::now(),
tasks,
));
}
db
}
#[test]
fn read_dir_files() {
let db = DB::new("./src/testdata/taskss.d/files.d".to_string()).expect("failed to construct from dir of files");
assert_eq!(2, db.0.len());
assert_eq!(2, db.tasks_and_metadatas.len());
assert_eq!(2, db.due().len());
assert_eq!(2, db.incomplete().len());
}
@@ -133,7 +491,7 @@ mod test_taskss {
#[test]
fn read_dir_file() {
let db = DB::new("./src/testdata/taskss.d/file.d".to_string()).expect("failed to construct from dir of a single file");
assert_eq!(1, db.0.len());
assert_eq!(1, db.tasks_and_metadatas.len());
assert_eq!(1, db.due().len());
assert_eq!(1, db.incomplete().len());
}
@@ -141,13 +499,13 @@ mod test_taskss {
#[test]
fn read_single_file() {
let db = DB::new("./src/testdata/taskss.d/single_file.yaml".to_string()).expect("failed to construct from single file");
assert_eq!(1, db.0.len());
assert_eq!(1, db.tasks_and_metadatas.len());
assert_eq!(1, db.due().len());
assert_eq!(2, db.incomplete().len());
}
}
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq)]
pub struct TasksAndMetadata {
tasks: Tasks,
file: String,
@@ -155,6 +513,14 @@ pub struct TasksAndMetadata {
}
impl TasksAndMetadata {
pub fn new_with(file: String, version: TS, tasks: Tasks) -> TasksAndMetadata {
TasksAndMetadata{
file: file,
version: version,
tasks: tasks,
}
}
pub fn new(file: String) -> Result<TasksAndMetadata, String> {
let version = file_version(file.clone())?;
match Tasks::from_file(file.clone()) {
@@ -167,22 +533,29 @@ impl TasksAndMetadata {
}
}
pub fn due(&self) -> TasksAndMetadata {
let mut cloned = self.clone();
cloned.tasks = cloned.tasks.due();
cloned
}
pub fn not_due(&self) -> TasksAndMetadata {
let mut cloned = self.clone();
cloned.tasks = cloned.tasks.not_due();
cloned
}
pub fn save(&self, dry_run: bool) -> Result<(), String> {
let version = file_version(self.file.clone())?;
let version = match file_version(self.file.clone()){
Ok(v) => v,
Err(_) => self.version.clone(),
};
let mut file = self.file.clone();
if version != self.version {
file = format!("{}.{}", &self.file, &self.version.to_string());
file = format!("{}.{}", &self.file, TS::now().to_string());
eprintln!("saving conflicting {} as {}", &self.file, &file);
}
self.save_as(file.clone(), dry_run)?;
match file == self.file {
true => Ok(()),
false => Err(format!("{} has been updated", self.file)),
}
}
fn save_as(&self, file: String, dry_run: bool) -> Result<(), String> {
match dry_run {
true => {
match &file == &self.file {
@@ -192,7 +565,15 @@ impl TasksAndMetadata {
eprintln!("{}", self.tasks.to_string());
Ok(())
},
false => {
false => self.save_as(file),
}
}
pub fn save_due_as(&self, file: String) -> Result<(), String> {
self.due().save_as(file)
}
pub fn save_as(&self, file: String) -> Result<(), String> {
match std::fs::File::create(&file) {
Ok(mut f) => {
match f.write_all(self.tasks.to_string().as_bytes()) {
@@ -202,8 +583,6 @@ impl TasksAndMetadata {
},
Err(msg) => Err(format!("failed to create {}: {}", file, msg)),
}
},
}
}
//fn debug(&self) -> String {
@@ -222,7 +601,7 @@ fn file_version(file: String) -> Result<TS, String> {
}
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
pub struct Tasks(Vec<Task>);
impl Tasks {
@@ -340,6 +719,14 @@ impl Tasks {
)
}
pub fn not_due(&self) -> Tasks {
Tasks(self.0.iter()
.filter(|x| !x.is_due())
.map(|x| x.clone())
.collect()
)
}
pub fn due(&self) -> Tasks {
Tasks(self.0.iter()
.filter(|x| x.is_due())
@@ -396,7 +783,7 @@ mod test_tasks {
}
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
pub struct Task(serde_yaml::Mapping);
impl Task {
@@ -835,9 +1222,8 @@ mod test_duration {
}
}
#[derive(Debug)]
#[derive(PartialEq)]
struct TS(u64);
#[derive(Debug, PartialEq, Clone)]
pub struct TS(u64);
impl TS {
fn now() -> TS {

View File

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

814
pttodoest/Cargo.lock generated Executable file
View File

@@ -0,0 +1,814 @@
# 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"

17
pttodoest/Cargo.toml Executable file
View File

@@ -0,0 +1,17 @@
[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"

17
pttodoest/src/deltas.rs Normal file
View File

@@ -0,0 +1,17 @@
pub fn new(added: String, add_schedule: Option<String>) -> serde_yaml::Value {
match add_schedule.clone() {
None => new_add(added),
Some(add_schedule) => new_add_with_schedule(added, add_schedule),
}
}
fn new_add(added: String) -> serde_yaml::Value {
serde_yaml::Value::String(added)
}
fn new_add_with_schedule(added: String, schedule: String) -> serde_yaml::Value {
let mut m = serde_yaml::Mapping::new();
m.insert("schedule".into(), schedule.into());
m.insert("do".into(), added.into());
serde_yaml::Value::Mapping(m)
}

90
pttodoest/src/flags.rs Normal file
View File

@@ -0,0 +1,90 @@
use crate::tests as tests_pkg;
use clap::Parser;
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::io::{BufRead, Read, Write};
#[derive(Debug, Parser)]
pub struct Flags {
#[arg(short = 'f', long = "path", default_value = "$PTTODO_FILE")]
pub path: String,
#[arg(short = 'a', long = "add")]
pub add: Option<String>,
#[arg(short = 'e', long = "edit", default_value = "false")]
pub edit: bool,
#[arg(short = 'd', long = "dry-run", default_value = "false")]
pub dry_run: bool,
#[arg(short = 's', long = "add-schedule")]
pub add_schedule: Option<String>,
}
impl Flags {
pub fn new() -> Result<Flags, String> {
let mut result = Flags::parse();
if result.path.get(..1) == Some("$") {
result.path = match std::env::var(result.path.get(1..).unwrap()) {
Ok(v) => Ok(v),
Err(msg) => Err(format!("'{}' unset: {}", result.path, msg)),
}?;
}
let _ = result.files()?;
Ok(result)
}
pub fn files(&self) -> Result<Vec<String>, String> {
Self::files_with(&self.path)
}
fn files_with(p: &String) -> Result<Vec<String>, String> {
let metadata = match std::fs::metadata(p.clone()) {
Ok(v) => Ok(v),
Err(msg) => Err(format!("failed to load {}: {}", p.clone(), msg)),
}?;
let files = match metadata.is_dir() {
false => Ok(vec![p.clone()]),
true => match std::fs::read_dir(p.clone()) {
Ok(paths) => Ok(paths
.filter(|x| x.is_ok())
.map(|x| x.unwrap())
.filter(|x| x.metadata().unwrap().is_file())
.map(|x| x.path().display().to_string())
.filter(|x| !x.contains("/."))
.collect()),
Err(msg) => Err(format!("failed to read {}: {}", p.clone(), msg)),
},
}?;
assert!(files.len() > 0, "no files");
Ok(files)
}
}
#[cfg(test)]
mod test_flags {
use super::*;
#[test]
fn test_flags_files_unhidden_only() {
tests_pkg::with_dir(|d| {
std::fs::File::create(d.path().join("plain")).unwrap();
std::fs::File::create(d.path().join(".hidden")).unwrap();
let flags = Flags {
path: d.path().to_str().unwrap().to_string(),
add: None,
edit: false,
dry_run: true,
add_schedule: None,
};
let files = flags.files().expect("failed to files from dir");
assert_eq!(1, files.len());
});
}
}

1190
pttodoest/src/main.rs Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
{"ts":1762915973,"op":"Add","task":"read; https://topicpartition.io/blog/postgres-pubsub-queue-benchmarks","tasks":null}
{"ts":1762915973,"op":"Add","task":"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","tasks":null}
{"ts":1762915973,"op":"Add","task":"drive; VERIFY spoc posts daily summary w/ unresolved","tasks":null}
{"ts":1762915973,"op":"Add","task":"drive; VERIFY spoc refreshes summary w/ thread comment contianing 'refresh'","tasks":null}
{"ts":1762915973,"op":"Add","task":"637; reconcile deploy if replicas wrong; https://github.com/renderinc/api/pull/26540/files","tasks":null}
{"ts":1762915973,"op":"Add","task":"https://linear.app/render-com/issue/KEYS-633/add-3-when-max-connections-overridden-for-3-superuser-connections","tasks":null}
{"ts":1762915973,"op":"Add","task":"https://linear.app/render-com/issue/KEYS-637/billing-resume-should-1-unsuspend-pg-in-cloudsql-2-unsuspend-pg-in-cr","tasks":null}
{"ts":1762915973,"op":"Add","task":"https://linear.app/render-com/issue/KEYS-638/pgoperator-generates-new-ha-patroni-cert-every-reconcile-no-matter","tasks":null}
{"ts":1762915973,"op":"Add","task":"pg; how2partition; https://renderinc.slack.com/archives/C0319NYCSSG/p1756357545556659?thread_ts=1756357467.613369&cid=C0319NYCSSG","tasks":null}
{"ts":1762915973,"op":"Add","task":"pitr; backup purge cronjob for PL types","tasks":null}
{"ts":1762915973,"op":"Add","task":"pg11 pgbackup doesnt write to envsetting mucked env key","tasks":null}
{"ts":1762915973,"op":"Add","task":"incident io; teach spocbotvr to read slacks","tasks":null}
{"ts":1762915973,"op":"Add","task":"userdb to internal; peer packages can use internal as userdb","tasks":null}
{"ts":1762915973,"op":"Add","task":"fcr; cannot pitr because pgbackrest doesnt know wal spans thus pgexporter and friends cant know pitr works","tasks":null}
{"ts":1762915973,"op":"Add","task":"etcd statefulset of 1 (for no random podname, no conflict, k8s ensures pod replace)\npatroni always\n","tasks":null}
{"ts":1762915973,"op":"Add","task":"maher; https://slab.render.com/posts/hopes-and-dreams-blegf8fx#hdsyt-valkey-bundle","tasks":null}
{"ts":1762915973,"op":"Add","task":"maher; shadow lizhi pm loops","tasks":null}
{"ts":1762915973,"op":"Add","task":"maher; get more interviewers","tasks":null}
{"ts":1762915973,"op":"Add","task":"maher; get concrete career and project plans so i can get promo in 2y; no manager to advocate","tasks":null}
{"ts":1762915973,"op":"Add","task":"read; https://trychroma.com/engineering/wal3","tasks":null}
{"ts":1762915973,"op":"Add","task":"read; https://github.com/renderinc/dashboard/pull/8883","tasks":null}
{"ts":1762915973,"op":"Add","task":"read; https://litestream.io/getting-started/","tasks":null}
{"ts":1762915973,"op":"Add","task":"kr\nto del gcloud old key\nie https://console.cloud.google.com/iam-admin/serviceaccounts/details/104206017956912104938/keys?hl=en&project=render-prod\n","tasks":null}
{"ts":1762915973,"op":"Add","task":{"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"},"tasks":null}
{"ts":1762915973,"op":"Add","task":"hi","tasks":null}
{"ts":1764635053,"op":"Snapshot","task":null,"tasks":["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"},"hi"]}
{"ts":1764636274,"op":"Add","task":{"schedule":"2026-01-01","todo":"not yet due"},"tasks":null}
{"ts":1764721753,"op":"Add","task":"just_add","tasks":null}
{"ts":1764721753,"op":"Snapshot","task":null,"tasks":["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"},"hi","just_add"]}
{"ts":1764721753,"op":"Add","task":{"schedule":"2000-01-01","do":"add_past"},"tasks":null}
{"ts":1764721753,"op":"Snapshot","task":null,"tasks":["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"},"hi","just_add",{"schedule":"2000-01-01","do":"add_past"}]}
{"ts":2051222400,"op":"Add","task":{"schedule":"2035-01-01","do":"add_future"},"tasks":null}

67
pttodoest/src/testdata/root.yaml vendored Executable file
View File

@@ -0,0 +1,67 @@
- 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
- just_add
- schedule: 2000-01-01
do: add_past

3
pttodoest/src/tests.rs Normal file
View File

@@ -0,0 +1,3 @@
pub fn with_dir(mut foo: impl FnMut(tempdir::TempDir)) {
foo(tempdir::TempDir::new("").unwrap());
}

30
pttodoest/todo.yaml Normal file
View File

@@ -0,0 +1,30 @@
- scheduled tasks hide
- |
file format
/name
- {any: kind}
- of yaml
- [payload list]
- but: schedule might be
schedule1: YYYY-MM-DD
schedule2: YYYY-MM-DDTHH
schedule3: 1d2h3m5s
schedule4: '* * * * *'
/.name.$HOSTNAME
- {ts:1, op:add, value: x}
- {ts:2, op:del, value: x}
- {ts:3, op:snapshot: value:[]}
on run,
- exec /.name.*
- diff /name
- add /name[]
- at modtime(/name)
- to /.name as new snapshot
- |
schedules
- {ts:1, op:add, value:{schedule: X}}
- adds to snapshot iff fired