elasticshell 0.0.2 → 0.0.4

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.
Files changed (52) hide show
  1. data/README.rdoc +119 -55
  2. data/VERSION +1 -1
  3. data/bin/es +0 -2
  4. data/lib/elasticshell.rb +30 -25
  5. data/lib/elasticshell/client.rb +34 -13
  6. data/lib/elasticshell/command.rb +14 -170
  7. data/lib/elasticshell/commands/blank.rb +14 -0
  8. data/lib/elasticshell/commands/cd.rb +27 -0
  9. data/lib/elasticshell/commands/connect.rb +31 -0
  10. data/lib/elasticshell/commands/df.rb +23 -0
  11. data/lib/elasticshell/commands/help.rb +77 -0
  12. data/lib/elasticshell/commands/ls.rb +66 -0
  13. data/lib/elasticshell/commands/pretty.rb +21 -0
  14. data/lib/elasticshell/commands/pwd.rb +17 -0
  15. data/lib/elasticshell/commands/request.rb +81 -0
  16. data/lib/elasticshell/commands/request_parser.rb +77 -0
  17. data/lib/elasticshell/commands/set_verb.rb +23 -0
  18. data/lib/elasticshell/commands/unknown.rb +17 -0
  19. data/lib/elasticshell/scopes.rb +81 -50
  20. data/lib/elasticshell/scopes/cluster.rb +8 -16
  21. data/lib/elasticshell/scopes/global.rb +22 -23
  22. data/lib/elasticshell/scopes/index.rb +35 -29
  23. data/lib/elasticshell/scopes/mapping.rb +8 -28
  24. data/lib/elasticshell/scopes/nodes.rb +6 -15
  25. data/lib/elasticshell/shell.rb +155 -93
  26. data/lib/elasticshell/utils.rb +10 -0
  27. data/lib/elasticshell/{error.rb → utils/error.rb} +1 -0
  28. data/lib/elasticshell/utils/has_name.rb +14 -0
  29. data/lib/elasticshell/utils/has_verb.rb +15 -0
  30. data/lib/elasticshell/{log.rb → utils/log.rb} +1 -1
  31. data/lib/elasticshell/utils/recognizes_verb.rb +25 -0
  32. data/spec/elasticshell/client_spec.rb +55 -0
  33. data/spec/elasticshell/commands/blank_spec.rb +14 -0
  34. data/spec/elasticshell/commands/cd_spec.rb +23 -0
  35. data/spec/elasticshell/commands/connect_spec.rb +21 -0
  36. data/spec/elasticshell/commands/df_spec.rb +18 -0
  37. data/spec/elasticshell/commands/help_spec.rb +21 -0
  38. data/spec/elasticshell/commands/ls_spec.rb +24 -0
  39. data/spec/elasticshell/commands/pretty_spec.rb +19 -0
  40. data/spec/elasticshell/commands/pwd_spec.rb +14 -0
  41. data/spec/elasticshell/commands/request_parser_spec.rb +4 -0
  42. data/spec/elasticshell/commands/request_spec.rb +60 -0
  43. data/spec/elasticshell/commands/set_verb_spec.rb +14 -0
  44. data/spec/elasticshell/scopes_spec.rb +79 -0
  45. data/spec/elasticshell/shell_spec.rb +19 -0
  46. data/spec/elasticshell/utils/has_name_spec.rb +15 -0
  47. data/spec/elasticshell/utils/has_verb_spec.rb +24 -0
  48. data/spec/elasticshell/utils/recognizes_verb_spec.rb +23 -0
  49. data/spec/spec_helper.rb +4 -5
  50. data/spec/support/data.yml +45 -0
  51. data/spec/support/fake_output.rb +27 -0
  52. metadata +73 -4
@@ -1,5 +1,3 @@
1
- require 'elasticshell/scopes'
2
-
3
1
  module Elasticshell
4
2
 
5
3
  module Scopes
