@@ -3,6 +3,7 @@ package main
33import (
44 "fmt"
55 "os"
6+ "os/exec"
67 "path/filepath"
78 "regexp"
89 "strconv"
@@ -16,9 +17,20 @@ type Command interface {
1617
1718type Commands map [string ]Command
1819
20+ ////////////////////////////////////////////////////////////////////////////////
21+
1922type 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]+)?)?}(?:{(.*)})?)?$" )
50102var 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+ }
0 commit comments