all repos

viye @ 6c40e20

my shot at reimplementing xiki
15 files changed, 382 insertions(+), 103 deletions(-)
resturcture + tests + expand shell
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-07-22 12:22:09 +0300
Authored at: 2026-07-22 12:13:49 +0300
Change ID: kxmrmqylqrzwvtkpnxsxwmwumwwuzoxw
Parent: 63835f5
M core/core.go → internal/viye/viye.go
···
        1
        
        -package core

      
        
        1
        +package viye

      
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"fmt"

      ···
        10
        10
         type Context struct {

      
        11
        11
         	Path []string // ancestor chain [root, ..., target]

      
        12
        12
         	Dir  string   // working directory

      
        
        13
        +

      
        
        14
        +	Cmd  string   // first word of the leaf path component

      
        
        15
        +	Args []string // remaining words of the leaf path component

      
        13
        16
         

      
        14
        17
         	dispatch func(*Context) (string, error) // set by [Viye.dispatch] for tool chaining

      
        15
        18
         }

      ···
        47
        50
         		os.Exit(0)

      
        48
        51
         	}

      
        49
        52
         

      
        
        53
        +	cmd, cmdArgs := splitLeaf(path[len(path)-1])

      
        50
        54
         	res, err := v.dispatch(&Context{

      
        51
        55
         		Path: path,

      
        52
        56
         		Dir:  ".", // TODO: get cwd

      
        
        57
        +		Cmd:  cmd,

      
        
        58
        +		Args: cmdArgs,

      
        53
        59
         	})

      
        54
        60
         	if err != nil {

      
        55
        61
         		return err

      ···
        81
        87
         	return "", nil

      
        82
        88
         }

      
        83
        89
         

      
        
        90
        +// splitLeaf splits a leaf path component into command name and args.

      
        
        91
        +// For "mkdir dir" returns ("mkdir", []string{"dir"}).

      
        
        92
        +// For "$ go run ." returns ("$", []string{"go", "run", "."}).

      
        
        93
        +// For "ip" returns ("ip", nil).

      
        
        94
        +func splitLeaf(leaf string) (cmd string, args []string) {

      
        
        95
        +	parts := strings.Fields(leaf)

      
        
        96
        +	if len(parts) == 0 {

      
        
        97
        +		return "", nil

      
        
        98
        +	}

      
        
        99
        +	return parts[0], parts[1:]

      
        
        100
        +}

      
        
        101
        +

      
        84
        102
         // splitArgs builds path by splitting each arg on "/"

      
        85
        103
         // for absolute paths(/...), leading / is preserved as part of first component.

      
        86
        104
         // for home paths(~...), ~ is the first comonents

      ···
        107
        125
         		}

      
        108
        126
         		// parts[i] is the first non-empty segment → attach it to "/"

      
        109
        127
         		result := []string{"/" + parts[i]}

      
        110
        
        -		result = append(result, parts[i+1:]...)

      
        
        128
        +		i++

      
        
        129
        +		for ; i < len(parts); i++ {

      
        
        130
        +			if parts[i] != "" {

      
        
        131
        +				result = append(result, parts[i])

      
        
        132
        +			}

      
        
        133
        +		}

      
        111
        134
         		return result

      
        112
        135
         	case strings.HasPrefix(arg, "~"):

      
        113
        
        -		return strings.Split(arg, "/")

      
        
        136
        +		parts := strings.Split(arg, "/")

      
        
        137
        +		var result []string

      
        
        138
        +		for _, p := range parts {

      
        
        139
        +			if p != "" {

      
        
        140
        +				result = append(result, p)

      
        
        141
        +			}

      
        
        142
        +		}

      
        
        143
        +		return result

      
        114
        144
         	case strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://"):

      
        115
        145
         		return []string{arg}

      
        116
        146
         	default:

      
        117
        
        -		return strings.Split(arg, "/")

      
        
        147
        +		parts := strings.Split(arg, "/")

      
        
        148
        +		var result []string

      
        
        149
        +		for _, p := range parts {

      
        
        150
        +			if p != "" {

      
        
        151
        +				result = append(result, p)

      
        
        152
        +			}

      
        
        153
        +		}

      
        
        154
        +		return result

      
        118
        155
         	}

      
        119
        156
         }

      
