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