diff options
Diffstat (limited to 'runner/shell')
| -rw-r--r-- | runner/shell/hydrated.go | 17 | ||||
| -rw-r--r-- | runner/shell/shell.go | 176 | ||||
| -rw-r--r-- | runner/shell/shell_test.go | 22 | ||||
| -rw-r--r-- | runner/shell/target.go | 35 |
4 files changed, 250 insertions, 0 deletions
diff --git a/runner/shell/hydrated.go b/runner/shell/hydrated.go new file mode 100644 index 0000000..5a86d5a --- /dev/null +++ b/runner/shell/hydrated.go @@ -0,0 +1,17 @@ +package shell + +import ( + "pik/runner" +) + +type HydratedShellTarget struct { + runner.BaseHydration[*ShellTarget] +} + +func (h *HydratedShellTarget) Icon() string { + return "\uF489" +} + +func (h *HydratedShellTarget) Description() string { + return "//TODO" +} diff --git a/runner/shell/shell.go b/runner/shell/shell.go new file mode 100644 index 0000000..5333279 --- /dev/null +++ b/runner/shell/shell.go @@ -0,0 +1,176 @@ +package shell + +import ( + "bufio" + "errors" + "io/fs" + "os/exec" + "path/filepath" + "pik/identity" + "pik/indexers/pikdex" + "pik/model" + "pik/runner" + "pik/spool" + "slices" + "strings" +) + +//TODO: Clean up shell selection? Maybe default to bash? + +var NoContentError = errors.New("not enough content in target") +var NoShellError = errors.New("could not find any shell interpreters") + +var ExtShellMap = map[string]string{ + ".sh": "bash", + ".ps1": "powershell", +} + +var Shells = []string{"bash", "bash.exe", "zsh", "fish", "powershell", "powershell.exe", "cmd.exe"} + +var Runner = &shell{ + Locations: map[string]string{}, +} + +type shell struct { + Locations map[string]string +} + +var WrongTargetError = errors.New("wrong target type") + +func (s *shell) Hydrate(target model.Target) (model.HydratedTarget, error) { + cast, ok := target.(*ShellTarget) + if !ok { + return nil, WrongTargetError + } + hyd := &HydratedShellTarget{BaseHydration: runner.Hydrated(cast)} + return hyd, nil +} + +func (s *shell) Wants(f fs.FS, file string, entry fs.DirEntry) (bool, error) { + if entry != nil && entry.IsDir() { + return false, nil + } + + fd, err := f.Open(file) + if err != nil { + return false, err + } + scanner := bufio.NewScanner(fd) + scanner.Split(bufio.ScanRunes) + if !scanner.Scan() { + return false, nil + } + txt := scanner.Text() + if txt == "#" { // + return true, nil + } + for k, _ := range ExtShellMap { + if strings.HasSuffix(file, k) { + return true, nil + } + } + return false, nil +} + +func (s *shell) Find(shell string) (string, error) { + if s.Locations[shell] != "" { + return s.Locations[shell], nil + } + + if p, err := exec.LookPath(shell); err == nil { + s.Locations[shell] = p + return shell, nil + } else { + return "", err + } +} + +func (s *shell) CreateTarget(fs fs.FS, src string, file string, _ fs.DirEntry) (model.Target, error) { + shell, err := s.ShellFor(fs, file) + if err != nil { + return nil, err + } + _, filename := filepath.Split(file) + var sub []string + split := strings.Split(file, "/") + for _, p := range split { + if slices.Contains(pikdex.Roots, p) { + continue + } + if filename == p { + continue + } + sub = append(sub, p) + } + return &ShellTarget{ + BaseTarget: runner.BaseTarget{ + Identity: identity.New(filename), + MyTags: model.TagsFromFilename(filename), + }, + Shell: shell, + Script: file, + SubValue: sub, + }, nil +} + +func (s *shell) ShellFor(fs fs.FS, file string) (string, error) { + + var shell, shebang string + + // low-hanging fruit - indicative filename + if byFile := s.ShellByFilename(file); byFile != "" { + return byFile, nil + } + + fd, err := fs.Open(file) + if err != nil { + return "", err + } + scanner := bufio.NewScanner(fd) + scanner.Split(bufio.ScanLines) + if !scanner.Scan() { + return "", NoContentError + } + txt := scanner.Text() + if strings.HasPrefix(txt, "#!") { + // shebang found + for _, potentialShell := range Shells { + if strings.Contains(txt, potentialShell) { + shebang = shell + if loc, err := s.Find(potentialShell); err == nil { + shell = loc + } else { + _, _ = spool.Warn("script has %s but could not find %s (%s)\n", shebang, potentialShell) + } + } + } + } + + if shebang == "" { + // if no shebang, just send the first one we find + for _, s := range Shells { + if p, err := exec.LookPath(s); err != nil { + shell = p + } + } + } + + if shell == "" { + return "", NoShellError + } + + return shell, nil + +} + +func (s *shell) ShellByFilename(file string) string { + ext := filepath.Ext(file) + if ExtShellMap[ext] != "" { + sh, err := s.Find(ExtShellMap[ext]) + if err == nil { + return sh + } + } + + return "" +} diff --git a/runner/shell/shell_test.go b/runner/shell/shell_test.go new file mode 100644 index 0000000..b28bd6c --- /dev/null +++ b/runner/shell/shell_test.go @@ -0,0 +1,22 @@ +package shell + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestShell_ShellByFilename(t *testing.T) { + s := &shell{ + Locations: map[string]string{"powershell": "/pws", "bash": "/bash"}, + } + inputs := map[string]string{ + "script.ps1": "/pws", + "script.sh": "/bash", + "asdf": "", + } + + for k, v := range inputs { + sh := s.ShellByFilename(k) + assert.Equal(t, sh, v) + } +} diff --git a/runner/shell/target.go b/runner/shell/target.go new file mode 100644 index 0000000..e823d41 --- /dev/null +++ b/runner/shell/target.go @@ -0,0 +1,35 @@ +package shell + +import ( + "os/exec" + "path/filepath" + "pik/model" + "pik/runner" +) + +type ShellTarget struct { + runner.BaseTarget + Shell string + Script string + SubValue []string +} + +func (s *ShellTarget) String() string { + return s.Label() +} + +func (s *ShellTarget) Hydrate(_ *model.Source) (model.HydratedTarget, error) { + return Runner.Hydrate(s) +} + +func (s *ShellTarget) Sub() []string { + return s.SubValue +} + +func (s *ShellTarget) Label() string { + return s.Identity.Full +} + +func (s *ShellTarget) Create(src *model.Source) *exec.Cmd { + return exec.Command(s.Shell, filepath.Join(src.Path, s.Script)) +} |