A internal/plumbing/gobin/gobin_test.go
···
        
        1
        +package gobin

      
        
        2
        +

      
        
        3
        +import "testing"

      
        
        4
        +

      
        
        5
        +func TestMatch(t *testing.T) {

      
        
        6
        +}

      
        
        7
        +

      
        
        8
        +func TestExecute(t *testing.T) {

      
        
        9
        +}

      
A internal/plumbing/ip/ip_test.go
···
        
        1
        +package ip

      
A internal/plumbing/shell/shell.go
···
        
        1
        +package shell

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"fmt"

      
        
        5
        +	"os"

      
        
        6
        +	"os/exec"

      
        
        7
        +	"path/filepath"

      
        
        8
        +	"strconv"

      
        
        9
        +	"strings"

      
        
        10
        +	"syscall"

      
        
        11
        +

      
        
        12
        +	"github.com/olexsmir/viye/internal/viye"

      
        
        13
        +)

      
        
        14
        +

      
        
        15
        +type Tool struct{}

      
        
        16
        +

      
        
        17
        +func (Tool) Name() string { return "shell($, $$, kill, mkdir)" }

      
        
        18
        +func (Tool) Match(ctx *viye.Context) bool {

      
        
        19
        +	return isCmd(ctx) || isBGCmd(ctx) || isKill(ctx) || isMkdir(ctx)

      
        
        20
        +}

      
        
        21
        +

      
        
        22
        +func (Tool) Execute(ctx *viye.Context) (string, error) {

      
        
        23
        +	switch {

      
        
        24
        +	case isCmd(ctx):

      
        
        25
        +		cmd := exec.Command("sh", "-c", strings.Join(ctx.Args, " "))

      
        
        26
        +		cmd.Dir = ctx.Dir

      
        
        27
        +		out, _ := cmd.CombinedOutput()

      
        
        28
        +

      
        
        29
        +		lines := strings.Split(string(out), "\n")

      
        
        30
        +		for i, line := range lines {

      
        
        31
        +			if line != "" {

      
        
        32
        +				lines[i] = "| " + line

      
        
        33
        +			}

      
        
        34
        +		}

      
        
        35
        +		return strings.Join(lines, "\n"), nil

      
        
        36
        +

      
        
        37
        +	case isBGCmd(ctx):

      
        
        38
        +		cmd := exec.Command("sh", "-c", strings.Join(ctx.Args, " "))

      
        
        39
        +		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        
        40
        +		cmd.Dir = ctx.Dir

      
        
        41
        +		if err := cmd.Start(); err != nil {

      
        
        42
        +			return "", err

      
        
        43
        +		}

      
        
        44
        +		return fmt.Sprintf("| pid: %d", cmd.Process.Pid), nil

      
        
        45
        +

      
        
        46
        +	case isKill(ctx):

      
        
        47
        +		pid, err := strconv.Atoi(ctx.Args[0])

      
        
        48
        +		if err != nil {

      
        
        49
        +			return "", fmt.Errorf("kill: invalid pid %q", ctx.Args[0])

      
        
        50
        +		}

      
        
        51
        +		p, err := os.FindProcess(pid)

      
        
        52
        +		if err != nil {

      
        
        53
        +			return "", fmt.Errorf("kill: %w", err)

      
        
        54
        +		}

      
        
        55
        +		if err := p.Kill(); err != nil {

      
        
        56
        +			return "", fmt.Errorf("kill: %w", err)

      
        
        57
        +		}

      
        
        58
        +		return "| done\n", nil

      
        
        59
        +

      
        
        60
        +	case isMkdir(ctx):

      
        
        61
        +		path := filepath.Join(append([]string{ctx.Dir}, ctx.Args...)...)

      
        
        62
        +		if err := os.MkdirAll(path, 0o755); err != nil {

      
        
        63
        +			return "", fmt.Errorf("mkdir: %w", err)

      
        
        64
        +		}

      
        
        65
        +		return "| created " + path + "\n", nil

      
        
        66
        +

      
        
        67
        +	default:

      
        
        68
        +		panic("shouldn't have paniced")

      
        
        69
        +	}

      
        
        70
        +}

      
        
        71
        +

      
        
        72
        +func isCmd(c *viye.Context) bool   { return c.Cmd == "$" }

      
        
        73
        +func isBGCmd(c *viye.Context) bool { return c.Cmd == "$$" }

      
        
        74
        +func isKill(c *viye.Context) bool  { return c.Cmd == "kill" && len(c.Args) == 1 }

      
        
        75
        +func isMkdir(c *viye.Context) bool { return c.Cmd == "mkdir" && len(c.Args) >= 1 }

      
