all repos

viye @ 0dd05131dc9f72c1b37a19c75e9c9329086f299f

my shot at reimplementing xiki

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

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
add make tool, 1 month ago
1
package makefile
2
3
import (
4
	"bufio"
5
	"bytes"
6
	"errors"
7
	"os"
8
	"os/exec"
9
	"path/filepath"
10
	"slices"
11
	"strings"
12
13
	"github.com/olexsmir/viye/internal/viye"
14
)
15
16
var ErrMakefileNotFound = errors.New("makefile not found")
17
18
type Tool struct{}
19
20
func (Tool) Name() string               { return "make" }
21
func (Tool) Match(c *viye.Context) bool { return c.Cmd == "make" }
22
func (Tool) Execute(c *viye.Context) (string, error) {
23
	fpath, found := findMakefile(c)
24
	if !found {
25
		return "", ErrMakefileNotFound
26
	}
27
28
	contents, err := os.ReadFile(fpath)
29
	if err != nil {
30
		return "", err
31
	}
32
33
	tasks, err := listTasksFromMakefile(contents)
34
	if err != nil {
35
		return "", err
36
	}
37
38
	switch len(c.Args) {
39
	case 0: // list make file tasks
40
		return viye.FormatBulletList(tasks), nil
41
42
	case 1: // run specified task
43
		if !slices.Contains(tasks, c.Args[0]) {
44
			return "", ErrTaskNotFound
45
		}
46
		cmd := exec.Command("make", c.Args[0])
47
		cmd.Dir = c.Dir
48
		out, err := cmd.Output()
49
		if err != nil {
50
			return "", err
51
		}
52
		return viye.FormatOutput(string(out)), nil
53
54
	default:
55
		return "", errors.New("invalid command, make usage: make [task]")
56
	}
57
}
58
59
var ErrTaskNotFound = errors.New("task not found")
60
61
func findMakefile(c *viye.Context) (name string, found bool) {
62
	for _, mfile := range []string{"makefile", "Makefile", "GNUMakefile"} {
63
		path := filepath.Join(c.Dir, mfile)
64
		if _, err := os.Stat(path); err == nil {
65
			return path, true
66
		}
67
	}
68
	return "", false
69
}
70
71
func listTasksFromMakefile(contents []byte) ([]string, error) {
72
	var tasks []string
73
	scanner := bufio.NewScanner(bytes.NewReader(contents))
74
	for scanner.Scan() {
75
		line := scanner.Text()
76
77
		if len(line) == 0 || line[0] == '\t' || line[0] == ' ' {
78
			continue
79
		}
80
		if strings.HasPrefix(line, "#") { // comments
81
			continue
82
		}
83
84
		colon := strings.IndexByte(line, ':')
85
		if colon <= 0 {
86
			continue
87
		}
88
89
		name := strings.TrimSpace(line[:colon])
90
		if name == "" {
91
			continue
92
		}
93
94
		rest := line[colon+1:]
95
		if strings.HasPrefix(rest, "=") { // variable assigments
96
			continue
97
		}
98
		if strings.HasPrefix(rest, ":") { // ::= or double-colon rule
99
			rest = rest[1:]
100
			if strings.HasPrefix(rest, "=") {
101
				continue
102
			}
103
		}
104
105
		if strings.Contains(name, "%") || strings.HasPrefix(name, ".") { // pattern rules and special targets
106
			continue
107
		}
108
109
		for n := range strings.FieldsSeq(name) {
110
			tasks = append(tasks, n)
111
		}
112
	}
113
	return tasks, nil
114
}