blob: bbc5f7033617ca6ef23ee3cb95d04eb71443c66b [file] [log] [blame]
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())
}
}
}