You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
zk/core/zk/zk.go

172 lines
3.7 KiB
Go

package zk
import (
"fmt"
4 years ago
"io/ioutil"
"os"
"path/filepath"
"github.com/mickael-menu/zk/util/errors"
"github.com/mickael-menu/zk/util/paths"
)
4 years ago
const defaultConfig = `editor = "nvim"
dir "log" {
template = "log.md"
}
`
// Zk (Zettelkasten) represents an opened slip box.
4 years ago
type Zk struct {
// Slip box root path.
Path string
// Global user configuration.
Config Config
4 years ago
}
// Dir represents a directory inside a slip box.
type Dir struct {
// Name of the directory, which is the path relative to the slip box's root.
Name string
// Absolute path to the directory.
Path string
// User configuration for this directory.
Config DirConfig
}
// Open locates a slip box at the given path and parses its configuration.
4 years ago
func Open(path string) (*Zk, error) {
wrap := errors.Wrapper("open failed")
path, err := filepath.Abs(path)
if err != nil {
4 years ago
return nil, wrap(err)
}
4 years ago
path, err = locateRoot(path)
if err != nil {
return nil, wrap(err)
}
configContent, err := ioutil.ReadFile(filepath.Join(path, ".zk/config.hcl"))
if err != nil {
return nil, wrap(err)
}
templatesDir := filepath.Join(path, ".zk/templates")
config, err := ParseConfig(configContent, templatesDir)
4 years ago
if err != nil {
return nil, wrap(err)
}
return &Zk{
Path: path,
Config: *config,
}, nil
}
// Create initializes a new slip box at the given path.
func Create(path string) error {
wrap := errors.Wrapper("init failed")
path, err := filepath.Abs(path)
if err != nil {
return wrap(err)
}
if existingPath, err := locateRoot(path); err == nil {
return wrap(fmt.Errorf("a slip box already exists in %v", existingPath))
}
// Create .zk and .zk/templates directories.
err = os.MkdirAll(filepath.Join(path, ".zk/templates"), os.ModePerm)
if err != nil {
return wrap(err)
}
// Write default config.toml.
4 years ago
f, err := os.Create(filepath.Join(path, ".zk/config.hcl"))
if err != nil {
return wrap(err)
}
_, err = f.WriteString(defaultConfig)
if err != nil {
return wrap(err)
}
return nil
}
// locate finds the root of the slip box containing the given path.
func locateRoot(path string) (string, error) {
if !filepath.IsAbs(path) {
panic("absolute path expected")
}
var locate func(string) (string, error)
locate = func(currentPath string) (string, error) {
if currentPath == "/" || currentPath == "." {
return "", fmt.Errorf("no slip box found in %v or a parent directory", path)
}
exists, err := paths.DirExists(filepath.Join(currentPath, ".zk"))
switch {
case err != nil:
return "", err
case exists:
return currentPath, nil
default:
return locate(filepath.Dir(currentPath))
}
}
return locate(path)
}
// DirAt returns a Dir representation of the slip box directory at the given path.
func (zk *Zk) DirAt(path string, overrides ...ConfigOverrides) (*Dir, error) {
wrap := errors.Wrapperf("%v: not a valid slip box directory", path)
path, err := filepath.Abs(path)
if err != nil {
return nil, wrap(err)
}
name, err := filepath.Rel(zk.Path, path)
if err != nil {
return nil, wrap(err)
}
config, ok := zk.Config.Dirs[name]
if !ok {
// Fallback on root config.
config = zk.Config.DirConfig
}
config = config.Clone()
for _, v := range overrides {
config.Override(v)
}
return &Dir{
Name: name,
Path: path,
Config: config,
}, nil
}
// RequiredDirAt is the same as DirAt, but checks that the directory exists
// before returning the Dir.
func (zk *Zk) RequireDirAt(path string, overrides ...ConfigOverrides) (*Dir, error) {
dir, err := zk.DirAt(path, overrides...)
if err != nil {
return nil, err
}
exists, err := paths.Exists(dir.Path)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("%v: directory not found", path)
}
return dir, nil
}