From 370f13b58e9cb74eeab3461afb779cf4d013c94b Mon Sep 17 00:00:00 2001 From: ewy Date: Tue, 14 Apr 2026 18:58:37 +0200 Subject: handle not having stuff installed a little more gracefully --- README.md | 45 +++++++++++++++++++++++++++++++-------------- cache/cache.go | 2 +- main.go | 25 +++++++++++++++---------- menu/menu.go | 4 ++++ model/new.go | 11 +++++++---- runner/just/just.go | 7 ++++++- 6 files changed, 64 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c2fdb89..a03559b 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,42 @@ pik > file-based task runner +## the goal + +are you ever uncertain what to do after cloning a repository? `pik` aims to fix this by making tasks findable, +predictable and programmable. + +running `pik` in a supported repository will index its own file-based task system, as well as + +## getting started + +1. create a `.pik` folder in your project +2. put a script in there, for example: `.pik/build.sh` containing `go build .` +3. you can now access this script from almost anywhere by calling `pik build`. If you want to trigger a specific + projects build, specify `pik project build`, where `project` is the folder name. + ## current features -* run targets by their approximate name: `uwu build` will trigger `.uwu/build.sh`, or `.uwu/build.py`, or `make build` depending on what's possible. - * including external targets from `just` -* specify source names to search for the target in that source explicitly: `uwu myproject build` can run `../../myproject/.uwu/build.sh` +* run targets by their approximate name: `uwu build` will trigger `.uwu/build.sh`, or `.uwu/build.py`, or `make build` + depending on what's possible. + * including external targets from `just` +* specify source names to search for the target in that source explicitly: `uwu myproject build` can + run `../../myproject/.uwu/build.sh` * unless overridden, targets will run in the directory where the `.uwu` folder resides. * use `--all` flag to start out-of-tree targets without having to navigate to the directory. * `--here` to run the target in the current working directory instead of the source directory. - * `--at` to run the target in an arbitrary location + * `--at` to run the target in an arbitrary location * target tags in filenames which trigger flag behaviours * aliases to sources through the `.alias` file * tui for viewing and running targets * y/n confirmation with yes as default - * will be used if we have an uncertain target guess - * `--yes` to automatically confirm y/n prompts + * will be used if we have an uncertain target guess + * `--yes` to automatically confirm y/n prompts * autoload .env files - * both the project root and `.pik` folder will be searched - * values specified with `--env` will be tried as pre- and suffixes: `--env asdf` will load `.env-asdf` and `asdf.env` if they exist. - * env files are reread for every trigger, meaning you can have a pre-trigger fetch credentials and save it in .env + * both the project root and `.pik` folder will be searched + * values specified with `--env` will be tried as pre- and suffixes: `--env asdf` will load `.env-asdf` + and `asdf.env` if they exist. + * env files are reread for every trigger, meaning you can have a pre-trigger fetch credentials and save it in .env * create any kind of target: high-level support for shell and python, and arbitrary shells with the shebang. ## planned features @@ -32,12 +49,12 @@ time to catch up with all the features it used to have. This list is not exhaust attach to these features. * runner for executable files - * this will also enable arbitrary shells like node by way of the shebang + * this will also enable arbitrary shells like node by way of the shebang * indexers for other target types such as `make` and `npm` * expand tui: - * adding descriptions to targets based on the first comment in a target - * support for categories and ordering of targets through the `.order` file - * search - * more hotkeys (filter jumping, toggle all, etc.) + * adding descriptions to targets based on the first comment in a target + * support for categories and ordering of targets through the `.order` file + * search + * more hotkeys (filter jumping, toggle all, etc.) * git pre-commit and pre-push triggers * linking sources together by `.include` and `.wants` files diff --git a/cache/cache.go b/cache/cache.go index b57d8b8..ae2d081 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -104,7 +104,7 @@ func Save(s *model.State) error { return os.WriteFile(Path, []byte(c.String()), os.ModePerm) } -func LoadState(f fs.FS, cache Cache, indexers []model.Indexer, runners []model.Runner) (*model.State, error) { +func LoadState(f fs.FS, cache Cache, indexers []model.Indexer, runners []model.Runner) (*model.State, []error) { var locs []string for _, e := range cache.Entries { locs = append(locs, e.Path) diff --git a/main.go b/main.go index 4b0dcee..cc8275e 100644 --- a/main.go +++ b/main.go @@ -60,21 +60,22 @@ func main() { panic(err) } var st *model.State - var stateError error + var stateErrors []error if !*flags.All { - st, stateError = model.NewState(fs, locs, indexers, runners) + st, stateErrors = model.NewState(fs, locs, indexers, runners) } else { c, err := cache.Load() if err != nil { panic(err) } - st, stateError = cache.LoadState(fs, c, indexers, runners) + st, stateErrors = cache.LoadState(fs, c, indexers, runners) } - if stateError != nil { - panic(stateError) + if stateErrors != nil { + spool.Warn("%v\n", stateErrors) + } else { + err = cache.Save(st) } - err = cache.Save(st) if err != nil { _, _ = spool.Warn("%v", err) } @@ -84,11 +85,13 @@ func main() { if len(args) == 0 { source, target, err := menu.Show(st, hydrators) if err != nil { - panic(err) + spool.Warn("%v\n", err) + os.Exit(1) } err = run.Run(source.Source, target, args...) if err != nil { - panic(err) + spool.Warn("%v\n", err) + os.Exit(1) } return @@ -99,7 +102,8 @@ func main() { err := pflag.Set("all", "true") ForceConfirm = true if err != nil { - panic(err) + spool.Warn("%v\n", err) + os.Exit(1) } main() return @@ -119,6 +123,7 @@ func main() { err = run.Run(src, target, args...) if err != nil { - panic(err) + spool.Warn("%v\n", err) + os.Exit(1) } } diff --git a/menu/menu.go b/menu/menu.go index 232a837..7f71e2d 100644 --- a/menu/menu.go +++ b/menu/menu.go @@ -8,8 +8,12 @@ import ( ) var WrongModelTypeError = errors.New("wrong model type") +var NoSourcesIndexedError = errors.New("no sources indexed") func Show(st *model.State, hydrators []model.Hydrator) (*model.HydratedSource, model.HydratedTarget, error) { + if len(st.Sources) == 0 { + return nil, nil, NoSourcesIndexedError + } md := NewModel(st, hydrators) program := tea.NewProgram(md) resultModel, err := program.Run() diff --git a/model/new.go b/model/new.go index f26bc8f..41086c2 100644 --- a/model/new.go +++ b/model/new.go @@ -7,7 +7,8 @@ import ( "strings" ) -func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, error) { +func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) (*State, []error) { + var errs []error st := &State{} for _, loc := range locations { _, dirName := filepath.Split(loc) @@ -26,11 +27,13 @@ func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) s, err := fs.Sub(f, loc) if err != nil { - return nil, err + errs = append(errs, err) + continue } targets, err := indexer.Index("/"+loc, s, runners) if err != nil { - return nil, err + errs = append(errs, err) + continue } src.Targets = append(src.Targets, targets...) } @@ -41,5 +44,5 @@ func NewState(f fs.FS, locations []string, indexers []Indexer, runners []Runner) } - return st, nil + return st, errs } diff --git a/runner/just/just.go b/runner/just/just.go index e574536..a800212 100644 --- a/runner/just/just.go +++ b/runner/just/just.go @@ -1,6 +1,7 @@ package just import ( + "errors" "io/fs" "os/exec" "pik/identity" @@ -83,9 +84,13 @@ func ParseOutput(input string) []model.Target { return result } +var NoJustError = errors.New("no just in $PATH but source contains justfile") + func (j *just) findJust() error { loc, err := exec.LookPath("just") - if err != nil { + if errors.Is(err, exec.ErrNotFound) { + return NoJustError + } else if err != nil { return err } j.path = loc -- cgit v1.3