github.com/MontFerret/ferret@v0.18.0/pkg/stdlib/io/fs/write.go (about) 1 package fs 2 3 import ( 4 "context" 5 "os" 6 "sort" 7 8 "github.com/MontFerret/ferret/pkg/runtime/core" 9 "github.com/MontFerret/ferret/pkg/runtime/values" 10 "github.com/MontFerret/ferret/pkg/runtime/values/types" 11 ) 12 13 // WRITE writes the given data into the file. 14 // @param {String} path - File path to write into. 15 // @param {Binary} data - Data to write. 16 // @param {Object} [params] - additional parameters: 17 // @param {String} [params.mode] - Write mode. 18 // * x - Exclusive: returns an error if the file exist. It can be combined with other modes 19 // * a - Append: will create a file if the specified file does not exist 20 // * w - Write (Default): will create a file if the specified file does not exist 21 func Write(_ context.Context, args ...core.Value) (core.Value, error) { 22 err := validateRequiredWriteArgs(args) 23 24 if err != nil { 25 return values.None, err 26 } 27 28 fpath := args[0].String() 29 arg1 := args[1] 30 31 var data []byte 32 33 if arg1.Type() == types.Binary { 34 data = arg1.(values.Binary) 35 } else { 36 data = []byte(arg1.String()) 37 } 38 39 params := defaultParams 40 41 if len(args) == 3 { 42 params, err = parseParams(args[2]) 43 44 if err != nil { 45 return values.None, core.Error( 46 err, 47 "parse `params` argument", 48 ) 49 } 50 } 51 52 // 0666 - read & write 53 file, err := os.OpenFile(fpath, params.ModeFlag, 0666) 54 55 if err != nil { 56 return values.None, core.Error(err, "open file") 57 } 58 59 defer file.Close() 60 61 _, err = file.Write(data) 62 63 if err != nil { 64 return values.None, core.Error(err, "write file") 65 } 66 67 return values.None, nil 68 } 69 70 func validateRequiredWriteArgs(args []core.Value) error { 71 err := core.ValidateArgs(args, 2, 3) 72 73 if err != nil { 74 return err 75 } 76 77 err = core.ValidateType(args[0], types.String) 78 79 if err != nil { 80 return err 81 } 82 83 return nil 84 } 85 86 // parsedParams contains parsed additional parameters. 87 type parsedParams struct { 88 ModeFlag int 89 } 90 91 var defaultParams = parsedParams{ 92 // the same as `w` 93 ModeFlag: os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 94 } 95 96 func parseParams(value core.Value) (parsedParams, error) { 97 err := core.ValidateType(value, types.Object) 98 99 if err != nil { 100 return parsedParams{}, err 101 } 102 103 obj := value.(*values.Object) 104 105 params := defaultParams 106 107 modestr, exists := obj.Get(values.NewString("mode")) 108 109 if exists { 110 flag, err := parseWriteMode(modestr.String()) 111 112 if err != nil { 113 return parsedParams{}, core.Error( 114 core.ErrInvalidArgument, 115 "parse write mode", 116 ) 117 } 118 119 params.ModeFlag = flag 120 } 121 122 return params, nil 123 } 124 125 func parseWriteMode(s string) (int, error) { 126 letters := []rune(s) 127 count := len(letters) 128 129 if count == 0 || count > 2 { 130 return -1, core.Errorf( 131 core.ErrInvalidArgument, 132 "must be from 1 to 2 mode letters, got `%d`", count, 133 ) 134 } 135 136 // sort letters for more convenient work with it 137 sort.Slice(letters, func(i, j int) bool { return letters[i] < letters[j] }) 138 139 // minimum flag for writing to file 140 flag := os.O_WRONLY | os.O_CREATE 141 142 if count == 2 { 143 // since letter is sorted, `x` will always be the letters[1] 144 if letters[1] != 'x' { 145 return -1, core.Errorf( 146 core.ErrInvalidArgument, 147 "invalid mode `%s`", s, 148 ) 149 } 150 151 flag |= os.O_EXCL 152 } 153 154 switch letters[0] { 155 case 'a': 156 flag |= os.O_APPEND 157 case 'w': 158 flag |= os.O_TRUNC 159 default: 160 return -1, core.Errorf( 161 core.ErrInvalidArgument, 162 "invalid mode `%s`", s, 163 ) 164 } 165 166 return flag, nil 167 }