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 }