summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorewy <ewy0@protonmail.com>2026-05-02 18:28:40 +0200
committerewy <ewy0@protonmail.com>2026-05-02 18:28:40 +0200
commite8b9ca135642c76000bc0ec2e78674c602a748dd (patch)
tree4e502ce767a4a4f70e40d6e72a3817bc139f1300
parent35e6ac334f6a62cf63b743182cd5355f6baf4406 (diff)
add .js and .ts support
also: remove unused/duplicate method runner.Hydrate
-rw-r--r--README.md10
-rw-r--r--main.go15
-rw-r--r--menu/menu.go11
-rw-r--r--model/runner.go1
-rw-r--r--runner/exc/runner.go8
-rw-r--r--runner/js/indexer.go39
-rw-r--r--runner/js/js.go106
-rw-r--r--runner/js/runner.go28
-rw-r--r--runner/js/target_npm.go42
-rw-r--r--runner/js/target_script.go55
-rw-r--r--runner/python/runner.go5
-rw-r--r--runner/shell/hydrated.go14
-rw-r--r--runner/shell/target.go4
13 files changed, 300 insertions, 38 deletions
diff --git a/README.md b/README.md
index 89fccb1..098c522 100644
--- a/README.md
+++ b/README.md
@@ -76,11 +76,18 @@ pik reads the first comment line from your targets and informs you in the tui!
* external targets will also show up in the tui
* search with `/` and `?`, scroll results with `n` and `N`
+### supported pik runners
+* python
+* shell
+* executable files
+* java- and typescript
### supported external runners
* `just`
* `make`
+* `pyproject.toml`
+*
* if you want any more let me know and it should be done very fast
## planned features
@@ -89,9 +96,6 @@ As this program has already gone through a number of iterations and forms, this
time to catch up with all the features it used to have. This list is not exhaustive, but it is ordered by importance I
attach to these features.
-* runner for executable files
- * this will also enable arbitrary shells like node by way of the shebang
-* indexers for other target types such as `npm`
* whitelists for external runners in `.pik`
* adding descriptions to external targets
* expand tui:
diff --git a/main.go b/main.go
index 4e94ee0..7de29e8 100644
--- a/main.go
+++ b/main.go
@@ -14,6 +14,7 @@ import (
"github.com/ewy1/pik/run"
"github.com/ewy1/pik/runner/exc"
"github.com/ewy1/pik/runner/gnumake"
+ "github.com/ewy1/pik/runner/js"
"github.com/ewy1/pik/runner/just"
"github.com/ewy1/pik/runner/python"
"github.com/ewy1/pik/runner/shell"
@@ -36,6 +37,7 @@ var initializers = ComponentList[model.Initializer]{
pikdex.Indexer,
python.Python,
git.Git,
+ js.Js,
}
// indexers are methods which scan a directory and return a number of targets.
@@ -43,6 +45,7 @@ var indexers = ComponentList[model.Indexer]{
pikdex.Indexer,
just.Indexer,
gnumake.Indexer,
+ js.Js,
}
// runners are modules which know how to turn a file into an exec.Cmd
@@ -51,6 +54,7 @@ var runners = ComponentList[model.Runner]{
shell.Runner,
python.Python,
exc.Exc,
+ js.Js,
}
// hydrators are ran when the menu is required
@@ -131,13 +135,16 @@ func main() {
args := pflag.Args()
var result *search.Result
+ cancelled := false
if len(args) == 0 {
- source, target, err := menu.Show(st, hydrators)
+ md, err := menu.Show(st, hydrators)
if err != nil {
_, _ = spool.Warn("%v\n", err)
os.Exit(1)
}
+ cancelled = md.Cancel
+ source, target := md.Result()
if target != nil {
t := target.Target()
result = &search.Result{
@@ -166,6 +173,12 @@ func main() {
return
}
+ if cancelled {
+ _, _ = spool.Warn("no target selected\n")
+ os.Exit(0)
+ return
+ }
+
if result.Target == nil {
_, _ = spool.Warn("target not found\n")
os.Exit(1)
diff --git a/menu/menu.go b/menu/menu.go
index 3d379d0..b524d95 100644
--- a/menu/menu.go
+++ b/menu/menu.go
@@ -10,24 +10,23 @@ import (
var WrongModelTypeError = errors.New("wrong model type")
var NoSourcesIndexedError = errors.New("no sources indexed")
-func Show(st *model.State, hydrators []model.Modder) (*model.HydratedSource, model.HydratedTarget, error) {
+func Show(st *model.State, hydrators []model.Modder) (*Model, error) {
if len(st.Sources) == 0 {
- return nil, nil, NoSourcesIndexedError
+ return nil, NoSourcesIndexedError
}
md := NewModel(st, hydrators)
var opts []tea.ProgramOption
program := tea.NewProgram(md, opts...)
resultModel, err := program.Run()
if err != nil {
- return nil, nil, err
+ return nil, err
}
result, ok := resultModel.(*Model)
if !ok {
- return nil, nil, WrongModelTypeError
+ return nil, WrongModelTypeError
}
- src, t := result.Result()
- return src, t, nil
+ return result, nil
}
func Hydrate(st *model.State, hydrators []model.Modder) *model.HydratedState {
diff --git a/model/runner.go b/model/runner.go
index e8cbcc0..eda2505 100644
--- a/model/runner.go
+++ b/model/runner.go
@@ -8,7 +8,6 @@ import (
// these are mostly used for pikdex but other runners can use them as well
// these have to be registered in main.go
type Runner interface {
- Hydrate(target Target) (HydratedTarget, error)
Wants(fs fs.FS, file string, entry fs.DirEntry) (bool, error)
CreateTarget(fs fs.FS, source string, file string, entry fs.DirEntry) (Target, error)
}
diff --git a/runner/exc/runner.go b/runner/exc/runner.go
index 092fd80..2eae44c 100644
--- a/runner/exc/runner.go
+++ b/runner/exc/runner.go
@@ -9,14 +9,6 @@ import (
"path/filepath"
)
-func (e *exc) Hydrate(target model.Target) (model.HydratedTarget, error) {
- return &Hydrated{
- BaseHydration: &runner.BaseHydration[*Executable]{
- Self: target.(*Executable),
- },
- }, nil
-}
-
func (e *exc) Wants(fs fs.FS, file string, entry fs.DirEntry) (bool, error) {
if entry.IsDir() {
return false, nil
diff --git a/runner/js/indexer.go b/runner/js/indexer.go
new file mode 100644
index 0000000..8a2a6b2
--- /dev/null
+++ b/runner/js/indexer.go
@@ -0,0 +1,39 @@
+package js
+
+import (
+ "encoding/json"
+ "errors"
+ "github.com/ewy1/pik/model"
+ "io/fs"
+ "os"
+ "path/filepath"
+)
+
+type Package struct {
+ Scripts map[string]string `json:"scripts"`
+}
+
+func (n *js) Index(path string, f fs.FS, runners []model.Runner) ([]model.Target, error) {
+ p := &Package{}
+ // are there any other package.jsons? i hope not, because i don't know them
+ content, err := os.ReadFile(filepath.Join(path, "package.json"))
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, nil
+ } else if err != nil {
+ return nil, err
+ }
+ err = json.Unmarshal(content, p)
+ if err != nil {
+ return nil, err
+ }
+
+ var targets []model.Target
+ if n.Npm == "" {
+ return nil, NoNpm
+ }
+ for k, s := range p.Scripts {
+ targets = append(targets, n.CreateRun(k, s))
+ }
+
+ return targets, nil
+}
diff --git a/runner/js/js.go b/runner/js/js.go
new file mode 100644
index 0000000..7c2af58
--- /dev/null
+++ b/runner/js/js.go
@@ -0,0 +1,106 @@
+package js
+
+import (
+ "errors"
+ "github.com/ewy1/pik/identity"
+ "github.com/ewy1/pik/model"
+ "github.com/ewy1/pik/runner"
+ "os/exec"
+ "path/filepath"
+ "slices"
+)
+
+var jsExtensions = []string{
+ ".js",
+ ".cjs",
+}
+
+var tsExtensions = []string{
+ ".ts",
+}
+
+var extensions = append(jsExtensions, tsExtensions...)
+
+var managers = []string{
+ "pnpm",
+ "yarn",
+ "npm",
+}
+
+var jsInterpreters = []string{
+ "node",
+ "bun",
+}
+
+var tsInterpeters = []string{
+ "ts",
+ "ts-node",
+ "bun",
+}
+
+type js struct {
+ JsInterpreter string
+ TsInterpreter string
+ Npm string
+}
+
+var Js = &js{}
+
+var UnsupportedFile = errors.New("unsupported file")
+var NoJsInterpreter = errors.New("no js interpreter found in $PATH")
+var NoTsInterpreter = errors.New("no ts interpreter found in $PATH")
+var NoNpm = errors.New("npm not found in $PATH")
+
+func (n *js) Interpreter(file string) (string, error) {
+ ext := filepath.Ext(file)
+ if slices.Contains(jsInterpreters, ext) {
+ if n.JsInterpreter == "" {
+ return "", NoJsInterpreter
+ }
+ return n.JsInterpreter, nil
+ }
+ if slices.Contains(tsInterpeters, ext) {
+ if n.TsInterpreter == "" {
+ return "", NoTsInterpreter
+ }
+ return n.TsInterpreter, nil
+ }
+ return "", UnsupportedFile
+}
+
+func (n *js) Init() error {
+ for _, p := range jsInterpreters {
+ if r, err := exec.LookPath(p); err != nil {
+ n.JsInterpreter = r
+ }
+ }
+ for _, p := range tsInterpeters {
+ if r, err := exec.LookPath(p); err != nil {
+ n.TsInterpreter = r
+ }
+ }
+
+ for _, m := range managers {
+ if r, err := exec.LookPath(m); err == nil {
+ n.Npm = r
+ }
+ }
+
+ return nil
+}
+
+var npmSub = []string{
+ "npm",
+}
+
+func (n *js) CreateRun(name, cmd string) model.Target {
+ return &Npm{
+ BaseTarget: runner.BaseTarget{
+ Identity: identity.New(cmd),
+ MyTags: model.TagsFromFilename(cmd),
+ MySub: npmSub,
+ },
+ Name: name,
+ Cmd: cmd,
+ }
+}
diff --git a/runner/js/runner.go b/runner/js/runner.go
new file mode 100644
index 0000000..8b5f4f1
--- /dev/null
+++ b/runner/js/runner.go
@@ -0,0 +1,28 @@
+package js
+
+import (
+ "github.com/ewy1/pik/identity"
+ "github.com/ewy1/pik/model"
+ "github.com/ewy1/pik/runner"
+ "io/fs"
+ "path/filepath"
+ "slices"
+)
+
+func (n *js) Wants(fs fs.FS, file string, entry fs.DirEntry) (bool, error) {
+ ext := filepath.Ext(entry.Name())
+ return slices.Contains(extensions, ext), nil
+}
+
+func (n *js) CreateTarget(fs fs.FS, source string, file string, entry fs.DirEntry) (model.Target, error) {
+ ext := filepath.Ext(entry.Name())
+ typed := slices.Contains(tsExtensions, ext)
+ return &Script{
+ BaseTarget: runner.BaseTarget{
+ Identity: identity.New(entry.Name()),
+ MyTags: model.TagsFromFilename(entry.Name()),
+ MySub: runner.SubFromFile(file),
+ },
+ Typed: typed,
+ }, nil
+}
diff --git a/runner/js/target_npm.go b/runner/js/target_npm.go
new file mode 100644
index 0000000..8319fad
--- /dev/null
+++ b/runner/js/target_npm.go
@@ -0,0 +1,42 @@
+package js
+
+import (
+ "github.com/ewy1/pik/model"
+ "github.com/ewy1/pik/runner"
+ "os/exec"
+ "path/filepath"
+)
+
+type Npm struct {
+ runner.BaseTarget
+ Name string
+ Cmd string
+}
+
+func (n *Npm) Icon() string {
+ return "\uE60B"
+}
+
+func (n *Npm) Description(src *model.HydratedSource) string {
+ return n.Cmd
+}
+
+func (n *Npm) Target() model.Target {
+ return n
+}
+
+func (n *Npm) Create(s *model.Source) *exec.Cmd {
+ return exec.Command(Js.Npm, "run", n.Name)
+}
+
+func (n *Npm) Label() string {
+ return n.Name
+}
+
+func (n *Npm) Hydrate(src *model.Source) (model.HydratedTarget, error) {
+ return n, nil
+}
+
+func (n *Npm) File(src *model.Source) string {
+ return filepath.Join(src.Path, "package.json")
+}
diff --git a/runner/js/target_script.go b/runner/js/target_script.go
new file mode 100644
index 0000000..6ad213d
--- /dev/null
+++ b/runner/js/target_script.go
@@ -0,0 +1,55 @@
+package js
+
+import (
+ "github.com/ewy1/pik/describe"
+ "github.com/ewy1/pik/model"
+ "github.com/ewy1/pik/runner"
+ "github.com/ewy1/pik/spool"
+ "os/exec"
+ "path/filepath"
+)
+
+type Script struct {
+ runner.BaseTarget
+ Typed bool
+}
+
+func (t *Script) Icon() string {
+ if t.Typed {
+ return "\uE628"
+ } else {
+ return "\uE60C"
+ }
+}
+
+func (t *Script) Description(src *model.HydratedSource) string {
+ d, err := describe.Describe(t, t.File(src.Source))
+ if err != nil {
+ _, _ = spool.Warn("%v\n", err)
+ }
+ return d
+}
+
+func (t *Script) Target() model.Target {
+ return t
+}
+
+func (t *Script) Create(s *model.Source) *exec.Cmd {
+ if t.Typed {
+ return exec.Command(Js.TsInterpreter, t.File(s))
+ } else {
+ return exec.Command(Js.JsInterpreter, t.File(s))
+ }
+}
+
+func (t *Script) Label() string {
+ return t.Identity.Full
+}
+
+func (t *Script) Hydrate(src *model.Source) (model.HydratedTarget, error) {
+ return t, nil
+}
+
+func (t *Script) File(src *model.Source) string {
+ return filepath.Join(src.Path, "package.json")
+}
diff --git a/runner/python/runner.go b/runner/python/runner.go
index 6aa6017..8d03f52 100644
--- a/runner/python/runner.go
+++ b/runner/python/runner.go
@@ -30,11 +30,6 @@ func (p python) Init() error {
return err
}
-func (p python) Hydrate(target model.Target) (model.HydratedTarget, error) {
- //TODO implement me
- panic("implement me")
-}
-
func (p python) Wants(fs fs.FS, file string, entry fs.DirEntry) (bool, error) {
return !entry.IsDir() && filepath.Ext(entry.Name()) == ".py", nil
}
diff --git a/runner/shell/hydrated.go b/runner/shell/hydrated.go
index fec74f1..440d9cd 100644
--- a/runner/shell/hydrated.go
+++ b/runner/shell/hydrated.go
@@ -1,7 +1,6 @@
package shell
import (
- "errors"
"github.com/ewy1/pik/describe"
"github.com/ewy1/pik/model"
"github.com/ewy1/pik/runner"
@@ -19,19 +18,8 @@ func (h *Hydrated) Icon() string {
func (h *Hydrated) Description(src *model.HydratedSource) string {
desc, err := describe.Describe(h.Target(), h.Target().File(src.Source))
if err != nil {
- spool.Warn("%v\n", err)
+ _, _ = spool.Warn("%v\n", err)
return ""
}
return desc
}
-
-var WrongTargetError = errors.New("wrong target type")
-
-func (s *shell) Hydrate(target model.Target) (model.HydratedTarget, error) {
- cast, ok := target.(*Target)
- if !ok {
- return nil, WrongTargetError
- }
- hyd := &Hydrated{BaseHydration: runner.Hydrated(cast)}
- return hyd, nil
-}
diff --git a/runner/shell/target.go b/runner/shell/target.go
index 49f7a18..d1262af 100644
--- a/runner/shell/target.go
+++ b/runner/shell/target.go
@@ -21,7 +21,9 @@ func (s *Target) String() string {
}
func (s *Target) Hydrate(_ *model.Source) (model.HydratedTarget, error) {
- return Runner.Hydrate(s)
+ return &Hydrated{
+ BaseHydration: runner.Hydrated(s),
+ }, nil
}
func (s *Target) Sub() []string {