diff --git a/home/bin/SimpleHTTPServer b/home/bin/SimpleHTTPServer index 6406322..339c5f4 100755 --- a/home/bin/SimpleHTTPServer +++ b/home/bin/SimpleHTTPServer @@ -1,4 +1,4 @@ -///bin/true && exec /usr/bin/env gosh "$0" "$@" +///bin/true && exec /usr/bin/env script.go "$0" "$@" // vim:set ft=go: package main diff --git a/home/bin/script.go b/home/bin/script.go new file mode 100755 index 0000000..4f598cc --- /dev/null +++ b/home/bin/script.go @@ -0,0 +1,163 @@ +///bin/true && exec /usr/bin/env go run "$0" "$@" +// +// script.go is a Go program that can run itself as a shell script, as well as +// help other Go scripts to run themselves. +// +// To make your Go scripts work with this, use the following "shebang" header +// at the top of your script: +// +// ///bin/true && exec /usr/bin/env script.go "$0" "$@" +// // vim:set ft=go: +// package main +// +// The first line will cause your shell to run `script.go` passing the current +// filename and the rest of the command line arguments. The vim modeline comment +// may help your code editor to highlight the file as Go syntax. +package main + +import ( + "crypto/rand" + "encoding/hex" + "flag" + "fmt" + "io/ioutil" + "os" + "os/exec" + "os/signal" + "path/filepath" + "syscall" +) + +// Version of this script. +const Version = "1.0.0" + +// Command line arguments +var ( + debug bool + version bool +) + +func init() { + flag.BoolVar(&debug, "debug", false, "Verbose debug logging") + flag.BoolVar(&version, "version", false, "Show version number and exit") +} + +func main() { + flag.Parse() + if version { + fmt.Printf("This is script.go v%s\n", Version) + os.Exit(0) + } + + // Parse the script name and remaining arguments. + args := flag.Args() + if len(args) == 0 { + usage() + } + scriptName := args[0] + argv := args[1:] + + // Verify it's a file. + if _, err := os.Stat(scriptName); os.IsNotExist(err) { + die("%s: not a file", scriptName) + } + + // Make a temp file with a *.go extension + tmpfile, err := NamedTempFile("", "script", ".go") + if err != nil { + die("tempfile error: %s", err) + } + log("scriptName: %s; tmpFile: %s", scriptName, tmpfile.Name()) + + // Read the source and write it to the new file. + src, err := ioutil.ReadFile(scriptName) + dieIfError(err) + _, err = tmpfile.Write(src) + dieIfError(err) + err = tmpfile.Close() + dieIfError(err) + + // Catch interrupt signals to clean up the tempfile. + interrupt := make(chan os.Signal, 2) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) + go func() { + <-interrupt + log("interrupt detected; cleaning up tempfile") + err = os.Remove(tmpfile.Name()) + if err != nil { + die("remove tmpfile error: %s", err) + } + }() + + // Finally, `go run` the script from $TMPDIR. + goArgs := append([]string{"run", tmpfile.Name()}, argv...) + c := exec.Command( + "go", + goArgs..., + ) + c.Stdin = os.Stdin + c.Stdout = os.Stdout + c.Stderr = os.Stderr + err = c.Run() + if err != nil { + fmt.Printf("[script.go] script error: %s\n", err) + } + + log("cleaning up tempfile") + os.Remove(tmpfile.Name()) +} + +// handler for Ctrl-C cleaning up the temp file. +func cleanup() { + fmt.Println("cleanup") +} + +// NamedTempFile is like ioutil.TempFile but accepts a suffix too. +func NamedTempFile(dir, prefix, suffix string) (f *os.File, err error) { + if dir == "" { + dir = os.TempDir() + } + + // Random string generator. + randomString := func() string { + randBytes := make([]byte, 16) + rand.Read(randBytes) + return hex.EncodeToString(randBytes) + } + + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+randomString()+suffix) + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + continue + } + break + } + + return +} + +func usage() { + fmt.Print( + "Usage: script.go [options] \n", + "See script.go -h for command line options.\n", + ) + os.Exit(0) +} + +func log(message string, v ...interface{}) { + if debug { + fmt.Printf("[script.go] "+message+"\n", v...) + } +} + +func die(message string, v ...interface{}) { + fmt.Printf(message+"\n", v...) + os.Exit(1) +} + +func dieIfError(err error) { + if err != nil { + die(err.Error()) + } +}