treerepl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +21 -0
- data/README +11 -0
- data/lib/treerepl.rb +30 -0
- data/lib/treerepl/cmds.rb +156 -0
- data/lib/treerepl/repl.rb +149 -0
- data/lib/treerepl/tree.rb +71 -0
- metadata +73 -0
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011 Jeffrey Palm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Provides a way to create command line itnerfaces for web services with
|
|
2
|
+
inherent tree-like structure. There are two examples:
|
|
3
|
+
|
|
4
|
+
- githubrepl
|
|
5
|
+
- rdiorepl
|
|
6
|
+
|
|
7
|
+
They both require some other libraries. The basic idea is this:
|
|
8
|
+
|
|
9
|
+
1. Define the tree model for your service
|
|
10
|
+
2. Define the mapping from tree to service
|
|
11
|
+
3. See the example and go from there
|
data/lib/treerepl.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
|
|
2
|
+
|
|
3
|
+
# stdlib
|
|
4
|
+
require 'logger'
|
|
5
|
+
|
|
6
|
+
# internal requires
|
|
7
|
+
require 'treerepl/tree'
|
|
8
|
+
require 'treerepl/cmds'
|
|
9
|
+
require 'treerepl/repl'
|
|
10
|
+
|
|
11
|
+
module TreeRepl
|
|
12
|
+
VERSION = '0.0.1'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
attr_accessor :debug
|
|
17
|
+
attr_accessor :logger
|
|
18
|
+
def log(str)
|
|
19
|
+
logger.debug { str }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
self.debug = false
|
|
23
|
+
|
|
24
|
+
@logger ||= ::Logger.new(STDERR)
|
|
25
|
+
|
|
26
|
+
def self.version
|
|
27
|
+
VERSION
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
module TreeRepl
|
|
2
|
+
|
|
3
|
+
class Cmd
|
|
4
|
+
attr_reader :repl,:name
|
|
5
|
+
def initialize(repl,name)
|
|
6
|
+
@repl = repl
|
|
7
|
+
@name = name
|
|
8
|
+
end
|
|
9
|
+
def help
|
|
10
|
+
raise 'Implement help()'
|
|
11
|
+
end
|
|
12
|
+
def exec(args)
|
|
13
|
+
raise 'Implement exec(string[])'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Ls < Cmd
|
|
18
|
+
def initialize(repl)
|
|
19
|
+
super(repl,'ls')
|
|
20
|
+
end
|
|
21
|
+
def exec(args)
|
|
22
|
+
def ls_print(n,prefix=nil)
|
|
23
|
+
def pr(s,prefix)
|
|
24
|
+
if prefix
|
|
25
|
+
puts File.join prefix,s
|
|
26
|
+
else
|
|
27
|
+
puts s
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
puts if prefix
|
|
31
|
+
['.','..'].each do |s|
|
|
32
|
+
pr s,prefix
|
|
33
|
+
end
|
|
34
|
+
n.children.each do |node|
|
|
35
|
+
pr node.name,prefix
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
args = ['.'] if args.empty?
|
|
39
|
+
args.uniq!
|
|
40
|
+
args.each do |path|
|
|
41
|
+
n = repl.eval_path repl.cur,path
|
|
42
|
+
if n
|
|
43
|
+
prefix = args.length > 1 ? path : nil
|
|
44
|
+
ls_print n,prefix
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
def help
|
|
49
|
+
'List directory'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class Cd < Cmd
|
|
54
|
+
def initialize(repl)
|
|
55
|
+
super(repl,'cd')
|
|
56
|
+
end
|
|
57
|
+
def exec(args)
|
|
58
|
+
args = ['/'] if args.empty?
|
|
59
|
+
path = args.join ' '
|
|
60
|
+
n = repl.eval_path repl.cur,path
|
|
61
|
+
repl.cur = n if n
|
|
62
|
+
end
|
|
63
|
+
def help
|
|
64
|
+
'Change directory'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Pwd < Cmd
|
|
69
|
+
def initialize(repl)
|
|
70
|
+
super(repl,'pwd')
|
|
71
|
+
end
|
|
72
|
+
def exec(args)
|
|
73
|
+
puts cwd
|
|
74
|
+
end
|
|
75
|
+
def help
|
|
76
|
+
'Print the current working directory'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Help < Cmd
|
|
81
|
+
def initialize(repl)
|
|
82
|
+
super(repl,'help')
|
|
83
|
+
end
|
|
84
|
+
def help
|
|
85
|
+
'Print this message'
|
|
86
|
+
end
|
|
87
|
+
def exec(args)
|
|
88
|
+
puts 'Commands:'
|
|
89
|
+
repl.commands.sort {|a,b| a.name <=> b.name}.each do |cmd|
|
|
90
|
+
printf " %-10s %s\n",cmd.name,cmd.help
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class Quit < Cmd
|
|
96
|
+
def initialize(repl)
|
|
97
|
+
super(repl,'quit')
|
|
98
|
+
end
|
|
99
|
+
def help
|
|
100
|
+
'Quit'
|
|
101
|
+
end
|
|
102
|
+
def exec(args)
|
|
103
|
+
exit 0
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
class Use < Cmd
|
|
108
|
+
def initialize(repl)
|
|
109
|
+
super(repl,'use')
|
|
110
|
+
end
|
|
111
|
+
def help
|
|
112
|
+
'Start exploring a certain user'
|
|
113
|
+
end
|
|
114
|
+
def exec(args)
|
|
115
|
+
if args.empty?
|
|
116
|
+
STDERR.puts 'Missing required argument'
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
username = args[0]
|
|
120
|
+
user = nil
|
|
121
|
+
begin
|
|
122
|
+
user = repl.finder.call username
|
|
123
|
+
rescue Exception => e
|
|
124
|
+
STDERR.puts "Could not create user for name: #{username}"
|
|
125
|
+
STDERR.puts e
|
|
126
|
+
end
|
|
127
|
+
if user
|
|
128
|
+
repl.use repl.root_class.new user
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
class Cat < Cmd
|
|
134
|
+
def initialize(repl)
|
|
135
|
+
super(repl,'cat')
|
|
136
|
+
end
|
|
137
|
+
def help
|
|
138
|
+
'Shows the contents of whatever it is you selected'
|
|
139
|
+
end
|
|
140
|
+
def exec(args)
|
|
141
|
+
if args.empty?
|
|
142
|
+
STDERR.put 'Required argument'
|
|
143
|
+
return
|
|
144
|
+
end
|
|
145
|
+
args.each do |arg|
|
|
146
|
+
n = repl.eval_path repl.cur,arg
|
|
147
|
+
if n.kind_of? NamedNode
|
|
148
|
+
p n.obj
|
|
149
|
+
else
|
|
150
|
+
STDERR.puts "Uh, not with this node #{n}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module TreeRepl
|
|
2
|
+
|
|
3
|
+
class Repl
|
|
4
|
+
|
|
5
|
+
attr_reader :root
|
|
6
|
+
attr_accessor :commands
|
|
7
|
+
attr_accessor :cur
|
|
8
|
+
attr_accessor :root_class
|
|
9
|
+
|
|
10
|
+
# String -> TreeNode
|
|
11
|
+
attr_accessor :finder
|
|
12
|
+
|
|
13
|
+
def initialize(root_class)
|
|
14
|
+
@root_class = root_class
|
|
15
|
+
@finder = nil
|
|
16
|
+
@commands = [Ls,Cd,Pwd,Use,Help,Quit,Cat].map {|cls| cls.new self}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_command(command_class)
|
|
20
|
+
@commands << command_class.new(self)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# ----------------------------------------------------------------------
|
|
24
|
+
# Main
|
|
25
|
+
# ----------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
# TreeNode -> Void
|
|
28
|
+
def main(root)
|
|
29
|
+
use root
|
|
30
|
+
strings2cmds = {}
|
|
31
|
+
commands.each do |cmd|
|
|
32
|
+
strings2cmds[cmd.name] = cmd
|
|
33
|
+
end
|
|
34
|
+
def loop(strings2cmds)
|
|
35
|
+
print cwd + '> '
|
|
36
|
+
orig_line = STDIN.readline.strip
|
|
37
|
+
line = orig_line.split(/ /)
|
|
38
|
+
return false if line.empty?
|
|
39
|
+
str = line.shift.downcase
|
|
40
|
+
return true if str == 'quit'
|
|
41
|
+
if not cur
|
|
42
|
+
if str != 'use' and str != 'help'
|
|
43
|
+
STDERR.puts 'You must set a user with \'use\' before anything else.'
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
args = line
|
|
48
|
+
cmd = strings2cmds[str]
|
|
49
|
+
if cmd
|
|
50
|
+
begin
|
|
51
|
+
cmd.exec args
|
|
52
|
+
rescue Exception => e
|
|
53
|
+
STDERR.puts 'Trouble executing: ' + orig_line
|
|
54
|
+
raise e
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
STDERR.puts 'Unknown command: ' + str
|
|
58
|
+
end
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
while true
|
|
62
|
+
break if loop strings2cmds
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ----------------------------------------------------------------------
|
|
67
|
+
# 'Private'
|
|
68
|
+
# ----------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def use(user)
|
|
71
|
+
if user
|
|
72
|
+
@cur = user
|
|
73
|
+
@root = user
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# TreeNode string -> TreeNode
|
|
78
|
+
def eval_path(node,path)
|
|
79
|
+
path = '.' if not path or path == ''
|
|
80
|
+
if path =~ /^\//
|
|
81
|
+
args = ['/'] + path.gsub(/^\/+/,'').split(/\//)
|
|
82
|
+
else
|
|
83
|
+
args = path.split /\//
|
|
84
|
+
end
|
|
85
|
+
trav = node
|
|
86
|
+
args.each do |arg|
|
|
87
|
+
case arg
|
|
88
|
+
when '.'
|
|
89
|
+
trav = trav
|
|
90
|
+
when '..'
|
|
91
|
+
trav = trav.parent
|
|
92
|
+
when '/'
|
|
93
|
+
trav = root
|
|
94
|
+
else
|
|
95
|
+
found = false
|
|
96
|
+
trav.children.each do |n|
|
|
97
|
+
if n.name == arg
|
|
98
|
+
trav = n
|
|
99
|
+
found = true
|
|
100
|
+
break
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
if not found
|
|
104
|
+
STDERR.puts 'Invalid path: ' + path
|
|
105
|
+
return nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
return trav
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# void -> string
|
|
113
|
+
def cwd
|
|
114
|
+
path(root,cur).map {|n| n.name}.join('/')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# TreeNode string -> TreeNode
|
|
118
|
+
def node_relative_to(node,name)
|
|
119
|
+
if name == '.'
|
|
120
|
+
return node
|
|
121
|
+
elsif name == '..'
|
|
122
|
+
return node.parent
|
|
123
|
+
elsif name == '/'
|
|
124
|
+
return root
|
|
125
|
+
else
|
|
126
|
+
node.children.each do |n|
|
|
127
|
+
if name == n.name
|
|
128
|
+
return n
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
return nil
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# TreeNode TreeNode -> TreeNode[]
|
|
136
|
+
def path(from,dest)
|
|
137
|
+
res = []
|
|
138
|
+
trav = dest
|
|
139
|
+
while trav
|
|
140
|
+
res.push trav
|
|
141
|
+
break if trav == from
|
|
142
|
+
trav = trav.parent
|
|
143
|
+
end
|
|
144
|
+
return res.reverse
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module TreeRepl
|
|
2
|
+
|
|
3
|
+
class TreeNode
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :parent, :leaf
|
|
6
|
+
|
|
7
|
+
@kids = nil
|
|
8
|
+
|
|
9
|
+
def find_children
|
|
10
|
+
raise 'Implement find_children'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def children(force=false)
|
|
14
|
+
if force or not @kids
|
|
15
|
+
@kids = find_children || []
|
|
16
|
+
@kids.each do |node|
|
|
17
|
+
node.parent = self
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
@kids
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class CmdNode < TreeNode
|
|
26
|
+
attr_reader :cmd
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
attr_accessor :classes2tree_nodes
|
|
30
|
+
end
|
|
31
|
+
self.classes2tree_nodes = {}
|
|
32
|
+
|
|
33
|
+
def initialize(obj,cmd,name)
|
|
34
|
+
@obj = obj
|
|
35
|
+
@cmd = cmd
|
|
36
|
+
@name = name || cmd.to_s
|
|
37
|
+
end
|
|
38
|
+
def find_children
|
|
39
|
+
@obj.send(@cmd).map {|v| CmdNode.classes2tree_nodes[v.class].new v}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class NamedNode < TreeNode
|
|
44
|
+
attr_reader :obj
|
|
45
|
+
attr_reader :name
|
|
46
|
+
def initialize(obj,name)
|
|
47
|
+
@obj = obj
|
|
48
|
+
@name = name
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Creates the children from the provided symbols defined by
|
|
52
|
+
# implementing symbols()
|
|
53
|
+
def find_children
|
|
54
|
+
lst = symbols
|
|
55
|
+
if lst.is_a? Hash
|
|
56
|
+
lst.keys.sort.map {|n| CmdNode.new @obj,lst[n],n}
|
|
57
|
+
else
|
|
58
|
+
lst.sort {|a,b| a.to_s <=> b.to_s}.map {|n| CmdNode.new @obj,n,nil}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# -> [symbol] | {string => symbol}
|
|
63
|
+
#
|
|
64
|
+
# The symbols (with possible) mapped names to use for children
|
|
65
|
+
#
|
|
66
|
+
def symbols
|
|
67
|
+
raise 'Implement symbols()'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: treerepl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 29
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 0
|
|
9
|
+
- 1
|
|
10
|
+
version: 0.0.1
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Jeffrey Palm
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-03-19 00:00:00 -04:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies: []
|
|
21
|
+
|
|
22
|
+
description: System for accessing web services for tree-like things via the repl
|
|
23
|
+
email: jeff@jeffpalm.com
|
|
24
|
+
executables: []
|
|
25
|
+
|
|
26
|
+
extensions: []
|
|
27
|
+
|
|
28
|
+
extra_rdoc_files:
|
|
29
|
+
- README
|
|
30
|
+
- LICENSE
|
|
31
|
+
files:
|
|
32
|
+
- README
|
|
33
|
+
- LICENSE
|
|
34
|
+
- lib/treerepl.rb
|
|
35
|
+
- lib/treerepl/tree.rb
|
|
36
|
+
- lib/treerepl/repl.rb
|
|
37
|
+
- lib/treerepl/cmds.rb
|
|
38
|
+
has_rdoc: true
|
|
39
|
+
homepage: http://github.com/spudtrooper/treerepl
|
|
40
|
+
licenses: []
|
|
41
|
+
|
|
42
|
+
post_install_message:
|
|
43
|
+
rdoc_options:
|
|
44
|
+
- --charset=UTF-8
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
none: false
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
hash: 3
|
|
53
|
+
segments:
|
|
54
|
+
- 0
|
|
55
|
+
version: "0"
|
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
hash: 3
|
|
62
|
+
segments:
|
|
63
|
+
- 0
|
|
64
|
+
version: "0"
|
|
65
|
+
requirements: []
|
|
66
|
+
|
|
67
|
+
rubyforge_project: "%NAME"
|
|
68
|
+
rubygems_version: 1.6.1
|
|
69
|
+
signing_key:
|
|
70
|
+
specification_version: 2
|
|
71
|
+
summary: Tree Repl
|
|
72
|
+
test_files: []
|
|
73
|
+
|