couch-shell 0.0.5 → 0.0.6
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/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
|