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 }