Skip to content
Open
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
41 changes: 41 additions & 0 deletions py/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func init() {
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.")
FileType.Dict["readline"] = MustNewMethod("readline", func(self Object, args Tuple, kwargs StringDict) (Object, error) {
return self.(*File).ReadLine(args, kwargs)
}, 0, "readline(size=-1, /) -> Read and return one line from the stream. If size is specified, at most size bytes will be read.\n\nThe line terminator is always b'\\n' for binary files; for text files, the newline argument to open can be used to select the line terminator(s) recognized.")
}

type FileMode int
Expand Down Expand Up @@ -143,6 +146,44 @@ func (o *File) Read(args Tuple, kwargs StringDict) (Object, error) {
return o.readResult(b)
}

func (o *File) ReadLine(args Tuple, kwargs StringDict) (Object, error) {
var size Object = None
err := UnpackTuple(args, kwargs, "readline", 0, 1, &size)
if err != nil {
return nil, err
}
limit := int64(-1)
if size != None {
pyN, ok := size.(Int)
if !ok {
return nil, ExceptionNewf(TypeError, "integer argument expected, got '%s'", size.Type().Name)
}
limit, _ = pyN.GoInt64()
}

var buf []byte
b := make([]byte, 1)
for {
if limit >= 0 && int64(len(buf)) >= limit {
break
}
n, err := o.File.Read(b)
if n > 0 {
buf = append(buf, b[0])
if b[0] == '\n' {
break
}
}
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return o.readResult(buf)
}

func (o *File) Close() (Object, error) {
_ = o.File.Close()
return None, nil
Expand Down
5 changes: 5 additions & 0 deletions py/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ var (
// Compiles a python buffer into a py.Code object.
// Returns a py.Code object or otherwise an error.
Compile func(src, srcDesc string, mode CompileMode, flags int, dont_inherit bool) (*Code, error)

// InputHook is an optional function that can be set to provide a custom input
// mechanism for the input() builtin. If nil, input() reads from sys.stdin.
// This is used by the REPL to integrate with the liner library.
InputHook func(prompt string) (string, error)
)

// RunFile resolves the given pathname, compiles as needed, executes the code in the given module, and returns the Module to indicate success.
Expand Down
6 changes: 6 additions & 0 deletions py/tests/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
b = f.read()
assert b == ''

doc = "readline"
f2 = open(__file__)
line = f2.readline()
assert line == '# Copyright 2018 The go-python Authors. All rights reserved.\n'
f2.close()

doc = "write"
assertRaises(TypeError, f.write, 42)

Expand Down
8 changes: 8 additions & 0 deletions repl/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os/user"
"path/filepath"

"github.com/go-python/gpython/py"
"github.com/go-python/gpython/repl"
"github.com/peterh/liner"
)
Expand Down Expand Up @@ -124,6 +125,13 @@ func RunREPL(replCtx *repl.REPL) error {
rl := newReadline(replCtx)
replCtx.SetUI(rl)
defer rl.Close()

// Set up InputHook for the input() builtin function
py.InputHook = func(prompt string) (string, error) {
return rl.Prompt(prompt)
}
defer func() { py.InputHook = nil }()

err := rl.ReadHistory()
if err != nil {
if !os.IsNotExist(err) {
Expand Down
83 changes: 82 additions & 1 deletion stdlib/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
package builtin

import (
"errors"
"fmt"
"io"
"math/big"
"strconv"
"strings"
"unicode/utf8"

"github.com/go-python/gpython/compile"
Expand Down Expand Up @@ -44,7 +47,7 @@ func init() {
// py.MustNewMethod("hash", builtin_hash, 0, hash_doc),
py.MustNewMethod("hex", builtin_hex, 0, hex_doc),
// py.MustNewMethod("id", builtin_id, 0, id_doc),
// py.MustNewMethod("input", builtin_input, 0, input_doc),
py.MustNewMethod("input", builtin_input, 0, input_doc),
py.MustNewMethod("isinstance", builtin_isinstance, 0, isinstance_doc),
// py.MustNewMethod("issubclass", builtin_issubclass, 0, issubclass_doc),
py.MustNewMethod("iter", builtin_iter, 0, iter_doc),
Expand Down Expand Up @@ -1181,6 +1184,84 @@ func builtin_chr(self py.Object, args py.Tuple) (py.Object, error) {
return py.String(buf[:n]), nil
}

const input_doc = `input([prompt]) -> string

Read a string from standard input. The trailing newline is stripped.
The prompt string, if given, is printed to standard output without a
trailing newline before reading input.
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.`

func builtin_input(self py.Object, args py.Tuple) (py.Object, error) {
var prompt py.Object = py.None

err := py.UnpackTuple(args, nil, "input", 0, 1, &prompt)
if err != nil {
return nil, err
}

// Use InputHook if available (e.g., in REPL mode)
if py.InputHook != nil {
promptStr := ""
if prompt != py.None {
s, ok := prompt.(py.String)
if !ok {
return nil, py.ExceptionNewf(py.TypeError, "input() prompt must be a string")
}
promptStr = string(s)
}
line, err := py.InputHook(promptStr)
if err != nil {
if errors.Is(err, io.EOF) {
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
}
return nil, err
}
return py.String(line), nil
}

sysModule, err := self.(*py.Module).Context.GetModule("sys")
if err != nil {
return nil, err
}

stdin := sysModule.Globals["stdin"]
stdout := sysModule.Globals["stdout"]

if prompt != py.None {
write, err := py.GetAttrString(stdout, "write")
if err != nil {
return nil, err
}
_, err = py.Call(write, py.Tuple{prompt}, nil)
if err != nil {
return nil, err
}

flush, err := py.GetAttrString(stdout, "flush")
if err == nil {
py.Call(flush, nil, nil)
}
}

readline, err := py.GetAttrString(stdin, "readline")
if err != nil {
return nil, err
}
result, err := py.Call(readline, nil, nil)
if err != nil {
return nil, err
}
line, ok := result.(py.String)
if !ok {
return nil, py.ExceptionNewf(py.TypeError, "object.readline() should return a str object, got %s", result.Type().Name)
}
if line == "" {
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
}
line = py.String(strings.TrimRight(string(line), "\r\n"))
return line, nil
}

const locals_doc = `locals() -> dictionary

Update and return a dictionary containing the current scope's local variables.`
Expand Down
12 changes: 12 additions & 0 deletions stdlib/builtin/tests/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,4 +469,16 @@ class C: pass
assert lib.libvar == 43
assert lib.libclass().method() == 44

doc="input"
import sys
class MockStdin:
def __init__(self, line):
self._line = line
def readline(self):
return self._line
old_stdin = sys.stdin
sys.stdin = MockStdin("hello\n")
assert input() == "hello"
sys.stdin = old_stdin

doc="finished"
Loading