Create a new note

pull/6/head
Mickaël Menu 3 years ago
parent ca0d95f242
commit 8bb9f83eef
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -3,15 +3,43 @@ package cmd
import (
"fmt"
"github.com/mickael-menu/zk/core/note"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util/opt"
)
// New adds a new note to the slip box.
type New struct {
Directory string `arg optional name:"directory" default:"."`
Directory string `arg optional type:"path" default:"." help:"Directory in which to create the note"`
ShowPath bool `help:"Shows the path of the created note instead of editing it"`
Title string `short:"t" help:"Title of the new note" placeholder:"TITLE"`
Template string `type:"path" help:"Custom template to use to render the note" placeholder:"PATH"`
Extra map[string]string `help:"Extra variables passed to the templates"`
}
func (cmd *New) Run() error {
zk, err := zk.Open(cmd.Directory)
fmt.Printf("%+v\n", zk)
return err
func (cmd *New) Run(container *Container) error {
zk, err := zk.Open(".")
if err != nil {
return err
}
dir, err := zk.DirAt(cmd.Directory)
if err != nil {
return err
}
opts := note.CreateOpts{
Dir: *dir,
Title: opt.NewNotEmptyString(cmd.Title),
Content: opt.NullString,
Template: opt.NewNotEmptyString(cmd.Template),
Extra: cmd.Extra,
}
file, err := note.Create(zk, opts, container.Renderer())
if err != nil {
return err
}
fmt.Printf("%+v\n", file)
return nil
}

@ -0,0 +1,124 @@
package note
import (
"fmt"
"path/filepath"
"strings"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util/errors"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/paths"
"github.com/mickael-menu/zk/util/rand"
)
// Renderer renders templates.
type Renderer interface {
// Render renders a handlebars string template with the given context.
Render(template string, context interface{}) (string, error)
// RenderFile renders a handlebars template file with the given context.
RenderFile(path string, context interface{}) (string, error)
}
// CreateOpts holds the options to create a new note.
type CreateOpts struct {
// Parent directory for the new note.
Dir zk.Dir
// Title of the note.
Title opt.String
// Initial content of the note, which will be injected in the template.
Content opt.String
// Custom template to use for the note, overriding the one declared in the config.
Template opt.String
// Extra template variables to expand.
Extra map[string]string
}
// Create generates a new note in the given slip box from the given options.
func Create(zk *zk.Zk, opts CreateOpts, renderer Renderer) (string, error) {
wrap := errors.Wrapper("note creation failed")
exists, err := paths.Exists(opts.Dir.Path)
if err != nil {
return "", wrap(err)
}
if !exists {
return "", wrap(fmt.Errorf("directory not found at %v", opts.Dir.Path))
}
extra := zk.Extra(opts.Dir)
for k, v := range opts.Extra {
extra[k] = v
}
context := renderContext{
// FIXME Customize default title in config
Title: opts.Title.OrDefault("Untitled"),
Content: opts.Content.Unwrap(),
Extra: extra,
}
file, err := genFilepath(zk, opts.Dir, renderer, &context)
if err != nil {
return "", wrap(err)
}
template := opts.Template.OrDefault(
zk.Template(opts.Dir).OrDefault(""),
)
if template != "" {
content, err := renderer.RenderFile(template, context)
if err != nil {
return "", wrap(err)
}
err = paths.WriteString(path, content)
if err != nil {
return "", wrap(err)
}
}
return file, nil
}
// renderContext holds the placeholder values which will be expanded in the templates.
type renderContext struct {
Title string
Content string
Filename string
FilenameStem string `handlebars:"filename-stem"`
RandomID string `handlebars:"random-id"`
Extra map[string]string
}
func genFilepath(zk *zk.Zk, dir zk.Dir, renderer Renderer, context *renderContext) (string, error) {
template := zk.FilenameTemplate(dir)
isRandom := strings.Contains(template, "random-id")
i := 0
for {
context.RandomID = rand.GenID(zk.RandIDOpts(dir))
filename, err := renderer.Render(template, context)
if err != nil {
return "", err
}
// FIXME Customize extension in config
path := filepath.Join(dir.Path, filename+".md")
exists, err := paths.Exists(path)
if err != nil {
return "", err
}
if !exists {
context.Filename = filepath.Base(path)
context.FilenameStem = paths.FilenameStem(path)
return path, nil
} else if !isRandom || i > 50 { // Attempts 50 tries if the filename template contains a random ID before failing.
return "", fmt.Errorf("%v: file already exists", path)
}
i++
}
}

@ -41,3 +41,7 @@ func (s String) OrDefault(def string) string {
func (s String) Unwrap() string {
return s.OrDefault("")
}
func (s String) String() string {
return s.OrDefault("")
}

@ -0,0 +1,37 @@
package paths
import (
"os"
"path/filepath"
"strings"
)
// Exists returns whether the given path exists on the file system.
func Exists(path string) (bool, error) {
if _, err := os.Stat(path); err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}
// FilenameStem returns the filename component of the given path,
// after removing its file extension.
func FilenameStem(path string) string {
filename := filepath.Base(path)
ext := filepath.Ext(filename)
return strings.TrimSuffix(filename, ext)
}
// WriteString writes the given content into a new file at the given path.
func WriteString(path string, content string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(content)
return err
}
Loading…
Cancel
Save