Add an interactive wizard for `zk init` (#35)

pull/36/head
Mickaël Menu 3 years ago committed by GitHub
parent f3ebdb4813
commit 980244d2c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Added
* Interactive wizard for the `zk init` command.
* An experimental Language Server for LSP-compatible editors:
* Auto-complete Markdown links with `[[` (setup wiki-links in the [note formats configuration](docs/note-format.md))
* Auto-complete [hashtags and colon-separated tags](docs/tags.md).
@ -27,6 +28,7 @@ All notable changes to this project will be documented in this file.
* For convenience, `ZK_NOTEBOOK_DIR` behaves like setting a `--working-dir` fallback, instead of `--notebook-dir`. This way, paths will be relative to the root of the notebook.
* A practical use case is to use `zk list -W .` when outside a notebook. This will list the notes in `ZK_NOTEBOOK_DIR` but print paths relative to the current directory, making them actionable from your terminal emulator.
## 0.3.0
### Added

@ -5,7 +5,7 @@ go 1.15
replace github.com/tliron/glsp => github.com/mickael-menu/glsp v0.1.0
require (
github.com/AlecAivazis/survey/v2 v2.2.7
github.com/AlecAivazis/survey/v2 v2.2.12
github.com/alecthomas/kong v0.2.16-0.20210209082517-405b2f4fd9a4
github.com/aymerick/raymond v2.0.2+incompatible
github.com/fatih/color v1.10.0
@ -16,6 +16,7 @@ require (
github.com/lestrrat-go/strftime v1.0.4
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-sqlite3 v1.14.6
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mickael-menu/pretty v0.2.3
github.com/mvdan/xurls v1.1.0
github.com/pelletier/go-toml v1.8.1
@ -28,5 +29,9 @@ require (
github.com/tliron/kutil v0.1.22
github.com/yuin/goldmark v1.3.2
github.com/yuin/goldmark-meta v1.0.0
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/djherbis/times.v1 v1.2.0
)

@ -25,6 +25,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.2.7 h1:5NbxkF4RSKmpywYdcRgUmos1o+roJY8duCLZXbVjoig=
github.com/AlecAivazis/survey/v2 v2.2.7/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk=
github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8=
github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
@ -369,6 +371,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mickael-menu/glsp v0.1.0 h1:we6mTssWXxGPVeEcTpCW8AOpdCuUXwUZ6Q2UiYVnCOw=
github.com/mickael-menu/glsp v0.1.0/go.mod h1:ouzTGvQteTU4hdsG+32vIx0if7E9CzMa64d7tYJJ91g=
github.com/mickael-menu/pretty v0.2.3 h1:AXi5WcBuWxwQV6iY/GhmCFpaoboQO2SLtzfujrn7dv0=
@ -561,6 +565,8 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -623,6 +629,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -683,9 +690,14 @@ golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0=
golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -693,6 +705,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

@ -4,7 +4,11 @@ import (
"fmt"
"path/filepath"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/mickael-menu/zk/internal/cli"
"github.com/mickael-menu/zk/internal/core"
"github.com/mickael-menu/zk/internal/util/strings"
)
// Init creates a notebook in the given directory
@ -13,7 +17,17 @@ type Init struct {
}
func (cmd *Init) Run(container *cli.Container) error {
notebook, err := container.Notebooks.Init(cmd.Directory)
opts, err := startInitWizard()
if err != nil {
if err == terminal.InterruptErr {
return nil
}
return err
}
fmt.Println()
notebook, err := container.Notebooks.Init(cmd.Directory, opts)
if err != nil {
return err
}
@ -32,3 +46,45 @@ func (cmd *Init) Run(container *cli.Container) error {
fmt.Printf("Initialized a notebook in %v\n", path)
return nil
}
func startInitWizard() (core.InitOpts, error) {
answers := struct {
WikiLink bool
Tags []string
}{}
hashtag := "#hashtag"
multiwordTag := "#Bear's multi-word tag#"
colonTag := ":colon:tag:"
questions := []*survey.Question{
{
Name: "wikilink",
Prompt: &survey.Confirm{
Message: "Do you prefer [[WikiLinks]] over regular Markdown links?",
Default: false,
},
},
{
Name: "tags",
Prompt: &survey.MultiSelect{
Message: "Choose your favorite inline tag syntaxes:",
Options: []string{hashtag, multiwordTag, colonTag},
},
},
}
var opts core.InitOpts
err := survey.Ask(questions, &answers)
if err != nil {
return opts, err
}
opts.WikiLinks = answers.WikiLink
opts.Hashtags = strings.InList(answers.Tags, hashtag)
opts.MultiwordTags = strings.InList(answers.Tags, multiwordTag)
opts.ColonTags = strings.InList(answers.Tags, colonTag)
return opts, nil
}

@ -70,6 +70,14 @@ func NewContainer(version string) (*Container, error) {
FS: fs,
Notebooks: core.NewNotebookStore(config, core.NotebookStorePorts{
FS: fs,
TemplateLoaderFactory: func(language string) (core.TemplateLoader, error) {
loader := handlebars.NewLoader(handlebars.LoaderOpts{
LookupPaths: []string{},
Styler: styler,
})
return loader, nil
},
NotebookFactory: func(path string, config core.Config) (*core.Notebook, error) {
dbPath := filepath.Join(path, ".zk/notebook.db")
db, err := sqlite.Open(dbPath)

@ -9,27 +9,30 @@ import (
// NotebookStore retrieves or creates new notebooks.
type NotebookStore struct {
config Config
notebookFactory NotebookFactory
fs FileStorage
config Config
notebookFactory NotebookFactory
templateLoaderFactory TemplateLoaderFactory
fs FileStorage
// Cached opened notebooks.
notebooks map[string]*Notebook
}
type NotebookStorePorts struct {
NotebookFactory NotebookFactory
FS FileStorage
NotebookFactory NotebookFactory
TemplateLoaderFactory TemplateLoaderFactory
FS FileStorage
}
// NewNotebookStore creates a new NotebookStore instance using the given
// options and port implementations.
func NewNotebookStore(config Config, ports NotebookStorePorts) *NotebookStore {
return &NotebookStore{
config: config,
notebookFactory: ports.NotebookFactory,
fs: ports.FS,
notebooks: map[string]*Notebook{},
config: config,
notebookFactory: ports.NotebookFactory,
templateLoaderFactory: ports.TemplateLoaderFactory,
fs: ports.FS,
notebooks: map[string]*Notebook{},
}
}
@ -91,8 +94,16 @@ func (ns *NotebookStore) cachedNotebookAt(path string) *Notebook {
return nil
}
// InitOpts holds the user preferences when creating a new notebook.
type InitOpts struct {
WikiLinks bool
Hashtags bool
ColonTags bool
MultiwordTags bool
}
// Init creates a new notebook at the given file path.
func (ns *NotebookStore) Init(path string) (*Notebook, error) {
func (ns *NotebookStore) Init(path string, options InitOpts) (*Notebook, error) {
wrap := errors.Wrapper("init")
path, err := ns.fs.Abs(path)
@ -105,7 +116,11 @@ func (ns *NotebookStore) Init(path string) (*Notebook, error) {
}
// Create the default configuration file.
err = ns.fs.Write(filepath.Join(path, ".zk/config.toml"), []byte(defaultConfig))
config, err := ns.generateConfig(options)
if err != nil {
return nil, wrap(err)
}
err = ns.fs.Write(filepath.Join(path, ".zk/config.toml"), []byte(config))
if err != nil {
return nil, wrap(err)
}
@ -144,6 +159,18 @@ func (ns *NotebookStore) locateNotebook(path string) (string, error) {
return locate(path)
}
func (ns *NotebookStore) generateConfig(options InitOpts) (string, error) {
loader, err := ns.templateLoaderFactory("en")
if err != nil {
return "", err
}
template, err := loader.LoadTemplate(defaultConfig)
if err != nil {
return "", err
}
return template.Render(options)
}
const defaultConfig = `# zk configuration file
#
# Uncomment the properties you want to customize.
@ -161,7 +188,7 @@ const defaultConfig = `# zk configuration file
#default-title = "Untitled"
# Template used to generate a note's filename, without extension.
#filename = "{{id}}"
#filename = "\{{id}}"
# The file extension used for the notes.
#extension = "md"
@ -190,7 +217,7 @@ template = "default.md"
# EXTRA VARIABLES
#
# A dictionary of variables you can use for any custom values when generating
# new notes. They are accessible in templates with {{extra.<key>}}
# new notes. They are accessible in templates with \{{extra.<key>}}
[extra]
#key = "value"
@ -211,7 +238,7 @@ template = "default.md"
#[group."<NAME>"]
#paths = ["<DIR1>", "<DIR2>"]
#[group."<NAME>".note]
#filename = "{{date now}}"
#filename = "\{{date now}}"
#[group."<NAME>".extra]
#key = "value"
@ -221,7 +248,11 @@ template = "default.md"
# Format used to generate links between notes.
# Either "wiki", "markdown" or a custom template. Default is "markdown".
{{#if WikiLinks}}
link-format = "wiki"
{{else}}
#link-format = "wiki"
{{/if}}
# Indicates whether a link's path will be percent-encoded.
# Defaults to true for "markdown" format and false for "wiki" format.
#link-encode-path = true
@ -230,12 +261,24 @@ template = "default.md"
#link-drop-extension = true
# Enable support for #hashtags.
#hashtags = true
{{#if Hashtags}}
hashtags = true
{{else}}
hashtags = false
{{/if}}
# Enable support for :colon:separated:tags:.
#colon-tags = true
{{#if ColonTags}}
colon-tags = true
{{else}}
colon-tags = false
{{/if}}
# Enable support for Bear's #multi-word tags#
# Hashtags must be enabled for multi-word tags to work.
#multiword-tags = true
{{#if MultiwordTags}}
multiword-tags = true
{{else}}
multiword-tags = false
{{/if}}
# EXTERNAL TOOLS
@ -298,7 +341,7 @@ template = "default.md"
# arguments. This can be useful to expand a complex search query into a flag
# taking only paths. For example:
# zk list --link-to "` + "`" + `zk path -m potatoe` + "`" + `"
#path = "zk list --quiet --format {{path}} --delimiter , $@"
#path = "zk list --quiet --format \{{path}} --delimiter , $@"
# Show a random note.
#lucky = "zk list --quiet --format full --sort random --limit 1"

Loading…
Cancel
Save