Skip to content

Commit a5440e2

Browse files
committed
feat: support to terminate script with ctrl+c
1 parent 8c1cb50 commit a5440e2

10 files changed

+91
-44
lines changed

cli/golang_runner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"fmt"
56
"os"
67
"path"
@@ -21,7 +22,7 @@ func main(){
2122
`
2223

2324
// Run executes the script
24-
func (s *GolangScript) Run() (err error) {
25+
func (s *GolangScript) Run(ctx context.Context) (err error) {
2526
s.Content = strings.ReplaceAll(s.Content, "#!title: "+s.Title, "")
2627

2728
var shellFile string

cli/golang_runner_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package cli
22

33
import (
4+
"context"
5+
"testing"
6+
47
"github.com/linuxsuren/http-downloader/pkg/exec"
58
"github.com/stretchr/testify/assert"
6-
"testing"
79
)
810

911
func TestGolangRunner(t *testing.T) {
@@ -31,7 +33,7 @@ echo 1`,
3133
},
3234
}
3335
assert.Equal(t, tt.title, shell.GetTitle())
34-
err := shell.Run()
36+
err := shell.Run(context.Background())
3537
assert.Equal(t, tt.hasErr, err != nil)
3638
})
3739
}

cli/python_runner.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"os"
56
"path"
67
)
@@ -11,7 +12,7 @@ type PythonScript struct {
1112
}
1213

