Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit fb8d732

Browse files
committed
Add arg limiting special comment
1 parent eb4c0cd commit fb8d732

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ In order to document the script, `sd` pays attention to a few special comments:
8181
# bar: Bars the foos.
8282
#
8383
# example: foo bar 123
84+
# args: 3
8485
#
8586
echo "sd foo bar has been called"
8687
```

cli/cli.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"path/filepath"
99
"regexp"
10+
"strconv"
1011
"strings"
1112
"syscall"
1213

@@ -259,6 +260,43 @@ func shortDescriptionFrom(path string) (string, error) {
259260
return "", nil
260261
}
261262

263+
/*
264+
Argument validators. Looks for a line like this:
265+
266+
# args: 3
267+
268+
*/
269+
func argsFrom(path string) (cobra.PositionalArgs, error) {
270+
file, err := os.Open(path)
271+
if err != nil {
272+
return nil, err
273+
}
274+
defer func() {
275+
err = file.Close()
276+
if err != nil {
277+
logrus.Error(err)
278+
}
279+
}()
280+
281+
r := regexp.MustCompile(`^# args: (\d+)$`)
282+
scanner := bufio.NewScanner(file)
283+
for scanner.Scan() {
284+
match := r.FindStringSubmatch(scanner.Text())
285+
if len(match) == 2 {
286+
logrus.Debug("Found example line: ", filepath.Join(path), ", set to: ", match[1])
287+
i, err := strconv.ParseUint(match[1], 10, 32)
288+
if err != nil {
289+
return nil, err
290+
}
291+
if i == 0 {
292+
return cobra.NoArgs, nil
293+
}
294+
return cobra.ExactArgs(int(i)), nil
295+
}
296+
}
297+
return cobra.ArbitraryArgs, nil
298+
}
299+
262300
/*
263301
264302
Looks for a line like this:
@@ -301,13 +339,19 @@ func commandFromScript(path string) (*cobra.Command, error) {
301339
return nil, err
302340
}
303341

342+
args, err := argsFrom(path)
343+
if err != nil {
344+
return nil, err
345+
}
346+
304347
cmd := &cobra.Command{
305348
Use: filepath.Base(path),
306349
Short: shortDesc,
307350
Example: example,
308351
Annotations: map[string]string{
309352
"Source": path,
310353
},
354+
Args: args,
311355
RunE: execCommand,
312356
}
313357

@@ -318,7 +362,7 @@ func commandFromScript(path string) (*cobra.Command, error) {
318362
// these get mocked in tests
319363
var (
320364
syscallExec = syscall.Exec
321-
env = os.Getenv
365+
env = os.Getenv
322366
)
323367

324368
func execCommand(cmd *cobra.Command, args []string) error {

cli/cli_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,92 @@ func TestShortDescriptionFrom(t *testing.T) {
152152
})
153153
}
154154

155+
func TestArgsFrom(t *testing.T) {
156+
t.Run("happy path with 3 args", func(t *testing.T) {
157+
f, err := ioutil.TempFile("", "test-args3-ok")
158+
assert.NoError(t, err)
159+
160+
f.WriteString("#\n# args: 2\n#\n")
161+
defer func() {
162+
f.Close()
163+
os.Remove(f.Name())
164+
}()
165+
166+
v, err := argsFrom(f.Name())
167+
assert.NoError(t, err)
168+
assert.Error(t, v(&cobra.Command{}, []string{}))
169+
assert.Error(t, v(&cobra.Command{}, []string{"first"}))
170+
assert.NoError(t, v(&cobra.Command{}, []string{"first", "second"}))
171+
assert.Error(t, v(&cobra.Command{}, []string{"first", "second", "third"}))
172+
})
173+
174+
t.Run("happy path with no args", func(t *testing.T) {
175+
f, err := ioutil.TempFile("", "test-args0-ok")
176+
assert.NoError(t, err)
177+
178+
f.WriteString("#\n# args: 0\n#\n")
179+
defer func() {
180+
f.Close()
181+
os.Remove(f.Name())
182+
}()
183+
184+
v, err := argsFrom(f.Name())
185+
assert.NoError(t, err)
186+
assert.NoError(t, v(&cobra.Command{}, []string{}))
187+
assert.Error(t, v(&cobra.Command{}, []string{"first"}))
188+
assert.Error(t, v(&cobra.Command{}, []string{"first", "second"}))
189+
})
190+
191+
t.Run("missing", func(t *testing.T) {
192+
f, err := ioutil.TempFile("", "test-args-missing")
193+
assert.NoError(t, err)
194+
195+
f.WriteString("#\n#\n#\n")
196+
defer func() {
197+
f.Close()
198+
os.Remove(f.Name())
199+
}()
200+
201+
v, err := argsFrom(f.Name())
202+
assert.NoError(t, err)
203+
assert.NoError(t, v(&cobra.Command{}, []string{}))
204+
assert.NoError(t, v(&cobra.Command{}, []string{"first", "second"}))
205+
})
206+
207+
t.Run("bad value: not numeric", func(t *testing.T) {
208+
f, err := ioutil.TempFile("", "test-args-not-numeric")
209+
assert.NoError(t, err)
210+
211+
f.WriteString("#\n# args: banana\n#\n")
212+
defer func() {
213+
f.Close()
214+
os.Remove(f.Name())
215+
}()
216+
217+
v, err := argsFrom(f.Name())
218+
assert.NoError(t, err)
219+
assert.NoError(t, v(&cobra.Command{}, []string{}))
220+
assert.NoError(t, v(&cobra.Command{}, []string{"first", "second"}))
221+
})
222+
223+
t.Run("bad value: too large", func(t *testing.T) {
224+
f, err := ioutil.TempFile("", "test-args-too-large")
225+
assert.NoError(t, err)
226+
227+
f.WriteString("#\n# args: 999999999999999999999999999999999999999999999999999999999999999999999999\n#\n")
228+
defer func() {
229+
f.Close()
230+
os.Remove(f.Name())
231+
}()
232+
233+
v, err := argsFrom(f.Name())
234+
if !assert.Error(t, err) {
235+
assert.NoError(t, v(&cobra.Command{}, []string{}))
236+
assert.NoError(t, v(&cobra.Command{}, []string{"first", "second"}))
237+
}
238+
})
239+
}
240+
155241
func TestExampleFrom(t *testing.T) {
156242
t.Run("happy path", func(t *testing.T) {
157243
f, err := ioutil.TempFile("", "test-example-ok")

0 commit comments

Comments
 (0)