Skip to content

Commit 530fdbd

Browse files
authored
py,repl/cli,stdlib/builtin: implement input
- py: implement input - add missing getline method on py.File - add parameters to readfile and add test
1 parent 759eb83 commit 530fdbd

File tree

6 files changed

+154
-1
lines changed

6 files changed

+154
-1
lines changed

py/file.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ func init() {
3131
FileType.Dict["flush"] = MustNewMethod("flush", func(self Object) (Object, error) {
3232
return self.(*File).Flush()
3333
}, 0, "flush() -> Flush the write buffers of the stream if applicable. This does nothing for read-only and non-blocking streams.")
34+
FileType.Dict["readline"] = MustNewMethod("readline", func(self Object, args Tuple, kwargs StringDict) (Object, error) {
35+
return self.(*File).ReadLine(args, kwargs)
36+
}, 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.")
3437
}
3538

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

149+
func (o *File) ReadLine(args Tuple, kwargs StringDict) (Object, error) {
150+
var size Object = None
151+
err := UnpackTuple(args, kwargs, "readline", 0, 1, &size)
152+
if err != nil {
153+
return nil, err
154+
}
155+
limit := int64(-1)
156+
if size != None {
157+
pyN, ok := size.(Int)
158+
if !ok {
159+
return nil, ExceptionNewf(TypeError, "integer argument expected, got '%s'", size.Type().Name)
160+
}
161+
limit, _ = pyN.GoInt64()
162+
}
163+
164+
var buf []byte
165+
b := make([]byte, 1)
166+
for {
167+
if limit >= 0 && int64(len(buf)) >= limit {
168+
break
169+
}
170+
n, err := o.File.Read(b)
171+
if n > 0 {
172+
buf = append(buf, b[0])
173+
if b[0] == '\n' {
174+
break
175+
}
176+
}
177+
if err != nil {
178+
if err == io.EOF {
179+
break
180+
}
181+
return nil, err
182+
}
183+
}
184+
return o.readResult(buf)
185+
}
186+
146187
func (o *File) Close() (Object, error) {
147188
_ = o.File.Close()
148189
return None, nil

py/run.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ var (
105105
// Compiles a python buffer into a py.Code object.
106106
// Returns a py.Code object or otherwise an error.
107107
Compile func(src, srcDesc string, mode CompileMode, flags int, dont_inherit bool) (*Code, error)
108+
109+
// InputHook is an optional function that can be set to provide a custom input
110+
// mechanism for the input() builtin. If nil, input() reads from sys.stdin.
111+
// This is used by the REPL to integrate with the liner library.
112+
InputHook func(prompt string) (string, error)
108113
)
109114

110115
// RunFile resolves the given pathname, compiles as needed, executes the code in the given module, and returns the Module to indicate success.

py/tests/file.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
b = f.read()
2626
assert b == ''
2727

28+
doc = "readline"
29+
f2 = open(__file__)
30+
line = f2.readline()
31+
assert line == '# Copyright 2018 The go-python Authors. All rights reserved.\n'
32+
f2.close()
33+
2834
doc = "write"
2935
assertRaises(TypeError, f.write, 42)
3036

repl/cli/cli.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os/user"
1313
"path/filepath"
1414

15+
"github.com/go-python/gpython/py"
1516
"github.com/go-python/gpython/repl"
1617
"github.com/peterh/liner"
1718
)
@@ -124,6 +125,13 @@ func RunREPL(replCtx *repl.REPL) error {
124125
rl := newReadline(replCtx)
125126
replCtx.SetUI(rl)
126127
defer rl.Close()
128+
129+
// Set up InputHook for the input() builtin function
130+
py.InputHook = func(prompt string) (string, error) {
131+
return rl.Prompt(prompt)
132+
}
133+
defer func() { py.InputHook = nil }()
134+
127135
err := rl.ReadHistory()
128136
if err != nil {
129137
if !os.IsNotExist(err) {

stdlib/builtin/builtin.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
package builtin
77

88
import (
9+
"errors"
910
"fmt"
11+
"io"
1012
"math/big"
1113
"strconv"
14+
"strings"
1215
"unicode/utf8"
1316

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

1187+
const input_doc = `input([prompt]) -> string
1188+
1189+
Read a string from standard input. The trailing newline is stripped.
1190+
The prompt string, if given, is printed to standard output without a
1191+
trailing newline before reading input.
1192+
If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.`
1193+
1194+
func builtin_input(self py.Object, args py.Tuple) (py.Object, error) {
1195+
var prompt py.Object = py.None
1196+
1197+
err := py.UnpackTuple(args, nil, "input", 0, 1, &prompt)
1198+
if err != nil {
1199+
return nil, err
1200+
}
1201+
1202+
// Use InputHook if available (e.g., in REPL mode)
1203+
if py.InputHook != nil {
1204+
promptStr := ""
1205+
if prompt != py.None {
1206+
s, ok := prompt.(py.String)
1207+
if !ok {
1208+
return nil, py.ExceptionNewf(py.TypeError, "input() prompt must be a string")
1209+
}
1210+
promptStr = string(s)
1211+
}
1212+
line, err := py.InputHook(promptStr)
1213+
if err != nil {
1214+
if errors.Is(err, io.EOF) {
1215+
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
1216+
}
1217+
return nil, err
1218+
}
1219+
return py.String(line), nil
1220+
}
1221+
1222+
sysModule, err := self.(*py.Module).Context.GetModule("sys")
1223+
if err != nil {
1224+
return nil, err
1225+
}
1226+
1227+
stdin := sysModule.Globals["stdin"]
1228+
stdout := sysModule.Globals["stdout"]
1229+
1230+
if prompt != py.None {
1231+
write, err := py.GetAttrString(stdout, "write")
1232+
if err != nil {
1233+
return nil, err
1234+
}
1235+
_, err = py.Call(write, py.Tuple{prompt}, nil)
1236+
if err != nil {
1237+
return nil, err
1238+
}
1239+
1240+
flush, err := py.GetAttrString(stdout, "flush")
1241+
if err == nil {
1242+
py.Call(flush, nil, nil)
1243+
}
1244+
}
1245+
1246+
readline, err := py.GetAttrString(stdin, "readline")
1247+
if err != nil {
1248+
return nil, err
1249+
}
1250+
result, err := py.Call(readline, nil, nil)
1251+
if err != nil {
1252+
return nil, err
1253+
}
1254+
line, ok := result.(py.String)
1255+
if !ok {
1256+
return nil, py.ExceptionNewf(py.TypeError, "object.readline() should return a str object, got %s", result.Type().Name)
1257+
}
1258+
if line == "" {
1259+
return nil, py.ExceptionNewf(py.EOFError, "EOF when reading a line")
1260+
}
1261+
line = py.String(strings.TrimRight(string(line), "\r\n"))
1262+
return line, nil
1263+
}
1264+
11841265
const locals_doc = `locals() -> dictionary
11851266
11861267
Update and return a dictionary containing the current scope's local variables.`

stdlib/builtin/tests/builtin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,4 +502,16 @@ class C: pass
502502
assert lib.libvar == 43
503503
assert lib.libclass().method() == 44
504504

505+
doc="input"
506+
import sys
507+
class MockStdin:
508+
def __init__(self, line):
509+
self._line = line
510+
def readline(self):
511+
return self._line
512+
old_stdin = sys.stdin
513+
sys.stdin = MockStdin("hello\n")
514+
assert input() == "hello"
515+
sys.stdin = old_stdin
516+
505517
doc="finished"

0 commit comments

Comments
 (0)