From faf6c549d83d6c76fb8220ad00983d7c842da1c6 Mon Sep 17 00:00:00 2001 From: Raffaele Sena Date: Sat, 8 Sep 2018 12:20:28 -0700 Subject: [PATCH] Add support for print to file and file flush. --- builtin/builtin.go | 35 ++++++++++++++++++++++++++++------ builtin/tests/builtin.py | 39 +++++++++++++++++++++++++++++++++++--- py/file.go | 41 +++++++++++++++++++++++++++++++++++----- py/tests/file.py | 8 ++++++-- pytest/pytest.go | 1 - 5 files changed, 107 insertions(+), 17 deletions(-) diff --git a/builtin/builtin.go b/builtin/builtin.go index 4c2e9ef9..05896c78 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -6,7 +6,6 @@ package builtin import ( - "fmt" "unicode/utf8" "github.com/go-python/gpython/compile" @@ -177,7 +176,7 @@ func builtin_print(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Obje var ( sepObj py.Object = py.String(" ") endObj py.Object = py.String("\n") - file py.Object + file py.Object = py.MustGetModule("sys").Globals["stdout"] flush py.Object ) kwlist := []string{"sep", "end", "file", "flush"} @@ -187,19 +186,43 @@ func builtin_print(self py.Object, args py.Tuple, kwargs py.StringDict) (py.Obje } sep := sepObj.(py.String) end := endObj.(py.String) - // FIXME ignoring file and flush + + write, err := py.GetAttrString(file, "write") + if err != nil { + return nil, err + } + for i, v := range args { v, err := py.Str(v) if err != nil { return nil, err } - fmt.Printf("%v", v) + _, err = py.Call(write, py.Tuple{v}, nil) + if err != nil { + return nil, err + } + if i != len(args)-1 { - fmt.Print(sep) + _, err = py.Call(write, py.Tuple{sep}, nil) + if err != nil { + return nil, err + } } } - fmt.Print(end) + + _, err = py.Call(write, py.Tuple{end}, nil) + if err != nil { + return nil, err + } + + if shouldFlush, _ := py.MakeBool(flush); shouldFlush == py.True { + fflush, err := py.GetAttrString(file, "flush") + if err == nil { + return py.Call(fflush, nil, nil) + } + } + return py.None, nil } diff --git a/builtin/tests/builtin.py b/builtin/tests/builtin.py index c7c8cd18..623e4187 100644 --- a/builtin/tests/builtin.py +++ b/builtin/tests/builtin.py @@ -147,9 +147,42 @@ def gen2(): assert repr("hello") == "'hello'" doc="print" -# FIXME - need io redirection to test -#print("hello world") -#print(1,2,3,sep=",",end=",\n") +ok = False +try: + print("hello", sep=1) +except TypeError as e: + #if e.args[0] != "sep must be None or a string, not int": + # raise + ok = True +assert ok, "TypeError not raised" + +try: + print("hello", sep=" ", end=1) +except TypeError as e: + #if e.args[0] != "end must be None or a string, not int": + # raise + ok = True +assert ok, "TypeError not raised" + +try: + print("hello", sep=" ", end="\n", file=1) +except AttributeError as e: + #if e.args[0] != "'int' object has no attribute 'write'": + # raise + ok = True +assert ok, "AttributeError not raised" + +with open("testfile", "w") as f: + print("hello", "world", sep=" ", end="\n", file=f) + +with open("testfile", "r") as f: + assert f.read() == "hello world\n" + +with open("testfile", "w") as f: + print(1,2,3,sep=",",end=",\n", file=f) + +with open("testfile", "r") as f: + assert f.read() == "1,2,3,\n" doc="round" assert round(1.1) == 1.0 diff --git a/py/file.go b/py/file.go index 31ac6766..fa270865 100644 --- a/py/file.go +++ b/py/file.go @@ -16,6 +16,7 @@ import ( ) var FileType = NewType("file", `represents an open file`) +var errClosed = ExceptionNewf(ValueError, "I/O operation on closed file.") func init() { FileType.Dict["write"] = MustNewMethod("write", func(self Object, value Object) (Object, error) { @@ -28,6 +29,9 @@ func init() { 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.") + FileType.Dict["flush"] = MustNewMethod("flush", func(self Object) (Object, error) { + return self.(*File).Flush() + }, 0, "flush() -> Flush the write buffers of the stream if applicable. This does nothing for read-only and non-blocking streams.") } type FileMode int @@ -71,6 +75,9 @@ func (o *File) Write(value Object) (Object, error) { } n, err := o.File.Write(b) + if err != nil && err.(*os.PathError).Err == os.ErrClosed { + return nil, errClosed + } return Int(n), err } @@ -123,10 +130,14 @@ func (o *File) Read(args Tuple, kwargs StringDict) (Object, error) { } b, err := ioutil.ReadAll(r) - if err == io.EOF { - return o.readResult(nil) - } if err != nil { + if err == io.EOF { + return o.readResult(nil) + } + if perr, ok := err.(*os.PathError); ok && perr.Err == os.ErrClosed { + return nil, errClosed + } + return nil, err } @@ -138,6 +149,23 @@ func (o *File) Close() (Object, error) { return None, nil } +func (o *File) Flush() (Object, error) { + err := o.File.Sync() + if perr, ok := err.(*os.PathError); ok && perr.Err == os.ErrClosed { + return nil, errClosed + } + + return None, nil +} + +func (o *File) M__enter__() (Object, error) { + return o, nil +} + +func (o *File) M__exit__(exc_type, exc_value, traceback Object) (Object, error) { + return o.Close() +} + func OpenFile(filename, mode string, buffering int) (Object, error) { var fileMode FileMode var truncate bool @@ -177,8 +205,8 @@ func OpenFile(filename, mode string, buffering int) (Object, error) { return nil, ExceptionNewf(ValueError, "Must have exactly one of create/read/write/append mode and at most one plus") } + truncate = (fileMode & FileWrite) != 0 fileMode |= FileReadWrite - truncate = false case 'b': if fileMode&FileReadWrite == 0 { @@ -229,7 +257,6 @@ func OpenFile(filename, mode string, buffering int) (Object, error) { 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()) @@ -237,6 +264,8 @@ func OpenFile(filename, mode string, buffering int) (Object, error) { case os.IsNotExist(err): return nil, ExceptionNewf(FileNotFoundError, err.Error()) } + + return nil, ExceptionNewf(OSError, err.Error()) } if finfo, err := f.Stat(); err == nil { @@ -250,3 +279,5 @@ func OpenFile(filename, mode string, buffering int) (Object, error) { } // Check interface is satisfied +var _ I__enter__ = (*File)(nil) +var _ I__exit__ = (*File)(nil) diff --git a/py/tests/file.py b/py/tests/file.py index c2a12c85..898bc1bd 100644 --- a/py/tests/file.py +++ b/py/tests/file.py @@ -35,9 +35,13 @@ assert n == 5 doc = "close" -f.close() +assert f.close() == None + +assertRaises(ValueError, f.read, 1) +assertRaises(ValueError, f.write, "") +assertRaises(ValueError, f.flush) # closing a closed file should not throw an error -f.close() +assert f.close() == None doc = "finished" diff --git a/pytest/pytest.go b/pytest/pytest.go index 44c8979d..54d4b980 100644 --- a/pytest/pytest.go +++ b/pytest/pytest.go @@ -12,7 +12,6 @@ import ( "testing" _ "github.com/go-python/gpython/builtin" - _ "github.com/go-python/gpython/sys" "github.com/go-python/gpython/compile" "github.com/go-python/gpython/py" _ "github.com/go-python/gpython/sys"