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"
2020-12-24 15:02:19 +00:00
"github.com/alecthomas/kong"
"github.com/mickael-menu/zk/cmd"
2021-02-10 19:27:34 +00:00
"github.com/mickael-menu/zk/core/style"
2021-01-24 11:10:13 +00:00
executil "github.com/mickael-menu/zk/util/exec"
2020-12-24 15:02:19 +00:00
)
2021-01-02 11:08:58 +00:00
var Version = "dev"
var Build = "dev"
2020-12-24 15:02:19 +00:00
var cli 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-03-18 18:47:49 +00:00
NoInput NoInput ` help:"Never prompt or ask for confirmation." `
NotebookDir string ` placeholder:"PATH" help:"Run as if zk was started in <PATH> instead of the current working directory." `
2021-02-10 19:53:25 +00:00
2021-02-07 18:03:27 +00:00
ShowHelp ShowHelp ` cmd default:"1" hidden:true `
2021-02-10 19:53:25 +00:00
Version kong . VersionFlag ` help:"Print zk version." hidden:true `
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
func ( f NoInput ) BeforeApply ( container * cmd . Container ) error {
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 { }
func ( cmd * ShowHelp ) Run ( container * cmd . Container ) error {
parser , err := kong . New ( & cli , options ( container ) ... )
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-03-17 17:04:27 +00:00
container , err := cmd . NewContainer ( )
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.
searchPaths , err := notebookSearchPaths ( )
fatalIfError ( err )
container . OpenNotebook ( searchPaths )
// 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-02-07 18:03:27 +00:00
ctx := kong . Parse ( & cli , options ( container ) ... )
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-02-07 18:03:27 +00:00
func options ( container * cmd . 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" ,
"notes" : term . MustStyle ( "NOTES" , style . RuleYellow , style . RuleBold ) + "\n" + term . MustStyle ( "Edit or browse your notes" , style . RuleBold ) ,
2021-02-15 21:44:31 +00:00
"zk" : term . MustStyle ( "NOTEBOOK" , style . RuleYellow , style . RuleBold ) + "\n" + term . MustStyle ( "A notebook is a directory containing a collection of notes" , style . RuleBold ) ,
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.
func runAlias ( container * cmd . 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-03-18 18:47:49 +00:00
// Move to the provided working directory if it is not the current one,
// before running the alias.
cmdStr = ` cd " ` + container . WorkingDir + ` " && ` + cmdStr
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
// notebookSearchPaths returns the places where zk will look for a notebook.
// 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
func notebookSearchPaths ( ) ( [ ] string , error ) {
// 1. --notebook-dir flag
notebookDir , err := parseNotebookDirFlag ( )
if err != nil {
return [ ] string { } , err
}
if notebookDir != "" {
// If --notebook-dir is used, we want to only check there to report errors.
return [ ] string { notebookDir } , nil
}
candidates := [ ] string { }
// 2. current working directory
wd , err := os . Getwd ( )
if err != nil {
return nil , err
}
candidates = append ( candidates , wd )
// 3. ZK_NOTEBOOK_DIR environment variable
if notebookDir , ok := os . LookupEnv ( "ZK_NOTEBOOK_DIR" ) ; ok {
candidates = append ( candidates , notebookDir )
}
return candidates , nil
}
// parseNotebookDir returns the path to the notebook specified with the
// --notebook-dir flag.
//
// We need to parse the --notebook-dir flag before Kong, because we might need
// it to resolve zk command aliases before parsing the CLI.
func parseNotebookDirFlag ( ) ( string , error ) {
foundFlag := false
for _ , arg := range os . Args {
if arg == "--notebook-dir" {
foundFlag = true
} else if foundFlag {
return arg , nil
}
}
if foundFlag {
return "" , errors . New ( "--notebook-dir requires an argument" )
}
return "" , nil
}