A internal/plumbing/shell/shell_test.go
···
        
        1
        +package shell

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"os"

      
        
        5
        +	"path/filepath"

      
        
        6
        +	"testing"

      
        
        7
        +

      
        
        8
        +	"github.com/olexsmir/viye/internal/viye"

      
        
        9
        +)

      
        
        10
        +

      
        
        11
        +func TestMatch(t *testing.T) {

      
        
        12
        +	tests := []struct {

      
        
        13
        +		path string

      
        
        14
        +		cmd  string

      
        
        15
        +		args []string

      
        
        16
        +		want bool

      
        
        17
        +	}{

      
        
        18
        +		{"$", "$", nil, true},

      
        
        19
        +		{"$ go run .", "$", []string{"go", "run", "."}, true},

      
        
        20
        +		{"$$", "$$", nil, true},

      
        
        21
        +		{"$$ sleep 10", "$$", []string{"sleep", "10"}, true},

      
        
        22
        +		{"kill 1234", "kill", []string{"1234"}, true},

      
        
        23
        +		{"kill abc", "kill", []string{"abc"}, true},

      
        
        24
        +		{"mkdir dir", "mkdir", []string{"dir"}, true},

      
        
        25
        +		{"mkdir dir1 dir2", "mkdir", []string{"dir1", "dir2"}, true},

      
        
        26
        +		{"", "", nil, false},

      
        
        27
        +		{"ip", "ip", nil, false},

      
        
        28
        +		{"kill", "kill", nil, false},

      
        
        29
        +		{"kill 1 2", "kill", []string{"1", "2"}, false},

      
        
        30
        +		{"mkdir", "mkdir", nil, false},

      
        
        31
        +	}

      
        
        32
        +	for _, tt := range tests {

      
        
        33
        +		got := (&Tool{}).Match(&viye.Context{

      
        
        34
        +			Path: []string{tt.path},

      
        
        35
        +			Cmd:  tt.cmd,

      
        
        36
        +			Args: tt.args,

      
        
        37
        +		})

      
        
        38
        +		if got != tt.want {

      
        
        39
        +			t.Errorf("Match(%q) = %v; want %v", tt.path, got, tt.want)

      
        
        40
        +		}

      
        
        41
        +	}

      
        
        42
        +}

      
        
        43
        +

      
        
        44
        +func TestExecute(t *testing.T) {

      
        
        45
        +	t.Run("cmd", func(t *testing.T) {

      
        
        46
        +		got, err := (&Tool{}).Execute(&viye.Context{

      
        
        47
        +			Path: []string{"$ echo hello"},

      
        
        48
        +			Cmd:  "$",

      
        
        49
        +			Args: []string{"echo", "hello"},

      
        
        50
        +			Dir:  ".",

      
        
        51
        +		})

      
        
        52
        +		if err != nil {

      
        
        53
        +			t.Fatal(err)

      
        
        54
        +		}

      
        
        55
        +		if got != "| hello\n" {

      
        
        56
        +			t.Errorf("Execute($ echo hello) = %q; want %q", got, "| hello\\n")

      
        
        57
        +		}

      
        
        58
        +	})

      
        
        59
        +

      
        
        60
        +	t.Run("bg cmd", func(t *testing.T) {

      
        
        61
        +		got, err := (&Tool{}).Execute(&viye.Context{

      
        
        62
        +			Path: []string{"$$ sleep 0"},

      
        
        63
        +			Cmd:  "$$",

      
        
        64
        +			Args: []string{"sleep", "0"},

      
        
        65
        +			Dir:  ".",

      
        
        66
        +		})

      
        
        67
        +		if err != nil {

      
        
        68
        +			t.Fatal(err)

      
        
        69
        +		}

      
        
        70
        +		if got != "| pid: " && len(got) < 10 {

      
        
        71
        +			t.Errorf("Execute($$ sleep 0) = %q; want pid string", got)

      
        
        72
        +		}

      
        
        73
        +	})

      
        
        74
        +

      
        
        75
        +	t.Run("mkdir", func(t *testing.T) {

      
        
        76
        +		dir := t.TempDir()

      
        
        77
        +		got, err := (&Tool{}).Execute(&viye.Context{

      
        
        78
        +			Path: []string{"mkdir sub"},

      
        
        79
        +			Cmd:  "mkdir",

      
        
        80
        +			Args: []string{"sub"},

      
        
        81
        +			Dir:  dir,

      
        
        82
        +		})

      
        
        83
        +		if err != nil {

      
        
        84
        +			t.Fatal(err)

      
        
        85
        +		}

      
        
        86
        +		if _, err := os.Stat(filepath.Join(dir, "sub")); err != nil {

      
        
        87
        +			t.Errorf("dir not created: %v", err)

      
        
        88
        +		}

      
        
        89
        +		if got != "| created "+filepath.Join(dir, "sub")+"\n" {

      
        
        90
        +			t.Errorf("output = %q; want %q", got, "| created "+filepath.Join(dir, "sub")+"\n")

      
        
        91
        +		}

      
        
        92
        +	})

      
        
        93
        +

      
        
        94
        +	t.Run("kill", func(t *testing.T) {

      
        
        95
        +		// no reliable way to test kill without a real process,

      
        
        96
        +		// just verify it doesn't panic with valid args structure

      
        
        97
        +		if _, err := (&Tool{}).Execute(&viye.Context{

      
        
        98
        +			Path: []string{"kill 999999"},

      
        
        99
        +			Cmd:  "kill",

      
        
        100
        +			Args: []string{"999999"},

      
        
        101
        +			Dir:  ".",

      
        
        102
        +		}); err == nil {

      
        
        103
        +			t.Log("kill on bogus pid returned nil (expected error or success)")

      
        
        104
        +		}

      
        
        105
        +	})

      
        
        106
        +}

      
