Commit
Author: Kevin Schoon [me@kevinschoon.com]
Hash: dad35335a2511bae98e9f4bcfb104174b09fc096
Timestamp: Tue, 31 May 2022 04:07:47 +0000 (2 years ago)

+401 -88 +/-16 browse
implement basic cli skeleton and project structure
1diff --git a/Makefile b/Makefile
2index 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
34index 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
70new file mode 100644
71index 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
93new file mode 100644
94index 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
122new file mode 100644
123index 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
149deleted file mode 100644
150index 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
187new file mode 100644
188index 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
205new file mode 100644
206index 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
224deleted file mode 100644
225index 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
265new file mode 100644
266index 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
358deleted file mode 100644
359index 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
369new file mode 100644
370index 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
534new file mode 100644
535index 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
575new file mode 100644
576index 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
586new file mode 100644
587index 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
598index 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