src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/shell/script.go (about)

     1  package shell
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"unicode/utf8"
    10  
    11  	"src.elv.sh/pkg/diag"
    12  	"src.elv.sh/pkg/eval"
    13  	"src.elv.sh/pkg/eval/vals"
    14  	"src.elv.sh/pkg/parse"
    15  )
    16  
    17  // Configuration for the script mode.
    18  type scriptCfg struct {
    19  	Cmd         bool
    20  	CompileOnly bool
    21  	JSON        bool
    22  }
    23  
    24  // Executes a shell script.
    25  func script(ev *eval.Evaler, fds [3]*os.File, args []string, cfg *scriptCfg) int {
    26  	arg0 := args[0]
    27  	ev.Args = vals.MakeListSlice(args[1:])
    28  
    29  	var name, code string
    30  	if cfg.Cmd {
    31  		name = "code from -c"
    32  		code = arg0
    33  	} else {
    34  		var err error
    35  		name, err = filepath.Abs(arg0)
    36  		if err != nil {
    37  			fmt.Fprintf(fds[2],
    38  				"cannot get full path of script %q: %v\n", arg0, err)
    39  			return 2
    40  		}
    41  		code, err = readFileUTF8(name)
    42  		if err != nil {
    43  			fmt.Fprintf(fds[2], "cannot read script %q: %v\n", name, err)
    44  			return 2
    45  		}
    46  	}
    47  
    48  	src := parse.Source{Name: name, Code: code, IsFile: true}
    49  	if cfg.CompileOnly {
    50  		parseErr, _, compileErr := ev.Check(src, fds[2])
    51  		if cfg.JSON {
    52  			fmt.Fprintf(fds[1], "%s\n", errorsToJSON(parseErr, compileErr))
    53  		} else {
    54  			if parseErr != nil {
    55  				diag.ShowError(fds[2], parseErr)
    56  			}
    57  			if compileErr != nil {
    58  				diag.ShowError(fds[2], compileErr)
    59  			}
    60  		}
    61  		if parseErr != nil || compileErr != nil {
    62  			return 2
    63  		}
    64  	} else {
    65  		err := evalInTTY(fds, ev, nil, src)
    66  		if err != nil {
    67  			diag.ShowError(fds[2], err)
    68  			return 2
    69  		}
    70  	}
    71  
    72  	return 0
    73  }
    74  
    75  var errSourceNotUTF8 = errors.New("source is not UTF-8")
    76  
    77  func readFileUTF8(fname string) (string, error) {
    78  	bytes, err := os.ReadFile(fname)
    79  	if err != nil {
    80  		return "", err
    81  	}
    82  	if !utf8.Valid(bytes) {
    83  		return "", errSourceNotUTF8
    84  	}
    85  	return string(bytes), nil
    86  }
    87  
    88  // An auxiliary struct for converting errors with diagnostics information to JSON.
    89  type errorInJSON struct {
    90  	FileName string `json:"fileName"`
    91  	Start    int    `json:"start"`
    92  	End      int    `json:"end"`
    93  	Message  string `json:"message"`
    94  }
    95  
    96  // Converts parse and compilation errors into JSON.
    97  func errorsToJSON(parseErr, compileErr error) []byte {
    98  	var converted []errorInJSON
    99  	for _, e := range parse.UnpackErrors(parseErr) {
   100  		converted = append(converted,
   101  			errorInJSON{e.Context.Name, e.Context.From, e.Context.To, e.Message})
   102  	}
   103  	for _, e := range eval.UnpackCompilationErrors(compileErr) {
   104  		converted = append(converted,
   105  			errorInJSON{e.Context.Name, e.Context.From, e.Context.To, e.Message})
   106  	}
   107  
   108  	jsonError, errMarshal := json.Marshal(converted)
   109  	if errMarshal != nil {
   110  		return []byte(`[{"message":"Unable to convert the errors to JSON"}]`)
   111  	}
   112  	return jsonError
   113  }