Commit
+401 -88 +/-16 browse
1 | diff --git a/Makefile b/Makefile |
2 | index d72b810..2ada88e 100644 |
3 | --- a/Makefile |
4 | +++ b/Makefile |
5 | @@ -1,7 +1,16 @@ |
6 | + VERSION ?= $(shell git describe --tags 2>/dev/null) |
7 | + ifeq "$(VERSION)" "" |
8 | + VERSION := UNKNOWN |
9 | + endif |
10 | + |
11 | + LDFLAGS=\ |
12 | + -X kevinschoon.com/hierarchy/pkg/version/internal.Version=$(VERSION) |
13 | + |
14 | .PHONY: bin/hierarchy |
15 | |
16 | bin/hierarchy: bin |
17 | - hare build -o $@ |
18 | + cd cmd/hierarchy && \ |
19 | + go build -ldflags '${LDFLAGS}' -o ../../$@ |
20 | |
21 | bin: |
22 | mkdir $@ |
23 | @@ -10,4 +19,8 @@ clean: |
24 | [[ -d bin ]] && rm -r bin |
25 | |
26 | watch: |
27 | - ./watch.sh "hare build -o bin/hierarchy" |
28 | + ./watch.sh "go build -o bin/hierarchy" |
29 | + |
30 | + man/hierarchy.8: bin/hierarchy |
31 | + bin/hierarchy gen_man > $@ |
32 | + |
33 | diff --git a/README.md b/README.md |
34 | index 551aabd..f0f14a3 100644 |
35 | --- a/README.md |
36 | +++ b/README.md |
37 | @@ -3,24 +3,18 @@ |
38 | `hierarchy` is a hierarchical based note taking system for Linux. It is the |
39 | successor to another project I worked on called [note](https://github.com/kevinschoon/note). |
40 | |
41 | - Hierarchy is written in [hare](https://harelang.org). |
42 | - |
43 | - ## Usage |
44 | + ## Installation |
45 | |
46 | - ``` |
47 | - # list all notes |
48 | - hierarchy |
49 | - # create a new note |
50 | - hierarchy create hello |
51 | - # write the contents of a note to stdout |
52 | - hierarchy cat hello |
53 | - ``` |
54 | + Hierarchy is [available](https://aur.archlinux.org/packages/hierarchy) on the AUR. |
55 | |
56 | + ## Source Installation |
57 | |
58 | - ## Building |
59 | + You need a Go compiler as well as standard Linux development tooling. |
60 | |
61 | ``` |
62 | make |
63 | ``` |
64 | |
65 | - ## Installation |
66 | + ## Usage |
67 | + |
68 | + See `man hierarchy` or browse the docs/ directory. |
69 | diff --git a/cmd/hierarchy/main.go b/cmd/hierarchy/main.go |
70 | new file mode 100644 |
71 | index 0000000..b8ce5c0 |
72 | --- /dev/null |
73 | +++ b/cmd/hierarchy/main.go |
74 | @@ -0,0 +1,17 @@ |
75 | + package main |
76 | + |
77 | + import ( |
78 | + "fmt" |
79 | + "os" |
80 | + |
81 | + "kevinschoon.com/hierarchy/pkg/cmd" |
82 | + ) |
83 | + |
84 | + func main() { |
85 | + cmd := cmd.New() |
86 | + err := cmd.Run(os.Args) |
87 | + if err != nil { |
88 | + fmt.Println(err) |
89 | + os.Exit(1) |
90 | + } |
91 | + } |
92 | diff --git a/completion/hierarchy_autocomplete.bash b/completion/hierarchy_autocomplete.bash |
93 | new file mode 100644 |
94 | index 0000000..a572338 |
95 | --- /dev/null |
96 | +++ b/completion/hierarchy_autocomplete.bash |
97 | @@ -0,0 +1,23 @@ |
98 | + #! /bin/bash |
99 | + |
100 | + # copied from https://github.com/urfave/cli |
101 | + |
102 | + : ${PROG:=$(basename ${BASH_SOURCE})} |
103 | + |
104 | + _cli_bash_autocomplete() { |
105 | + if [[ "${COMP_WORDS[0]}" != "source" ]]; then |
106 | + local cur opts base |
107 | + COMPREPLY=() |
108 | + cur="${COMP_WORDS[COMP_CWORD]}" |
109 | + if [[ "$cur" == "-"* ]]; then |
110 | + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) |
111 | + else |
112 | + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) |
113 | + fi |
114 | + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) |
115 | + return 0 |
116 | + fi |
117 | + } |
118 | + |
119 | + complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG |
120 | + unset PROG |
121 | diff --git a/completion/hierarchy_autocomplete.zsh b/completion/hierarchy_autocomplete.zsh |
122 | new file mode 100644 |
123 | index 0000000..f9e2ab0 |
124 | --- /dev/null |
125 | +++ b/completion/hierarchy_autocomplete.zsh |
126 | @@ -0,0 +1,21 @@ |
127 | + #compdef $PROG |
128 | + # copied from https://github.com/urfave/cli |
129 | + |
130 | + _cli_zsh_autocomplete() { |
131 | + local -a opts |
132 | + local cur |
133 | + cur=${words[-1]} |
134 | + if [[ "$cur" == "-"* ]]; then |
135 | + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") |
136 | + else |
137 | + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") |
138 | + fi |
139 | + |
140 | + if [[ "${opts[1]}" != "" ]]; then |
141 | + _describe 'values' opts |
142 | + else |
143 | + _files |
144 | + fi |
145 | + } |
146 | + |
147 | + compdef _cli_zsh_autocomplete $PROG |
148 | diff --git a/config.ha b/config.ha |
149 | deleted file mode 100644 |
150 | index 5f378f6..0000000 |
151 | --- a/config.ha |
152 | +++ /dev/null |
153 | @@ -1,32 +0,0 @@ |
154 | - use fmt; |
155 | - use dirs; |
156 | - use path; |
157 | - |
158 | - // configuration file for hierarchy |
159 | - type config = struct { |
160 | - // base directory for storing notes |
161 | - base_dir: str, |
162 | - // editor for writing notes, if not specified it will try $EDITOR or |
163 | - // vim |
164 | - editor: str, |
165 | - }; |
166 | - |
167 | - fn default_config_path() str = { |
168 | - let base = dirs::config("hierarchy"); |
169 | - return path::join(base, "config"); |
170 | - }; |
171 | - |
172 | - fn default_data_path() str = { |
173 | - let base = dirs::data("hierarchy"); |
174 | - return base; |
175 | - }; |
176 | - |
177 | - export fn load_config(path: str) config = { |
178 | - let default_path = default_config_path(); |
179 | - fmt::println(default_path)!; |
180 | - let cfg = config { |
181 | - base_dir = "", |
182 | - editor = "vim" |
183 | - }; |
184 | - return cfg; |
185 | - }; |
186 | diff --git a/go.mod b/go.mod |
187 | new file mode 100644 |
188 | index 0000000..49876aa |
189 | --- /dev/null |
190 | +++ b/go.mod |
191 | @@ -0,0 +1,12 @@ |
192 | + module kevinschoon.com/hierarchy |
193 | + |
194 | + go 1.18 |
195 | + |
196 | + require ( |
197 | + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect |
198 | + github.com/ghodss/yaml v1.0.0 // indirect |
199 | + github.com/russross/blackfriday/v2 v2.1.0 // indirect |
200 | + github.com/urfave/cli/v2 v2.8.1 // indirect |
201 | + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect |
202 | + gopkg.in/yaml.v2 v2.4.0 // indirect |
203 | + ) |
204 | diff --git a/go.sum b/go.sum |
205 | new file mode 100644 |
206 | index 0000000..ed05ce3 |
207 | --- /dev/null |
208 | +++ b/go.sum |
209 | @@ -0,0 +1,13 @@ |
210 | + github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= |
211 | + github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= |
212 | + github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= |
213 | + github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
214 | + github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= |
215 | + github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
216 | + github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4= |
217 | + github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= |
218 | + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= |
219 | + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= |
220 | + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
221 | + gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |
222 | + gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
223 | diff --git a/main.ha b/main.ha |
224 | deleted file mode 100644 |
225 | index d0da76f..0000000 |
226 | --- a/main.ha |
227 | +++ /dev/null |
228 | @@ -1,35 +0,0 @@ |
229 | - use fmt; |
230 | - use os; |
231 | - use getopt; |
232 | - |
233 | - export fn main() void = { |
234 | - // Usage for sed |
235 | - const cmd = getopt::parse(os::args, |
236 | - "hierarchy", |
237 | - ('E', "use extended regular expressions"), |
238 | - ('s', "treat files as separate, rather than one continuous stream"), |
239 | - ('i', "edit files in place"), |
240 | - ('z', "separate lines by NUL characters"), |
241 | - ('e', "script", "execute commands from script"), |
242 | - ('f', "file", "execute commands from a file"), |
243 | - "files...", |
244 | - ); |
245 | - defer getopt::finish(&cmd); |
246 | - |
247 | - for (let i = 0z; i < len(cmd.opts); i += 1) { |
248 | - const opt = cmd.opts[i]; |
249 | - switch (opt.0) { |
250 | - case 'E' => |
251 | - fmt::println("heyya")!; |
252 | - case 's' => |
253 | - fmt::println("heyya")!; |
254 | - }; |
255 | - }; |
256 | - |
257 | - for (let i = 0z; i < len(cmd.args); i += 1) { |
258 | - const arg = cmd.args[i]; |
259 | - // ... |
260 | - }; |
261 | - |
262 | - let cfg = load_config("nope"); |
263 | - }; |
264 | diff --git a/man/hierarchy.8 b/man/hierarchy.8 |
265 | new file mode 100644 |
266 | index 0000000..5668233 |
267 | --- /dev/null |
268 | +++ b/man/hierarchy.8 |
269 | @@ -0,0 +1,87 @@ |
270 | + .nh |
271 | + .TH hierarchy 8 |
272 | + |
273 | + .SH NAME |
274 | + .PP |
275 | + hierarchy - simple hierarchial note taking program |
276 | + |
277 | + |
278 | + .SH SYNOPSIS |
279 | + .PP |
280 | + hierarchy |
281 | + |
282 | + .PP |
283 | + .RS |
284 | + |
285 | + .nf |
286 | + [--config|-c]=[value] |
287 | + [--help|-h] |
288 | + [--version|-v] |
289 | + |
290 | + .fi |
291 | + .RE |
292 | + |
293 | + .PP |
294 | + \fBUsage\fP: |
295 | + |
296 | + .PP |
297 | + .RS |
298 | + |
299 | + .nf |
300 | + hierarchy [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] |
301 | + |
302 | + .fi |
303 | + .RE |
304 | + |
305 | + |
306 | + .SH GLOBAL OPTIONS |
307 | + .PP |
308 | + \fB--config, -c\fP="": path to your configuration file (default: /home/kevin/.config/hierarchy/config.yaml) |
309 | + |
310 | + .PP |
311 | + \fB--help, -h\fP: show help |
312 | + |
313 | + .PP |
314 | + \fB--version, -v\fP: print the version |
315 | + |
316 | + |
317 | + .SH COMMANDS |
318 | + .SH config, cfg |
319 | + .PP |
320 | + display and interact with the configuration |
321 | + |
322 | + .SH edit, e |
323 | + .PP |
324 | + create a new note or edit an existing one |
325 | + |
326 | + .SH list, ls, l |
327 | + .PP |
328 | + display all of your notes |
329 | + |
330 | + .SH remove, rm |
331 | + .PP |
332 | + remove an existing note |
333 | + |
334 | + .PP |
335 | + \fB--recursive, ---r\fP: delete any sibling notes recursively |
336 | + |
337 | + .SH cat, c |
338 | + .PP |
339 | + write one or more notes to standard output |
340 | + |
341 | + .PP |
342 | + \fB--encoding, -e\fP="": output format |
343 | + |
344 | + .SH mount |
345 | + .PP |
346 | + mount the note hierarchy as a file system |
347 | + |
348 | + .PP |
349 | + \fB--path\fP="": mountpoint for the note hierarchy |
350 | + |
351 | + .PP |
352 | + \fB--read-only\fP: disallow writing |
353 | + |
354 | + .SH help, h |
355 | + .PP |
356 | + Shows a list of commands or help for one command |
357 | diff --git a/note.ha b/note.ha |
358 | deleted file mode 100644 |
359 | index ff92350..0000000 |
360 | --- a/note.ha |
361 | +++ /dev/null |
362 | @@ -1,5 +0,0 @@ |
363 | - |
364 | - type note = struct { |
365 | - frontmatter: str, |
366 | - content: str |
367 | - }; |
368 | diff --git a/pkg/cmd/app.go b/pkg/cmd/app.go |
369 | new file mode 100644 |
370 | index 0000000..fc82477 |
371 | --- /dev/null |
372 | +++ b/pkg/cmd/app.go |
373 | @@ -0,0 +1,159 @@ |
374 | + package cmd |
375 | + |
376 | + import ( |
377 | + "encoding/json" |
378 | + "fmt" |
379 | + "os" |
380 | + "path" |
381 | + |
382 | + "github.com/urfave/cli/v2" |
383 | + "kevinschoon.com/hierarchy/pkg/config" |
384 | + "kevinschoon.com/hierarchy/pkg/version" |
385 | + ) |
386 | + |
387 | + func getConfigDir() string { |
388 | + base, err := os.UserConfigDir() |
389 | + if err != nil { |
390 | + panic(err) |
391 | + } |
392 | + return path.Join(base, "hierarchy/config.yaml") |
393 | + } |
394 | + |
395 | + func New() *cli.App { |
396 | + cfg := config.Default() |
397 | + app := cli.NewApp() |
398 | + app.Name = "hierarchy" |
399 | + app.Usage = "simple hierarchial note taking program" |
400 | + app.Description = ` |
401 | + Hierarchy is a note taking application that organizes your notes in a tree-like |
402 | + data structure. It uses SQLite to store note content and metadata. |
403 | + ` |
404 | + app.Version = version.Version |
405 | + app.EnableBashCompletion = true |
406 | + app.Flags = []cli.Flag{ |
407 | + &cli.StringFlag{ |
408 | + Name: "config", |
409 | + Usage: "path to your configuration file", |
410 | + Aliases: []string{"c"}, |
411 | + Value: getConfigDir(), |
412 | + }, |
413 | + } |
414 | + app.Before = func(ctx *cli.Context) error { |
415 | + loaded, err := config.Load(ctx.String("config"), cfg) |
416 | + if err != nil { |
417 | + return err |
418 | + } |
419 | + cfg = loaded |
420 | + return nil |
421 | + } |
422 | + app.Commands = []*cli.Command{ |
423 | + { |
424 | + Name: "config", |
425 | + Usage: "display and interact with the configuration", |
426 | + Aliases: []string{"cfg"}, |
427 | + Action: func(ctx *cli.Context) error { |
428 | + return json.NewEncoder(os.Stdout).Encode(cfg) |
429 | + }, |
430 | + }, |
431 | + { |
432 | + Name: "edit", |
433 | + Usage: "create a new note or edit an existing one", |
434 | + Aliases: []string{"e"}, |
435 | + Action: func(ctx *cli.Context) error { |
436 | + fmt.Println(cfg) |
437 | + return nil |
438 | + }, |
439 | + }, |
440 | + { |
441 | + Name: "list", |
442 | + Usage: "display all of your notes", |
443 | + Aliases: []string{"ls", "l"}, |
444 | + Flags: []cli.Flag{ |
445 | + &cli.BoolFlag{ |
446 | + Name: "flat", |
447 | + Aliases: []string{"f"}, |
448 | + Usage: "list only one note per line", |
449 | + Value: false, |
450 | + }, |
451 | + }, |
452 | + }, |
453 | + { |
454 | + Name: "remove", |
455 | + Usage: "remove an existing note", |
456 | + Aliases: []string{"rm"}, |
457 | + Flags: []cli.Flag{ |
458 | + &cli.BoolFlag{ |
459 | + Name: "recursive", |
460 | + Aliases: []string{"-r"}, |
461 | + Usage: "delete any sibling notes recursively", |
462 | + }, |
463 | + }, |
464 | + }, |
465 | + { |
466 | + Name: "cat", |
467 | + Usage: "write one or more notes to standard output", |
468 | + Aliases: []string{"c"}, |
469 | + ArgsUsage: "note path", |
470 | + Flags: []cli.Flag{ |
471 | + &cli.StringFlag{ |
472 | + Name: "encoding", |
473 | + Aliases: []string{"e"}, |
474 | + Usage: "output format", |
475 | + }, |
476 | + }, |
477 | + }, |
478 | + { |
479 | + Name: "mount", |
480 | + Usage: "mount the note hierarchy as a file system", |
481 | + Flags: []cli.Flag{ |
482 | + &cli.StringFlag{ |
483 | + Name: "path", |
484 | + Usage: "mountpoint for the note hierarchy", |
485 | + Required: true, |
486 | + }, |
487 | + &cli.BoolFlag{ |
488 | + Name: "read-only", |
489 | + Usage: "disallow writing", |
490 | + }, |
491 | + }, |
492 | + }, |
493 | + { |
494 | + Name: "serve", |
495 | + Usage: "launch an HTTP server for viewing notes in a web browser", |
496 | + Flags: []cli.Flag{ |
497 | + &cli.StringFlag{ |
498 | + Name: "address", |
499 | + Usage: "interface and ip address to listen on", |
500 | + Value: "127.0.0.1:9090", |
501 | + }, |
502 | + }, |
503 | + }, |
504 | + { |
505 | + Name: "gen_markdown", |
506 | + Usage: "generate a markdown description of this tool", |
507 | + Hidden: true, |
508 | + Action: func(ctx *cli.Context) error { |
509 | + md, err := app.ToMarkdown() |
510 | + if err != nil { |
511 | + return err |
512 | + } |
513 | + fmt.Fprint(os.Stdout, md) |
514 | + return nil |
515 | + }, |
516 | + }, |
517 | + { |
518 | + Name: "gen_man", |
519 | + Usage: "generate a manpage for this tool", |
520 | + Hidden: true, |
521 | + Action: func(ctx *cli.Context) error { |
522 | + man, err := app.ToMan() |
523 | + if err != nil { |
524 | + return err |
525 | + } |
526 | + fmt.Fprint(os.Stdout, man) |
527 | + return nil |
528 | + }, |
529 | + }, |
530 | + } |
531 | + return app |
532 | + } |
533 | diff --git a/pkg/config/config.go b/pkg/config/config.go |
534 | new file mode 100644 |
535 | index 0000000..6a39dfa |
536 | --- /dev/null |
537 | +++ b/pkg/config/config.go |
538 | @@ -0,0 +1,35 @@ |
539 | + package config |
540 | + |
541 | + import ( |
542 | + "errors" |
543 | + "io/ioutil" |
544 | + "os" |
545 | + |
546 | + "github.com/ghodss/yaml" |
547 | + ) |
548 | + |
549 | + type Config struct { |
550 | + // Path to a SQLite database |
551 | + Database string `json:"database"` |
552 | + // Editor |
553 | + Editor string `json:"editor"` |
554 | + } |
555 | + |
556 | + func Default() *Config { |
557 | + return &Config{} |
558 | + } |
559 | + |
560 | + func Load(path string, cfg *Config) (*Config, error) { |
561 | + raw, err := ioutil.ReadFile(path) |
562 | + if err != nil { |
563 | + if errors.Is(err, os.ErrNotExist) { |
564 | + return cfg, nil |
565 | + } |
566 | + return nil, err |
567 | + } |
568 | + err = yaml.Unmarshal(raw, cfg) |
569 | + if err != nil { |
570 | + return nil, err |
571 | + } |
572 | + return cfg, nil |
573 | + } |
574 | diff --git a/pkg/version/version.go b/pkg/version/version.go |
575 | new file mode 100644 |
576 | index 0000000..a315f97 |
577 | --- /dev/null |
578 | +++ b/pkg/version/version.go |
579 | @@ -0,0 +1,5 @@ |
580 | + package version |
581 | + |
582 | + var ( |
583 | + Version = "UNDEFINED" |
584 | + ) |
585 | diff --git a/schema.sql b/schema.sql |
586 | new file mode 100644 |
587 | index 0000000..7cfadb5 |
588 | --- /dev/null |
589 | +++ b/schema.sql |
590 | @@ -0,0 +1,6 @@ |
591 | + CREATE TABLE notes ( |
592 | + id INTEGER PRIMARY KEY, |
593 | + parent INTEGER REFERENCES notes(id), |
594 | + name VARCHAR NOT NULL UNIQUE, |
595 | + content VARCHAR |
596 | + ); |
597 | diff --git a/watch.sh b/watch.sh |
598 | index 9a4dd44..26081c1 100755 |
599 | --- a/watch.sh |
600 | +++ b/watch.sh |
601 | @@ -11,7 +11,7 @@ _do_build() { |
602 | } |
603 | |
604 | _do_build |
605 | - inotifywait -m -e close_write,moved_to --include '.*ha$' --format %e/%f . | \ |
606 | + inotifywait -m -e close_write,moved_to --include '.*go$' --format %e/%f . | \ |
607 | while IFS=/ read -r events file; do |
608 | echo "file $file modified, rebuilding" |
609 | _do_build |