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.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
- @vardict = vardict
31
+ @_vardict = vardict
9
32
  end
10
33
 
11
34
  def method_missing(msg, *args)
12
35
  if args.empty?
13
- @vardict.lookup_var msg.to_s
36
+ @_vardict.lookup_var msg.to_s
14
37
  else
15
- super
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(ruby_value.size)
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 == :format || msg == :couch_shell_format_string ||
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
- ::Kernel.raise ::TypeError unless @type == :array
81
- @value[i]
82
- end
83
-
84
- def to_s
85
- case @type
86
- when :object, :array
87
- ::JSON.generate(@ruby_value)
88
- when :null
89
- "null"
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
- @ruby_value.to_s
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
- ::JSON.pretty_generate(@ruby_value)
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
- when
163
+ else
102
164
  @ruby_value.to_s
103
165
  end
104
166
  end
105
167
 
106
- def couch_shell_format_string
107
- format
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