diff --git a/cli/golang_runner.go b/cli/golang_runner.go index e004bc0..bd4898d 100644 --- a/cli/golang_runner.go +++ b/cli/golang_runner.go @@ -1,6 +1,7 @@ package cli import ( + "context" "fmt" "os" "path" @@ -21,7 +22,7 @@ func main(){ ` // Run executes the script -func (s *GolangScript) Run() (err error) { +func (s *GolangScript) Run(ctx context.Context) (err error) { s.Content = strings.ReplaceAll(s.Content, "#!title: "+s.Title, "") var shellFile string diff --git a/cli/golang_runner_test.go b/cli/golang_runner_test.go index 5e53542..d51f857 100644 --- a/cli/golang_runner_test.go +++ b/cli/golang_runner_test.go @@ -1,9 +1,11 @@ package cli import ( + "context" + "testing" + "github.com/linuxsuren/http-downloader/pkg/exec" "github.com/stretchr/testify/assert" - "testing" ) func TestGolangRunner(t *testing.T) { @@ -31,7 +33,7 @@ echo 1`, }, } assert.Equal(t, tt.title, shell.GetTitle()) - err := shell.Run() + err := shell.Run(context.Background()) assert.Equal(t, tt.hasErr, err != nil) }) } diff --git a/cli/python_runner.go b/cli/python_runner.go index 2d6a802..88a382c 100644 --- a/cli/python_runner.go +++ b/cli/python_runner.go @@ -1,6 +1,7 @@ package cli import ( + "context" "os" "path" ) @@ -11,7 +12,7 @@ type PythonScript struct { } // Run executes the script -func (s *PythonScript) Run() (err error) { +func (s *PythonScript) Run(ctx context.Context) (err error) { var shellFile string if shellFile, err = writeAsShell(s.Content, s.Dir); err == nil { if !s.KeepScripts { diff --git a/cli/python_runner_test.go b/cli/python_runner_test.go index 9521e71..44af9e3 100644 --- a/cli/python_runner_test.go +++ b/cli/python_runner_test.go @@ -1,9 +1,11 @@ package cli import ( + "context" + "testing" + "github.com/linuxsuren/http-downloader/pkg/exec" "github.com/stretchr/testify/assert" - "testing" ) func TestPythonRunner(t *testing.T) { @@ -31,7 +33,7 @@ echo 1`, }, } assert.Equal(t, tt.title, shell.GetTitle()) - err := shell.Run() + err := shell.Run(context.Background()) assert.Equal(t, tt.hasErr, err != nil) }) } diff --git a/cli/root.go b/cli/root.go index e14cc7f..a47c0f0 100644 --- a/cli/root.go +++ b/cli/root.go @@ -2,9 +2,11 @@ package cli import ( + "context" "fmt" "io" "os" + "os/signal" "path" "path/filepath" "strings" @@ -15,7 +17,7 @@ import ( "github.com/spf13/cobra" ) -// should be inject during the build process +// should be injected during the build process var version string // NewRootCommand returns the instance of cobra.Command @@ -35,39 +37,42 @@ func NewRootCommand(execer exec.Execer, out io.Writer) (cmd *cobra.Command) { flags.BoolVarP(&opt.loop, "loop", "", true, "Run the Markdown in loop mode.") flags.BoolVarP(&opt.keepFilter, "keep-filter", "", true, "Indicate if keep the filter.") flags.BoolVarP(&opt.keepScripts, "keep-scripts", "", false, "Indicate if keep the temporary scripts.") + flags.IntVarP(&opt.pageSize, "page-size", "", 6, "Number of the select items.") return } func (o *option) runE(cmd *cobra.Command, args []string) (err error) { - scriptRunners := NewScriptRunners() - - for _, mdFilePath := range args { - var files []string - if files, err = filepath.Glob(mdFilePath); err != nil { - return - } - - for _, file := range files { - if !strings.HasSuffix(file, ".md") { - continue + var scriptRunners ScriptRunners + if scriptRunners, err = o.parseMarkdownRunners(args); err == nil && scriptRunners.Size() > 1 { + for { + if err = o.executeScripts(scriptRunners); err != nil { + _, _ = fmt.Fprintln(cmd.ErrOrStderr(), err.Error()) } - var runners ScriptRunners - if runners, err = o.parseMarkdownRunner(file); err != nil { + + if !o.loop { break } - - scriptRunners = append(scriptRunners, runners...) } } + return +} - if scriptRunners.Size() > 1 { - for { - if err = o.executeScripts(scriptRunners); err != nil { - fmt.Fprintln(cmd.ErrOrStderr(), err.Error()) - } +func (o *option) parseMarkdownRunners(files []string) (scriptRunners ScriptRunners, err error) { + scriptRunners = NewScriptRunners() - if !o.loop { - break + for _, mdFilePath := range files { + var files []string + if files, err = filepath.Glob(mdFilePath); err == nil { + for _, file := range files { + if !strings.HasSuffix(file, ".md") { + continue + } + var runners ScriptRunners + if runners, err = o.parseMarkdownRunner(file); err != nil { + break + } + + scriptRunners = append(scriptRunners, runners...) } } } @@ -147,17 +152,23 @@ type option struct { loop bool keepFilter bool keepScripts bool + pageSize int execer exec.Execer } func (o *option) executeScripts(scriptRunners ScriptRunners) (err error) { + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt) + selector := &survey.MultiSelect{ Message: "Choose the code block to run", Options: scriptRunners.GetTitles(), } var titles []string - if err = survey.AskOne(selector, &titles, survey.WithKeepFilter(o.keepFilter)); err != nil { + if err = survey.AskOne(selector, &titles, + survey.WithKeepFilter(o.keepFilter), + survey.WithPageSize(o.pageSize)); err != nil { return } @@ -167,9 +178,15 @@ func (o *option) executeScripts(scriptRunners ScriptRunners) (err error) { break } + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-c + cancel() + }() + if runner := scriptRunners.GetRunner(title); runner == nil { fmt.Println("cannot found runner:", title) - } else if err = runner.Run(); err != nil { + } else if err = runner.Run(ctx); err != nil { break } } diff --git a/cli/root_test.go b/cli/root_test.go index bfb6f87..901b4f1 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -41,7 +41,7 @@ func TestNewRootCommand(t *testing.T) { } } -func TestParseMarkdownRUnner(t *testing.T) { +func TestParseMarkdownRunner(t *testing.T) { opt := &option{} runners, err := opt.parseMarkdownRunner("../README.md") if assert.Nil(t, err) { @@ -52,3 +52,15 @@ func TestParseMarkdownRUnner(t *testing.T) { assert.NotNil(t, runners.GetRunner("Golang Hello World")) } } + +func TestParseMarkdownRunners(t *testing.T) { + opt := &option{} + runners, err := opt.parseMarkdownRunners([]string{"../README.md"}) + if assert.Nil(t, err) { + assert.True(t, len(runners) > 0) + assert.NotNil(t, runners.GetRunner("Variable Input Hello World")) + assert.NotNil(t, runners.GetRunner("Python Hello World")) + assert.NotNil(t, runners.GetRunner("Run long time")) + assert.NotNil(t, runners.GetRunner("Golang Hello World")) + } +} diff --git a/cli/shell_runner.go b/cli/shell_runner.go index 98c86b3..283811e 100644 --- a/cli/shell_runner.go +++ b/cli/shell_runner.go @@ -1,6 +1,7 @@ package cli import ( + "context" "io" "os" "path" @@ -19,13 +20,8 @@ type ShellScript struct { } // Run executes the script -func (s *ShellScript) Run() (err error) { - // handle the break line - breakline := regexp.MustCompile(`\\\n`) - s.Content = breakline.ReplaceAllString(s.Content, "") - - whitespaces := regexp.MustCompile(` +`) - s.Content = whitespaces.ReplaceAllString(s.Content, " ") +func (s *ShellScript) Run(ctx context.Context) (err error) { + s.Content = strings.ReplaceAll(s.Content, "\r\n", "\n") lines := strings.Split(s.Content, "\n")[1:] @@ -89,6 +85,11 @@ func (s *ShellScript) runCmdLine(cmdLine, contextDir string, keepScripts bool) ( } func findPotentialCommands(cmdLine string) (cmds []string) { + // TODO should find a better way to skip EOF part + if strings.Contains(cmdLine, "EOF") { + return + } + lines := strings.Split(cmdLine, "\n") for _, line := range lines { line = strings.TrimSpace(line) diff --git a/cli/shell_runner_test.go b/cli/shell_runner_test.go index 5834e51..179b4ca 100644 --- a/cli/shell_runner_test.go +++ b/cli/shell_runner_test.go @@ -1,6 +1,7 @@ package cli import ( + "context" "testing" "github.com/linuxsuren/http-downloader/pkg/exec" @@ -41,7 +42,7 @@ echo 1`, ShellType: tt.shellType, } assert.Equal(t, tt.title, shell.GetTitle()) - err := shell.Run() + err := shell.Run(context.Background()) assert.Equal(t, tt.hasErr, err != nil) }) } @@ -105,6 +106,11 @@ docker ps`, name: "with extra whitespace", cmd: " k3d create cluster", expect: []string{"k3d"}, + }, { + name: "with EOF", + cmd: `EOF +k3d create cluster`, + expect: nil, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cli/types.go b/cli/types.go index 3891244..185ae86 100644 --- a/cli/types.go +++ b/cli/types.go @@ -1,6 +1,10 @@ package cli -import "github.com/linuxsuren/http-downloader/pkg/exec" +import ( + "context" + + "github.com/linuxsuren/http-downloader/pkg/exec" +) // Script represents a script object type Script struct { @@ -14,7 +18,7 @@ type Script struct { // ScriptRunner is the interface of a common runner type ScriptRunner interface { - Run() error + Run(context.Context) error GetTitle() string } @@ -54,7 +58,7 @@ func (s ScriptRunners) Size() int { type QuitRunner struct{} // Run does nothing -func (r *QuitRunner) Run() error { +func (r *QuitRunner) Run(context.Context) error { return nil } diff --git a/cli/types_test.go b/cli/types_test.go index 065a4be..bba2593 100644 --- a/cli/types_test.go +++ b/cli/types_test.go @@ -1,6 +1,7 @@ package cli import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -16,7 +17,7 @@ func TestScriptRunners(t *testing.T) { quitRunner := runners.GetRunner("Quit") if assert.NotNil(t, quitRunner) { assert.Equal(t, "Quit", quitRunner.GetTitle()) - assert.Nil(t, quitRunner.Run()) + assert.Nil(t, quitRunner.Run(context.Background())) } assert.Equal(t, []string{"Quit"}, runners.GetTitles()) }