Skip to content

Commit e0d3653

Browse files
committed
Initial work at implementing file methods:
- open (builtin) - for now only for read - File.read - File.write - File.close
1 parent c6c49d2 commit e0d3653

File tree

5 files changed

+171
-3
lines changed

5 files changed

+171
-3
lines changed

builtin/builtin.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func init() {
5151
// py.MustNewMethod("max", builtin_max, 0, max_doc),
5252
// py.MustNewMethod("min", builtin_min, 0, min_doc),
5353
py.MustNewMethod("next", builtin_next, 0, next_doc),
54+
py.MustNewMethod("open", builtin_open, 0, open_doc),
5455
// py.MustNewMethod("oct", builtin_oct, 0, oct_doc),
5556
py.MustNewMethod("ord", builtin_ord, 0, ord_doc),
5657
py.MustNewMethod("pow", builtin_pow, 0, pow_doc),
@@ -443,6 +444,19 @@ fromlist is not empty. Level is used to determine whether to perform
443444
absolute or relative imports. 0 is absolute while a positive number
444445
is the number of parent directories to search relative to the current module.`
445446

447+
const open_doc = `open(name[, mode[, buffering]]) -> file object
448+
449+
Open a file using the file() type, returns a file object. This is the
450+
preferred way to open a file. See file.__doc__ for further information.`
451+
452+
func builtin_open(self, obj py.Object) (py.Object, error) {
453+
if filename, ok := obj.(py.String); ok {
454+
return py.OpenFile(string(filename))
455+
} else {
456+
return nil, py.ExceptionNewf(py.TypeError, "coercing to Unicode: need string or buffer, %v found", obj.Type().Name)
457+
}
458+
}
459+
446460
const ord_doc = `ord(c) -> integer
447461
448462
Return the integer ordinal of a one-character string.`

builtin/tests/builtin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ def gen2():
135135
ok = True
136136
assert ok, "TypeError not raised"
137137

138+
doc="open"
139+
assert open(__file__) is not None
140+
138141
doc="pow"
139142
assert pow(2, 10) == 1024
140143
assert pow(2, 10, 17) == 4

py/file.go

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,126 @@
1010
package py
1111

1212
import (
13+
"io"
14+
"io/ioutil"
1315
"os"
1416
)
1517

16-
var FileType = NewTypeX("file", `represents an open file`,
17-
nil, nil)
18-
1918
type File os.File
2019

20+
var FileType = NewType("file", `represents an open file`)
21+
22+
func init() {
23+
FileType.Dict["write"] = MustNewMethod("write", func(self Object, value Object) (Object, error) {
24+
return self.(*File).Write(value)
25+
}, 0, "write(arg) -> writes the contents of arg to the file, returning the number of characters written.")
26+
27+
FileType.Dict["read"] = MustNewMethod("read", func(self Object, args Tuple, kwargs StringDict) (Object, error) {
28+
return self.(*File).Read(args, kwargs)
29+
}, 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.")
30+
FileType.Dict["close"] = MustNewMethod("close", func(self Object) (Object, error) {
31+
return self.(*File).Close()
32+
}, 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.")
33+
}
34+
2135
// Type of this object
2236
func (o *File) Type() *Type {
2337
return FileType
2438
}
2539

40+
func (o *File) Write(value Object) (Object, error) {
41+
var b []byte
42+
43+
switch v := value.(type) {
44+
// FIXME Bytearray
45+
case Bytes:
46+
b = v
47+
48+
case String:
49+
b = []byte(v)
50+
51+
default:
52+
return nil, ExceptionNewf(TypeError, "expected a string or other character buffer object")
53+
}
54+
55+
n, err := (*os.File)(o).Write(b)
56+
return Int(n), err
57+
}
58+
59+
func (o *File) readResult(b []byte) (Object, error) {
60+
var asBytes = false // default mode is "as string" - also move this in File struct
61+
62+
if b == nil {
63+
if asBytes {
64+
return Bytes{}, nil
65+
} else {
66+
return String(""), nil
67+
}
68+
}
69+
70+
if asBytes {
71+
return Bytes(b), nil
72+
} else {
73+
return String(b), nil
74+
}
75+
}
76+
77+
func (o *File) Read(args Tuple, kwargs StringDict) (Object, error) {
78+
var arg Object = None
79+
80+
err := UnpackTuple(args, kwargs, "read", 0, 1, &arg)
81+
if err != nil {
82+
return nil, err
83+
}
84+
85+
var r io.Reader = (*os.File)(o)
86+
87+
switch pyN, ok := arg.(Int); {
88+
case arg == None:
89+
// read all
90+
91+
case ok:
92+
// number of bytes to read
93+
// 0: read nothing
94+
// < 0: read all
95+
// > 0: read n
96+
n, _ := pyN.GoInt64()
97+
if n == 0 {
98+
return o.readResult(nil)
99+
}
100+
if n > 0 {
101+
r = io.LimitReader(r, n)
102+
}
103+
104+
default:
105+
// invalid type
106+
return nil, ExceptionNewf(TypeError, "read() argument 1 must be int, not %s", arg.Type().Name)
107+
}
108+
109+
b, err := ioutil.ReadAll(r)
110+
if err == io.EOF {
111+
return o.readResult(nil)
112+
}
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
return o.readResult(b)
118+
}
119+
120+
func (o *File) Close() (Object, error) {
121+
_ = (*os.File)(o).Close()
122+
return None, nil
123+
}
124+
125+
func OpenFile(filename string) (Object, error) {
126+
f, err := os.Open(filename)
127+
if err != nil {
128+
// XXX: should check for different types of errors
129+
return nil, ExceptionNewf(FileNotFoundError, err.Error())
130+
}
131+
132+
return (*File)(f), nil
133+
}
134+
26135
// Check interface is satisfied

py/tests/file.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2018 The go-python Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
from libtest import assertRaises
6+
7+
doc = "open"
8+
assertRaises(FileNotFoundError, open, "not-existent.file")
9+
10+
f = open(__file__)
11+
assert f is not None
12+
13+
doc = "read"
14+
b = f.read(12)
15+
assert b == '# Copyright '
16+
17+
b = f.read(4)
18+
assert b == '2018'
19+
20+
b = f.read()
21+
assert b != ''
22+
23+
b = f.read()
24+
assert b == ''
25+
26+
doc = "write"
27+
assertRaises(TypeError, f.write, 42)
28+
29+
# assertRaises(io.UnsupportedOperation, f.write, 'hello')
30+
31+
import sys
32+
n = sys.stdout.write('hello')
33+
assert n == 5
34+
35+
doc = "close"
36+
f.close()
37+
38+
# closing a closed file should not throw an error
39+
f.close()
40+
41+
doc = "finished"

pytest/pytest.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313

1414
_ "github.com/go-python/gpython/builtin"
15+
_ "github.com/go-python/gpython/sys"
1516
"github.com/go-python/gpython/compile"
1617
"github.com/go-python/gpython/py"
1718
"github.com/go-python/gpython/vm"

0 commit comments

Comments
 (0)