Skip to content

Commit 1949707

Browse files
diamondburnediwasaki-kenta
authored andcommitted
Improve autocompletion indexing using a radix tree. (#149)
* Switched to Radix impl for autocorrection, significantly lower heap, fixed Windows wavelet bug * Added contract path completion * Added wasm validator into spawn
1 parent 69442c3 commit 1949707

File tree

9 files changed

+644
-291
lines changed

9 files changed

+644
-291
lines changed
Lines changed: 213 additions & 242 deletions
Large diffs are not rendered by default.

cmd/wavelet/cli.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Copyright (c) 2019 Perlin
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
// this software and associated documentation files (the "Software"), to deal in
5+
// the Software without restriction, including without limitation the rights to
6+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
// the Software, and to permit persons to whom the Software is furnished to do so,
8+
// subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
20+
package main
21+
22+
import (
23+
"encoding/csv"
24+
"fmt"
25+
"io"
26+
"strings"
27+
"text/tabwriter"
28+
29+
"github.com/benpye/readline"
30+
"github.com/perlin-network/noise/skademlia"
31+
"github.com/perlin-network/wavelet"
32+
"github.com/perlin-network/wavelet/log"
33+
"github.com/rs/zerolog"
34+
"github.com/urfave/cli"
35+
)
36+
37+
const (
38+
vtRed = "\033[31m"
39+
vtReset = "\033[39m"
40+
prompt = "»»»"
41+
)
42+
43+
type CLI struct {
44+
app *cli.App
45+
rl *readline.Instance
46+
client *skademlia.Client
47+
ledger *wavelet.Ledger
48+
logger zerolog.Logger
49+
keys *skademlia.Keypair
50+
51+
completion []string
52+
}
53+
54+
func NewCLI(client *skademlia.Client, ledger *wavelet.Ledger, keys *skademlia.Keypair) (*CLI, error) {
55+
c := &CLI{
56+
client: client,
57+
ledger: ledger,
58+
logger: log.Node(),
59+
keys: keys,
60+
app: cli.NewApp(),
61+
}
62+
63+
c.app.Name = "wavelet"
64+
c.app.HideVersion = true
65+
c.app.UsageText = "command [arguments...]"
66+
c.app.CommandNotFound = func(ctx *cli.Context, s string) {
67+
c.logger.Error().
68+
Msg("Unknown command: " + s)
69+
}
70+
71+
// List of commands and their actions
72+
c.app.Commands = []cli.Command{
73+
{
74+
Name: "status",
75+
Aliases: []string{"l"},
76+
Action: a(c.status),
77+
Description: "print out information about your node",
78+
},
79+
{
80+
Name: "pay",
81+
Aliases: []string{"p"},
82+
Action: a(c.pay),
83+
Description: "pay the address an amount of PERLs",
84+
},
85+
{
86+
Name: "call",
87+
Aliases: []string{"c"},
88+
Action: a(c.call),
89+
Description: "invoke a function on a smart contract",
90+
},
91+
{
92+
Name: "find",
93+
Aliases: []string{"f"},
94+
Action: a(c.find),
95+
Description: "search for any wallet/smart contract/transaction",
96+
},
97+
{
98+
Name: "spawn",
99+
Aliases: []string{"s"},
100+
Action: a(c.spawn),
101+
Description: "test deploy a smart contract",
102+
},
103+
{
104+
Name: "deposit-gas",
105+
Aliases: []string{"g"},
106+
Action: a(c.depositGas),
107+
Description: "deposit gas to a smart contract",
108+
},
109+
{
110+
Name: "place-stake",
111+
Aliases: []string{"ps"},
112+
Action: a(c.placeStake),
113+
Description: "deposit a stake of PERLs into the network",
114+
},
115+
{
116+
Name: "withdraw-stake",
117+
Aliases: []string{"ws"},
118+
Action: a(c.withdrawStake),
119+
Description: "withdraw stake and diminish voting power",
120+
},
121+
{
122+
Name: "withdraw-reward",
123+
Aliases: []string{"wr"},
124+
Action: a(c.withdrawReward),
125+
Description: "withdraw rewards into PERLs",
126+
},
127+
{
128+
Name: "exit",
129+
Aliases: []string{"quit", ":q"},
130+
Action: a(c.exit),
131+
},
132+
}
133+
134+
// Generate the help message
135+
s := strings.Builder{}
136+
s.WriteString("Commands:\n")
137+
w := tabwriter.NewWriter(&s, 0, 0, 1, ' ', 0)
138+
139+
for _, c := range c.app.VisibleCommands() {
140+
fmt.Fprintf(w,
141+
" %s (%s) %s\t%s\n",
142+
c.Name, strings.Join(c.Aliases, ", "), c.Usage,
143+
c.Description,
144+
)
145+
}
146+
147+
w.Flush()
148+
c.app.CustomAppHelpTemplate = s.String()
149+
150+
// Add in autocompletion
151+
var completers = make(
152+
[]readline.PrefixCompleterInterface,
153+
0, len(c.app.Commands)*2+1,
154+
)
155+
156+
for _, cmd := range c.app.Commands {
157+
switch cmd.Name {
158+
case "spawn":
159+
commandAddCompleter(&completers, cmd,
160+
c.getPathCompleter())
161+
default:
162+
commandAddCompleter(&completers, cmd,
163+
c.getCompleter())
164+
}
165+
}
166+
167+
var completer = readline.NewPrefixCompleter(completers...)
168+
169+
// Make a new readline struct
170+
rl, err := readline.NewEx(&readline.Config{
171+
Prompt: vtRed + prompt + vtReset + " ",
172+
AutoComplete: completer,
173+
HistoryFile: "/tmp/wavelet-history.tmp",
174+
InterruptPrompt: "^C",
175+
EOFPrompt: "exit",
176+
HistorySearchFold: true,
177+
})
178+
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
c.rl = rl
184+
185+
log.SetWriter(
186+
log.LoggerWavelet,
187+
log.NewConsoleWriter(rl.Stdout(), log.FilterFor(
188+
log.ModuleNode,
189+
log.ModuleNetwork,
190+
log.ModuleSync,
191+
log.ModuleConsensus,
192+
log.ModuleContract,
193+
)),
194+
)
195+
196+
return c, nil
197+
}
198+
199+
func (cli *CLI) Start() {
200+
ReadLoop:
201+
for {
202+
line, err := cli.rl.Readline()
203+
switch err {
204+
case readline.ErrInterrupt:
205+
if len(line) == 0 {
206+
break ReadLoop
207+
}
208+
209+
continue ReadLoop
210+
211+
case io.EOF:
212+
break ReadLoop
213+
}
214+
215+
r := csv.NewReader(strings.NewReader(line))
216+
r.Comma = ' '
217+
218+
s, err := r.Read()
219+
if err != nil {
220+
s = strings.Fields(line)
221+
}
222+
223+
// Add an app name as $0
224+
s = append([]string{cli.app.Name}, s...)
225+
226+
if err := cli.app.Run(s); err != nil {
227+
cli.logger.Error().Err(err).
228+
Msg("Failed to run command.")
229+
}
230+
}
231+
232+
cli.rl.Close()
233+
}
234+
235+
func (cli *CLI) exit(ctx *cli.Context) {
236+
cli.rl.Close()
237+
}
238+
239+
func a(f func(*cli.Context)) func(*cli.Context) error {
240+
return func(ctx *cli.Context) error {
241+
f(ctx)
242+
return nil
243+
}
244+
}

cmd/wavelet/completion.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) 2019 Perlin
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
// this software and associated documentation files (the "Software"), to deal in
5+
// the Software without restriction, including without limitation the rights to
6+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7+
// the Software, and to permit persons to whom the Software is furnished to do so,
8+
// subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in all
11+
// copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19+
20+
package main
21+
22+
import (
23+
"os"
24+
"path/filepath"
25+
"strings"
26+
27+
"github.com/benpye/readline"
28+
"github.com/urfave/cli"
29+
)
30+
31+
func (cli *CLI) getCompleter() *readline.PrefixCompleter {
32+
return readline.PcItemDynamic(func(line string) []string {
33+
f := strings.Split(line, " ")
34+
if len(f) < 2 {
35+
return nil
36+
}
37+
38+
text := f[len(f)-1]
39+
40+
return cli.ledger.Find(text, 10)
41+
})
42+
}
43+
44+
type PathCompleter struct {
45+
*readline.PrefixCompleter
46+
}
47+
48+
func (p *PathCompleter) GetDynamicNames(line []rune) [][]rune {
49+
var path string
50+
words := strings.Split(string(line), " ")
51+
if len(words) > 1 && words[1] != "" { // has some file
52+
path = filepath.Dir(strings.Join(words[1:], " "))
53+
} else {
54+
path = "."
55+
}
56+
57+
f, err := os.Open(path)
58+
if err != nil {
59+
return nil
60+
}
61+
62+
defer f.Close()
63+
64+
files, err := f.Readdir(-1)
65+
if err != nil {
66+
return nil
67+
}
68+
69+
names := make([][]rune, 0, len(files))
70+
71+
for _, f := range files {
72+
filename := filepath.Join(path, f.Name())
73+
if f.IsDir() {
74+
filename += "/"
75+
} else {
76+
filename += " "
77+
}
78+
79+
names = append(names, []rune(filename))
80+
}
81+
82+
return names
83+
}
84+
85+
func (cli *CLI) getPathCompleter() readline.PrefixCompleterInterface {
86+
return &PathCompleter{
87+
PrefixCompleter: &readline.PrefixCompleter{
88+
Callback: func(string) []string { return nil },
89+
Dynamic: true,
90+
Children: nil,
91+
},
92+
}
93+
}
94+
95+
func joinFolder(fs []string) (p string) {
96+
for _, f := range fs {
97+
p += f + "/"
98+
}
99+
100+
return
101+
}
102+
103+
func commandAddCompleter(completers *[]readline.PrefixCompleterInterface,
104+
cmd cli.Command, completer readline.PrefixCompleterInterface) {
105+
106+
*completers = append(*completers, readline.PcItem(
107+
cmd.Name, completer,
108+
))
109+
110+
for _, alias := range cmd.Aliases {
111+
*completers = append(*completers, readline.PcItem(
112+
alias, completer,
113+
))
114+
}
115+
}

0 commit comments

Comments
 (0)