all repos

viye @ 896d55427eac536be9d99d6c941215e4bf165ac1

my shot at reimplementing xiki

viye/internal/viye/viye.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
fix splitLeaf; add some tests; move some code around, 1 month ago
1
package viye
2
3
import (
4
	"errors"
5
	"fmt"
6
	"io"
7
	"os"
8
)
9
10
const Version = "0.0.1-alpha"
11
12
var ErrPlumbingNotFound = errors.New("tool not found")
13
14
type Context struct {
15
	Dir  string   // working directory
16
	Path []string // ancestor chain [root, ..., target]
17
	Cmd  string
18
	Args []string
19
	Body []string // body lines from : children
20
21
	dispatch func(*Context) (string, error) // set by [Viye.dispatch] for tool chaining
22
}
23
24
// Next continues dispatch with currect [Context] state.
25
func (c *Context) Next() (string, error) { return c.dispatch(c) }
26
27
type Tool interface {
28
	Name() string
29
	Match(*Context) bool
30
	Execute(*Context) (string, error)
31
}
32
33
type Viye struct {
34
	tools []Tool
35
}
36
37
func New() *Viye { return &Viye{} }
38
39
func (v *Viye) Register(tool Tool) {
40
	v.tools = append(v.tools, tool)
41
}
42
43
func (v *Viye) Run(out io.Writer, args []string) error {
44
	if len(args) <= 1 {
45
		return nil
46
	}
47
	if args[1] == "help" {
48
		fmt.Fprintln(out, "| Available tools:")
49
		for _, tool := range v.tools {
50
			fmt.Fprintf(out, "| %s\n", tool.Name())
51
		}
52
		return nil
53
	}
54
	if args[1] == "version" {
55
		fmt.Fprint(out, "| viye version: "+Version)
56
		return nil
57
	}
58
59
	path, body := splitArgs(args[1:])
60
	if len(path) == 0 {
61
		os.Exit(0)
62
	}
63
64
	res, err := v.dispatch(&Context{
65
		Path: path,
66
		Dir:  mustGetCwd(),
67
		Body: body,
68
	})
69
	if err != nil {
70
		return err
71
	}
72
73
	_, err = fmt.Fprint(out, res)
74
	return err
75
}
76
77
// dispatch executes the path against registered tools, first-match-wins
78
func (v *Viye) dispatch(ctx *Context) (string, error) {
79
	if len(ctx.Path) == 0 {
80
		return "", nil
81
	}
82
	ctx.Cmd, ctx.Args = splitLeaf(ctx.Path)
83
	ctx.dispatch = v.dispatch
84
	for _, tool := range v.tools {
85
		if tool.Match(ctx) {
86
			return tool.Execute(ctx)
87
		}
88
	}
89
	return "", ErrPlumbingNotFound
90
}
91
92
// splitLeaf splits a path into command name and args.
93
// ["get", "http://site.com"] → ("get", ["http://site.com"]).
94
func splitLeaf(path []string) (cmd string, args []string) {
95
	if len(path) == 0 {
96
		return "", nil
97
	}
98
	return path[0], path[1:]
99
}
100
101
// splitArgs builds path and body from args.
102
// Everything before "--" is the path (each arg is one component).
103
// Everything after "--" is the body (kept as-is, one element per line).
104
// If there's no "--", body is nil.
105
func splitArgs(args []string) (path []string, body []string) {
106
	sawSep := false
107
	for _, arg := range args {
108
		if !sawSep && arg == "--" {
109
			sawSep = true
110
			continue
111
		}
112
		if !sawSep {
113
			path = append(path, arg)
114
		} else {
115
			body = append(body, arg)
116
		}
117
	}
118
	return
119
}
120
121
func mustGetCwd() string {
122
	dir, err := os.Getwd()
123
	if err != nil {
124
		panic(fmt.Sprintf("failed to get cwd: %v", err))
125
	}
126
	return dir
127
}