Skip to content

Commit 2364fd6

Browse files
committed
execute command
1 parent 7778064 commit 2364fd6

File tree

10 files changed

+657
-76
lines changed

10 files changed

+657
-76
lines changed

Makefile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ BUILD_FLAGS := "-s -w \
1616
-X main.gitCommit=$(COMMIT) \
1717
-X main.buildDate=$(NOW)"
1818

19-
build: ${SOURCES}
19+
.PHONY: build
20+
build: bin/mdref
21+
bin/mdref --list --headings src .
22+
23+
bin/mdref: ${SOURCES}
2024
mkdir -p bin
2125
CGO_ENABLED=0 go build -ldflags $(BUILD_FLAGS) -o bin/mdref .
26+
27+
.PHONY: test
28+
test: bin/mdref
2229
bin/mdref --list --headings src .
30+
diff -ur doc test/doc
31+
diff README.md test/README.md
32+

cmds.go

Lines changed: 194 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"os"
6+
"os/exec"
67
"path/filepath"
78
"regexp"
89
"strconv"
@@ -16,9 +17,20 @@ type Command interface {
1617

1718
type Commands map[string]Command
1819

20+
////////////////////////////////////////////////////////////////////////////////
21+
1922
type Include struct {
2023
_Position
21-
file string
24+
file string
25+
filter *filter
26+
extractor extractor
27+
}
28+
29+
type extractor interface {
30+
extract(data []byte) ([]byte, error)
31+
}
32+
33+
type filter struct {
2234
filter *regexp.Regexp
2335
}
2436

@@ -33,20 +45,60 @@ func (i *Include) getData(p string) ([]byte, error) {
3345
return data, nil
3446
}
3547

36-
type IncludeNum struct {
37-
Include
48+
// --- begin filter ---
49+
// An optional third argument can be used to specify a filter regular
50+
// expression. It must contain one matching group. The
51+
// selected file range is matched by this regular expression and
52+
// the content of the first matching group of the all matches is
53+
// concatenated. If the expression uses the multi-line mode, the matches
54+
// are suffixed with a newline.
55+
// --- end filter ---
56+
57+
func (i *filter) Filter(data []byte) ([]byte, error) {
58+
if i.filter == nil {
59+
return data, nil
60+
}
61+
sep := ""
62+
if strings.HasPrefix(i.filter.String(), "(?m)") {
63+
sep = "\n"
64+
}
65+
matches := i.filter.FindAllSubmatch(data, -1)
66+
var result []byte
67+
for _, m := range matches {
68+
if len(m) != 2 {
69+
return nil, fmt.Errorf("regular expressin must contain one matching group")
70+
}
71+
result = append(result, m[1]...)
72+
result = append(result, []byte(sep)...)
73+
}
74+
return result, nil
75+
}
76+
77+
func (i *Include) GetSubstitution(p string) ([]byte, error) {
78+
data, err := i.getData(p)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
data, err = i.extractor.extract(data)
84+
if err != nil {
85+
return nil, fmt.Errorf("include file %q; %w", err)
86+
}
87+
return i.filter.Filter(data)
88+
}
89+
90+
type NumExtractor struct {
3891
start int
3992
end int
4093
}
4194

42-
type IncludePat struct {
43-
Include
95+
type PatternExtractor struct {
4496
pattern string
4597
}
4698

4799
// --- begin example ---
48100
// --- begin include args ---
49-
var includeExpNum = regexp.MustCompile("^{([^}]+)}(?:{([0-9]+)?(?::([0-9]+)?)?}(?:{(.*)})?)?$")
101+
var includeExpNum = regexp.MustCompile("^{([^}]+)}(?:{([0-9]+)?(?:(:)([0-9]+)?)?}(?:{(.*)})?)?$")
50102
var includeExpPat = regexp.MustCompile("^{([^}]+)}{([a-zA-Z -]+)}(?:{(.*)})?$")
51103

52104
// --- end include args ---
@@ -64,97 +116,63 @@ func NewInclude(line, col int, args []byte) (Command, error) {
64116
if err != nil {
65117
return nil, fmt.Errorf("invalid start line: %w", err)
66118
}
119+
end = start
67120
}
68121
if matches[3] != nil {
69-
end, err = strconv.ParseInt(string(matches[3]), 10, 32)
70-
if err != nil {
71-
return nil, fmt.Errorf("invalid start line: %w", err)
122+
if matches[4] != nil {
123+
end, err = strconv.ParseInt(string(matches[4]), 10, 32)
124+
if err != nil {
125+
return nil, fmt.Errorf("invalid start line: %w", err)
126+
}
127+
} else {
128+
end = 0
72129
}
73130
}
74-
var filter *regexp.Regexp
75-
if matches[4] != nil {
76-
filter, err = regexp.Compile(string(matches[4]))
131+
var fexp *regexp.Regexp
132+
if matches[5] != nil {
133+
fexp, err = regexp.Compile(string(matches[5]))
77134
if err != nil {
78135
return nil, fmt.Errorf("invalid filter expression: %w", err)
79136
}
80137
}
81-
return &IncludeNum{Include{Position{line, col}, string(matches[1]), filter}, int(start), int(end)}, nil
138+
return &Include{Position{line, col}, string(matches[1]), &filter{fexp}, &NumExtractor{int(start), int(end)}}, nil
82139
}
83140

84141
matches = includeExpPat.FindSubmatch(args)
85142
if len(matches) != 0 {
86-
var filter *regexp.Regexp
143+
var fexp *regexp.Regexp
87144
if matches[3] != nil {
88-
filter, err = regexp.Compile(string(matches[3]))
145+
fexp, err = regexp.Compile(string(matches[3]))
89146
if err != nil {
90147
return nil, fmt.Errorf("invalid filter expression: %w", err)
91148
}
92149
}
93-
return &IncludePat{Include{Position{line, col}, string(matches[1]), filter}, string(matches[2])}, nil
150+
return &Include{Position{line, col}, string(matches[1]), &filter{fexp}, &PatternExtractor{string(matches[2])}}, nil
94151
}
95152

96153
return nil, fmt.Errorf("invalid include arguments %q", string(args))
97154
}
98155

99-
// --- begin filter ---
100-
// An optional third argument can be used to specify a filter regular
101-
// expression. It must contain one matching group. The
102-
// selected file range is matched by this regular expression and
103-
// the content of the first matching group of the all matches is
104-
// concatenated. If the expression uses the multi-line mode, the matches
105-
// are suffixed with a newline.
106-
// --- end filter ---
107-
108-
func (i *Include) Filter(data []byte) ([]byte, error) {
109-
if i.filter == nil {
110-
return data, nil
111-
}
112-
sep := ""
113-
if strings.HasPrefix(i.filter.String(), "(?m)") {
114-
sep = "\n"
115-
}
116-
matches := i.filter.FindAllSubmatch(data, -1)
117-
var result []byte
118-
for _, m := range matches {
119-
if len(m) != 2 {
120-
return nil, fmt.Errorf("regular expressin must contain one matching group")
121-
}
122-
result = append(result, m[1]...)
123-
result = append(result, []byte(sep)...)
124-
}
125-
return result, nil
126-
}
127-
128-
func (i *IncludeNum) GetSubstitution(p string) ([]byte, error) {
129-
data, err := i.getData(p)
130-
if err != nil {
131-
return nil, err
132-
}
133-
156+
func (i *NumExtractor) extract(data []byte) ([]byte, error) {
134157
lines := strings.Split(string(data), "\n")
135158
start := 0
136159
if i.start > 0 {
137160
start = i.start - 1
138161
}
139162
if start >= len(lines) {
140-
return nil, fmt.Errorf("start line %d after end of file (%q %d lines", start, i.file, len(lines))
163+
return nil, fmt.Errorf("start line %d after end of data (%d lines)", start, len(lines))
141164
}
142165
end := len(lines)
143166
if i.end > 0 {
144167
end = i.end
145168
}
146169
if end > len(lines) {
147-
return nil, fmt.Errorf("end line %d after end of file (%q %d lines", end, i.file, len(lines))
170+
return nil, fmt.Errorf("end line %d after end of file (%d lines", end, len(lines))
148171
}
149-
return i.Filter([]byte(strings.Join(lines[start:end], "\n")))
172+
return []byte(strings.Join(lines[start:end], "\n")), nil
150173
}
151174

152-
func (i *IncludePat) GetSubstitution(p string) ([]byte, error) {
153-
data, err := i.getData(p)
154-
if err != nil {
155-
return nil, err
156-
}
157-
175+
func (i *PatternExtractor) extract(data []byte) ([]byte, error) {
158176
_, start, err := i.match(data, "begin")
159177
if err != nil {
160178
return nil, err
@@ -164,18 +182,18 @@ func (i *IncludePat) GetSubstitution(p string) ([]byte, error) {
164182
return nil, err
165183
}
166184

167-
return i.Filter(data[start:end])
185+
return data[start:end], nil
168186
}
169187

170-
func (i *IncludePat) match(data []byte, key string) (int, int, error) {
188+
func (i *PatternExtractor) match(data []byte, key string) (int, int, error) {
171189
exp := regexp.MustCompile(fmt.Sprintf("(?m)^.*--- %s %s ---.*$", key, regexp.QuoteMeta(i.pattern)))
172190

173191
matches := exp.FindAllIndex(data, -1)
174192
if len(matches) == 0 {
175-
return -1, -1, fmt.Errorf("%s pattern (%s) not found in %q", key, i.pattern, i.file)
193+
return -1, -1, fmt.Errorf("%s pattern (%s) not found", key, i.pattern)
176194
}
177195
if len(matches) != 1 {
178-
return -1, -1, fmt.Errorf("%s pattern (%s) in %q is not unique", key, i.pattern, i.file)
196+
return -1, -1, fmt.Errorf("%s pattern (%s) is not unique", key, i.pattern)
179197
}
180198

181199
start := matches[0][0]
@@ -195,3 +213,115 @@ func (i *IncludePat) match(data []byte, key string) (int, int, error) {
195213
}
196214
return start, end, nil
197215
}
216+
217+
////////////////////////////////////////////////////////////////////////////////
218+
219+
type Execute struct {
220+
_Position
221+
cmd []string
222+
filter *filter
223+
extractor extractor
224+
}
225+
226+
var _ Command = (*Execute)(nil)
227+
228+
func (e *Execute) GetSubstitution(path string) ([]byte, error) {
229+
230+
cmd := exec.Command(e.cmd[0], e.cmd[1:]...)
231+
cmd.Dir = filepath.Dir(path)
232+
r, err := cmd.Output()
233+
if err != nil {
234+
return nil, fmt.Errorf("cannot execute %v: %w", e.cmd, err)
235+
}
236+
if e.extractor != nil {
237+
r, err = e.extractor.extract(r)
238+
if err != nil {
239+
return nil, fmt.Errorf("extract failed %v: %w", e.cmd, err)
240+
}
241+
}
242+
if e.filter != nil {
243+
r, err = e.filter.Filter(r)
244+
if err != nil {
245+
return nil, fmt.Errorf("extract failed %v: %w", e.cmd, err)
246+
}
247+
}
248+
return r, nil
249+
}
250+
251+
var nextarg = regexp.MustCompile("^{([^}]+)}(.*)$")
252+
var extractExpNum = regexp.MustCompile("^([0-9]+)?(?:(:)([0-9]+)?)?$")
253+
var extractExpPat = regexp.MustCompile("^([a-zA-Z -]+)$")
254+
255+
func NewExecute(line, col int, args []byte) (Command, error) {
256+
var cmd []string
257+
258+
for {
259+
m := nextarg.FindSubmatch(args)
260+
if m == nil {
261+
break
262+
}
263+
cmd = append(cmd, string(m[1]))
264+
args = m[2]
265+
}
266+
267+
var extract []string
268+
for i := range cmd {
269+
if cmd[i] == "<extract>" {
270+
extract = cmd[i+1:]
271+
cmd = cmd[:i]
272+
break
273+
}
274+
}
275+
if len(cmd) == 0 {
276+
return nil, fmt.Errorf("command argument required")
277+
}
278+
if len(extract) > 2 {
279+
return nil, fmt.Errorf("extraction mode requires a maximum of 2 arguments (found %d)", len(extract))
280+
}
281+
282+
var ext extractor
283+
var err error
284+
if len(extract) > 0 {
285+
m := extractExpNum.FindSubmatch([]byte(extract[0]))
286+
if m != nil {
287+
start := int64(0)
288+
end := int64(0)
289+
if m[1] != nil {
290+
start, err = strconv.ParseInt(string(m[1]), 10, 32)
291+
if err != nil {
292+
return nil, fmt.Errorf("invalid start line: %w", err)
293+
}
294+
end = start
295+
}
296+
if m[2] != nil {
297+
if m[3] != nil {
298+
end, err = strconv.ParseInt(string(m[3]), 10, 32)
299+
if err != nil {
300+
return nil, fmt.Errorf("invalid start line: %w", err)
301+
}
302+
} else {
303+
end = 0
304+
}
305+
}
306+
ext = &NumExtractor{
307+
start: int(start),
308+
end: int(end),
309+
}
310+
} else {
311+
m = extractExpPat.FindSubmatch([]byte(extract[0]))
312+
if m == nil {
313+
return nil, fmt.Errorf("invalid range specification (%s)", extract[0])
314+
}
315+
ext = &PatternExtractor{extract[0]}
316+
}
317+
}
318+
319+
var fexp *regexp.Regexp
320+
if len(extract) == 2 {
321+
fexp, err = regexp.Compile(string(extract[1]))
322+
if err != nil {
323+
return nil, fmt.Errorf("invalid filter expression: %w", err)
324+
}
325+
}
326+
return &Execute{Position{line, col}, cmd, &filter{fexp}, ext}, nil
327+
}

democmd/main.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
func main() {
9+
fmt.Printf("this is a demo file\n")
10+
fmt.Printf("// --- begin %s ---\n", os.Args[1])
11+
fmt.Printf("%s\n", os.Args[2])
12+
fmt.Printf("// --- end %s ---\n", os.Args[1])
13+
fmt.Printf("this is line 5 of the demo output\n")
14+
fmt.Printf("and some other text.\n")
15+
}

0 commit comments

Comments
 (0)