small pixel drawing of a pufferfish zoa

main.go

package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"mvdan.cc/sh/v3/interp"
	"mvdan.cc/sh/v3/syntax"
)

var ctx = context.Background()
var rootDir = "test/"

func main() {
	// TODO: this writer is responsible for the random stdout
	// maybe save the stdout for debug mode somehow
	r, err := interp.New(interp.StdIO(nil, os.Stdout, os.Stderr))
	if err != nil {
		log.Fatal(err)
	}

	entrypoint := filepath.Join(rootDir, "main")
	runCommands(entrypoint, r)
}

// this is used to detect when the script
// name changes from run to run, which allows
// us to prettily-print
var lastScriptPath string

func runCommands(scriptPath string, r *interp.Runner) {
	script, err := parseFile(scriptPath)
	if err != nil {
		log.Fatal(err)
	}

	// execute every statement individually, decorating
	// each with ->, and doing some speshul logicks against
	// certain strings
	for _, stmt := range script.Stmts {
		cmdName := commandName(stmt)
		command, after, _ := strings.Cut(cmdName, " ")
		if command == "zoa-script" {
			// recursion detected!! :3
			subScriptPath := filepath.Join(rootDir + "scripts/" + after)
			runCommands(subScriptPath, r)
			continue
		}

		// if the script name changed between runs,
		// print it
		if scriptPath != lastScriptPath {
			fmt.Println("\t" + scriptPath)
			lastScriptPath = scriptPath
		}

		fmt.Printf("\t$ %s\n", cmdName)
		err = r.Run(ctx, stmt)
		if err != nil {
			os.Exit(1)
		}
	}
}

func runCommand(c context.Context, s *syntax.Stmt, r *interp.Runner) {
	name := commandName(s)
	fmt.Printf(" -> %s\n", name)
	err := r.Run(c, s)
	if err != nil {
		os.Exit(1)
	}
}

func commandName(statement *syntax.Stmt) string {
	b := new(bytes.Buffer)
	syntax.NewPrinter().Print(b, statement)
	return b.String()
}

func parseFile(filename string) (*syntax.File, error) {
	var result = &syntax.File{}
	f, err := os.Open(filename)
	if err != nil {
		return result, err
	}
	result, err = syntax.NewParser().Parse(f, "")
	return result, err
}

// runStatements takes a file & runs individual
// commands from that file, prepending the decorator
// and returning the first error
// func runScript(file *syntax.File) error {
// fmt.Printf("%s%s\n", decorator, output)
// return nil
// }