A internal/plumbing/url/url.go
···
        
        1
        +package url

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"strings"

      
        
        5
        +

      
        
        6
        +	"github.com/olexsmir/viye/internal/osutil"

      
        
        7
        +	"github.com/olexsmir/viye/internal/viye"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +type Tool struct{}

      
        
        11
        +

      
        
        12
        +func (Tool) Name() string { return "url(http)" }

      
        
        13
        +func (Tool) Match(ctx *viye.Context) bool {

      
        
        14
        +	return strings.HasPrefix(ctx.Path[0], "http://") || strings.HasPrefix(ctx.Path[0], "https://")

      
        
        15
        +}

      
        
        16
        +

      
        
        17
        +func (Tool) Execute(ctx *viye.Context) (string, error) {

      
        
        18
        +	return "", osutil.Open(ctx.Path[0])

      
        
        19
        +}

      
A internal/viye/viye_test.go
···
        
        1
        +package viye

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"testing"

      
        
        5
        +)

      
        
        6
        +

      
        
        7
        +func TestSplitLeaf(t *testing.T) {

      
        
        8
        +	tests := []struct {

      
        
        9
        +		leaf, cmd string

      
        
        10
        +		args      []string

      
        
        11
        +	}{

      
        
        12
        +		{"", "", nil},

      
        
        13
        +		{"ip", "ip", nil},

      
        
        14
        +		{"$", "$", nil},

      
        
        15
        +		{"$$", "$$", nil},

      
        
        16
        +		{"mkdir", "mkdir", nil},

      
        
        17
        +		{"$ go run .", "$", []string{"go", "run", "."}},

      
        
        18
        +		{"$$ sleep 10", "$$", []string{"sleep", "10"}},

      
        
        19
        +		{"mkdir dir", "mkdir", []string{"dir"}},

      
        
        20
        +		{"mkdir dir1 dir2", "mkdir", []string{"dir1", "dir2"}},

      
        
        21
        +		{"kill 1234", "kill", []string{"1234"}},

      
        
        22
        +		{"  spaced  ", "spaced", nil},

      
        
        23
        +		{"mkdir  spaced  dir", "mkdir", []string{"spaced", "dir"}},

      
        
        24
        +		{"   ", "", nil},

      
        
        25
        +	}

      
        
        26
        +	for _, tt := range tests {

      
        
        27
        +		cmd, args := splitLeaf(tt.leaf)

      
        
        28
        +		if cmd != tt.cmd {

      
        
        29
        +			t.Errorf("splitLeaf(%q) cmd = %q; want %q", tt.leaf, cmd, tt.cmd)

      
        
        30
        +		}

      
        
        31
        +		if len(args) != len(tt.args) {

      
        
        32
        +			t.Errorf("splitLeaf(%q) len(args) = %d; want %d", tt.leaf, len(args), len(tt.args))

      
        
        33
        +		} else {

      
        
        34
        +			for i := range args {

      
        
        35
        +				if args[i] != tt.args[i] {

      
        
        36
        +					t.Errorf("splitLeaf(%q) args[%d] = %q; want %q", tt.leaf, i, args[i], tt.args[i])

      
        
        37
        +				}

      
        
        38
        +			}

      
        
        39
        +		}

      
        
        40
        +	}

      
        
        41
        +}

      
        
        42
        +

      
        
        43
        +func TestSplitArg(t *testing.T) {

      
        
        44
        +	tests := []struct {

      
        
        45
        +		arg  string

      
        
        46
        +		want []string

      
        
        47
        +	}{

      
        
        48
        +		{"foo", []string{"foo"}},

      
        
        49
        +		{"foo/bar", []string{"foo", "bar"}},

      
        
        50
        +		{"foo/bar/", []string{"foo", "bar"}},

      
        
        51
        +		{"a/b/c", []string{"a", "b", "c"}},

      
        
        52
        +

      
        
        53
        +		// absolute paths

      
        
        54
        +		{"/", []string{"/"}},

      
        
        55
        +		{"/tmp", []string{"/tmp"}},

      
        
        56
        +		{"/tmp/foo", []string{"/tmp", "foo"}},

      
        
        57
        +		{"/tmp/foo/", []string{"/tmp", "foo"}},

      
        
        58
        +		{"/a/b/c", []string{"/a", "b", "c"}},

      
        
        59
        +

      
        
        60
        +		// home paths

      
        
        61
        +		{"~", []string{"~"}},

      
        
        62
        +		{"~/foo", []string{"~", "foo"}},

      
        
        63
        +		{"~/a/b", []string{"~", "a", "b"}},

      
        
        64
        +

      
        
        65
        +		// urls

      
        
        66
        +		{"http://example.com", []string{"http://example.com"}},

      
        
        67
        +		{"https://example.com/path", []string{"https://example.com/path"}},

      
        
        68
        +	}

      
        
        69
        +	for _, tt := range tests {

      
        
        70
        +		got := splitArg(tt.arg)

      
        
        71
        +		if len(got) != len(tt.want) {

      
        
        72
        +			t.Errorf("splitArg(%q) = %v; want %v", tt.arg, got, tt.want)

      
        
        73
        +			continue

      
        
        74
        +		}

      
        
        75
        +		for i := range got {

      
        
        76
        +			if got[i] != tt.want[i] {

      
        
        77
        +				t.Errorf("splitArg(%q) = %v; want %v", tt.arg, got, tt.want)

      
        
        78
        +				break

      
        
        79
        +			}

      
        
        80
        +		}

      
        
        81
        +	}

      
        
        82
        +}

      
        
        83
        +

      
        
        84
        +func TestSplitArgs(t *testing.T) {

      
        
        85
        +	got := splitArgs([]string{"/tmp/foo", "bar/baz"})

      
        
        86
        +	want := []string{"/tmp", "foo", "bar", "baz"}

      
        
        87
        +	if len(got) != len(want) {

      
        
        88
        +		t.Errorf("splitArgs = %v; want %v", got, want)

      
        
        89
        +	} else {

      
        
        90
        +		for i := range got {

      
        
        91
        +			if got[i] != want[i] {

      
        
        92
        +				t.Errorf("splitArgs = %v; want %v", got, want)

      
        
        93
        +				break

      
        
        94
        +			}

      
        
        95
        +		}

      
        
        96
        +	}

      
        
        97
        +}

      
