Skip to content

Commit

Permalink
feat: support run python script
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen committed Feb 15, 2023
1 parent 51f3bea commit 6ee87b3
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 94 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ name=linuxsuren
echo hello $name
```

### Run Python Script
```python3
#!title: Python Hello World
print('hello python world');
```

## Limitation
Please make sure the Markdown files meet Linux end-of-line.
You could turn it via: `dos2unix your.md`
44 changes: 44 additions & 0 deletions cli/python_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cli

import (
"fmt"
"os"
"os/exec"
)

type PythonScript struct {
Script
}

func (s *PythonScript) Run() (err error) {
var shellFile string
if shellFile, err = writeAsShell(s.Content, s.Dir); err != nil {
fmt.Println(err)
return
}
if !s.KeepScripts {
defer func() {
_ = os.RemoveAll(shellFile)
}()
}

var pyExec string
if pyExec, err = exec.LookPath("python3"); err != nil {
return
}

cmd := exec.Command(pyExec, shellFile)
cmd.Env = os.Environ()

var output []byte
if output, err = cmd.CombinedOutput(); err != nil {
fmt.Println(string(output), err)
return
}
fmt.Print(string(output))
return
}

func (s *PythonScript) GetTitle() string {
return s.Title
}
169 changes: 75 additions & 94 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package cli

import (
"fmt"
"io"
"os"
"os/exec"
"path"
"regexp"
"strings"

"github.com/AlecAivazis/survey/v2"
Expand Down Expand Up @@ -59,7 +57,8 @@ func (o *option) runMarkdown(mdFilePath string) (err error) {
md := markdown.New(markdown.XHTMLOutput(true), markdown.Nofollow(true))
tokens := md.Parse(mdFile)

cmdMap := map[string][]string{}
// cmdMap := map[string][]string{}
scriptList := NewScriptRunners()

// Print the result
var title string
Expand All @@ -75,40 +74,62 @@ func (o *option) runMarkdown(mdFilePath string) (err error) {
lang = tok.Params
}

if content != "" && lang == "shell" {
originalContent := content
if content == "" {
continue
}

originalContent := content
lines := strings.Split(content, "\n")
if len(lines) < 2 {
continue
}
title = lines[0]
if !strings.HasPrefix(title, "#!title: ") {
continue
}
title = strings.TrimPrefix(title, "#!title: ")

script := Script{
Kind: lang,
Title: title,
Content: originalContent,
Dir: path.Dir(mdFilePath),
KeepScripts: o.keepScripts,
}

if lang == "shell" {

// handle the break line
breakline := regexp.MustCompile(`\\\n`)
content = breakline.ReplaceAllString(content, "")
// breakline := regexp.MustCompile(`\\\n`)
// content = breakline.ReplaceAllString(content, "")

whitespaces := regexp.MustCompile(` +`)
content = whitespaces.ReplaceAllString(content, " ")
// whitespaces := regexp.MustCompile(` +`)
// content = whitespaces.ReplaceAllString(content, " ")

lines := strings.Split(content, "\n")
if len(lines) < 2 {
continue
}
title = lines[0]
if !strings.HasPrefix(title, "#!title: ") {
continue
}
title = strings.TrimPrefix(title, "#!title: ")
// support multiple lines mode
if strings.Contains(title, "+f") {
title = strings.ReplaceAll(title, "+f", "")
title = strings.TrimSpace(title)
scriptList = append(scriptList, &ShellScript{
Script: script,
})

cmdMap[title] = append(cmdMap[title], originalContent)
} else {
cmdMap[title] = append(cmdMap[title], lines[1:]...)
}
// support multiple lines mode
// if strings.Contains(title, "+f") {
// title = strings.ReplaceAll(title, "+f", "")
// title = strings.TrimSpace(title)

// cmdMap[title] = append(cmdMap[title], originalContent)
// } else {
// cmdMap[title] = append(cmdMap[title], lines[1:]...)
// }
} else if lang == "python3" {
scriptList = append(scriptList, &PythonScript{
Script: script,
})
}
}
err = o.executeScripts(scriptList)

contextDir := path.Dir(mdFilePath)
// TODO this should be a treemap instead of hashmap
err = o.execute(cmdMap, contextDir)
// contextDir := path.Dir(mdFilePath)
// // TODO this should be a treemap instead of hashmap
// err = o.execute(cmdMap, contextDir)
return
}

Expand All @@ -118,6 +139,31 @@ type option struct {
keepScripts bool
}

func (o *option) executeScripts(scriptRunners ScriptRunners) (err error) {
selector := &survey.MultiSelect{
Message: "Choose the code block to run",
Options: scriptRunners.GetTitles(),
}
titles := []string{}
if err = survey.AskOne(selector, &titles, survey.WithKeepFilter(o.keepFilter)); err != nil {
return
}

for _, title := range titles {
if title == "Quit" {
o.loop = false
break
}

if runner := scriptRunners.GetRunner(title); runner == nil {
fmt.Println("cannot found runner:", title)
} else if err = runner.Run(); err != nil {
break
}
}
return
}

func (o *option) execute(cmdMap map[string][]string, contextDir string) (err error) {
var items []string
for key := range cmdMap {
Expand Down Expand Up @@ -172,71 +218,6 @@ func (o *option) execute(cmdMap map[string][]string, contextDir string) (err err
return
}

func isInputRequest(cmdLine string) (ok bool, pair []string, err error) {
var reg *regexp.Regexp
if reg, err = regexp.Compile(`^\w+=.+$`); err == nil {
items := strings.Split(cmdLine, "=")
if reg.MatchString(cmdLine) && len(items) == 2 {
pair = []string{strings.TrimSpace(items[0]), strings.TrimSpace(items[1])}
ok = true
}
}
return
}

func inputRequest(pair []string) (result []string, err error) {
input := survey.Input{
Message: pair[0],
Default: pair[1],
}
result = pair

var value string
if err = survey.AskOne(&input, &value); err == nil {
result[1] = value
}

return
}

func runCmdLine(cmdLine, contextDir string, keepScripts bool) (err error) {
var shellFile string
if shellFile, err = writeAsShell(cmdLine, contextDir); err != nil {
fmt.Println(err)
return
}
if !keepScripts {
defer func() {
_ = os.RemoveAll(shellFile)
}()
}

cmd := exec.Command("bash", path.Base(shellFile))
cmd.Dir = contextDir
cmd.Env = os.Environ()

var output []byte
if output, err = cmd.CombinedOutput(); err != nil {
fmt.Println(string(output), err)
return
}
fmt.Print(string(output))
return
}

func writeAsShell(content, dir string) (targetPath string, err error) {
var f *os.File
if f, err = os.CreateTemp(dir, "sh"); err == nil {
defer func() {
_ = f.Close()
}()

targetPath = f.Name()
_, err = io.WriteString(f, content)
}
return
}

func runAsInlineCommand(cmdLine, contextDir string) (err error) {
args := strings.Split(cmdLine, " ")
cmd := strings.TrimSpace(args[0])
Expand Down
120 changes: 120 additions & 0 deletions cli/shell_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cli

import (
"fmt"
"io"
"os"
"os/exec"
"path"
"regexp"
"strings"

"github.com/AlecAivazis/survey/v2"
)

type ShellScript struct {
Script
}

func (s *ShellScript) Run() (err error) {
lines := strings.Split(s.Content, "\n")[1:]

preDefinedEnv := os.Environ()
for _, cmdLine := range lines {
var pair []string
var ok bool
ok, pair, err = isInputRequest(cmdLine)
if err != nil {
break
}

if ok {
if pair, err = inputRequest(pair); err != nil {
break
}
os.Setenv(pair[0], pair[1])
continue
}

err = runCmdLine(cmdLine, s.Dir, s.KeepScripts)
if err != nil {
break
}
}

// reset the env
os.Clearenv()
for _, pair := range preDefinedEnv {
os.Setenv(strings.Split(pair, "=")[0], strings.Split(pair, "=")[1])
}
return
}

func (s *ShellScript) GetTitle() string {
return s.Title
}

func isInputRequest(cmdLine string) (ok bool, pair []string, err error) {
var reg *regexp.Regexp
if reg, err = regexp.Compile(`^\w+=.+$`); err == nil {
items := strings.Split(cmdLine, "=")
if reg.MatchString(cmdLine) && len(items) == 2 {
pair = []string{strings.TrimSpace(items[0]), strings.TrimSpace(items[1])}
ok = true
}
}
return
}

func inputRequest(pair []string) (result []string, err error) {
input := survey.Input{
Message: pair[0],
Default: pair[1],
}
result = pair

var value string
if err = survey.AskOne(&input, &value); err == nil {
result[1] = value
}

return
}

func runCmdLine(cmdLine, contextDir string, keepScripts bool) (err error) {
var shellFile string
if shellFile, err = writeAsShell(cmdLine, contextDir); err != nil {
fmt.Println(err)
return
}
if !keepScripts {
defer func() {
_ = os.RemoveAll(shellFile)
}()
}

cmd := exec.Command("bash", path.Base(shellFile))
cmd.Dir = contextDir
cmd.Env = os.Environ()

var output []byte
if output, err = cmd.CombinedOutput(); err != nil {
fmt.Println(string(output), err)
return
}
fmt.Print(string(output))
return
}

func writeAsShell(content, dir string) (targetPath string, err error) {
var f *os.File
if f, err = os.CreateTemp(dir, "sh"); err == nil {
defer func() {
_ = f.Close()
}()

targetPath = f.Name()
_, err = io.WriteString(f, content)
}
return
}
Loading

0 comments on commit 6ee87b3

Please sign in to comment.