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
@@ -0,0 +1,23 @@
1
+ require 'elasticshell/utils/recognizes_verb'
2
+
3
+ module Elasticshell
4
+ module Commands
5
+ class SetVerb < Command
6
+
7
+ include RecognizesVerb
8
+ extend RecognizesVerb
9
+
10
+ def self.matches? input
11
+ is_http_verb?(input)
12
+ end
13
+
14
+ def evaluate!
15
+ v = canonicalize_verb(input)
16
+ shell.verb = v
17
+ shell.scope.verb = v
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,17 @@
1
+ module Elasticshell
2
+ module Commands
3
+ class Unknown < Command
4
+
5
+ def self.matches? input
6
+ true
7
+ end
8
+
9
+ def evaluate!
10
+ raise ArgumentError.new("Invalid command: '#{shell.input}' for scope '#{shell.scope.path}'. Try typing 'help' for a list of available commands.")
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -1,4 +1,3 @@
1
- require 'elasticshell/error'
2
1
  require 'uri'
3
2
 
4
3
  module Elasticshell
@@ -52,12 +51,19 @@ module Elasticshell
52
51
 
53
52
  class Scope
54
53
 
55
- attr_accessor :path, :client, :last_refresh_at, :contents
54
+ include Elasticshell::HasVerb
56
55
 
56
+ attr_accessor :path, :client, :last_refresh_at, :scopes
57
+
58
+ def self.requests
59
+ @requests ||= {}
60
+ end
61
+
57
62
  def initialize path, options
58
- self.path = path
59
- self.client = options[:client]
60
- self.contents = initial_contents
63
+ self.verb = options.delete(:verb)
64
+ self.path = path
65
+ self.client = options[:client]
66
+ self.scopes = initial_scopes
61
67
  end
62
68
 
63
69
  def to_s
@@ -65,89 +71,114 @@ module Elasticshell
65
71
  end
66
72
 
67
73
  def completion_proc
68
- Proc.new do |prefix|
69
- refresh
74
+ Proc.new do |shell, prefix|
75
+ refresh if client.connected?
70
76
  case
71
77
  when Readline.line_buffer =~ /^\s*cd\s+\S*$/
72
- contents.find_all do |content|
73
- content[0...prefix.length] == prefix
74
- end
78
+ # User has typed 'cd' so we should be completing scopes only
79
+ completing_scope_path, prefix_within_completing_scope = completing_scope_path_and_prefix(prefix)
80
+ completing_scope = shell.scope_from_path(File.expand_path(completing_scope_path, shell.scope.path))
81
+ completing_scope.refresh if client.connected?
82
+ completing_scope.scopes_matching(prefix_within_completing_scope)
75
83
  when Readline.line_buffer =~ /^\s*\S*$/
76
- command_names.find_all do |command_name|
77
- command_name[0...prefix.length] == prefix
78
- end
84
+ # User has started but not completed the first word in the
85
+ # line so it must be a request available in the current
86
+ # scope.
87
+ requests_matching(prefix)
88
+ when Readline.line_buffer =~ />.*$/
89
+ # User has started to complete the name of a filesystem path
90
+ # to redirect output to.
91
+ Dir[prefix + '*']
79
92
  else
93
+ # The user has finished the first word on the line so we try
94
+ # to match to a filesystem path.
80
95
  Dir[prefix + '*']
81
96
  end
82
97
  end
83
98
  end
84
99
 
85
- def command_names
86
- refresh
87
- commands.keys.sort
100
+ def requests
101
+ self.class.requests[verb] || {}
88
102
  end
89
103
 
90
- def commands
91
- {}
104
+ def request_names
105
+ requests.keys
92
106
  end
93
107
 
108
+ def requests_matching prefix
109
+ if prefix.empty?
110
+ request_names.sort
111
+ else
112
+ request_names.find_all { |name| name[0...prefix.length] == prefix }.sort
113
+ end
114
+ end
115
+
94
116
  def refresh
95
117
  refresh! unless refreshed?
96
118
  end
97
119
 
98
120
  def refresh!
99
121
  reset!
100
- fetch_contents
122
+ fetch_scopes
101
123
  self.last_refresh_at = Time.now
102
124
  true
103
125
  end
104
126
 
105
- def fetch_contents
127
+ def fetch_scopes
106
128
  end
107
129
 
108
- def initial_contents
130
+ def initial_scopes
109
131
  []
110
132
  end
111
133
 
