2020-12-24 15:02:19 +00:00
package main
import (
2021-03-18 18:47:49 +00:00
"errors"
2021-01-24 11:10:13 +00:00
"fmt"
"os"
"os/exec"
2021-04-17 09:28:38 +00:00
"path/filepath"
2021-03-18 19:27:08 +00:00
"strings"
2021-01-24 11:10:13 +00:00
2020-12-24 15:02:19 +00:00
"github.com/alecthomas/kong"
2021-04-14 18:14:01 +00:00
"github.com/mickael-menu/zk/internal/cli"
"github.com/mickael-menu/zk/internal/cli/cmd"
"github.com/mickael-menu/zk/internal/core"
executil "github.com/mickael-menu/zk/internal/util/exec"
2020-12-24 15:02:19 +00:00
)
2021-01-02 11:08:58 +00:00
var Version = "dev"
var Build = "dev"
2021-04-14 18:14:01 +00:00
var root struct {
2021-02-15 21:44:31 +00:00
Init cmd . Init ` cmd group:"zk" help:"Create a new notebook in the given directory." `
2021-02-10 19:53:25 +00:00
Index cmd . Index ` cmd group:"zk" help:"Index the notes to be searchable." `
2021-02-10 19:27:34 +00:00
2021-02-15 21:44:31 +00:00
New cmd . New ` cmd group:"notes" help:"Create a new note in the given notebook directory." `
2021-02-10 19:27:34 +00:00
List cmd . List ` cmd group:"notes" help:"List notes matching the given criteria." `
Edit cmd . Edit ` cmd group:"notes" help:"Edit notes matching the given criteria." `
2021-04-17 09:28:38 +00:00
NotebookDir string ` type:path placeholder:PATH help:"Turn off notebook auto-discovery and set manually the notebook where commands are run." `
WorkingDir string ` short:W type:path placeholder:PATH help:"Run as if zk was started in <PATH> instead of the current working directory." `
2021-03-18 18:47:49 +00:00
NoInput NoInput ` help:"Never prompt or ask for confirmation." `
2021-02-10 19:53:25 +00:00
2021-04-04 13:31:54 +00:00
ShowHelp ShowHelp ` cmd hidden default:"1" `
LSP cmd . LSP ` cmd hidden `
Version kong . VersionFlag ` hidden help:"Print zk version." `
2020-12-24 15:02:19 +00:00
}
2021-01-24 11:10:13 +00:00
// NoInput is a flag preventing any user prompt when enabled.
type NoInput bool
2021-04-14 18:14:01 +00:00
func ( f NoInput ) BeforeApply ( container * cli . Container ) error {
2021-01-24 11:10:13 +00:00
container . Terminal . NoInput = true
return nil
}
2021-02-07 18:03:27 +00:00
// ShowHelp is the default command run. It's equivalent to `zk --help`.
type ShowHelp struct { }
2021-04-14 18:14:01 +00:00
func ( cmd * ShowHelp ) Run ( container * cli . Container ) error {
parser , err := kong . New ( & root , options ( container ) ... )
2021-02-07 18:03:27 +00:00
if err != nil {
return err
}
ctx , err := parser . Parse ( [ ] string { "--help" } )
if err != nil {
return err
}
return ctx . Run ( container )
}
2020-12-24 15:02:19 +00:00
func main ( ) {
2020-12-28 12:15:56 +00:00
// Create the dependency graph.
2021-04-14 18:14:01 +00:00
container , err := cli . NewContainer ( Version )
2021-03-17 17:04:27 +00:00
fatalIfError ( err )
2020-12-27 17:58:22 +00:00
2021-03-18 18:47:49 +00:00
// Open the notebook if there's any.
2021-04-17 09:28:38 +00:00
dirs , err := parseDirs ( )
2021-03-18 18:47:49 +00:00
fatalIfError ( err )
2021-04-17 09:28:38 +00:00
searchDirs , err := notebookSearchDirs ( dirs )
fatalIfError ( err )
2021-05-16 20:11:31 +00:00
err = container . SetCurrentNotebook ( searchDirs )
fatalIfError ( err )
2021-03-18 18:47:49 +00:00
// Run the alias or command.
2021-01-24 11:10:13 +00:00
if isAlias , err := runAlias ( container , os . Args [ 1 : ] ) ; isAlias {
fatalIfError ( err )
} else {
2021-04-14 18:14:01 +00:00
ctx := kong . Parse ( & root , options ( container ) ... )
// Index the current notebook except if the user is running the `index`
// command, otherwise it would hide the stats.
if ctx . Command ( ) != "index" {
if notebook , err := container . CurrentNotebook ( ) ; err == nil {
_ , err = notebook . Index ( false )
ctx . FatalIfErrorf ( err )
}
}
2021-01-24 11:10:13 +00:00
err := ctx . Run ( container )
ctx . FatalIfErrorf ( err )
}
2020-12-24 15:02:19 +00:00
}
2021-01-23 20:15:17 +00:00
2021-04-14 18:14:01 +00:00
func options ( container * cli . Container ) [ ] kong . Option {
2021-02-10 19:27:34 +00:00
term := container . Terminal
2021-02-07 18:03:27 +00:00
return [ ] kong . Option {
kong . Bind ( container ) ,
kong . Name ( "zk" ) ,
kong . UsageOnError ( ) ,
2021-02-10 19:27:34 +00:00
kong . HelpOptions {
Compact : true ,
FlagsLast : true ,
} ,
2021-02-07 18:03:27 +00:00
kong . Vars {
2021-03-18 18:39:06 +00:00
"version" : "zk " + strings . TrimPrefix ( Version , "v" ) ,
2021-02-07 18:03:27 +00:00
} ,
2021-02-10 19:27:34 +00:00
kong . Groups ( map [ string ] string {
"filter" : "Filtering" ,
"sort" : "Sorting" ,
"format" : "Formatting" ,
2021-04-14 18:14:01 +00:00
"notes" : term . MustStyle ( "NOTES" , core . StyleYellow , core . StyleBold ) + "\n" + term . MustStyle ( "Edit or browse your notes" , core . StyleBold ) ,
"zk" : term . MustStyle ( "NOTEBOOK" , core . StyleYellow , core . StyleBold ) + "\n" + term . MustStyle ( "A notebook is a directory containing a collection of notes" , core . StyleBold ) ,
2021-02-10 19:27:34 +00:00
} ) ,
2021-02-07 18:03:27 +00:00
}
}
2021-01-24 11:10:13 +00:00
func fatalIfError ( err error ) {
if err != nil {
fmt . Fprintf ( os . Stderr , "zk: error: %v\n" , err )
os . Exit ( 1 )
}
}
2021-01-23 20:15:17 +00:00
2021-01-24 11:10:13 +00:00
// runAlias will execute a user alias if the command is one of them.
2021-04-14 18:14:01 +00:00
func runAlias ( container * cli . Container , args [ ] string ) ( bool , error ) {
2021-03-17 17:04:27 +00:00
if len ( args ) < 1 {
return false , nil
}
2021-02-13 20:07:06 +00:00
runningAlias := os . Getenv ( "ZK_RUNNING_ALIAS" )
2021-03-17 17:04:27 +00:00
for alias , cmdStr := range container . Config . Aliases {
if alias == runningAlias || alias != args [ 0 ] {
continue
}
2021-01-24 11:10:13 +00:00
2021-03-17 17:04:27 +00:00
// Prevent infinite loop if an alias calls itself.
os . Setenv ( "ZK_RUNNING_ALIAS" , alias )
2021-04-14 18:14:01 +00:00
// Move to the current notebook's root directory before running the alias.
if notebook , err := container . CurrentNotebook ( ) ; err == nil {
cmdStr = ` cd " ` + notebook . Path + ` " && ` + cmdStr
}
2021-03-18 18:47:49 +00:00
2021-03-17 17:04:27 +00:00
cmd := executil . CommandFromString ( cmdStr , args [ 1 : ] ... )
cmd . Stdin = os . Stdin
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
err := cmd . Run ( )
if err != nil {
if err , ok := err . ( * exec . ExitError ) ; ok {
os . Exit ( err . ExitCode ( ) )
return true , nil
} else {
return true , err
2021-01-24 11:10:13 +00:00
}
}
2021-03-17 17:04:27 +00:00
return true , nil
2021-01-24 11:10:13 +00:00
}
return false , nil
2021-01-23 20:15:17 +00:00
}
2021-03-18 18:47:49 +00:00
2021-04-17 09:28:38 +00:00
// notebookSearchDirs returns the places where zk will look for a notebook.
2021-03-18 18:47:49 +00:00
// The first successful candidate will be used as the working directory from
// which path arguments are relative from.
//
// By order of precedence:
// 1. --notebook-dir flag
// 2. current working directory
// 3. ZK_NOTEBOOK_DIR environment variable
2021-04-17 09:28:38 +00:00
func notebookSearchDirs ( dirs cli . Dirs ) ( [ ] cli . Dirs , error ) {
wd , err := os . Getwd ( )
2021-03-18 18:47:49 +00:00
if err != nil {
2021-04-17 09:28:38 +00:00
return nil , err
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
// 1. --notebook-dir flag
if dirs . NotebookDir != "" {
// If --notebook-dir is used, we want to only check there to report
// "notebook not found" errors.
if dirs . WorkingDir == "" {
dirs . WorkingDir = wd
}
return [ ] cli . Dirs { dirs } , nil
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
candidates := [ ] cli . Dirs { }
2021-03-18 18:47:49 +00:00
// 2. current working directory
2021-04-17 09:28:38 +00:00
wdDirs := dirs
if wdDirs . WorkingDir == "" {
wdDirs . WorkingDir = wd
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
wdDirs . NotebookDir = wdDirs . WorkingDir
candidates = append ( candidates , wdDirs )
2021-03-18 18:47:49 +00:00
// 3. ZK_NOTEBOOK_DIR environment variable
if notebookDir , ok := os . LookupEnv ( "ZK_NOTEBOOK_DIR" ) ; ok {
2021-04-17 09:28:38 +00:00
dirs := dirs
dirs . NotebookDir = notebookDir
if dirs . WorkingDir == "" {
dirs . WorkingDir = notebookDir
}
candidates = append ( candidates , dirs )
2021-03-18 18:47:49 +00:00
}
return candidates , nil
}
2021-04-17 09:28:38 +00:00
// parseDirs returns the paths specified with the --notebook-dir and
// --working-dir flags.
2021-03-18 18:47:49 +00:00
//
2021-04-17 09:28:38 +00:00
// We need to parse these flags before Kong, because we might need it to
// resolve zk command aliases before parsing the CLI.
func parseDirs ( ) ( cli . Dirs , error ) {
var d cli . Dirs
var err error
findFlag := func ( long string , short string ) ( string , error ) {
foundFlag := ""
for _ , arg := range os . Args {
if arg == long || ( short != "" && arg == short ) {
foundFlag = arg
} else if foundFlag != "" {
return filepath . Abs ( arg )
}
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
if foundFlag != "" {
return "" , errors . New ( foundFlag + " requires a path argument" )
}
return "" , nil
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
d . NotebookDir , err = findFlag ( "--notebook-dir" , "" )
if err != nil {
return d , err
}
d . WorkingDir , err = findFlag ( "--working-dir" , "-W" )
if err != nil {
return d , err
2021-03-18 18:47:49 +00:00
}
2021-04-17 09:28:38 +00:00
return d , nil
2021-03-18 18:47:49 +00:00
}