Author:
Hash:
Timestamp:
+129 -82 +/-6 browse
Kevin Schoon [me@kevinschoon.com]
1e9aa15a1e62c51fda0728d37cddc1495b1f9add
Fri, 03 Jun 2022 20:53:30 +0000 (3.5 years ago)
| 1 | diff --git a/pkg/cmd/app.go b/pkg/cmd/app.go |
| 2 | index e48869a..a669c41 100644 |
| 3 | --- a/pkg/cmd/app.go |
| 4 | +++ b/pkg/cmd/app.go |
| 5 | @@ -63,11 +63,6 @@ data structure. It uses SQLite to store note content and metadata. |
| 6 | Aliases: []string{"e"}, |
| 7 | Flags: []cli.Flag{ |
| 8 | &cli.BoolFlag{ |
| 9 | - Name: "parents", |
| 10 | - Aliases: []string{"p"}, |
| 11 | - Usage: "create parent notes if they don't already exist", |
| 12 | - }, |
| 13 | - &cli.BoolFlag{ |
| 14 | Name: "stdin", |
| 15 | Aliases: []string{"r"}, |
| 16 | Usage: "read content from standard input", |
| 17 | @@ -79,7 +74,18 @@ data structure. It uses SQLite to store note content and metadata. |
| 18 | }, |
| 19 | }, |
| 20 | Action: func(ctx *cli.Context) error { |
| 21 | - return hierarchy.Edit(*cfg, ctx.Args().First()) |
| 22 | + path := ctx.Args().First() |
| 23 | + if path == "" { |
| 24 | + return fmt.Errorf("no path specified") |
| 25 | + } |
| 26 | + err := hierarchy.Create(*cfg, path) |
| 27 | + if err != nil { |
| 28 | + return err |
| 29 | + } |
| 30 | + if ctx.Bool("stdin") { |
| 31 | + return hierarchy.EditFromStdout(*cfg, path, ctx.Bool("append")) |
| 32 | + } |
| 33 | + return hierarchy.Edit(*cfg, path) |
| 34 | }, |
| 35 | }, |
| 36 | { |
| 37 | @@ -95,11 +101,20 @@ data structure. It uses SQLite to store note content and metadata. |
| 38 | }, |
| 39 | }, |
| 40 | Action: func(ctx *cli.Context) error { |
| 41 | - notes, err := hierarchy.LoadNotes(*cfg) |
| 42 | + path := ctx.Args().First() |
| 43 | + if path == "" { |
| 44 | + notes, err := hierarchy.LoadNotes(*cfg) |
| 45 | + if err != nil { |
| 46 | + return err |
| 47 | + } |
| 48 | + return json.NewEncoder(os.Stdout).Encode(notes) |
| 49 | + } |
| 50 | + |
| 51 | + note, err := hierarchy.Find(*cfg, path) |
| 52 | if err != nil { |
| 53 | return err |
| 54 | } |
| 55 | - return json.NewEncoder(os.Stdout).Encode(notes) |
| 56 | + return json.NewEncoder(os.Stdout).Encode(note) |
| 57 | }, |
| 58 | }, |
| 59 | { |
| 60 | diff --git a/pkg/hierarchy/database.go b/pkg/hierarchy/database.go |
| 61 | index 53579c5..540aaef 100644 |
| 62 | --- a/pkg/hierarchy/database.go |
| 63 | +++ b/pkg/hierarchy/database.go |
| 64 | @@ -3,8 +3,7 @@ package hierarchy |
| 65 | import ( |
| 66 | "database/sql" |
| 67 | _ "embed" |
| 68 | - "io/ioutil" |
| 69 | - "os" |
| 70 | + "fmt" |
| 71 | |
| 72 | _ "github.com/mattn/go-sqlite3" |
| 73 | "kevinschoon.com/hierarchy/pkg/config" |
| 74 | @@ -42,13 +41,6 @@ func With(path string, fn func(*sql.Tx) error) error { |
| 75 | return tx.Commit() |
| 76 | } |
| 77 | |
| 78 | - type dbNote struct { |
| 79 | - ID int64 |
| 80 | - Name string |
| 81 | - Content string |
| 82 | - Parent int64 |
| 83 | - } |
| 84 | - |
| 85 | func resolve(seed *Note, tx *sql.Tx) error { |
| 86 | rows, err := tx.Query(` |
| 87 | WITH RECURSIVE |
| 88 | @@ -103,9 +95,9 @@ func Find(cfg config.Config, notePath string) (*Note, error) { |
| 89 | note := new(Note) |
| 90 | return note, With(cfg.Database, func(tx *sql.Tx) error { |
| 91 | row := tx.QueryRow(` |
| 92 | - SELECT ( |
| 93 | + SELECT |
| 94 | id, name, content |
| 95 | - ) FROM notes |
| 96 | + FROM notes |
| 97 | WHERE |
| 98 | name = ? |
| 99 | `, np.String()) |
| 100 | @@ -117,33 +109,6 @@ WHERE |
| 101 | }) |
| 102 | } |
| 103 | |
| 104 | - func Edit(cfg config.Config, notePath string) error { |
| 105 | - np := ReadPath(notePath) |
| 106 | - return With(cfg.Database, func(tx *sql.Tx) error { |
| 107 | - raw, err := ioutil.ReadAll(os.Stdin) |
| 108 | - if err != nil { |
| 109 | - return err |
| 110 | - } |
| 111 | - var parent *int64 |
| 112 | - if np.Parent() != "" { |
| 113 | - parent = new(int64) |
| 114 | - row := tx.QueryRow(` |
| 115 | - SELECT id FROM notes |
| 116 | - WHERE name = ? |
| 117 | - `, np.Parent()) |
| 118 | - err = row.Scan(&parent) |
| 119 | - if err != nil { |
| 120 | - return err |
| 121 | - } |
| 122 | - } |
| 123 | - _, err = tx.Exec(` |
| 124 | - INSERT INTO notes (name, parent, content) |
| 125 | - VALUES (?, ?, ?) |
| 126 | - `, np.Name(), parent, string(raw)) |
| 127 | - return err |
| 128 | - }) |
| 129 | - } |
| 130 | - |
| 131 | func LoadNotes(cfg config.Config) (notes []*Note, err error) { |
| 132 | return notes, With(cfg.Database, func(tx *sql.Tx) error { |
| 133 | rows, err := tx.Query(` |
| 134 | @@ -170,3 +135,37 @@ WHERE parent IS NULL |
| 135 | return nil |
| 136 | }) |
| 137 | } |
| 138 | + |
| 139 | + func Create(cfg config.Config, path string) error { |
| 140 | + np := ReadPath(path) |
| 141 | + return With(cfg.Database, func(tx *sql.Tx) error { |
| 142 | + for _, path := range np.Paths() { |
| 143 | + _, err := tx.Exec(` |
| 144 | + INSERT INTO notes |
| 145 | + (name, parent) |
| 146 | + VALUES |
| 147 | + (?, (select id from notes where name = ?)) |
| 148 | + ON CONFLICT DO NOTHING |
| 149 | + `, path.String(), path.Parent()) |
| 150 | + if err != nil { |
| 151 | + return err |
| 152 | + } |
| 153 | + } |
| 154 | + return nil |
| 155 | + }) |
| 156 | + } |
| 157 | + |
| 158 | + func Save(cfg config.Config, path, content string) error { |
| 159 | + np := ReadPath(path) |
| 160 | + fmt.Println(content, np.String()) |
| 161 | + return With(cfg.Database, func(tx *sql.Tx) error { |
| 162 | + _, err := tx.Exec(` |
| 163 | + UPDATE notes |
| 164 | + set content = ?, |
| 165 | + updated_at = CURRENT_TIMESTAMP |
| 166 | + WHERE |
| 167 | + name = ? |
| 168 | + `, content, np.String()) |
| 169 | + return err |
| 170 | + }) |
| 171 | + } |
| 172 | diff --git a/pkg/hierarchy/editor.go b/pkg/hierarchy/editor.go |
| 173 | index aadda55..13a6ee2 100644 |
| 174 | --- a/pkg/hierarchy/editor.go |
| 175 | +++ b/pkg/hierarchy/editor.go |
| 176 | @@ -9,7 +9,11 @@ import ( |
| 177 | "kevinschoon.com/hierarchy/pkg/config" |
| 178 | ) |
| 179 | |
| 180 | - func OpenWithEditor(cfg config.Config, note *Note, cb func() error) error { |
| 181 | + func Edit(cfg config.Config, path string) error { |
| 182 | + note, err := Find(cfg, path) |
| 183 | + if err != nil { |
| 184 | + return err |
| 185 | + } |
| 186 | editor := cfg.Editor |
| 187 | if editor == "" { |
| 188 | editor = os.Getenv("EDITOR") |
| 189 | @@ -40,8 +44,20 @@ func OpenWithEditor(cfg config.Config, note *Note, cb func() error) error { |
| 190 | if err != nil { |
| 191 | return err |
| 192 | } |
| 193 | - if string(raw) != note.Content { |
| 194 | + return Save(cfg, path, string(raw)) |
| 195 | + } |
| 196 | + |
| 197 | + func EditFromStdout(cfg config.Config, path string, doAppend bool) error { |
| 198 | + raw, err := ioutil.ReadAll(os.Stdin) |
| 199 | + if err != nil { |
| 200 | + return err |
| 201 | + } |
| 202 | + if doAppend { |
| 203 | + note, err := Find(cfg, path) |
| 204 | + if err != nil { |
| 205 | + return err |
| 206 | + } |
| 207 | + return Save(cfg, path, note.Content+string(raw)) |
| 208 | } |
| 209 | - // TODO |
| 210 | - return nil |
| 211 | + return Save(cfg, path, string(raw)) |
| 212 | } |
| 213 | diff --git a/pkg/hierarchy/helpers.go b/pkg/hierarchy/helpers.go |
| 214 | deleted file mode 100644 |
| 215 | index 4bca583..0000000 |
| 216 | --- a/pkg/hierarchy/helpers.go |
| 217 | +++ /dev/null |
| 218 | @@ -1,30 +0,0 @@ |
| 219 | - package hierarchy |
| 220 | - |
| 221 | - import ( |
| 222 | - "path" |
| 223 | - "strings" |
| 224 | - ) |
| 225 | - |
| 226 | - type NotePath [2]string |
| 227 | - |
| 228 | - func (np NotePath) String() string { |
| 229 | - return path.Join(np[0], np[1]) |
| 230 | - } |
| 231 | - |
| 232 | - func (np NotePath) Parent() string { |
| 233 | - return np[0] |
| 234 | - } |
| 235 | - |
| 236 | - func (np NotePath) Name() string { |
| 237 | - return np[1] |
| 238 | - } |
| 239 | - |
| 240 | - func ReadPath(input string) NotePath { |
| 241 | - base, name := path.Split(input) |
| 242 | - if base == "/" { |
| 243 | - base = "" |
| 244 | - } |
| 245 | - base = strings.TrimLeft(base, "/") |
| 246 | - base = strings.TrimRight(base, "/") |
| 247 | - return [2]string{base, name} |
| 248 | - } |
| 249 | diff --git a/pkg/hierarchy/migrate/init.sql b/pkg/hierarchy/migrate/init.sql |
| 250 | index 176943d..716bd23 100644 |
| 251 | --- a/pkg/hierarchy/migrate/init.sql |
| 252 | +++ b/pkg/hierarchy/migrate/init.sql |
| 253 | @@ -3,10 +3,11 @@ PRAGMA foreign_keys = on; |
| 254 | -- main note table |
| 255 | CREATE TABLE IF NOT EXISTS notes ( |
| 256 | id INTEGER PRIMARY KEY, |
| 257 | - created_at INTEGER, |
| 258 | + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 259 | + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| 260 | parent INTEGER, |
| 261 | name VARCHAR NOT NULL UNIQUE, |
| 262 | - content VARCHAR, |
| 263 | + content VARCHAR DEFAULT '', |
| 264 | FOREIGN KEY(parent) REFERENCES notes(id) |
| 265 | ); |
| 266 | |
| 267 | diff --git a/pkg/hierarchy/path.go b/pkg/hierarchy/path.go |
| 268 | new file mode 100644 |
| 269 | index 0000000..fc22c45 |
| 270 | --- /dev/null |
| 271 | +++ b/pkg/hierarchy/path.go |
| 272 | @@ -0,0 +1,46 @@ |
| 273 | + package hierarchy |
| 274 | + |
| 275 | + import ( |
| 276 | + "path" |
| 277 | + "strings" |
| 278 | + ) |
| 279 | + |
| 280 | + type NotePath [2]string |
| 281 | + |
| 282 | + func (np NotePath) String() string { |
| 283 | + return path.Join(np[0], np[1]) |
| 284 | + } |
| 285 | + |
| 286 | + func (np NotePath) AtRoot() bool { |
| 287 | + return np[0] == "" |
| 288 | + } |
| 289 | + |
| 290 | + func (np NotePath) Paths() (paths []NotePath) { |
| 291 | + if np.AtRoot() { |
| 292 | + paths = append(paths, np) |
| 293 | + return paths |
| 294 | + } |
| 295 | + split := strings.Split(np.String(), "/") |
| 296 | + for i := 0; i < len(split); i++ { |
| 297 | + paths = append(paths, ReadPath(path.Join(split[0:i+1]...))) |
| 298 | + } |
| 299 | + return paths |
| 300 | + } |
| 301 | + |
| 302 | + func (np NotePath) Parent() string { |
| 303 | + return np[0] |
| 304 | + } |
| 305 | + |
| 306 | + func (np NotePath) Name() string { |
| 307 | + return np[1] |
| 308 | + } |
| 309 | + |
| 310 | + func ReadPath(input string) NotePath { |
| 311 | + base, name := path.Split(input) |
| 312 | + if base == "/" { |
| 313 | + base = "" |
| 314 | + } |
| 315 | + base = strings.TrimLeft(base, "/") |
| 316 | + base = strings.TrimRight(base, "/") |
| 317 | + return [2]string{base, name} |
| 318 | + } |