134
+ def completing_scope_path_and_prefix prefix
135
+ if prefix =~ %r!^/!
136
+ if prefix =~ %r!/$!
137
+ index = -1
138
+ prefix_within_completing_scope = ''
139
+ else
140
+ index = -2
141
+ end
142
+ completing_scope_path = prefix.split('/')[0..index].join('/')
143
+ completing_scope_path = '/' if completing_scope_path.empty?
144
+ prefix_within_completing_scope ||= (prefix.split('/').last || '')
145
+ else
146
+ if prefix =~ %r!/$!
147
+ index = -1
148
+ prefix_within_completing_scope = ''
149
+ else
150
+ index = -2
151
+ end
152
+ completing_scope_path = File.join(self.path, prefix.split('/')[0..index].join('/'))
153
+ completing_scope_path = self.path if completing_scope_path.empty?
154
+ prefix_within_completing_scope ||= (prefix.split('/').last || '')
155
+ end
156
+ [completing_scope_path, prefix_within_completing_scope]
157
+ end
158
+
159
+ def scopes_matching prefix
160
+ scopes.find_all do |scope|
161
+ prefix.empty? ? true : scope[0...prefix.length] == prefix
162
+ end.map do |scope|
163
+ scope =~ %r!/$! ? scope : scope + '/'
164
+ end.sort.map do |scope|
165
+ File.join(path, scope)
166
+ end
167
+ end
168
+
112
169
  def reset!
113
- self.contents = initial_contents
170
+ self.scopes = initial_scopes
114
171
  true
115
172
  end
116
173
 
117
174
  def refreshed?
118
- self.last_refresh_at
175
+ ! self.last_refresh_at.nil?
119
176
  end
120
177
 
121
178
  def exists?
122
179
  false
123
180
  end
124
-
125
- def command? command
126
- command_names.any? do |command_name|
127
- command[0...command_name.length] == command_name
128
- end
129
- end
130
-
131
- def execute command, shell
132
- if command_names.include?(command)
133
- raise NotImplementedError.new("Have not yet implemented '#{command}' for scope '#{path}'.")
134
- else
135
- raise ArgumentError.new("No such command '#{command}' in scope '#{path}'.")
136
- end
137
- end
138
-
139
- def help
140
- [].tap do |msg|
141
- msg << "Commands specific to the scope '#{path}':"
142
- msg << ''
143
- commands.each_pair do |command_name, description|
144
- msg << ' ' + command_name
145
- msg << (' ' + description)
146
- msg << ''
147
- end
148
- end.join("\n")
149
- end
150
-
181
+
151
182
  end
152
183
 
153
184
  end
@@ -1,5 +1,3 @@
1
- require 'elasticshell/scopes'
2
-
3
1
  module Elasticshell
4
2
 
5
3
  module Scopes
@@ -10,26 +8,20 @@ module Elasticshell
10
8
  super("/_cluster", options)
11
9
  end
12
10
 
