Skip to content

Commit 08903fc

Browse files
committed
Factor REPL into CLI part and agnostic part and add tests
This is to facilitate reuse of the REPL. It also makes testing possible.
1 parent d13383c commit 08903fc

File tree

4 files changed

+350
-138
lines changed

4 files changed

+350
-138
lines changed

main.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
"runtime/pprof"
1313

1414
_ "github.com/go-python/gpython/builtin"
15-
"github.com/go-python/gpython/repl"
15+
"github.com/go-python/gpython/repl/cli"
16+
1617
//_ "github.com/go-python/gpython/importlib"
1718
"io/ioutil"
1819
"log"
@@ -62,7 +63,7 @@ func main() {
6263
args := flag.Args()
6364
py.MustGetModule("sys").Globals["argv"] = pysys.MakeArgv(args)
6465
if len(args) == 0 {
65-
repl.Run()
66+
cli.RunREPL()
6667
return
6768
}
6869
prog := args[0]

repl/cli/cli.go

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
// Read Eval Print Loop for CLI
6+
package cli
7+
8+
import (
9+
"fmt"
10+
"io"
11+
"os"
12+
"os/user"
13+
"path/filepath"
14+
15+
"github.com/go-python/gpython/py"
16+
"github.com/go-python/gpython/repl"
17+
"github.com/peterh/liner"
18+
)
19+
20+
const HistoryFileName = ".gpyhistory"
21+
22+
// homeDirectory finds the home directory or returns ""
23+
func homeDirectory() string {
24+
usr, err := user.Current()
25+
if err == nil {
26+
return usr.HomeDir
27+
}
28+
// Fall back to reading $HOME - work around user.Current() not
29+
// working for cross compiled binaries on OSX.
30+
// https://github.com/golang/go/issues/6376
31+
return os.Getenv("HOME")
32+
}
33+
34+
// Holds state for readline services
35+
type readline struct {
36+
*liner.State
37+
repl *repl.REPL
38+
historyFile string
39+
module *py.Module
40+
prompt string
41+
}
42+
43+
// newReadline creates a new instance of readline
44+
func newReadline(repl *repl.REPL) *readline {
45+
rl := &readline{
46+
State: liner.NewLiner(),
47+
repl: repl,
48+
}
49+
home := homeDirectory()
50+
if home != "" {
51+
rl.historyFile = filepath.Join(home, HistoryFileName)
52+
}
53+
rl.SetTabCompletionStyle(liner.TabPrints)
54+
rl.SetWordCompleter(rl.Completer)
55+
return rl
56+
}
57+
58+
// readHistory reads the history into the term
59+
func (rl *readline) ReadHistory() error {
60+
f, err := os.Open(rl.historyFile)
61+
if err != nil {
62+
return err
63+
}
64+
defer f.Close()
65+
_, err = rl.State.ReadHistory(f)
66+
if err != nil {
67+
return err
68+
}
69+
return nil
70+
}
71+
72+
// writeHistory writes the history from the term
73+
func (rl *readline) WriteHistory() error {
74+
f, err := os.OpenFile(rl.historyFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
75+
if err != nil {
76+
return err
77+
}
78+
defer f.Close()
79+
_, err = rl.State.WriteHistory(f)
80+
if err != nil {
81+
return err
82+
}
83+
return nil
84+
}
85+
86+
// Close the readline and write history
87+
func (rl *readline) Close() error {
88+
err := rl.State.Close()
89+
if err != nil {
90+
return err
91+
}
92+
if rl.historyFile != "" {
93+
err := rl.WriteHistory()
94+
if err != nil {
95+
return err
96+
}
97+
}
98+
return nil
99+
}
100+
101+
// Completer takes the currently edited line with the cursor
102+
// position and returns the completion candidates for the partial word
103+
// to be completed. If the line is "Hello, wo!!!" and the cursor is
104+
// before the first '!', ("Hello, wo!!!", 9) is passed to the
105+
// completer which may returns ("Hello, ", {"world", "Word"}, "!!!")
106+
// to have "Hello, world!!!".
107+
func (rl *readline) Completer(line string, pos int) (head string, completions []string, tail string) {
108+
return rl.repl.Completer(line, pos)
109+
}
110+
111+
// SetPrompt sets the current terminal prompt
112+
func (rl *readline) SetPrompt(prompt string) {
113+
rl.prompt = prompt
114+
}
115+
116+
// Print prints the output
117+
func (rl *readline) Print(out string) {
118+
_, _ = os.Stdout.WriteString(out + "\n")
119+
}
120+
121+
// RunREPL starts the REPL loop
122+
func RunREPL() {
123+
repl := repl.New()
124+
rl := newReadline(repl)
125+
repl.SetUI(rl)
126+
defer rl.Close()
127+
err := rl.ReadHistory()
128+
if err != nil {
129+
fmt.Printf("Failed to open history: %v\n", err)
130+
}
131+
132+
fmt.Printf("Gpython 3.4.0\n")
133+
134+
for {
135+
line, err := rl.Prompt(rl.prompt)
136+
if err != nil {
137+
if err == io.EOF {
138+
fmt.Printf("\n")
139+
break
140+
}
141+
fmt.Printf("Problem reading line: %v\n", err)
142+
continue
143+
}
144+
if line != "" {
145+
rl.AppendHistory(line)
146+
}
147+
rl.repl.Run(line)
148+
}
149+
}

0 commit comments

Comments
 (0)