| package main |
| |
| import ( |
| "flag" |
| "fmt" |
| "github.com/monopole/mdrip/util" |
| "io/ioutil" |
| "log" |
| "os" |
| "strings" |
| "time" |
| ) |
| |
| var blockTimeOut = flag.Duration("blockTimeOut", 7*time.Second, |
| "The max amount of time to wait for a command block to exit.") |
| |
| // dumpBucket emits the contents of a util.ScriptBucket. |
| // |
| // If n <= 0, dump everything, else only dump the first n blocks. n |
| // is 1 relative, i.e., if you want the first two blocks dumped, pass |
| // n==2, not n==1. |
| func dumpBucket(label string, bucket *util.ScriptBucket, n int) { |
| fmt.Printf("#\n# Script @%s from %s \n#\n", label, bucket.GetFileName()) |
| delimFmt := "#" + strings.Repeat("-", 70) + "# %s %d\n" |
| for i, block := range bucket.GetScript() { |
| if n > 0 && i >= n { |
| break |
| } |
| fmt.Printf(delimFmt, "Start", i+1) |
| fmt.Printf("echo \"Block '%s' (%d/%d in %s) of %s\"\n####\n", |
| block.GetLabels()[0], i+1, len(bucket.GetScript()), label, bucket.GetFileName()) |
| fmt.Print(block.GetCodeText()) |
| fmt.Printf(delimFmt, "End", i+1) |
| fmt.Println() |
| } |
| } |
| |
| // emitStraightScript simply prints the contents of scriptBuckets. |
| func emitStraightScript(label string, scriptBuckets []*util.ScriptBucket) { |
| for _, bucket := range scriptBuckets { |
| dumpBucket(label, bucket, 0) |
| } |
| fmt.Printf("echo \" \"\n") |
| fmt.Printf("echo \"All done. No errors.\"\n") |
| } |
| |
| // emitPreambledScript emits the first script normally, then emit it |
| // again, as well as the the remaining scripts, so that they run in a |
| // subshell. |
| // |
| // This allows the aggregrate script to be structured as 1) a preamble |
| // initialization script that impacts the environment of the active |
| // shell, followed by 2) a script that executes as a subshell that |
| // exits on error. An exit in (2) won't cause the active shell (most |
| // likely a terminal) to close. |
| // |
| // The first script must be able to complete without exit on error |
| // because its not running as a subshell. So it should just set |
| // environment variables and/or define shell funtions. |
| func emitPreambledScript(label string, scriptBuckets []*util.ScriptBucket, n int) { |
| dumpBucket(label, scriptBuckets[0], n) |
| delim := "HANDLED_SCRIPT" |
| fmt.Printf(" bash -e <<'%s'\n", delim) |
| fmt.Printf("function handledTrouble() {\n") |
| fmt.Printf(" echo \" \"\n") |
| fmt.Printf(" echo \"Unable to continue!\"\n") |
| fmt.Printf(" exit 1\n") |
| fmt.Printf("}\n") |
| fmt.Printf("trap handledTrouble INT TERM\n") |
| emitStraightScript(label, scriptBuckets) |
| fmt.Printf("%s\n", delim) |
| } |
| |
| func usage() { |
| fmt.Fprintf(os.Stderr, "\nUsage: %s {label} {fileName}...\n", os.Args[0]) |
| flag.PrintDefaults() |
| fmt.Fprintf(os.Stderr, |
| ` |
| Reads markdown files, extracts code blocks with a given @label, and |
| either runs them in a subshell or emits them to stdout. |
| |
| If the markdown file contains |
| |
| Blah blah blah. |
| <!-- @goHome @foo --> |
| ''' |
| cd $HOME |
| ''' |
| Blah blah blah. |
| <!-- @echoApple @apple --> |
| ''' |
| echo "an apple a day keeps the doctor away" |
| ''' |
| Blah blah blah. |
| <!-- @echoCloseStar @foo @baz --> |
| ''' |
| echo "Proxima Centauri" |
| ''' |
| Blah blah blah. |
| |
| then the command '{this} foo {fileName}' emits: |
| |
| cd $HOME |
| echo "Proxima Centauri" |
| |
| Pipe output to 'source /dev/stdin' to run it directly. |
| |
| Use --subshell to run the blocks in a subshell leaving your current |
| shell env vars and pwd unchanged. The code blocks can, however, do |
| anything to your computer that you can. |
| `) |
| } |
| |
| func main() { |
| flag.Usage = usage |
| preambled := flag.Int("preambled", -1, |
| "Place all scripts in a subshell, preambled by the first {n} blocks in the first script.") |
| subshell := flag.Bool("subshell", false, |
| "Run extracted blocks in subshell (leaves your env vars and pwd unchanged).") |
| swallow := flag.Bool("swallow", false, |
| "Swallow errors from subshell (non-zero exit only on problems in driver code).") |
| flag.Parse() |
| if *swallow && !*subshell { |
| fmt.Fprintf(os.Stderr, "Makes no sense to specify --swallow but not --subshell.\n") |
| usage() |
| os.Exit(1) |
| } |
| if flag.NArg() < 2 { |
| usage() |
| os.Exit(1) |
| } |
| label := flag.Arg(0) |
| scriptBuckets := make([]*util.ScriptBucket, flag.NArg()-1) |
| |
| for i := 1; i < flag.NArg(); i++ { |
| fileName := flag.Arg(i) |
| contents, err := ioutil.ReadFile(fileName) |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "Unable to read %q\n", fileName) |
| usage() |
| os.Exit(2) |
| } |
| m := util.Parse(string(contents)) |
| script, ok := m[label] |
| if !ok { |
| fmt.Fprintf(os.Stderr, "No block labelled %q in file %q.\n", label, fileName) |
| os.Exit(3) |
| } |
| scriptBuckets[i-1] = util.NewScriptBucket(fileName, script) |
| } |
| |
| if len(scriptBuckets) < 1 { |
| return |
| } |
| |
| if !*subshell { |
| if *preambled >= 0 { |
| emitPreambledScript(label, scriptBuckets, *preambled) |
| } else { |
| emitStraightScript(label, scriptBuckets) |
| } |
| return |
| } |
| |
| result := util.RunInSubShell(scriptBuckets, *blockTimeOut) |
| if result.GetProblem() != nil { |
| util.Complain(result, label) |
| if !*swallow { |
| log.Fatal(result.GetProblem()) |
| } |
| } |
| } |