small pixel drawing of a pufferfish zoa

shell/shell.go

package shell

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"j3s.sh/zoa/color"
	"j3s.sh/zoa/utils"

	maybeio "github.com/google/renameio/maybe"
	"mvdan.cc/sh/v3/interp"
	"mvdan.cc/sh/v3/syntax"
)

func Run(r *interp.Runner, script string) {
	f, err := parseFile(script)
	if err != nil {
		log.Fatal(err)
	}
	ctx := context.TODO()
	for _, stmt := range f.Stmts {
		// 14:8
		pos := stmt.Pos()
		ctx = context.WithValue(ctx, "position", pos)
		err = r.Run(ctx, stmt)
		if err != nil {
			// we yell but keep plowing forward
			// twirling, twirling
			color.ZoaYell(fmt.Sprintf("%s:%s: %s", script, pos, err.Error()))
		}
	}
}

func CallHandler(ctx context.Context, args []string) ([]string, error) {
	command := args[0]

	fmt.Println()
	// shell tips go here
	if command == "echo" {
		color.ZoaSay(`prefer printf over echo!
ex: printf '%s\n' "hello world!"`)
	}
	fmt.Printf("$ %s\n", strings.Join(args, " "))

	return args, nil
}

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

func ZoaFmt(script string) {
	f, err := os.Open(script)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	p := syntax.NewParser()
	syntax.KeepComments(true)(p)
	syntax.Variant(syntax.LangPOSIX)(p)
	node, err := p.Parse(f, "")
	if err != nil {
		return
	}
	var writeBuf bytes.Buffer
	syntax.NewPrinter().Print(&writeBuf, node)
	res := writeBuf.Bytes()

	info, err := os.Lstat(script)
	if err != nil {
		log.Fatal(err)
	}
	perm := info.Mode().Perm()
	// TODO: remove maybeio when golang implements atomic writing
	if err := maybeio.WriteFile(script, res, perm); err != nil {
		log.Fatal(err)
	}
}

// ZoaCopy copies a file from src to dst, optionally modifying its mode.
// zoaCopy defaults to 0666 for permissions (before umask)
func ZoaCopy(src string, dst string, mode os.FileMode) {
	srcChk, err := utils.ChecksumFile(src)
	if err != nil {
		// source file should always exist, return error
		fmt.Println(err)
		os.Exit(1)
	}
	dstChk, err := utils.ChecksumFile(dst)
	if err != nil {
		// dstfile may not exist for a million
		// reasons, set checksum to blank
		// to force a mismatch
		// TODO: why did i do this? revise errors.
		dstChk = ""
	}

	// TODO: templating engine?
	// - expand vars
	// - loop support?

	if srcChk != dstChk {
		err = utils.Copy(src, dst)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	fi, err := os.Lstat(dst)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	if fi.Mode().Perm() != mode {
		err = os.Chmod(dst, mode)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}
}