github.com/hirochachacha/plua@v0.0.0-20170217012138-c82f520cc725/stdlib/io/io.go (about) 1 package io 2 3 import ( 4 "io/ioutil" 5 "os" 6 "os/exec" 7 "strings" 8 9 "github.com/hirochachacha/plua/internal/file" 10 "github.com/hirochachacha/plua/internal/version" 11 "github.com/hirochachacha/plua/object" 12 "github.com/hirochachacha/plua/object/fnutil" 13 ) 14 15 func Open(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 16 fileIndex := th.NewTableSize(0, 7) 17 18 fileIndex.Set(object.String("close"), object.GoFunction(fclose)) 19 fileIndex.Set(object.String("flush"), object.GoFunction(fflush)) 20 fileIndex.Set(object.String("lines"), object.GoFunction(flines)) 21 fileIndex.Set(object.String("read"), object.GoFunction(fread)) 22 fileIndex.Set(object.String("seek"), object.GoFunction(fseek)) 23 fileIndex.Set(object.String("setvbuf"), object.GoFunction(fsetvbuf)) 24 fileIndex.Set(object.String("write"), object.GoFunction(fwrite)) 25 26 mt := th.NewTableSize(0, 3) 27 28 mt.Set(object.String("__index"), fileIndex) 29 mt.Set(object.String("__tostring"), object.GoFunction(ftostring)) 30 mt.Set(object.TM_NAME, object.String("FILE*")) 31 32 stdin := &object.Userdata{ 33 Value: file.NewFile(os.Stdin, os.O_RDONLY, true), 34 Metatable: mt, 35 } 36 37 stdout := &object.Userdata{ 38 Value: file.NewFile(os.Stdout, os.O_WRONLY, true), 39 Metatable: mt, 40 } 41 42 stderr := &object.Userdata{ 43 Value: file.NewFile(os.Stderr, os.O_WRONLY, true), 44 Metatable: mt, 45 } 46 47 var _input = stdin 48 var _output = stdout 49 50 // close([file]) 51 var _close = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 52 if len(args) == 0 { 53 return fileResult(th, _output.Value.(file.File).Close()) 54 } 55 56 return fclose(th, args...) 57 } 58 59 var flush = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 60 if len(args) == 0 { 61 return fileResult(th, _output.Value.(file.File).Flush()) 62 } 63 64 return fflush(th, args...) 65 } 66 67 // input([file]) 68 var input = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 69 if len(args) == 0 { 70 return []object.Value{_input}, nil 71 } 72 73 ap := fnutil.NewArgParser(th, args) 74 75 var ud *object.Userdata 76 77 if fname, err := ap.ToGoString(0); err == nil { 78 f, e := file.OpenFile(fname, os.O_RDONLY, 0644) 79 if e != nil { 80 return nil, object.NewRuntimeError(e.Error()) 81 } 82 83 ud = &object.Userdata{ 84 Value: f, 85 Metatable: mt, 86 } 87 } else { 88 var err *object.RuntimeError 89 ud, err = ap.ToFullUserdata(0) 90 if err != nil { 91 return nil, ap.TypeError(0, "FILE*") 92 } 93 94 _, ok := ud.Value.(file.File) 95 if !ok { 96 return nil, ap.TypeError(0, "FILE*") 97 } 98 } 99 100 _input = ud 101 102 return []object.Value{ud}, nil 103 } 104 105 // lines([filename, ...]) 106 var lines = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 107 var f file.File 108 var off int 109 var doClose bool 110 111 switch { 112 case len(args) == 0: 113 f = _input.Value.(file.File) 114 115 off = 0 116 doClose = false 117 case args[0] == nil: 118 f = _input.Value.(file.File) 119 120 off = 1 121 doClose = false 122 default: 123 ap := fnutil.NewArgParser(th, args) 124 125 fname, err := ap.ToGoString(0) 126 if err != nil { 127 return nil, err 128 } 129 130 var e error 131 f, e = file.OpenFile(fname, os.O_RDONLY, 0644) 132 if e != nil { 133 return nil, object.NewRuntimeError(e.Error()) 134 } 135 136 off = 1 137 doClose = true 138 } 139 140 if len(args)-off > version.MAXARGLINE { 141 return nil, object.NewRuntimeError("too many arguments") 142 } 143 144 fnargs := append([]object.Value{}, args...) 145 146 retfn := func(_ object.Thread, _ ...object.Value) ([]object.Value, *object.RuntimeError) { 147 return _read(th, fnargs, f, off, doClose, true) 148 } 149 150 return []object.Value{object.GoFunction(retfn)}, nil 151 } 152 153 // open(filename, [, mode]) 154 var open = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 155 ap := fnutil.NewArgParser(th, args) 156 157 fname, err := ap.ToGoString(0) 158 if err != nil { 159 return nil, err 160 } 161 162 mode, err := ap.OptGoString(1, "r") 163 if err != nil { 164 return nil, err 165 } 166 167 if len(mode) > 0 && mode[len(mode)-1] == 'b' { 168 mode = mode[:len(mode)-1] 169 } 170 171 var f file.File 172 var e error 173 174 switch mode { 175 case "r": 176 f, e = file.OpenFile(fname, os.O_RDONLY, 0644) 177 case "w": 178 f, e = file.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 179 case "a": 180 f, e = file.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 181 case "r+": 182 f, e = file.OpenFile(fname, os.O_RDWR, 0644) 183 case "w+": 184 f, e = file.OpenFile(fname, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644) 185 case "a+": 186 f, e = file.OpenFile(fname, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644) 187 default: 188 return nil, ap.ArgError(1, "invalid mode") 189 } 190 if e != nil { 191 return fileResult(th, e) 192 } 193 194 ud := &object.Userdata{ 195 Value: f, 196 Metatable: mt, 197 } 198 199 return []object.Value{ud}, nil 200 } 201 202 // output([file]) 203 var output = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 204 if len(args) == 0 { 205 return []object.Value{_output}, nil 206 } 207 208 ap := fnutil.NewArgParser(th, args) 209 210 var ud *object.Userdata 211 212 if fname, err := ap.ToGoString(0); err == nil { 213 f, e := file.OpenFile(fname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 214 if e != nil { 215 return nil, object.NewRuntimeError(e.Error()) 216 } 217 218 ud = &object.Userdata{ 219 Value: f, 220 Metatable: mt, 221 } 222 } else { 223 var err *object.RuntimeError 224 ud, err = ap.ToFullUserdata(0) 225 if err != nil { 226 return nil, ap.TypeError(0, "FILE*") 227 } 228 229 _, ok := ud.Value.(file.File) 230 if !ok { 231 return nil, ap.TypeError(0, "FILE*") 232 } 233 } 234 235 _output = ud 236 237 return []object.Value{ud}, nil 238 } 239 240 var popen = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 241 ap := fnutil.NewArgParser(th, args) 242 243 prog, err := ap.ToGoString(0) 244 if err != nil { 245 return nil, err 246 } 247 248 mode, err := ap.OptGoString(1, "r") 249 if err != nil { 250 return nil, err 251 } 252 253 progArgs := strings.Fields(prog) 254 255 cmd := exec.Command(progArgs[0], progArgs[1:]...) 256 257 var ud *object.Userdata 258 259 switch mode { 260 case "r": 261 r, e := cmd.StdoutPipe() 262 if e != nil { 263 return execResult(th, e) 264 } 265 266 f, ok := r.(*os.File) 267 if !ok { 268 return nil, object.NewRuntimeError("pipe is not *os.File") 269 } 270 271 ud = &object.Userdata{ 272 Value: file.NewFile(f, os.O_RDONLY, false), 273 Metatable: mt, 274 } 275 case "w": 276 w, e := cmd.StdinPipe() 277 if e != nil { 278 return execResult(th, e) 279 } 280 281 f, ok := w.(*os.File) 282 if !ok { 283 return nil, object.NewRuntimeError("pipe is not *os.File") 284 } 285 286 ud = &object.Userdata{ 287 Value: file.NewFile(f, os.O_WRONLY, false), 288 Metatable: mt, 289 } 290 default: 291 return nil, ap.OptionError(1, mode) 292 } 293 294 return []object.Value{ud}, nil 295 } 296 297 var read = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 298 f := _input.Value.(file.File) 299 300 if f.IsClosed() { 301 return nil, object.NewRuntimeError("standard input file is closed") 302 } 303 304 return _read(th, args, f, 0, false, false) 305 } 306 307 var tmpfile = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 308 f, err := ioutil.TempFile("", "plua") 309 if err != nil { 310 return fileResult(th, err) 311 } 312 313 ud := &object.Userdata{ 314 Value: file.NewFile(f, os.O_RDWR, false), 315 Metatable: mt, 316 } 317 318 return []object.Value{ud}, nil 319 } 320 321 var _type = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 322 ap := fnutil.NewArgParser(th, args) 323 324 ud, err := ap.ToFullUserdata(0) 325 if err != nil { 326 return nil, nil 327 } 328 329 f, ok := ud.Value.(file.File) 330 if !ok { 331 return nil, nil 332 } 333 334 if f.IsClosed() { 335 return []object.Value{object.String("closed file")}, nil 336 } 337 338 return []object.Value{object.String("file")}, nil 339 } 340 341 var write = func(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 342 f := _output.Value.(file.File) 343 344 if f.IsClosed() { 345 return nil, object.NewRuntimeError("standard output file is closed") 346 } 347 348 ap := fnutil.NewArgParser(th, args) 349 350 for i := range args { 351 s, err := ap.ToGoString(i) 352 if err != nil { 353 return nil, err 354 } 355 356 _, e := f.WriteString(s) 357 if e != nil { 358 return fileResult(th, e) 359 } 360 } 361 362 return []object.Value{_output}, nil 363 } 364 365 m := th.NewTableSize(0, 14) 366 367 m.Set(object.String("stdin"), stdin) 368 m.Set(object.String("stdout"), stdout) 369 m.Set(object.String("stderr"), stderr) 370 371 m.Set(object.String("close"), object.GoFunction(_close)) 372 m.Set(object.String("flush"), object.GoFunction(flush)) 373 m.Set(object.String("input"), object.GoFunction(input)) 374 m.Set(object.String("lines"), object.GoFunction(lines)) 375 m.Set(object.String("open"), object.GoFunction(open)) 376 m.Set(object.String("output"), object.GoFunction(output)) 377 m.Set(object.String("popen"), object.GoFunction(popen)) 378 m.Set(object.String("read"), object.GoFunction(read)) 379 m.Set(object.String("tmpfile"), object.GoFunction(tmpfile)) 380 m.Set(object.String("type"), object.GoFunction(_type)) 381 m.Set(object.String("write"), object.GoFunction(write)) 382 383 return []object.Value{m}, nil 384 }