@@ -10,25 +8,18 @@ module Elasticshell
10
8
  super("/_nodes", options)
11
9
  end
12
10
 
13
- def commands
14
- {
15
- 'info' => "Retreive info about the cluster's ndoes.",
16
- 'stats' => "Retreive stats for the cluter's nodes.",
11
+ def self.requests
12
+ @requests ||= {
13
+ "GET" => {
14
+ 'info' => "Retreive info about the cluster's nodes.",
15
+ 'stats' => "Retreive stats for the cluter's nodes.",
16
+ }
17
17
  }
18
18
  end
19
19
 
20
20
  def exists?
21
21
  true
22
22
  end
23
-
24
- def execute command, shell
25
- case
26
- when command?(command)
27
- shell.request(:get, :index => '_nodes')
28
- else
29
- super(command, shell)
30
- end
31
- end
32
23
 
33
24
  end
34
25
  end
@@ -1,23 +1,68 @@
1
1
  require 'readline'
2
2
  require 'uri'
3
3
 
4
- require 'elasticshell/command'
5
- require 'elasticshell/scopes'
6
- require 'elasticshell/client'
7
-
8
4
  module Elasticshell
9
5
 
10
6
  class Shell
11
7
 
12
- VERBS = %w[GET POST PUT DELETE]
8
+ Settings.define(:passive_http_verb_format,
9
+ :description => "Format string for the passive HTTP verb GET. The string `%v' will be replaced by the verb.",
10
+ :default => "\e[34m%v",
11
+ :internal => true)
12
+
13
+ Settings.define(:active_http_verb_format,
14
+ :description => "Format string for the active HTTP verbs PUT, POST, and DELETE. The string `%v' will be replaced by the verb.",
15
+ :default => "\e[31m%v",
16
+ :internal => true)
17
+
18
+ Settings.define(:existing_scope_format,
19
+ :description => "Format string for an existing scope. The string `%s' will be replaced by the scope name.",
20
+ :default => "\e[32m%s",
21
+ :internal => true)
22
+
23
+ Settings.define(:missing_scope_format,
24
+ :description => "Format string for scope which doesn't exist. The string `%s' will be replaced by the scope name.",
25
+ :default => "\e[33m%s",
26
+ :internal => true)
27
+
28
+ Settings.define(:prompt_format,
29
+ :description => "Format string for the prompt. The strings `%v' and `%s' will be replaced by the (already-formatted) HTTP verb and current scope name.",
30
+ :default => "\e[1m%v %s$ \e[0m",
31
+ :internal => true)
32
+
33
+ Settings.define(:pretty_prompt_format,
34
+ :description => "Format string for the prompt when in pretty-mode. The strings `%v' and `%s' will be replaced by the (already-formatted) HTTP verb and current scope name.",
35
+ :default => "\e[1m%v %s$$ \e[0m",
36
+ :internal => true)
37
+
38
+ Settings.define(:scope_long_format,
39
+ :description => "Format string for displaying a scope in a long (`ll') listing. The string `%s' will be replaced by the scope name.",
40
+ :default => "s \e[32m%s\e[0m",
41
+ :internal => true)
42
+
43
+ Settings.define(:index_long_format,
44
+ :description => "Format string for displaying an index in a long (`ll') listing. The string `%n' will be replaced by the index name, `%T' with the total number of shards, `%S' with the number of successful shards, `%F' with the number of failed shards, `%s' with the size in bytes, `%h' with the human-readable size",
45
+ :default => "d %S/%F %h \e[32m%n\e[0m",
46
+ :internal => true)
13
47
 
14
- attr_accessor :client, :input, :command, :state, :only
48
+ Settings.define(:request_long_format,
49
+ :description => "Format string for displaying a request in a long (`ll') listing. The string `%r' will be replaced by the request name.",
50
+ :default => "- %r",
51
+ :internal => true)
52
+
53
+ Settings.define(:scope_format,
54
+ :description => "Format string for displaying a scope in a listing. The string `%s' will be replaced by the scope name.",
55
+ :default => "\e[32m%s\e[0m",
56
+ :internal => true)
57
+
58
+ Settings.define(:request_format,
59
+ :description => "Format string for displaying a request in a listing. The string `%r' will be replaced by the request name.",
60
+ :default => "%r",
61
+ :internal => true)
62
+
63
+ include Elasticshell::HasVerb
15
64
 
16
- attr_reader :verb
17
- def verb= v
18
- raise ArgumentError.new("'#{v}' is not a valid HTTP verb. Must be one of: #{VERBS.join(', ')}") unless VERBS.include?(v.upcase)
19
- @verb = v.upcase
20
- end
65
+ attr_accessor :client, :state, :only, :input, :cache, :output, :error, :line, :input_stream
21
66
 
22
67
  attr_reader :scope
23
68
  def scope= scope
@@ -25,35 +70,61 @@ module Elasticshell
25
70
  proc = scope.completion_proc
26
71
  Readline.completion_proc = Proc.new do |prefix|
27
72
  self.state = :completion
28
- proc.call(prefix)
73
+ proc.call(self, prefix)
29
74
  end
30
75
  end
31
76
 
77
+ def path
78
+ scope.path
79
+ end
80
+
81
+ def connected?
82
+ client.connected?
83
+ end
84
+
32
85
  def initialize options={}
86
+ @interactive = false
33
87
  self.state = :init
34
88
  self.client = Client.new(options)
89
+ self.cache = {}
90
+ @initial_servers = (options[:servers] || [])
35
91
  self.verb = (options[:verb] || 'GET')
36
- self.scope = Scopes.from_path((options[:scope] || '/'), :client => self.client)
92
+ self.scope = scope_from_path(options[:scope] || '/')
37
93
  self.only = options[:only]
94
+ self.input_stream = (options[:input] || $stdin)
95
+ self.output = (options[:output] || $stdout)
96
+ self.error = (options[:error] || $stderr)
97
+ self.line = 0
98
+ @log_requests = (options[:log_requests] == false ? false : true)
38
99
  pretty! if options[:pretty]
39
100
  end
40
101
 
41
- def prompt
42
- "\e[1m#{prompt_verb_color}#{verb} #{prompt_scope_color}#{scope.path} #{prompt_prettiness_indicator} \e[0m"
43
- end
44
-
45
- def prompt_scope_color
46
- scope.exists? ? "\e[32m" : "\e[33m"
102
+ def scope_from_path path
103
+ if cache[path]
104
+ cache[path]
105
+ else
106
+ cache[path] = Scopes.from_path(path, :client => self.client)
107
+ end
47
108
  end
48
109
 
49
- def prompt_verb_color
50
- verb == "GET" ? "\e[34m" : "\e[31m"
110
+ def format name, codes, values
111
+ cs = [codes].flatten
112
+ vs = [values].flatten
113
+ raise ArgumentError.new("Must provide the same number of format codes as value strings.") unless cs.length == vs.length
114
+ Settings[name].dup.tap do |s|
115
+ cs.each_with_index do |c, index|
116
+ v = vs[index]
117
+ s.gsub!(c, v)
118
+ end
119
+ end
51
120
  end
52
121
 
53
- def prompt_prettiness_indicator
54
- pretty? ? '$' : '>'
122
+ def prompt
123
+ verb_string = format((verb =~ /^(?:G|H)/i ? :passive_http_verb_format : :active_http_verb_format), "%v", verb)
124
+ scope_string = format((scope.exists? ? :existing_scope_format : :missing_scope_format), "%s", scope.path)
125
+ format((pretty? ? :pretty_prompt_format : :prompt_format), ["%s", "%v"], [scope_string, verb_string])
55
126
  end
56
-
127
+
57
128
  def pretty?
58
129
  @pretty
59
130
  end
@@ -66,6 +137,10 @@ module Elasticshell
66
137
  @pretty = false
67
138
  end
68
139
 
140
+ def interactive?
141
+ @interactive
142
+ end
143
+
69
144
  def setup
70
145
  trap("INT") do
71
146
  int
@@ -73,62 +148,87 @@ module Elasticshell
73
148
 
74
149
  Readline.completer_word_break_characters = " \t\n\"\\'`$><=|&{("
75
150
 
76
- puts <<EOF
151
+ print <<EOF
77
152
  Elasticshell v. #{Elasticshell.version}
78
153
  Type "help" for contextual help.
79
154
  EOF
155
+ @interactive = true
156
+
157
+ self.line = 1
80
158
  end
81
159
 
82
160
  def run
83
161
  setup
162
+ connect
84
163
  loop
85
164
  end
86
165
 
166
+ def connect
167
+ eval_line("connect #{@initial_servers.join(',')}")
168
+ end
169
+
87
170
  def loop
88
171
  self.state = :read
89
172
  while line = Readline.readline(prompt, true)
90
173
  eval_line(line)
91
174
  end
175
+ die
92
176
  end
93
177
 
94
178
  def eval_line line
95
179
  begin
96
- self.input = line.strip
97
- self.command = Command.new(self, input)
98
180
  self.state = :eval
99
- self.command.evaluate!
181
+ self.input = line.strip
182
+ command.evaluate!
100
183
  rescue ::Elasticshell::Error => e
101
- $stderr.puts e.message
184
+ error.puts e.message
102
185
  end
103
186
  self.state = :read
187
+ self.line += 1
188
+ self
104
189
  end
105
190
 
191
+ def command
192
+ matching_command_class_name = Commands::PRIORITY.detect do |command_class_name|
193
+ Commands.const_get(command_class_name).matches?(input)
194
+ end
195
+
196
+ # We should never hit the following ArgumentError as there
197
+ # exists a catch-all command: Unknown
198
+ raise ArgumentError.new("Could not parse command: '#{input}'") unless matching_command_class_name
199
+
200
+ Commands.const_get(matching_command_class_name).new(self, input)
201
+ end
202
+
106
203
  def print obj, ignore_only=false
107
- if self.only && !ignore_only
108
- if self.only == true
109
- print(obj, true)
110
- else
111
- only_parts = self.only.to_s.split('.')
112
- obj_to_print = obj
113
- while obj_to_print && only_parts.size > 0
114
- this_only = only_parts.shift
115
- obj_to_print = (obj_to_print || {})[this_only]
116
- end
117
- print(obj_to_print, true)
118
- end
204
+ self.output.puts(format_output(obj, ignore_only))
205
+ end
206
+
207
+ def format_output obj, ignore_only=false
208
+ case
209
+ when self.only == true && !ignore_only
210
+ format_output(obj, true)
211
+ when self.only && !ignore_only
212
+ format_only_part_of(obj)
213
+ when obj.nil?
214
+ nil
215
+ when String, Fixnum
216
+ obj
217
+ when pretty?
218
+ JSON.pretty_generate(obj)
119
219
  else
120
- case obj
121
- when nil
122
- when String, Fixnum
123
- puts obj
124
- else
125
- if pretty?
126
- puts JSON.pretty_generate(obj)
127
- else
128
- puts obj.to_json
129
- end
130
- end
220
+ obj.to_json
221
+ end
222
+ end
223
+
224
+ def format_only_part_of obj
225
+ only_parts = self.only.to_s.split('.')
226
+ obj_to_print = obj
227
+ while obj_to_print && only_parts.size > 0
228
+ this_only = only_parts.shift
229
+ obj_to_print = (obj_to_print || {})[this_only]
131
230
  end
231
+ format_output(obj_to_print, true)
132
232
  end
133
233
 
134
234
  def int
@@ -140,51 +240,13 @@ EOF
140
240
  end
141
241
  end
142
242
 
143
- def clear_line
144
- while Readline.point > 0
145
- $stdin.write("\b \b")
146
- end
147
- end
148
-
149
243
  def die
150
- puts "C-d"
151
- print("C-d...quitting")
152
- exit()
244
+ raise ShellError.new("C-d...quitting")
153
245
  end
154
246
 
155
- def command_and_query_and_body command
156
- parts = command.split
157
-
158
- c_and_q = parts[0]
159
- c, q = c_and_q.split('?')
160
- o = {}
161
- URI.decode_www_form(q || '').each do |k, v|
162
- o[k] = v
163
- end
164
-
165
- path = parts[1]
166
- case
167
- when path && File.exist?(path) && File.readable?(path)
168
- b = File.read(path)
169
- when path && path == '-'
170
- b = $stdin.gets(nil)
171
- when path
172
- b = path
173
- else
174
- # b = (command.split(' ', 2).last || '')
175
- b = ''
176
- end
177
-
178
- [c, o, b]
179
- end
180
-
181
- def request verb, params={}
182
- c, o, b = command_and_query_and_body(input)
183
- body = (params.delete(:body) || b || '')
184
- print(client.request(verb, params.merge(:op => c), o, b))
247
+ def log_requests?
248
+ @log_requests
185
249
  end
186
250
 
187
251
  end
188
-
189
252
  end
190
-
@@ -0,0 +1,10 @@
1
+ require 'elasticshell/utils/error'
2
+ require 'elasticshell/utils/log'
3
+
4
+ module Elasticshell
5
+
6
+ autoload :HasName, 'elasticshell/utils/has_name'
7
+ autoload :HasVerb, 'elasticshell/utils/has_verb'
8
+ autoload :RecognizesVerb, 'elasticshell/utils/recognizes_verb'
9
+
10
+ end
@@ -4,5 +4,6 @@ module Elasticshell
4
4
  ArgumentError = Class.new(Error)
5
5
  NotImplementedError = Class.new(Error)
6
6
  ClientError = Class.new(Error)
7
+ ShellError = Class.new(Error)
7
8
 
8
9
  end
@@ -0,0 +1,14 @@
1
+ module Elasticshell
2
+ module HasName
3
+
4
+ FORBIDDEN_NAME_CHARS = %r![/\s]!
5
+
6
+ attr_reader :name
7
+
8
+ def name= new_name
9
+ raise ArgumentError.new("Invalid index name: '#{new_name}'") if new_name =~ FORBIDDEN_NAME_CHARS
10
+ @name = new_name
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Elasticshell
2
+ module HasVerb
3
+
4
+ VERBS = %w[HEAD GET POST PUT DELETE]
5
+
6
+ def verb
7
+ @verb ||= "GET"
8
+ end
9
+
10
+ def verb= new_verb
11
+ @verb = new_verb.to_s.upcase if VERBS.include?(new_verb.to_s.upcase)
12
+ end
13
+
14
+ end
15
+ end
@@ -1,7 +1,7 @@
1
1
  module Elasticshell
2
2
 
3
3
  def self.log msg
4
- $stderr.puts("\n" + msg + "\n")
4
+ $stderr.puts(msg)
5
5
  end
6
6
 
7
7
  end
@@ -0,0 +1,25 @@
1
+ module Elasticshell
2
+ module RecognizesVerb
3
+
4
+ VERB_RE = "(?:HEAD|GET|PUT|POST|DELETE)"
5
+
6
+ def verb_re
7
+ RecognizesVerb::VERB_RE
8
+ end
9
+
10
+ def canonicalize_verb v
11
+ case v.to_s
12
+ when /^G/i then "GET"
13
+ when /^PO/i then "POST"
14
+ when /^PU/i then "PUT"
15
+ when /^D/i then "DELETE"
16
+ when /^H/i then "HEAD"
17
+ end
18
+ end
19
+
20
+ def is_http_verb? s
21
+ s =~ Regexp.new("^" + verb_re + "$", true)
22
+ end
23
+
24
+ end
25
+ end