M main.go
···
        4
        4
         	"fmt"

      
        5
        5
         	"os"

      
        6
        6
         

      
        7
        
        -	"github.com/olexsmir/viye/core"

      
        8
        
        -	"github.com/olexsmir/viye/plumbing/files"

      
        9
        
        -	"github.com/olexsmir/viye/plumbing/gobin"

      
        10
        
        -	"github.com/olexsmir/viye/plumbing/ip"

      
        11
        
        -	"github.com/olexsmir/viye/plumbing/shell"

      
        12
        
        -	"github.com/olexsmir/viye/plumbing/url"

      
        
        7
        +	"github.com/olexsmir/viye/internal/plumbing/files"

      
        
        8
        +	"github.com/olexsmir/viye/internal/plumbing/ip"

      
        
        9
        +	"github.com/olexsmir/viye/internal/plumbing/shell"

      
        
        10
        +	"github.com/olexsmir/viye/internal/plumbing/url"

      
        
        11
        +	"github.com/olexsmir/viye/internal/viye"

      
        13
        12
         )

      
        14
        13
         

      
        15
        14
         func main() {

      
        16
        
        -	v := core.New()

      
        
        15
        +	v := viye.New()

      
        17
        16
         	v.Register(&files.Tool{})

      
        18
        17
         	v.Register(&shell.Tool{})

      
        19
        18
         	v.Register(&url.Tool{})

      
        20
        19
         	v.Register(&ip.Tool{})

      
        21
        
        -	v.Register(&gobin.Tool{})

      
        22
        20
         

      
        23
        21
         	if err := v.Run(os.Stdout, os.Args); err != nil {

      
        24
        22
         		fmt.Fprintf(os.Stderr, "viye: %v\n", err)

      
M plumbing/files/files.go → internal/plumbing/files/files.go
···
        2
        2
         

      
        3
        3
         import (

      
        4
        4
         	"os"

      
        5
        
        -	"os/exec"

      
        6
        5
         	"path/filepath"

      
        7
        6
         	"strings"

      
        8
        7
         

      
        9
        
        -	"github.com/olexsmir/viye/core"

      
        10
        
        -	"github.com/olexsmir/viye/core/osutil"

      
        
        8
        +	"github.com/olexsmir/viye/internal/osutil"

      
        
        9
        +	"github.com/olexsmir/viye/internal/viye"

      
        11
        10
         )

      
        12
        11
         

      
        13
        12
         type Tool struct{}

      
        14
        13
         

      
        
        14
        +// TODO: add ./ paths support

      
        
        15
        +

      
        15
        16
         func (Tool) Name() string { return "files(~/..., /...)" }

      
        16
        
        -func (Tool) Match(ctx *core.Context) bool {

      
        
        17
        +

      
        
        18
        +func (Tool) Match(ctx *viye.Context) bool {

      
        17
        19
         	p := ctx.Path[0]

      
        18
        20
         	if p == "~" || strings.HasPrefix(p, "/") {

      
        19
        21
         		return true

      ···
        22
        24
         	return err == nil

      
        23
        25
         }

      
        24
        26
         

      
        25
        
        -func (Tool) Execute(ctx *core.Context) (string, error) {

      
        
        27
        +func (Tool) Execute(ctx *viye.Context) (string, error) {

      
        26
        28
         	path := resolve(ctx.Path[0], ctx.Dir)

      
        27
        29
         

      
        28
        30
         	info, err := os.Stat(path)

      ···
        39
        41
         		ctx.Path = ctx.Path[1:]

      
        40
        42
         		return ctx.Next()

      
        41
        43
         

      
        42
        
        -	case info.Mode()&0o111 != 0:

      
        43
        
        -		return runExec(path)

      
        
        44
        +	// case info.Mode()&0o111 != 0:

      
        
        45
        +	// 	return runExec(path)

      
        44
        46
         

      
        45
        47
         	default:

      
        46
        48
         		return "", osutil.Open(path)

      ···
        79
        81
         	return b.String(), nil

      
        80
        82
         }

      
        81
        83
         

      
        82
        
        -func runExec(path string) (string, error) {

      
        83
        
        -	cmd := exec.Command(path)

      
        84
        
        -	out, err := cmd.Output()

      
        85
        
        -	if err != nil {

      
        86
        
        -		return "", err

      
        87
        
        -	}

      
        88
        
        -	return string(out), nil

      
        89
        
        -}

      
        
        84
        +// func runExec(path string) (string, error) {

      
        
        85
        +// 	cmd := exec.Command(path)

      
        
        86
        +// 	out, err := cmd.Output()

      
        
        87
        +// 	if err != nil {

      
        
        88
        +// 		return "", err

      
        
        89
        +// 	}

      
        
        90
        +// 	return string(out), nil

      
        
        91
        +// }

      
M plumbing/files/files_test.go → internal/plumbing/files/files_test.go
···
        6
        6
         	"strings"

      
        7
        7
         	"testing"

      
        8
        8
         

      
        9
        
        -	"github.com/olexsmir/viye/core"

      
        
        9
        +	"github.com/olexsmir/viye/internal/viye"

      
        10
        10
         )

      
        11
        11
         

      
        12
        12
         func TestMatch(t *testing.T) {

      ···
        27
        27
         		{"$ ls", dir, false},

      
        28
        28
         	}

      
        29
        29
         	for _, tt := range tests {

      
        30
        
        -		got := (&Tool{}).Match(&core.Context{Path: []string{tt.p}, Dir: tt.dir})

      
        
        30
        +		got := (&Tool{}).Match(&viye.Context{Path: []string{tt.p}, Dir: tt.dir})

      
        31
        31
         		if got != tt.m {

      
        32
        32
         			t.Errorf("Match(%q, dir=%q) = %v; want %v", tt.p, tt.dir, got, tt.m)

      
        33
        33
         		}

      ···
        38
        38
         	t.Run("list dir", func(t *testing.T) {

      
        39
        39
         		dir := t.TempDir()

      
        40
        40
         		os.WriteFile(filepath.Join(dir, "x"), nil, 0o644)

      
        41
        
        -		ctx := &core.Context{Path: []string{dir}, Dir: "."}

      
        
        41
        +		ctx := &viye.Context{Path: []string{dir}, Dir: "."}

      
        42
        42
         		got, err := (&Tool{}).Execute(ctx)

      
        43
        43
         		if err != nil {

      
        44
        44
         			t.Fatal(err)

      ···
        54
        54
         		os.Mkdir(sub, 0o755)

      
        55
        55
         		os.WriteFile(filepath.Join(sub, "y"), nil, 0o644)

      
        56
        56
         

      
        57
        
        -		v := core.New()

      
        
        57
        +		v := viye.New()

      
        58
        58
         		v.Register(&Tool{})

      
        59
        59
         		var out strings.Builder

      
        60
        60
         		if err := v.Run(&out, []string{"viye", dir, "sub"}); err != nil {

      ···
        66
        66
         	})

      
        67
        67
         

      
        68
        68
         	t.Run("non existent path", func(t *testing.T) {

      
        69
        
        -		ctx := &core.Context{Path: []string{"/nonexistent_foobar"}, Dir: "."}

      
        
        69
        +		ctx := &viye.Context{Path: []string{"/nonexistent_foobar"}, Dir: "."}

      
        70
        70
         		if _, err := (&Tool{}).Execute(ctx); err == nil {

      
        71
        71
         			t.Error("expected error")

      
        72
        72
         		}

      
M plumbing/gobin/gobin.go → internal/plumbing/gobin/gobin.go
···
        1
        1
         package gobin

      
        2
        2
         

      
        3
        
        -import "github.com/olexsmir/viye/core"

      
        
        3
        +import "github.com/olexsmir/viye/internal/viye"

      
        4
        4
         

      
        5
        5
         const menu = `

      
        6
        6
         - build

      ···
        14
        14
         type Tool struct{}

      
        15
        15
         

      
        16
        16
         func (Tool) Name() string { return "go" }

      
        17
        
        -func (Tool) Match(ctx *core.Context) bool {

      
        
        17
        +func (Tool) Match(ctx *viye.Context) bool {

      
        18
        18
         	return ctx.Path[0] == "go"

      
        19
        19
         }

      
        20
        20
         

      
        21
        
        -func (Tool) Execute(ctx *core.Context) (string, error) {

      
        
        21
        +func (Tool) Execute(ctx *viye.Context) (string, error) {

      
        22
        22
         	switch ctx.Path[1] {

      
        23
        23
         	case "build":

      
        24
        24
         		return "| will build", nil

      
M plumbing/ip/ip.go → internal/plumbing/ip/ip.go
···
        7
        7
         	"net/http"

      
        8
        8
         	"strings"

      
        9
        9
         

      
        10
        
        -	"github.com/olexsmir/viye/core"

      
        
        10
        +	"github.com/olexsmir/viye/internal/viye"

      
        11
        11
         )

      
        12
        12
         

      
        13
        13
         type Tool struct{}

      
        14
        14
         

      
        15
        15
         func (Tool) Name() string                 { return "ip" }

      
        16
        
        -func (Tool) Match(ctx *core.Context) bool { return len(ctx.Path) == 1 && ctx.Path[0] == "ip" }

      
        17
        
        -func (Tool) Execute(ctx *core.Context) (string, error) {

      
        
        16
        +func (Tool) Match(ctx *viye.Context) bool { return len(ctx.Path) == 1 && ctx.Path[0] == "ip" }

      
        
        17
        +func (Tool) Execute(ctx *viye.Context) (string, error) {

      
        18
        18
         	// TODO: if it's not possile to get  public ip, report only local

      
        19
        19
         

      
        20
        20
         	var buf strings.Builder

      
D plumbing/shell/shell.go
···
        1
        
        -package shell

      
        2
        
        -

      
        3
        
        -import (

      
        4
        
        -	"fmt"

      
        5
        
        -	"os/exec"

      
        6
        
        -	"strings"

      
        7
        
        -	"syscall"

      
        8
        
        -

      
        9
        
        -	"github.com/olexsmir/viye/core"

      
        10
        
        -)

      
        11
        
        -

      
        12
        
        -type Tool struct{}

      
        13
        
        -

      
        14
        
        -func (Tool) Name() string                 { return "shell($)" }

      
        15
        
        -func (Tool) Match(ctx *core.Context) bool { return isCmd(ctx.Path[0]) || isBGCmd(ctx.Path[0]) }

      
        16
        
        -func (Tool) Execute(ctx *core.Context) (string, error) {

      
        17
        
        -	switch {

      
        18
        
        -	case isCmd(ctx.Path[0]):

      
        19
        
        -		cmd := exec.Command("sh", "-c", strings.TrimPrefix(ctx.Path[0], "$ "))

      
        20
        
        -		cmd.Dir = ctx.Dir

      
        21
        
        -		out, _ := cmd.CombinedOutput()

      
        22
        
        -

      
        23
        
        -		lines := strings.Split(string(out), "\n")

      
        24
        
        -		for i, line := range lines {

      
        25
        
        -			if line != "" {

      
        26
        
        -				lines[i] = "| " + line

      
        27
        
        -			}

      
        28
        
        -		}

      
        29
        
        -		return strings.Join(lines, "\n"), nil

      
        30
        
        -

      
        31
        
        -	case isBGCmd(ctx.Path[0]):

      
        32
        
        -		cmd := exec.Command("sh", "-c", strings.TrimPrefix(ctx.Path[0], "$$ "))

      
        33
        
        -		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

      
        34
        
        -		cmd.Dir = ctx.Dir

      
        35
        
        -		if err := cmd.Start(); err != nil {

      
        36
        
        -			return "", err

      
        37
        
        -		}

      
        38
        
        -		return fmt.Sprintf("| pid: %d", cmd.Process.Pid), nil

      
        39
        
        -

      
        40
        
        -	default:

      
        41
        
        -		panic("shouldn't have paniced")

      
        42
        
        -	}

      
        43
        
        -}

      
        44
        
        -

      
        45
        
        -func isCmd(s string) bool   { return strings.HasPrefix(s, "$ ") }

      
        46
        
        -func isBGCmd(s string) bool { return strings.HasPrefix(s, "$$ ") }

      
D plumbing/url/url.go
···
        1
        
        -package url

      
        2
        
        -

      
        3
        
        -import (

      
        4
        
        -	"strings"

      
        5
        
        -

      
        6
        
        -	"github.com/olexsmir/viye/core"

      
        7
        
        -	"github.com/olexsmir/viye/core/osutil"

      
        8
        
        -)

      
        9
        
        -

      
        10
        
        -type Tool struct{}

      
        11
        
        -

      
        12
        
        -func (Tool) Name() string { return "url(http)" }

      
        13
        
        -func (Tool) Match(ctx *core.Context) bool {

      
        14
        
        -	return strings.HasPrefix(ctx.Path[0], "http://") || strings.HasPrefix(ctx.Path[0], "https://")

      
        15
        
        -}

      
        16
        
        -

      
        17
        
        -func (Tool) Execute(ctx *core.Context) (string, error) {

      
        18
        
        -	return "", osutil.Open(ctx.Path[0])

      
        19
        
        -}