couch-shell 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/couch-shell-plugin/core.rb +241 -0
- data/lib/couch-shell-plugin/core_designs.rb +24 -0
- data/lib/couch-shell-plugin/core_edit.rb +38 -0
- data/lib/couch-shell-plugin/core_lucene.rb +54 -0
- data/lib/couch-shell-plugin/core_views.rb +165 -0
- data/lib/couch-shell.rb +5 -0
- data/lib/couch-shell/eval_context.rb +26 -3
- data/lib/couch-shell/exceptions.rb +65 -0
- data/lib/couch-shell/json_value.rb +91 -19
- data/lib/couch-shell/plugin.rb +251 -0
- data/lib/couch-shell/plugin_utils.rb +88 -0
- data/lib/couch-shell/response.rb +19 -14
- data/lib/couch-shell/shell.rb +204 -415
- data/lib/couch-shell/version.rb +1 -1
- metadata +11 -3
data/lib/couch-shell.rb
CHANGED
@@ -42,6 +42,11 @@ module CouchShell
|
|
42
42
|
end
|
43
43
|
|
44
44
|
shell = Shell.new(STDIN, STDOUT, STDERR)
|
45
|
+
shell.plugin "core"
|
46
|
+
shell.plugin "core_edit"
|
47
|
+
shell.plugin "core_views"
|
48
|
+
shell.plugin "core_designs"
|
49
|
+
shell.plugin "core_lucene"
|
45
50
|
shell.execute "user #{user}" if user
|
46
51
|
shell.execute "server #{server}" if server
|
47
52
|
shell.execute "cg #{path}" if path
|
@@ -1,18 +1,41 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require "couch-shell/exceptions"
|
4
|
+
|
3
5
|
module CouchShell
|
4
6
|
|
5
7
|
class EvalContext < BasicObject
|
6
8
|
|
9
|
+
# weird trickery ahead
|
10
|
+
# we want only instance_variable_set from kernel
|
11
|
+
include ::Kernel
|
12
|
+
alias_method :_instance_variable_set, :instance_variable_set
|
13
|
+
(::Kernel.instance_methods + ::Kernel.private_instance_methods).each { |m|
|
14
|
+
# Ruby 1.9.2p0 prints a warning when undefining object_id,
|
15
|
+
# although BasicObject doesn't define it anyway, making this
|
16
|
+
# warning obsolete IMHO.
|
17
|
+
if m == :object_id
|
18
|
+
oldv = $VERBOSE
|
19
|
+
begin
|
20
|
+
$VERBOSE = nil
|
21
|
+
undef_method m
|
22
|
+
ensure
|
23
|
+
$VERBOSE = oldv
|
24
|
+
end
|
25
|
+
else
|
26
|
+
undef_method m
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
7
30
|
def initialize(vardict)
|
8
|
-
@
|
31
|
+
@_vardict = vardict
|
9
32
|
end
|
10
33
|
|
11
34
|
def method_missing(msg, *args)
|
12
35
|
if args.empty?
|
13
|
-
@
|
36
|
+
@_vardict.lookup_var msg.to_s
|
14
37
|
else
|
15
|
-
|
38
|
+
raise ShellUserError, "unexpected syntax at #{msg}"
|
16
39
|
end
|
17
40
|
end
|
18
41
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module CouchShell
|
4
|
+
|
5
|
+
class Quit < Exception
|
6
|
+
end
|
7
|
+
|
8
|
+
class ShellUserError < Exception
|
9
|
+
end
|
10
|
+
|
11
|
+
class UndefinedVariable < ShellUserError
|
12
|
+
|
13
|
+
attr_reader :varname
|
14
|
+
|
15
|
+
def initialize(varname)
|
16
|
+
@varname = varname
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class NoSuchPluginRegistered < ShellUserError
|
22
|
+
|
23
|
+
attr_reader :plugin_name
|
24
|
+
|
25
|
+
def initialize(plugin_name)
|
26
|
+
@plugin_name = plugin_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def message
|
30
|
+
"No such plugin registered: #@plugin_name"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class NoSuchCommandInPlugin < ShellUserError
|
36
|
+
|
37
|
+
attr_reader :plugin_name
|
38
|
+
attr_reader :command_name
|
39
|
+
|
40
|
+
def initialize(plugin_name, command_name)
|
41
|
+
@plugin_name = plugin_name
|
42
|
+
@command_name = command_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def message
|
46
|
+
"Plugin #@plugin_name doesn't define a #@command_name command."
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
class NoSuchCommand < ShellUserError
|
52
|
+
|
53
|
+
attr_reader :command_name
|
54
|
+
|
55
|
+
def initialize(command_name)
|
56
|
+
@command_name = command_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def message
|
60
|
+
"No such command: #@command_name"
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -4,12 +4,31 @@ require "json"
|
|
4
4
|
|
5
5
|
module CouchShell
|
6
6
|
|
7
|
+
# Wraps a Ruby data structure that represents a json value. If the wrapped
|
8
|
+
# document is of type object, members can be accessed method call syntax. To
|
9
|
+
# avoid shadowing of object members, almost all JsonValue instance methods
|
10
|
+
# end in ! or ?.
|
11
|
+
#
|
12
|
+
# j = JsonValue.wrap({"a" => 1})
|
13
|
+
# j.a # => #<JsonValue 1>
|
14
|
+
# j.unwrapped! # => {"a" => 1}
|
15
|
+
#
|
16
|
+
# Attributes can also be accessed via []
|
17
|
+
#
|
18
|
+
# j["a"] # => #<JsonValue 1>
|
19
|
+
#
|
20
|
+
# Arrays elements too
|
21
|
+
#
|
22
|
+
# j = JsonValue.wrap(["a", "b"])
|
23
|
+
# j[0] # => #<JsonValue "a">
|
24
|
+
#
|
25
|
+
# The wrapped data structure mustn't be modified.
|
7
26
|
class JsonValue < BasicObject
|
8
27
|
|
9
28
|
def self.wrap(ruby_value)
|
10
29
|
case ruby_value
|
11
30
|
when ::Hash
|
12
|
-
h = ::Hash.new
|
31
|
+
h = ::Hash.new
|
13
32
|
ruby_value.each { |k, v|
|
14
33
|
h[k] = wrap(v)
|
15
34
|
}
|
@@ -36,6 +55,14 @@ module CouchShell
|
|
36
55
|
end
|
37
56
|
end
|
38
57
|
|
58
|
+
def self.rval(obj)
|
59
|
+
if obj.respond_to?(:couch_shell_ruby_value!)
|
60
|
+
obj.couch_shell_ruby_value!
|
61
|
+
else
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
39
66
|
def initialize(value, ruby_value)
|
40
67
|
@value = value
|
41
68
|
@ruby_value = ruby_value
|
@@ -59,9 +86,32 @@ module CouchShell
|
|
59
86
|
end
|
60
87
|
end
|
61
88
|
|
89
|
+
def object?
|
90
|
+
@type == :object
|
91
|
+
end
|
92
|
+
|
93
|
+
def array?
|
94
|
+
@type == :array
|
95
|
+
end
|
96
|
+
|
97
|
+
def string?
|
98
|
+
@type == :string
|
99
|
+
end
|
100
|
+
|
101
|
+
def number?
|
102
|
+
@type == :number
|
103
|
+
end
|
104
|
+
|
105
|
+
def boolean?
|
106
|
+
@type == :boolean
|
107
|
+
end
|
108
|
+
|
109
|
+
def null?
|
110
|
+
@type == :null
|
111
|
+
end
|
112
|
+
|
62
113
|
def respond_to?(msg)
|
63
|
-
msg == :
|
64
|
-
msg == :type || msg == :ruby_value || msg == :to_s ||
|
114
|
+
msg == :couch_shell_format_string! || msg == :to_s ||
|
65
115
|
msg == :couch_shell_ruby_value! ||
|
66
116
|
(@type == :object && @value.has_key?(msg.to_s))
|
67
117
|
end
|
@@ -76,35 +126,47 @@ module CouchShell
|
|
76
126
|
super
|
77
127
|
end
|
78
128
|
|
129
|
+
# Access object member (i must be a string) or array element (i must be an
|
130
|
+
# integer). Returns a JsonValue or nil if the object member or array index
|
131
|
+
# doesn't exist.
|
79
132
|
def [](i)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
when
|
89
|
-
|
133
|
+
ri = JsonValue.rval(i)
|
134
|
+
case ri
|
135
|
+
when ::String
|
136
|
+
unless @type == :object
|
137
|
+
::Kernel.raise ::TypeError,
|
138
|
+
"string indexing only allowed for objects"
|
139
|
+
end
|
140
|
+
@value[ri]
|
141
|
+
when ::Integer
|
142
|
+
unless @type == :array
|
143
|
+
::Kernel.raise ::TypeError,
|
144
|
+
"integer indexing only allowed for arrays"
|
145
|
+
end
|
146
|
+
@value[ri]
|
90
147
|
else
|
91
|
-
|
148
|
+
::Kernel.raise ::TypeError,
|
149
|
+
"index must be string or integer"
|
92
150
|
end
|
93
151
|
end
|
94
152
|
|
95
|
-
def format
|
153
|
+
def to_s(format = false)
|
96
154
|
case @type
|
97
155
|
when :object, :array
|
98
|
-
|
156
|
+
if format
|
157
|
+
::JSON.pretty_generate(@ruby_value)
|
158
|
+
else
|
159
|
+
::JSON.generate(@ruby_value)
|
160
|
+
end
|
99
161
|
when :null
|
100
162
|
"null"
|
101
|
-
|
163
|
+
else
|
102
164
|
@ruby_value.to_s
|
103
165
|
end
|
104
166
|
end
|
105
167
|
|
106
|
-
def couch_shell_format_string
|
107
|
-
|
168
|
+
def couch_shell_format_string!
|
169
|
+
to_s true
|
108
170
|
end
|
109
171
|
|
110
172
|
def delete_attr!(name)
|
@@ -124,6 +186,7 @@ module CouchShell
|
|
124
186
|
def couch_shell_ruby_value!
|
125
187
|
@ruby_value
|
126
188
|
end
|
189
|
+
alias unwrapped! couch_shell_ruby_value!
|
127
190
|
|
128
191
|
def nil?
|
129
192
|
false
|
@@ -134,6 +197,15 @@ module CouchShell
|
|
134
197
|
@value[name]
|
135
198
|
end
|
136
199
|
|
200
|
+
def inspect
|
201
|
+
"#<JsonValue #{to_s}>"
|
202
|
+
end
|
203
|
+
|
204
|
+
def length
|
205
|
+
raise ::TypeError, "length of #@type" unless array?
|
206
|
+
@ruby_value.length
|
207
|
+
end
|
208
|
+
|
137
209
|
end
|
138
210
|
|
139
211
|
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require "unicode_utils/uppercase_char_q"
|
4
|
+
require "unicode_utils/downcase"
|
5
|
+
require "decorate"
|
6
|
+
require "couch-shell/exceptions"
|
7
|
+
require "couch-shell/plugin_utils"
|
8
|
+
|
9
|
+
module CouchShell
|
10
|
+
|
11
|
+
class VariableInfo
|
12
|
+
|
13
|
+
attr_reader :name
|
14
|
+
attr_reader :prefix
|
15
|
+
attr_reader :tags
|
16
|
+
attr_reader :doc_line
|
17
|
+
attr_reader :doc_text
|
18
|
+
attr_reader :lookup_message
|
19
|
+
attr_reader :plugin
|
20
|
+
|
21
|
+
def initialize(opts)
|
22
|
+
@name = opts[:name]
|
23
|
+
@prefix = opts[:prefix]
|
24
|
+
raise "name or prefix required" unless @name || @prefix
|
25
|
+
raise "only name OR prefix allowed" if @name && @prefix
|
26
|
+
@tags = [@name || @prefix].concat(opts[:tags] || [])
|
27
|
+
@doc_line = opts[:doc_line] or raise "doc_line required"
|
28
|
+
@doc_text = opts[:doc_text]
|
29
|
+
@lookup_message = opts[:lookup_message] or raise "lookup_message required"
|
30
|
+
@plugin = opts[:plugin] or raise "plugin required"
|
31
|
+
end
|
32
|
+
|
33
|
+
def label
|
34
|
+
@name || "#@prefix*"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class CommandInfo
|
40
|
+
|
41
|
+
attr_reader :name
|
42
|
+
attr_reader :tags
|
43
|
+
attr_reader :synopsis
|
44
|
+
attr_reader :doc_line
|
45
|
+
attr_reader :doc_text
|
46
|
+
attr_reader :execute_message
|
47
|
+
attr_reader :plugin
|
48
|
+
|
49
|
+
def initialize(opts)
|
50
|
+
@name = opts[:name] or raise "name required"
|
51
|
+
@tags = [@name].concat(opts[:tags] || [])
|
52
|
+
@synopsis = opts[:synopsis]
|
53
|
+
@doc_line = opts[:doc_line] or raise "doc_line required"
|
54
|
+
@doc_text = opts[:doc_text]
|
55
|
+
@execute_message = opts[:execute_message] or raise "execute_message required"
|
56
|
+
@plugin = opts[:plugin] or raise "plugin required"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
class PluginInfo
|
62
|
+
|
63
|
+
@by_class_map = {}
|
64
|
+
@by_name_map = {}
|
65
|
+
|
66
|
+
class << self
|
67
|
+
|
68
|
+
def class_name_to_plugin_name(class_name)
|
69
|
+
String.new.tap do |plugin_name|
|
70
|
+
was_uppercase = false
|
71
|
+
extract = class_name[/[^:]+\z/].sub(/Plugin\z/, '')
|
72
|
+
i = 0
|
73
|
+
lastcase = nil
|
74
|
+
while i < extract.length
|
75
|
+
c = extract[i]
|
76
|
+
if UnicodeUtils.uppercase_char? c
|
77
|
+
if lastcase != :upper
|
78
|
+
if i > 0 && plugin_name[-1] != "_"
|
79
|
+
plugin_name << "_"
|
80
|
+
end
|
81
|
+
lastcase = :upper
|
82
|
+
end
|
83
|
+
plugin_name << UnicodeUtils.downcase(c, nil)
|
84
|
+
elsif UnicodeUtils.lowercase_char? c
|
85
|
+
if lastcase == :upper &&
|
86
|
+
(plugin_name.length > 1 && plugin_name[-2] != "_")
|
87
|
+
plugin_name.insert(-2, "_")
|
88
|
+
end
|
89
|
+
lastcase = :lower
|
90
|
+
plugin_name << c
|
91
|
+
else
|
92
|
+
lastcase = nil
|
93
|
+
plugin_name << c
|
94
|
+
end
|
95
|
+
i = i + 1
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def register(plugin_class)
|
101
|
+
plugin_name = class_name_to_plugin_name(plugin_class.name)
|
102
|
+
unless plugin_name =~ /\A\p{Alpha}(\p{Alpha}|\p{Digit}|_)*\z/
|
103
|
+
raise "invalid plugin name #{plugin_name}"
|
104
|
+
end
|
105
|
+
pi = PluginInfo.new(plugin_name, plugin_class)
|
106
|
+
@by_class_map[plugin_class] = pi
|
107
|
+
@by_name_map[plugin_name] = pi
|
108
|
+
end
|
109
|
+
|
110
|
+
def [](class_or_name)
|
111
|
+
@by_class_map[class_or_name] || @by_name_map[class_or_name]
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
attr_reader :plugin_class
|
117
|
+
attr_reader :plugin_name
|
118
|
+
# Enumerable of VariableInfo instances.
|
119
|
+
attr_reader :variables
|
120
|
+
# Map of name/CommandInfo instances.
|
121
|
+
attr_reader :commands
|
122
|
+
|
123
|
+
def initialize(plugin_name, plugin_class)
|
124
|
+
@plugin_class = plugin_class
|
125
|
+
@plugin_name = plugin_name
|
126
|
+
@variables = []
|
127
|
+
@commands = {}
|
128
|
+
end
|
129
|
+
|
130
|
+
def register_command(ci)
|
131
|
+
raise "command #{ci.name} already registered" if @commands[ci]
|
132
|
+
@commands[ci.name] = ci
|
133
|
+
end
|
134
|
+
|
135
|
+
def register_variable(variable_info)
|
136
|
+
@variables << variable_info
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
module PluginClass
|
142
|
+
|
143
|
+
def plugin_info
|
144
|
+
PluginInfo[self]
|
145
|
+
end
|
146
|
+
|
147
|
+
def var(doc_line, opts = {})
|
148
|
+
opts[:doc_line] = doc_line
|
149
|
+
Decorate.decorate { |klass, method_name|
|
150
|
+
unless opts[:name] || opts[:prefix]
|
151
|
+
if method_name =~ /\Alookup_prefix_(.+)\z/
|
152
|
+
opts[:prefix] = $1
|
153
|
+
elsif method_name =~ /\Alookup_(.+)\z/
|
154
|
+
opts[:name] = $1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
opts[:lookup_message] = method_name
|
158
|
+
opts[:plugin] = plugin_info
|
159
|
+
plugin_info.register_variable VariableInfo.new(opts)
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
def cmd(doc_line, opts = {})
|
164
|
+
opts[:doc_line] = doc_line
|
165
|
+
Decorate.decorate { |klass, method_name|
|
166
|
+
if !opts[:name] && method_name =~ /\Aexecute_(.+)\z/
|
167
|
+
opts[:name] = $1
|
168
|
+
end
|
169
|
+
opts[:execute_message] = method_name
|
170
|
+
opts[:plugin] = plugin_info
|
171
|
+
plugin_info.register_command CommandInfo.new(opts)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
class PluginVariablesObject < BasicObject
|
178
|
+
|
179
|
+
def initialize(plugin)
|
180
|
+
@plugin = plugin
|
181
|
+
end
|
182
|
+
|
183
|
+
def method_missing(msg, *args)
|
184
|
+
unless args.empty?
|
185
|
+
::Kernel.raise ShellUserError,
|
186
|
+
"expected plugin variable lookup: #{msg}"
|
187
|
+
end
|
188
|
+
varname = msg.to_s
|
189
|
+
@plugin.plugin_info.variables.each { |vi|
|
190
|
+
begin
|
191
|
+
if vi.name && vi.name == varname
|
192
|
+
return @plugin.send vi.lookup_message
|
193
|
+
end
|
194
|
+
if vi.prefix && varname.start_with?(vi.prefix) &&
|
195
|
+
varname.length > vi.prefix.length
|
196
|
+
return @plugin.send vi.lookup_message, varname[vi.prefix.length]
|
197
|
+
end
|
198
|
+
rescue Plugin::VarNotSet => e
|
199
|
+
e.var = vi
|
200
|
+
::Kernel.raise e
|
201
|
+
end
|
202
|
+
}
|
203
|
+
::Kernel.raise ShellUserError,
|
204
|
+
"no variable #{varname} in plugin #{@plugin.plugin_name}"
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
class Plugin
|
210
|
+
|
211
|
+
include PluginUtils
|
212
|
+
|
213
|
+
def self.inherited(klass)
|
214
|
+
klass.extend PluginClass
|
215
|
+
PluginInfo.register(klass)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Do not override this method, override plugin_initialization if you
|
219
|
+
# need custom initialization logic.
|
220
|
+
def initialize(shell)
|
221
|
+
@_couch_shell_shell = shell
|
222
|
+
end
|
223
|
+
|
224
|
+
def shell
|
225
|
+
@_couch_shell_shell
|
226
|
+
end
|
227
|
+
|
228
|
+
def plugin_info
|
229
|
+
self.class.plugin_info
|
230
|
+
end
|
231
|
+
|
232
|
+
def plugin_name
|
233
|
+
plugin_info.plugin_name
|
234
|
+
end
|
235
|
+
|
236
|
+
# Called by the shell after it instantiates the plugin. The shell
|
237
|
+
# attribute will be already set.
|
238
|
+
#
|
239
|
+
# Override this method if your plugin needs custom initialization
|
240
|
+
# logic or to alter the shell behaviour beyond adding variables and
|
241
|
+
# commands. The default implementation does nothing.
|
242
|
+
def plugin_initialization
|
243
|
+
end
|
244
|
+
|
245
|
+
def variables_object
|
246
|
+
PluginVariablesObject.new(self)
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|