1314
// Run executes the script
14-
func (s *PythonScript) Run() (err error) {
15+
func (s *PythonScript) Run(ctx context.Context) (err error) {
1516
var shellFile string
1617
if shellFile, err = writeAsShell(s.Content, s.Dir); err == nil {
1718
if !s.KeepScripts {

cli/python_runner_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package cli
22

33
import (
4+
"context"
5+
"testing"
6+
47
"github.com/linuxsuren/http-downloader/pkg/exec"
58
"github.com/stretchr/testify/assert"
6-
"testing"
79
)
810

911
func TestPythonRunner(t *testing.T) {
@@ -31,7 +33,7 @@ echo 1`,
3133
},
3234
}
3335
assert.Equal(t, tt.title, shell.GetTitle())
34-
err := shell.Run()
36+
err := shell.Run(context.Background())
3537
assert.Equal(t, tt.hasErr, err != nil)
3638
})
3739
}

cli/root.go

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
package cli
33

44
import (
5+
"context"
56
"fmt"
67
"io"
78
"os"
9+
"os/signal"
810
"path"
911
"path/filepath"
1012
"strings"
@@ -15,7 +17,7 @@ import (
1517
"github.com/spf13/cobra"
1618
)
1719

18-
// should be inject during the build process
20+
// should be injected during the build process
1921
var version string
2022

2123
// NewRootCommand returns the instance of cobra.Command
@@ -35,39 +37,42 @@ func NewRootCommand(execer exec.Execer, out io.Writer) (cmd *cobra.Command) {
3537
flags.BoolVarP(&opt.loop, "loop", "", true, "Run the Markdown in loop mode.")
3638
flags.BoolVarP(&opt.keepFilter, "keep-filter", "", true, "Indicate if keep the filter.")
3739
flags.BoolVarP(&opt.keepScripts, "keep-scripts", "", false, "Indicate if keep the temporary scripts.")
40+
flags.IntVarP(&opt.pageSize, "page-size", "", 6, "Number of the select items.")
3841
return
3942
}
4043

4144
func (o *option) runE(cmd *cobra.Command, args []string) (err error) {
42-
scriptRunners := NewScriptRunners()
43-
44-
for _, mdFilePath := range args {
45-
var files []string
46-
if files, err = filepath.Glob(mdFilePath); err != nil {
47-
return
48-
}
49-
50-
for _, file := range files {
51-
if !strings.HasSuffix(file, ".md") {
52-
continue
45+
var scriptRunners ScriptRunners
46+
if scriptRunners, err = o.parseMarkdownRunners(args); err == nil && scriptRunners.Size() > 1 {
47+
for {
48+
if err = o.executeScripts(scriptRunners); err != nil {
49+
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
5350
}
54-
var runners ScriptRunners
55-
if runners, err = o.parseMarkdownRunner(file); err != nil {
51+
52+
if !o.loop {
5653
break
5754
}
58-
59-
scriptRunners = append(scriptRunners, runners...)
6055
}
6156
}
57+
return
58+
}
6259

63-
if scriptRunners.Size() > 1 {
64-
for {
65-
if err = o.executeScripts(scriptRunners); err != nil {
66-
fmt.Fprintln(cmd.ErrOrStderr(), err.Error())
67-
}
60+
func (o *option) parseMarkdownRunners(files []string) (scriptRunners ScriptRunners, err error) {
61+
scriptRunners = NewScriptRunners()
6862

69-
if !o.loop {
70-
break
63+
for _, mdFilePath := range files {
64+
var files []string
65+
if files, err = filepath.Glob(mdFilePath); err == nil {
66+
for _, file := range files {
67+
if !strings.HasSuffix(file, ".md") {
68+
continue
69+
}
70+
var runners ScriptRunners
71+
if runners, err = o.parseMarkdownRunner(file); err != nil {
72+
break
73+
}
74+
75+
scriptRunners = append(scriptRunners, runners...)
7176
}
7277
}
7378
}
@@ -147,17 +152,23 @@ type option struct {
147152
loop bool
148153
keepFilter bool
149154
keepScripts bool
155+
pageSize int
150156

151157
execer exec.Execer
152158
}
153159

154160
func (o *option) executeScripts(scriptRunners ScriptRunners) (err error) {
161+
c := make(chan os.Signal)
162+
signal.Notify(c, os.Interrupt)
163+
155164
selector := &survey.MultiSelect{
156165
Message: "Choose the code block to run",
157166
Options: scriptRunners.GetTitles(),
158167
}
159168
var titles []string
160-
if err = survey.AskOne(selector, &titles, survey.WithKeepFilter(o.keepFilter)); err != nil {
169+
if err = survey.AskOne(selector, &titles,
170+
survey.WithKeepFilter(o.keepFilter),
171+
survey.WithPageSize(o.pageSize)); err != nil {
161172
return
162173
}
163174

@@ -167,9 +178,15 @@ func (o *option) executeScripts(scriptRunners ScriptRunners) (err error) {
167178
break
168179
}
169180

181+
ctx, cancel := context.WithCancel(context.Background())
182+
go func() {
183+
<-c
184+
cancel()
185+
}()
186+
170187
if runner := scriptRunners.GetRunner(title); runner == nil {
171188
fmt.Println("cannot found runner:", title)
172-
} else if err = runner.Run(); err != nil {
189+
} else if err = runner.Run(ctx); err != nil {
173190
break
174191
}
175192
}

cli/root_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func TestNewRootCommand(t *testing.T) {
4141
}
4242
}
4343

44-
func TestParseMarkdownRUnner(t *testing.T) {
44+
func TestParseMarkdownRunner(t *testing.T) {
4545
opt := &option{}
4646
runners, err := opt.parseMarkdownRunner("../README.md")
4747
if assert.Nil(t, err) {
@@ -52,3 +52,15 @@ func TestParseMarkdownRUnner(t *testing.T) {
5252
assert.NotNil(t, runners.GetRunner("Golang Hello World"))
5353
}
5454
}
55+
56+
func TestParseMarkdownRunners(t *testing.T) {
57+
opt := &option{}
58+
runners, err := opt.parseMarkdownRunners([]string{"../README.md"})
59+
if assert.Nil(t, err) {
60+
assert.True(t, len(runners) > 0)
61+
assert.NotNil(t, runners.GetRunner("Variable Input Hello World"))
62+
assert.NotNil(t, runners.GetRunner("Python Hello World"))
63+
assert.NotNil(t, runners.GetRunner("Run long time"))
64+
assert.NotNil(t, runners.GetRunner("Golang Hello World"))
65+
}
66+
}

cli/shell_runner.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"io"
56
"os"
67
"path"
@@ -19,13 +20,8 @@ type ShellScript struct {
1920
}
2021

2122
// Run executes the script
22-
func (s *ShellScript) Run() (err error) {
23-
// handle the break line
24-
breakline := regexp.MustCompile(`\\\n`)
25-
s.Content = breakline.ReplaceAllString(s.Content, "")
26-
27-
whitespaces := regexp.MustCompile(` +`)
28-
s.Content = whitespaces.ReplaceAllString(s.Content, " ")
23+
func (s *ShellScript) Run(ctx context.Context) (err error) {
24+
s.Content = strings.ReplaceAll(s.Content, "\r\n", "\n")
2925

3026
lines := strings.Split(s.Content, "\n")[1:]
3127

@@ -89,6 +85,11 @@ func (s *ShellScript) runCmdLine(cmdLine, contextDir string, keepScripts bool) (
8985
}
9086

9187
func findPotentialCommands(cmdLine string) (cmds []string) {
88+
// TODO should find a better way to skip EOF part
89+
if strings.Contains(cmdLine, "EOF") {
90+
return
91+
}
92+
9293
lines := strings.Split(cmdLine, "\n")
9394
for _, line := range lines {
9495
line = strings.TrimSpace(line)

cli/shell_runner_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/linuxsuren/http-downloader/pkg/exec"
@@ -41,7 +42,7 @@ echo 1`,
4142
ShellType: tt.shellType,
4243
}
4344
assert.Equal(t, tt.title, shell.GetTitle())
44-
err := shell.Run()
45+
err := shell.Run(context.Background())
4546
assert.Equal(t, tt.hasErr, err != nil)
4647
})
4748
}
@@ -105,6 +106,11 @@ docker ps`,
105106
name: "with extra whitespace",
106107
cmd: " k3d create cluster",
107108
expect: []string{"k3d"},
109+
}, {
110+
name: "with EOF",
111+
cmd: `EOF
112+
k3d create cluster`,
113+
expect: nil,
108114
}}
109115
for _, tt := range tests {
110116
t.Run(tt.name, func(t *testing.T) {

cli/types.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package cli
22

3-
import "github.com/linuxsuren/http-downloader/pkg/exec"
3+
import (
4+
"context"
5+
6+
"github.com/linuxsuren/http-downloader/pkg/exec"
7+
)
48

59
// Script represents a script object
610
type Script struct {
@@ -14,7 +18,7 @@ type Script struct {
1418

1519
// ScriptRunner is the interface of a common runner
1620
type ScriptRunner interface {
17-
Run() error
21+
Run(context.Context) error
1822
GetTitle() string
1923
}
2024

@@ -54,7 +58,7 @@ func (s ScriptRunners) Size() int {
5458
type QuitRunner struct{}
5559

5660
// Run does nothing
57-
func (r *QuitRunner) Run() error {
61+
func (r *QuitRunner) Run(context.Context) error {
5862
return nil
5963
}
6064

cli/types_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -16,7 +17,7 @@ func TestScriptRunners(t *testing.T) {
1617
quitRunner := runners.GetRunner("Quit")
1718
if assert.NotNil(t, quitRunner) {
1819
assert.Equal(t, "Quit", quitRunner.GetTitle())
19-
assert.Nil(t, quitRunner.Run())
20+
assert.Nil(t, quitRunner.Run(context.Background()))
2021
}
2122
assert.Equal(t, []string{"Quit"}, runners.GetTitles())
2223
}

0 commit comments

Comments
 (0)