all repos

viye @ 63835f54e77ecc1ae8e8ca126fffab519544f58e

my shot at reimplementing xiki

viye/core/core.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
init, 2 months ago
1
package core
2
3
import (
4
	"fmt"
5
	"io"
6
	"os"
7
	"strings"
8
)
9
10
type Context struct {
11
	Path []string // ancestor chain [root, ..., target]
12
	Dir  string   // working directory
13
14
	dispatch func(*Context) (string, error) // set by [Viye.dispatch] for tool chaining
15
}
16
17
// Next continues dispatch with currect [Context] state.
18
// Call after consuming .Path[0] and updating .Dir.
19
func (c *Context) Next() (string, error) { return c.dispatch(c) }
20
21
type Tool interface {
22
	Name() string
23
	Match(*Context) bool
24
	Execute(*Context) (string, error)
25
}
26
27
type Viye struct {
28
	tools []Tool
29
}
30
31
func New() *Viye { return &Viye{} }
32
33
func (v *Viye) Register(tool Tool) {
34
	v.tools = append(v.tools, tool)
35
}
36
37
func (v *Viye) Run(out io.Writer, args []string) error {
38
	if len(args) <= 1 {
39
		return nil
40
	}
41
	if args[1] == "--help" {
42
		return v.showHelp(out)
43
	}
44
45
	path := splitArgs(args[1:])
46
	if len(path) == 0 {
47
		os.Exit(0)
48
	}
49
50
	res, err := v.dispatch(&Context{
51
		Path: path,
52
		Dir:  ".", // TODO: get cwd
53
	})
54
	if err != nil {
55
		return err
56
	}
57
58
	_, err = fmt.Fprint(out, res)
59
	return err
60
}
61
62
func (v *Viye) showHelp(out io.Writer) error {
63
	fmt.Fprintln(out, "Available tools:")
64
	for _, tool := range v.tools {
65
		fmt.Fprintf(out, "  - %s\n", tool.Name())
66
	}
67
	return nil
68
}
69
70
// dispatch executes the path against registered tools, first-match-wins
71
func (v *Viye) dispatch(ctx *Context) (string, error) {
72
	if len(ctx.Path) == 0 {
73
		return "", nil
74
	}
75
	ctx.dispatch = v.dispatch
76
	for _, tool := range v.tools {
77
		if tool.Match(ctx) {
78
			return tool.Execute(ctx)
79
		}
80
	}
81
	return "", nil
82
}
83
84
// splitArgs builds path by splitting each arg on "/"
85
// for absolute paths(/...), leading / is preserved as part of first component.
86
// for home paths(~...), ~ is the first comonents
87
// urls are kept as single component
88
func splitArgs(args []string) []string {
89
	var path []string
90
	for _, arg := range args {
91
		path = append(path, splitArg(arg)...)
92
	}
93
	return path
94
}
95
96
func splitArg(arg string) []string {
97
	switch {
98
	case strings.HasPrefix(arg, "/"):
99
		parts := strings.Split(arg, "/")
100
		// parts[0] is always "" for absolute paths
101
		i := 0
102
		for i < len(parts) && parts[i] == "" {
103
			i++
104
		}
105
		if i >= len(parts) {
106
			return []string{"/"}
107
		}
108
		// parts[i] is the first non-empty segment → attach it to "/"
109
		result := []string{"/" + parts[i]}
110
		result = append(result, parts[i+1:]...)
111
		return result
112
	case strings.HasPrefix(arg, "~"):
113
		return strings.Split(arg, "/")
114
	case strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://"):
115
		return []string{arg}
116
	default:
117
		return strings.Split(arg, "/")
118
	}
119
}