Skip to content

Commit

Permalink
Initial work at implementing file methods:
Browse files Browse the repository at this point in the history
- open (builtin)
- File.read
- File.write
- File.close
  • Loading branch information
raff authored and sbinet committed Sep 5, 2018
1 parent 09f14d0 commit 6e7b5ec
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 4 deletions.
63 changes: 63 additions & 0 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func init() {
// py.MustNewMethod("max", builtin_max, 0, max_doc),
// py.MustNewMethod("min", builtin_min, 0, min_doc),
py.MustNewMethod("next", builtin_next, 0, next_doc),
py.MustNewMethod("open", builtin_open, 0, open_doc),
// py.MustNewMethod("oct", builtin_oct, 0, oct_doc),
py.MustNewMethod("ord", builtin_ord, 0, ord_doc),
py.MustNewMethod("pow", builtin_pow, 0, pow_doc),
Expand Down Expand Up @@ -437,6 +438,68 @@ fromlist is not empty. Level is used to determine whether to perform
absolute or relative imports. 0 is absolute while a positive number
is the number of parent directories to search relative to the current module.`

const open_doc = `open(name[, mode[, buffering]]) -> file object
Open a file using the file() type, returns a file object. This is the
preferred way to open a file. See file.__doc__ for further information.`

func builtin_open(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Object, error) {
kwlist := []string{
"file",
"mode",
"buffering",
"encoding",
"errors",
"newline",
"closefd",
"opener",
}

var (
filename py.Object
mode py.Object = py.String("r")
buffering py.Object = py.Int(-1)
encoding py.Object = py.None
errors py.Object = py.None
newline py.Object = py.None
closefd py.Object = py.Bool(true)
opener py.Object = py.None
)

err := py.ParseTupleAndKeywords(args, kwargs, "s|sizzzpO:open", kwlist,
&filename,
&mode,
&buffering,
&encoding,
&errors,
&newline,
&closefd,
&opener)
if err != nil {
return nil, err
}

if encoding != py.None && encoding.(py.String) != py.String("utf-8") {
return nil, py.ExceptionNewf(py.NotImplementedError, "encoding not implemented yet")
}

if errors != py.None {
return nil, py.ExceptionNewf(py.NotImplementedError, "errors not implemented yet")
}

if newline != py.None {
return nil, py.ExceptionNewf(py.NotImplementedError, "newline not implemented yet")
}

if opener != py.None {
return nil, py.ExceptionNewf(py.NotImplementedError, "opener not implemented yet")
}

return py.OpenFile(string(filename.(py.String)),
string(mode.(py.String)),
int(buffering.(py.Int)))
}

const ord_doc = `ord(c) -> integer
Return the integer ordinal of a one-character string.`
Expand Down
3 changes: 3 additions & 0 deletions builtin/tests/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ def gen2():
ok = True
assert ok, "TypeError not raised"

doc="open"
assert open(__file__) is not None

doc="pow"
assert pow(2, 10) == 1024
assert pow(2, 10, 17) == 4
Expand Down
11 changes: 11 additions & 0 deletions py/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,12 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
switch op {
case "O":
*result = arg
case "Z", "z":
if _, ok := arg.(NoneType); ok {
*result = arg
break
}
fallthrough
case "U", "s":
if _, ok := arg.(String); !ok {
return ExceptionNewf(TypeError, "%s() argument %d must be str, not %s", name, i+1, arg.Type().Name)
Expand All @@ -462,6 +468,11 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
return ExceptionNewf(TypeError, "%s() argument %d must be int, not %s", name, i+1, arg.Type().Name)
}
*result = arg
case "p":
if _, ok := arg.(Bool); !ok {
return ExceptionNewf(TypeError, "%s() argument %d must be bool, not %s", name, i+1, arg.Type().Name)
}
*result = arg
case "d":
switch x := arg.(type) {
case Int:
Expand Down
232 changes: 229 additions & 3 deletions py/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,243 @@
package py

import (
"io"
"io/ioutil"
"os"
)

var FileType = NewTypeX("file", `represents an open file`,
nil, nil)
var FileType = NewType("file", `represents an open file`)

type File os.File
func init() {
FileType.Dict["write"] = MustNewMethod("write", func(self Object, value Object) (Object, error) {
return self.(*File).Write(value)
}, 0, "write(arg) -> writes the contents of arg to the file, returning the number of characters written.")

FileType.Dict["read"] = MustNewMethod("read", func(self Object, args Tuple, kwargs StringDict) (Object, error) {
return self.(*File).Read(args, kwargs)
}, 0, "read([size]) -> read at most size bytes, returned as a string.\n\nIf the size argument is negative or omitted, read until EOF is reached.\nNotice that when in non-blocking mode, less data than what was requested\nmay be returned, even if no size parameter was given.")
FileType.Dict["close"] = MustNewMethod("close", func(self Object) (Object, error) {
return self.(*File).Close()
}, 0, "close() -> None or (perhaps) an integer. Close the file.\n\nSets data attribute .closed to True. A closed file cannot be used for\nfurther I/O operations. close() may be called more than once without\nerror. Some kinds of file objects (for example, opened by popen())\nmay return an exit status upon closing.")
}

type FileMode int

const (
FileRead FileMode = 0x01
FileWrite FileMode = 0x02
FileText FileMode = 0x4000
FileBinary FileMode = 0x8000

FileReadWrite = FileRead + FileWrite
)

type File struct {
*os.File
FileMode
}

// Type of this object
func (o *File) Type() *Type {
return FileType
}

func (o *File) Can(mode FileMode) bool {
return o.FileMode&mode == mode
}

func (o *File) Write(value Object) (Object, error) {
var b []byte

switch v := value.(type) {
// FIXME Bytearray
case Bytes:
b = v

case String:
b = []byte(v)

default:
return nil, ExceptionNewf(TypeError, "expected a string or other character buffer object")
}

n, err := o.File.Write(b)
return Int(n), err
}

func (o *File) readResult(b []byte) (Object, error) {
if o.Can(FileBinary) {
if b != nil {
return Bytes(b), nil
}

return Bytes{}, nil
}

if b != nil {
return String(b), nil
}

return String(""), nil
}

func (o *File) Read(args Tuple, kwargs StringDict) (Object, error) {
var arg Object = None

err := UnpackTuple(args, kwargs, "read", 0, 1, &arg)
if err != nil {
return nil, err
}

var r io.Reader = o.File

switch pyN, ok := arg.(Int); {
case arg == None:
// read all

case ok:
// number of bytes to read
// 0: read nothing
// < 0: read all
// > 0: read n
n, _ := pyN.GoInt64()
if n == 0 {
return o.readResult(nil)
}
if n > 0 {
r = io.LimitReader(r, n)
}

default:
// invalid type
return nil, ExceptionNewf(TypeError, "read() argument 1 must be int, not %s", arg.Type().Name)
}

b, err := ioutil.ReadAll(r)
if err == io.EOF {
return o.readResult(nil)
}
if err != nil {
return nil, err
}

return o.readResult(b)
}

func (o *File) Close() (Object, error) {
_ = o.File.Close()
return None, nil
}

func OpenFile(filename, mode string, buffering int) (Object, error) {
var fileMode FileMode
var truncate bool
var exclusive bool

for _, m := range mode {
switch m {
case 'r':
if fileMode&FileReadWrite != 0 {
return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode")
}
fileMode |= FileRead

case 'w':
if fileMode&FileReadWrite != 0 {
return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode")
}
fileMode |= FileWrite
truncate = true

case 'x':
if fileMode&FileReadWrite != 0 {
return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode")
}
fileMode |= FileWrite
exclusive = true

case 'a':
if fileMode&FileReadWrite != 0 {
return nil, ExceptionNewf(ValueError, "must have exactly one of create/read/write/append mode")
}
fileMode |= FileWrite
truncate = false

case '+':
if fileMode&FileReadWrite == 0 {
return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus")
}

fileMode |= FileReadWrite
truncate = false

case 'b':
if fileMode&FileReadWrite == 0 {
return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus")
}

if fileMode&FileText != 0 {
return nil, ExceptionNewf(ValueError, "can't have text and binary mode at once")
}

fileMode |= FileBinary

case 't':
if fileMode&FileReadWrite == 0 {
return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus")
}

if fileMode&FileBinary != 0 {
return nil, ExceptionNewf(ValueError, "can't have text and binary mode at once")
}

fileMode |= FileText
}
}

var fmode int

switch fileMode & FileReadWrite {
case FileReadWrite:
fmode = os.O_RDWR

case FileRead:
fmode = os.O_RDONLY

case FileWrite:
fmode = os.O_WRONLY
}

if exclusive {
fmode |= os.O_EXCL
}

if truncate {
fmode |= os.O_CREATE | os.O_TRUNC
} else {
fmode |= os.O_APPEND
}

f, err := os.OpenFile(filename, fmode, 0666)
if err != nil {
// XXX: should check for different types of errors
switch {
case os.IsExist(err):
return nil, ExceptionNewf(FileExistsError, err.Error())

case os.IsNotExist(err):
return nil, ExceptionNewf(FileNotFoundError, err.Error())
}
}

if finfo, err := f.Stat(); err == nil {
if finfo.IsDir() {
f.Close()
return nil, ExceptionNewf(IsADirectoryError, "Is a directory: '%s'", filename)
}
}

return &File{f, fileMode}, nil
}

// Check interface is satisfied
Loading

0 comments on commit 6e7b5ec

Please sign in to comment.