13
- def commands
14
- {
15
- 'health' => "Retreive the health of the cluster.",
16
- 'state' => "Retreive the state of the cluster.",
17
- 'settings'=> "Retreive the settings for the cluster.",
11
+ def self.requests
12
+ @requests = {
13
+ "GET" =>
14
+ {
15
+ 'health' => "Retreive the health of the cluster.",
16
+ 'state' => "Retreive the state of the cluster.",
17
+ 'settings'=> "Retreive the settings for the cluster.",
18
+ }
18
19
  }
19
20
  end
20
21
 
21
22
  def exists?
22
23
  true
23
24
  end
24
-
25
- def execute command, shell
26
- case
27
- when command?(command)
28
- shell.request(:get, :index => '_cluster')
29
- else
30
- super(command, shell)
31
- end
32
- end
33
25
 
34
26
  end
35
27
  end
@@ -1,27 +1,41 @@
1
- require 'elasticshell/scopes'
2
-
3
1
  module Elasticshell
4
2
 
5
3
  module Scopes
6
4
 
7
5
  class Global < Scope
8
6
 
7
+ attr_reader :indices
8
+
9
9
  def initialize options={}
10
+ @indices = []
10
11
  super("/", options)
11
12
  end
12
13
 
13
- def commands
14
- {
15
- '_status' => "Retreive the status of all indices in the cluster."
14
+ def self.requests
15
+ @requests ||= {
16
+ "GET" => {
17
+ '_status' => "Retreive the status of all indices in the cluster."
18
+ }
16
19
  }
17
20
  end
18
21
 
19
- def initial_contents
22
+ def initial_scopes
20
23
  ['_cluster', '_nodes']
21
24
  end
22
25
 
23
- def fetch_contents
24
- self.contents += client.safely(:get, {:index => '_status'}, :return => {"indices" => {}})["indices"].keys
26
+ def status
27
+ @status ||= client.safely(:get, {:index => '_status'}, :return => {"indices" => {}}, :log => false)
28
+ end
29
+
30
+ def reset!
31
+ @indices = []
32
+ @status = nil
33
+ super()
34
+ end
35
+
36
+ def fetch_scopes
37
+ @indices = status["indices"].keys
38
+ self.scopes += @indices
25
39
  end
26
40
 
27
41
  def index name, options={}
@@ -31,21 +45,6 @@ module Elasticshell
31
45
  def exists?
32
46
  true
33
47
  end
34
-
35
- def execute command, shell
36
- case
37
- when command =~ /^_cluster/
38
- shell.scope = Scopes.cluster(:client => client)
39
- when command =~ /^_nodes/
40
- shell.scope = Scopes.nodes(:client => client)
41
- when command?(command)
42
- shell.request(:get)
43
- when index_names.include?(command)
44
- shell.scope = index(command)
45
- else
46
- super(command, shell)
47
- end
48
- end
49
48
 
50
49
  end
51
50
  end
@@ -1,30 +1,27 @@
1
- require 'elasticshell/scopes'
2
-
3
1
  module Elasticshell
4
2
 
5
3
  module Scopes
6
4
 
7
5
  class Index < Scope
8
6
 
9
- VALID_INDEX_NAME_RE = %r![^/]!
7
+ include HasName
8
+
9
+ attr_reader :mappings
10
10
 
11
11
  def initialize name, options={}
12
12
  self.name = name
13
+ @mappings = []
13
14
  super("/#{self.name}", options)
14
15
  end
15
16
 
16
- attr_reader :name
17
- def name= name
18
- raise ArgumentError.new("Invalid index name: '#{name}'") unless name =~ VALID_INDEX_NAME_RE
19
- @name = name
20
- end
21
-
22
- def commands
23
- {
24
- "_aliases" => "Find the aliases for this index.",
25
- "_status" => "Retrieve the status of this index.",
26
- "_stats" => "Retrieve usage stats for this index.",
27
- "_search" => "Search records within this index.",
17
+ def self.requests
18
+ @requests ||= {
19
+ "GET" => {
20
+ "_aliases" => "Find the aliases for this index.",
21
+ "_status" => "Retrieve the status of this index.",
22
+ "_stats" => "Retrieve usage stats for this index.",
23
+ "_search" => "Search records within this index.",
24
+ }
28
25
  }
29
26
  end
30
27
 
@@ -32,28 +29,37 @@ module Elasticshell
32
29
  @global ||= Scopes.global(:client => client)
33
30
  end
34
31
 
32
+ def status
33
+ @status ||= client.safely(:get, {:index => name, :op => '_status'}, :return => {"indices" => {name => {}}}, :log => false)
34
+ end
35
+
36
+ def reset!
37
+ @status = nil
38
+ @mappings = []
39
+ super()
40
+ end
41
+
35
42
  def exists?
43
+ return false unless client.connected?
36
44
  global.refresh
37
- global.contents.include?(name)
45
+ global.scopes.include?(name)
38
46
  end
39
47
 
40
- def fetch_contents
41
- @contents = (client.safely(:get, {:index => name, :op => '_mapping'}, :return => { name => {}})[name] || {}).keys
48
+ def multi?
49
+ name.include?(',')
42
50
  end
43
51
 
44
- def mapping mapping_name, options={}
45
- Scopes.mapping(self.name, mapping_name, options.merge(:client => client))
52
+ def single?
53
+ ! multi?
54
+ end
55
+
56
+ def fetch_scopes
57
+ @mappings = (client.safely(:get, {:index => name, :op => '_mapping'}, :return => { name => {}}, :log => false)[name] || {}).keys
58
+ self.scopes += @mappings
46
59
  end
47
60
 
48
- def execute command, shell
49
- case
50
- when command?(command)
51
- shell.request(:get, :index => name)
52
- when mapping_names.include?(command)
53
- shell.scope = mapping(command)
54
- else
55
- super(command, shell)
56
- end
61
+ def mapping mapping_name, options={}
62
+ Scopes.mapping(self.name, mapping_name, options.merge(:client => client))
57
63
  end
58
64
 
59
65
  end
@@ -1,12 +1,10 @@
1
- require 'elasticshell/scopes'
2
-
3
1
  module Elasticshell
4
2
 
5
3
  module Scopes
6
4
 
7
5
  class Mapping < Scope
8
6
 
9
- VALID_MAPPING_NAME_RE = %r![^/]!
7
+ include HasName
10
8
 
11
9
  attr_accessor :index
12
10
 
@@ -16,38 +14,20 @@ module Elasticshell
16
14
  super("/#{index.name}/#{self.name}", options)
17
15
  end
18
16
 
19
- attr_reader :name
20
- def name= name
21
- raise ArgumentError.new("Invalid mapping name: '#{name}'") unless name =~ VALID_MAPPING_NAME_RE
22
- @name = name
23
- end
24
-
25
- def commands
26
- {
27
- "_search" => "Search records within this mapping.",
28
- "_mapping" => "Retrieve the mapping settings for this mapping.",
17
+ def self.requests
18
+ @requests ||= {
19
+ "GET" => {
20
+ "_search" => "Search records within this mapping.",
21
+ "_mapping" => "Retrieve the mapping settings for this mapping.",
22
+ }
29
23
  }
30
24
  end
31
25
 
32
26
  def exists?
33
27
  index.refresh
34
- index.contents.include?(name)
28
+ index.scopes.include?(name)
35
29
  end
36
30
 
37
- def command? command
38
- true
39
- end
40
-
41
- def execute command, shell
42
- case
43
- when command?(command)
44
- shell.request(:get, :index => index.name, :type => name)
45
- else
46
- record = shell.request(:get, :index => index.name, :type => name)
47
- shell.print(record) if record
48
- end
49
- end
50
-
51
31
  end
52
32
  end
53
33
  end