Skip to content

Initial work at implementing file methods #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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