elasticshell 0.0.1
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/LICENSE +20 -0
- data/README.rdoc +207 -0
- data/VERSION +1 -0
- data/bin/es +6 -0
- data/lib/elasticshell/client.rb +45 -0
- data/lib/elasticshell/command.rb +191 -0
- data/lib/elasticshell/error.rb +8 -0
- data/lib/elasticshell/log.rb +9 -0
- data/lib/elasticshell/scopes/cluster.rb +38 -0
- data/lib/elasticshell/scopes/global.rb +54 -0
- data/lib/elasticshell/scopes/index.rb +61 -0
- data/lib/elasticshell/scopes/mapping.rb +55 -0
- data/lib/elasticshell/scopes/nodes.rb +37 -0
- data/lib/elasticshell/scopes.rb +155 -0
- data/lib/elasticshell/shell.rb +189 -0
- data/lib/elasticshell.rb +59 -0
- data/spec/elasticshell/command_spec.rb +6 -0
- data/spec/elasticshell/scopes_spec.rb +22 -0
- data/spec/spec_helper.rb +12 -0
- metadata +132 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Dhruv Bansal
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
= Elasticshell
|
2
|
+
|
3
|
+
Elasticsearch[http://www.elasticsearch.org] is a wonderful database
|
4
|
+
for performing full-text on rich documents at terabyte-scale.
|
5
|
+
|
6
|
+
It's already pretty easy to talk to Elasticsearch. You can
|
7
|
+
|
8
|
+
- use the HTTP-based, {REST
|
9
|
+
API}[http://www.elasticsearch.org/guide/reference/api/] via
|
10
|
+
commmand-line tools like curl[http://en.wikipedia.org/wiki/CURL], your favorite HTTP library, or even
|
11
|
+
your browser's URL bar
|
12
|
+
|
13
|
+
- use the interface built on Apache Thrift
|
14
|
+
|
15
|
+
- use the native {Java classes}[http://www.elasticsearch.org/guide/reference/java-api/]
|
16
|
+
|
17
|
+
What's missing was a command-line shell that let you directly inspect
|
18
|
+
Elasticsearch's "filesystem" or "database schema", run queries, and in
|
19
|
+
general muck about. I got sick of writing things like
|
20
|
+
|
21
|
+
$ curl -s -X GET "http://localhost:9200/_status" | ruby -rjson -e 'puts JSON.parse($stdin.read)["indices"]["my_index"]["docs"]["num_docs"]'
|
22
|
+
|
23
|
+
How about
|
24
|
+
|
25
|
+
$ es /_status --only=indices.my_index.docs.num_docs
|
26
|
+
|
27
|
+
== Installation
|
28
|
+
|
29
|
+
Installing Elasticshell should be as simple as installing the gem:
|
30
|
+
|
31
|
+
$ sudo gem install elasticshell
|
32
|
+
|
33
|
+
This should install a binary program 'es' that you can run from the
|
34
|
+
command line to start Elasticshell. Try
|
35
|
+
|
36
|
+
$ es --help
|
37
|
+
|
38
|
+
right now to see that everything is properly installed. You'll also
|
39
|
+
see a brief survey of Elasticshell's startup options.
|
40
|
+
|
41
|
+
== Usage
|
42
|
+
|
43
|
+
To start an Elasticshell session, just run
|
44
|
+
|
45
|
+
$ es
|
46
|
+
|
47
|
+
Elasticshell will automatically try to connect to a local
|
48
|
+
Elasticsearch database running on the default port. You can modify
|
49
|
+
this with the startup options. Type +help+ at any time to get some
|
50
|
+
contextual help from Elasticshell.
|
51
|
+
|
52
|
+
Within Elasticshell, there are three variables whose values affect
|
53
|
+
behavior. These variables are reflected in the default prompt, for
|
54
|
+
example:
|
55
|
+
|
56
|
+
GET /my_index/my_type >
|
57
|
+
|
58
|
+
This prompt tells us three things:
|
59
|
+
|
60
|
+
1. The default HTTP verb we're using for requests is +GET+.
|
61
|
+
|
62
|
+
2. The default API "scope" we're in is <tt>/my_index/my_type</tt>.
|
63
|
+
|
64
|
+
3. Elasticshell will print raw responses from the database -- this is the <tt>></tt> at the end of the prompt. If we were in pretty-print mode, this would become a <tt>$</tt>.
|
65
|
+
|
66
|
+
=== Changing Scope
|
67
|
+
|
68
|
+
Use the +cd+ built-in to move between scopes:
|
69
|
+
|
70
|
+
GET /my_index/my_type > cd /other_index/other_type
|
71
|
+
GET /other_index/other_type > cd ..
|
72
|
+
GET /other_index > cd
|
73
|
+
GET / >
|
74
|
+
|
75
|
+
Tab-complete within a scope after typing +cd+ to see what other scopes
|
76
|
+
live under this one.
|
77
|
+
|
78
|
+
=== Changing HTTP Verb
|
79
|
+
|
80
|
+
You can change Elasticsearch's default HTTP verb by giving it one.
|
81
|
+
Here's the same thing in two steps:
|
82
|
+
|
83
|
+
GET / > PUT
|
84
|
+
PUT / > /my_new_index
|
85
|
+
|
86
|
+
Non-ambiguous shortcuts for HTTP verbs will also work, e.g. - +pu+ in
|
87
|
+
this case for +PUT+.
|
88
|
+
|
89
|
+
=== Changing Prettiness
|
90
|
+
|
91
|
+
Typing +pretty+ at any time will toggle Elasticsearch's
|
92
|
+
pretty-printing format on or off.
|
93
|
+
|
94
|
+
GET / > pretty
|
95
|
+
GET / $
|
96
|
+
|
97
|
+
The <tt>$</tt>-sign means it's pretty...
|
98
|
+
|
99
|
+
=== Running Commands
|
100
|
+
|
101
|
+
There are a lot of different ways of telling Elasticsearch what you
|
102
|
+
want done.
|
103
|
+
|
104
|
+
==== Named commands
|
105
|
+
|
106
|
+
Each scope has different commands, as per the {Elasticsearch API
|
107
|
+
documentation}[http://www.elasticsearch.org/guide/reference/api/].
|
108
|
+
Within a scope, tab-complete on the first word to see a list of
|
109
|
+
possible commands. Hit enter after a command to see output from
|
110
|
+
Elasticsearch.
|
111
|
+
|
112
|
+
Here's a command to get the status for the cluster:
|
113
|
+
|
114
|
+
GET / > _status
|
115
|
+
|
116
|
+
Here's a command to get the health of the cluster:
|
117
|
+
|
118
|
+
GET / > cd _cluster
|
119
|
+
GET /_cluster > health
|
120
|
+
|
121
|
+
==== Commands with query strings
|
122
|
+
|
123
|
+
Commands will also accept a query string, as in this example of a
|
124
|
+
search through +my_index+:
|
125
|
+
|
126
|
+
GET /my_index > _search?q=foo+AND+bar
|
127
|
+
|
128
|
+
==== Commands with query bodies
|
129
|
+
|
130
|
+
In this example the query <tt>foo AND bar</tt> was passed via the
|
131
|
+
query string part of a URL. Passing a more complex query requires we
|
132
|
+
put the query in the body of the request. If you're willing to forego
|
133
|
+
using spaces you can do this right on the same line:
|
134
|
+
|
135
|
+
GET /my_index > _search {"query":{"query_string":{"query":"foo"}}}
|
136
|
+
|
137
|
+
But if you want more expressiveness you can either name a file (with
|
138
|
+
tab-completion) that contains the body you want:
|
139
|
+
|
140
|
+
# in /tmp/query.json
|
141
|
+
{
|
142
|
+
"query": {
|
143
|
+
"query_string: {
|
144
|
+
"query": "foo AND bar"
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
followed by
|
150
|
+
|
151
|
+
GET /my_index > _search /tmp/query.json
|
152
|
+
|
153
|
+
Or you can do +cat+-style, pasting the query into the shell, by using
|
154
|
+
the <tt>-</tt> character:
|
155
|
+
|
156
|
+
GET /my_index > _search -
|
157
|
+
{
|
158
|
+
"query": {
|
159
|
+
"query_string: {
|
160
|
+
"query": "foo AND bar"
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
Don't forget to use <tt>Ctrl-D</tt> to send an +EOF+ to flush the
|
166
|
+
input of the query.
|
167
|
+
|
168
|
+
==== Arbitrary commands
|
169
|
+
|
170
|
+
You can send an arbitrary HTTP request to Elasticsearch, just spell
|
171
|
+
the command with a leading slash:
|
172
|
+
|
173
|
+
GET / > /my_index/_search?q=foo+AND+bar
|
174
|
+
|
175
|
+
You can specify a different HTTP verb by prefixing it before the path
|
176
|
+
you send the request to. Here's how to create an index using a +PUT+
|
177
|
+
request:
|
178
|
+
|
179
|
+
GET / > PUT /my_new_index
|
180
|
+
|
181
|
+
You can also change Elasticsearch's default HTTP verb by giving it
|
182
|
+
one. Here's the same thing in two steps:
|
183
|
+
|
184
|
+
GET / > PUT
|
185
|
+
PUT / > /my_new_index
|
186
|
+
|
187
|
+
Non-ambiguous shortcuts for HTTP verbs will also work, e.g. - +pu+ in
|
188
|
+
this case for +PUT+.
|
189
|
+
|
190
|
+
=== Running just a single command
|
191
|
+
|
192
|
+
Instead of running Elasticshell interactively, you can exit after
|
193
|
+
running only a single command by using the <tt>--only</tt> option on
|
194
|
+
startup. For example,
|
195
|
+
|
196
|
+
$ es --only /_cluster/health
|
197
|
+
|
198
|
+
will output the cluster health and exit immediately. This can be
|
199
|
+
combined with the <tt>--pretty</tt> option for readability.
|
200
|
+
|
201
|
+
The <tt>--only</tt> option can also be passed a <tt>.</tt>-separated
|
202
|
+
hierarchical list of keys to slice into the resulting object. This is
|
203
|
+
useful when trying to drill into a large amount of data returned by
|
204
|
+
Elasticsearch. The example from the start of this file is relevant
|
205
|
+
again here:
|
206
|
+
|
207
|
+
$ es /_status --only=indices.my_index.docs.num_docs
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/es
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubberband'
|
2
|
+
require 'elasticshell/error'
|
3
|
+
require 'elasticshell/log'
|
4
|
+
|
5
|
+
module Elasticshell
|
6
|
+
|
7
|
+
class Client
|
8
|
+
|
9
|
+
DEFAULT_SERVERS = ['http://localhost:9200']
|
10
|
+
|
11
|
+
def initialize options={}
|
12
|
+
@client ||= ::ElasticSearch::Client.new(options[:servers] || DEFAULT_SERVERS)
|
13
|
+
end
|
14
|
+
|
15
|
+
def request verb, params={}, options={}, body=''
|
16
|
+
safe = options.delete(:safely)
|
17
|
+
safe_return = options.delete(:return)
|
18
|
+
# log_request(verb, params, options)
|
19
|
+
begin
|
20
|
+
@client.execute(:standard_request, verb, params, options, body)
|
21
|
+
rescue ElasticSearch::RequestError, ArgumentError => e
|
22
|
+
if safe
|
23
|
+
safe_return
|
24
|
+
else
|
25
|
+
raise ClientError.new(e.message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def log_request verb, params, options={}
|
31
|
+
# FIXME digging way too deep into rubberband here...is it really
|
32
|
+
# necessary?
|
33
|
+
uri = @client.instance_variable_get('@connection').send(:generate_uri, params)
|
34
|
+
query = @client.instance_variable_get('@connection').send(:generate_query_string, options)
|
35
|
+
path = [uri, query].reject { |s| s.nil? || s.strip.empty? }.join("?")
|
36
|
+
Elasticshell.log("#{verb.to_s.upcase} #{path}")
|
37
|
+
end
|
38
|
+
|
39
|
+
def safely verb, params={}, options={}, body=''
|
40
|
+
request(verb, params, options.merge(:safely => true))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'elasticshell/error'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Elasticshell
|
5
|
+
|
6
|
+
class Command
|
7
|
+
|
8
|
+
HTTP_VERB_RE = "(?:G(?:ET?)?|PO(?:ST?)?|PUT?|D(?:E(?:L(?:E(?:TE?)?)?)?)?)"
|
9
|
+
|
10
|
+
attr_accessor :shell, :input
|
11
|
+
|
12
|
+
def initialize shell, input
|
13
|
+
self.shell = shell
|
14
|
+
self.input = input
|
15
|
+
end
|
16
|
+
|
17
|
+
def evaluate!
|
18
|
+
case
|
19
|
+
when setting_scope? then set_scope!
|
20
|
+
when setting_http_verb? then set_http_verb!
|
21
|
+
when making_explicit_req? then make_explicit_req!
|
22
|
+
when pretty? then pretty!
|
23
|
+
when help? then help!
|
24
|
+
when ls? then ls!
|
25
|
+
when blank? then nil
|
26
|
+
when scope_command? then run_scope_command!
|
27
|
+
else
|
28
|
+
raise ArgumentError.new("Unknown command '#{input}' for scope '#{shell.scope.path}'. Try typing 'help' for a list of available commands.")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def setting_scope?
|
33
|
+
input =~ /^cd/
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_scope!
|
37
|
+
if input =~ /^cd$/
|
38
|
+
shell.scope = Scopes.global(:client => shell.client)
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
return unless input =~ /^cd\s+(.+)$/
|
43
|
+
scope = $1
|
44
|
+
if scope =~ %r!^/!
|
45
|
+
shell.scope = Scopes.from_path(scope, :client => shell.client)
|
46
|
+
else
|
47
|
+
shell.scope = Scopes.from_path(File.expand_path(File.join(shell.scope.path, scope)), :client => shell.client)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def setting_http_verb?
|
52
|
+
input =~ Regexp.new("^" + HTTP_VERB_RE + "$", true)
|
53
|
+
end
|
54
|
+
|
55
|
+
def canonicalize_http_verb v
|
56
|
+
case v
|
57
|
+
when /^G/i then "GET"
|
58
|
+
when /^PO/i then "POST"
|
59
|
+
when /^PU/i then "PUT"
|
60
|
+
when /^D/i then "DELETE"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_http_verb!
|
65
|
+
shell.verb = canonicalize_http_verb(input)
|
66
|
+
end
|
67
|
+
|
68
|
+
def scope_command?
|
69
|
+
shell.scope.command?(input)
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_scope_command!
|
73
|
+
shell.scope.execute(input, shell)
|
74
|
+
end
|
75
|
+
|
76
|
+
def blank?
|
77
|
+
input.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def help?
|
81
|
+
input =~ /^help/i
|
82
|
+
end
|
83
|
+
|
84
|
+
def help!
|
85
|
+
shell.scope.refresh
|
86
|
+
shell.print <<HELP
|
87
|
+
|
88
|
+
Globally available commands:
|
89
|
+
|
90
|
+
cd [PATH]
|
91
|
+
Change scope to the given path. Current path is reflected in the
|
92
|
+
prompt (it's '#{shell.scope.path}' right now).
|
93
|
+
|
94
|
+
Ex:
|
95
|
+
GET / > cd /my_index
|
96
|
+
GET /my_index > cd /other_index/some_type
|
97
|
+
GET /other_index/some_type
|
98
|
+
|
99
|
+
[get|post|put|delete]
|
100
|
+
Set the default HTTP verb (can use a non-ambiguous shortcut like 'g'
|
101
|
+
for 'GET' or 'pu' for 'PUT'). Current default HTTP verb is '#{shell.verb}'.
|
102
|
+
|
103
|
+
ls
|
104
|
+
Show what indices or mappings are within the current scope.
|
105
|
+
|
106
|
+
help
|
107
|
+
Show contextual help.
|
108
|
+
|
109
|
+
[VERB] PATH
|
110
|
+
Send an HTTP request with the given VERB to the given PATH
|
111
|
+
(including query string if given). If no verb is given, use the
|
112
|
+
default.
|
113
|
+
|
114
|
+
Ex: Simple search
|
115
|
+
GET / > /my_index/_search?q=query+string
|
116
|
+
{...}
|
117
|
+
|
118
|
+
Ex: Create an index
|
119
|
+
GET / > PUT /my_new_index
|
120
|
+
{...}
|
121
|
+
|
122
|
+
or
|
123
|
+
|
124
|
+
GET / > put
|
125
|
+
PUT / > /my_new_index
|
126
|
+
{...}
|
127
|
+
|
128
|
+
#{shell.scope.help}
|
129
|
+
HELP
|
130
|
+
end
|
131
|
+
|
132
|
+
def ls?
|
133
|
+
input =~ /^l(s|l|a)?$/i
|
134
|
+
end
|
135
|
+
|
136
|
+
def ls!
|
137
|
+
shell.scope.refresh!
|
138
|
+
case
|
139
|
+
when input =~ /ll/
|
140
|
+
shell.print shell.scope.contents.join("\n")
|
141
|
+
else
|
142
|
+
shell.print shell.scope.contents.join(' ')
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def pretty?
|
147
|
+
input =~ /pretty/i
|
148
|
+
end
|
149
|
+
|
150
|
+
def pretty!
|
151
|
+
if shell.pretty?
|
152
|
+
shell.not_pretty!
|
153
|
+
else
|
154
|
+
shell.pretty!
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def making_explicit_req?
|
159
|
+
input =~ Regexp.new("^(" + HTTP_VERB_RE + "\s+)?/", true)
|
160
|
+
end
|
161
|
+
|
162
|
+
def make_explicit_req!
|
163
|
+
if input =~ Regexp.new("^(" + HTTP_VERB_RE + ")\s+(.+)$", true)
|
164
|
+
verb, path_and_query = canonicalize_http_verb($1), $2
|
165
|
+
else
|
166
|
+
verb, path_and_query = shell.verb, input
|
167
|
+
end
|
168
|
+
path, query = path_and_query.split('?')
|
169
|
+
|
170
|
+
params = {}
|
171
|
+
keys = [:index, :type, :id, :op]
|
172
|
+
parts = path.gsub(%r!^/!,'').gsub(%r!/$!,'').split('/')
|
173
|
+
while parts.size > 0
|
174
|
+
part = parts.shift
|
175
|
+
key = (keys.shift or ArgumentError.new("The input '#{path}' has too many path components."))
|
176
|
+
params[key] = part
|
177
|
+
end
|
178
|
+
|
179
|
+
options = {}
|
180
|
+
URI.decode_www_form(query || '').each do |key, value|
|
181
|
+
options[key] = value
|
182
|
+
end
|
183
|
+
|
184
|
+
shell.print(shell.client.request(verb.downcase.to_sym, params, options))
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'elasticshell/scopes'
|
2
|
+
|
3
|
+
module Elasticshell
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
class Cluster < Scope
|
8
|
+
|
9
|
+
def initialize options={}
|
10
|
+
super("/_cluster", options)
|
11
|
+
end
|
12
|
+
|
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.",
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def exists?
|
22
|
+
true
|
23
|
+
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
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'elasticshell/scopes'
|
2
|
+
|
3
|
+
module Elasticshell
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
class Global < Scope
|
8
|
+
|
9
|
+
def initialize options={}
|
10
|
+
super("/", options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def commands
|
14
|
+
{
|
15
|
+
'_status' => "Retreive the status of all indices in the cluster."
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def initial_contents
|
20
|
+
['_cluster', '_nodes']
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_contents
|
24
|
+
self.contents += client.safely(:get, {:index => '_status'}, :return => {"indices" => {}})["indices"].keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def index name, options={}
|
28
|
+
Scopes.index(name, options, :client => client)
|
29
|
+
end
|
30
|
+
|
31
|
+
def exists?
|
32
|
+
true
|
33
|
+
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
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'elasticshell/scopes'
|
2
|
+
|
3
|
+
module Elasticshell
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
class Index < Scope
|
8
|
+
|
9
|
+
VALID_INDEX_NAME_RE = %r![^/]!
|
10
|
+
|
11
|
+
def initialize name, options={}
|
12
|
+
self.name = name
|
13
|
+
super("/#{self.name}", options)
|
14
|
+
end
|
15
|
+
|
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.",
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def global
|
32
|
+
@global ||= Scopes.global(:client => client)
|
33
|
+
end
|
34
|
+
|
35
|
+
def exists?
|
36
|
+
global.refresh
|
37
|
+
global.contents.include?(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch_contents
|
41
|
+
@contents = (client.safely(:get, {:index => name, :op => '_mapping'}, :return => { name => {}})[name] || {}).keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def mapping mapping_name, options={}
|
45
|
+
Scopes.mapping(self.name, mapping_name, options.merge(:client => client))
|
46
|
+
end
|
47
|
+
|
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
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'elasticshell/scopes'
|
2
|
+
|
3
|
+
module Elasticshell
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
class Mapping < Scope
|
8
|
+
|
9
|
+
VALID_MAPPING_NAME_RE = %r![^/]!
|
10
|
+
|
11
|
+
attr_accessor :index
|
12
|
+
|
13
|
+
def initialize index, name, options={}
|
14
|
+
self.index = index
|
15
|
+
self.name = name
|
16
|
+
super("/#{index.name}/#{self.name}", options)
|
17
|
+
end
|
18
|
+
|
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.",
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def exists?
|
33
|
+
index.refresh
|
34
|
+
index.contents.include?(name)
|
35
|
+
end
|
36
|
+
|
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
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'elasticshell/scopes'
|
2
|
+
|
3
|
+
module Elasticshell
|
4
|
+
|
5
|
+
module Scopes
|
6
|
+
|
7
|
+
class Nodes < Scope
|
8
|
+
|
9
|
+
def initialize options={}
|
10
|
+
super("/_nodes", options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def commands
|
14
|
+
{
|
15
|
+
'info' => "Retreive info about the cluster's ndoes.",
|
16
|
+
'stats' => "Retreive stats for the cluter's nodes.",
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def exists?
|
21
|
+
true
|
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
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'elasticshell/error'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Elasticshell
|
5
|
+
|
6
|
+
module Scopes
|
7
|
+
|
8
|
+
autoload :Global, 'elasticshell/scopes/global'
|
9
|
+
autoload :Cluster, 'elasticshell/scopes/cluster'
|
10
|
+
autoload :Nodes, 'elasticshell/scopes/nodes'
|
11
|
+
autoload :Index, 'elasticshell/scopes/index'
|
12
|
+
autoload :Mapping, 'elasticshell/scopes/mapping'
|
13
|
+
|
14
|
+
def self.global options={}
|
15
|
+
Global.new(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.cluster options={}
|
19
|
+
Cluster.new(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.nodes options={}
|
23
|
+
Nodes.new(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.index name, options={}
|
27
|
+
Index.new(name, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.mapping index_name, mapping_name, options={}
|
31
|
+
Mapping.new(index(index_name, options), mapping_name, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_path path, options={}
|
35
|
+
segments = path.to_s.strip.gsub(%r!^/!,'').gsub(%r!/$!,'').split('/')
|
36
|
+
case
|
37
|
+
when segments.length == 0
|
38
|
+
global(options)
|
39
|
+
when segments.length == 1 && segments.first == '_cluster'
|
40
|
+
cluster(options)
|
41
|
+
when segments.length == 1 && segments.first == '_nodes'
|
42
|
+
nodes(options)
|
43
|
+
when segments.length == 1
|
44
|
+
index(segments.first, options)
|
45
|
+
when segments.length == 2
|
46
|
+
mapping(segments[0], segments[1], options)
|
47
|
+
else
|
48
|
+
raise ArgumentError.new("'#{path}' does not define a valid path for a scope.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Scope
|
54
|
+
|
55
|
+
attr_accessor :path, :client, :last_refresh_at, :contents
|
56
|
+
|
57
|
+
def initialize path, options
|
58
|
+
self.path = path
|
59
|
+
self.client = options[:client]
|
60
|
+
self.contents = initial_contents
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
self.path
|
65
|
+
end
|
66
|
+
|
67
|
+
def completion_proc
|
68
|
+
Proc.new do |prefix|
|
69
|
+
refresh
|
70
|
+
case
|
71
|
+
when Readline.line_buffer =~ /^\s*cd\s+\S*$/
|
72
|
+
contents.find_all do |content|
|
73
|
+
content[0...prefix.length] == prefix
|
74
|
+
end
|
75
|
+
when Readline.line_buffer =~ /^\s*\S*$/
|
76
|
+
command_names.find_all do |command_name|
|
77
|
+
command_name[0...prefix.length] == prefix
|
78
|
+
end
|
79
|
+
else
|
80
|
+
Dir[prefix + '*']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def command_names
|
86
|
+
refresh
|
87
|
+
commands.keys.sort
|
88
|
+
end
|
89
|
+
|
90
|
+
def commands
|
91
|
+
{}
|
92
|
+
end
|
93
|
+
|
94
|
+
def refresh
|
95
|
+
refresh! unless refreshed?
|
96
|
+
end
|
97
|
+
|
98
|
+
def refresh!
|
99
|
+
reset!
|
100
|
+
fetch_contents
|
101
|
+
self.last_refresh_at = Time.now
|
102
|
+
true
|
103
|
+
end
|
104
|
+
|
105
|
+
def fetch_contents
|
106
|
+
end
|
107
|
+
|
108
|
+
def initial_contents
|
109
|
+
[]
|
110
|
+
end
|
111
|
+
|
112
|
+
def reset!
|
113
|
+
self.contents = initial_contents
|
114
|
+
true
|
115
|
+
end
|
116
|
+
|
117
|
+
def refreshed?
|
118
|
+
self.last_refresh_at
|
119
|
+
end
|
120
|
+
|
121
|
+
def exists?
|
122
|
+
false
|
123
|
+
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
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
|
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
require 'elasticshell/command'
|
5
|
+
require 'elasticshell/scopes'
|
6
|
+
require 'elasticshell/client'
|
7
|
+
|
8
|
+
module Elasticshell
|
9
|
+
|
10
|
+
class Shell
|
11
|
+
|
12
|
+
VERBS = %w[GET POST PUT DELETE]
|
13
|
+
|
14
|
+
attr_accessor :client, :input, :command, :state, :only
|
15
|
+
|
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
|
21
|
+
|
22
|
+
attr_reader :scope
|
23
|
+
def scope= scope
|
24
|
+
@scope = scope
|
25
|
+
proc = scope.completion_proc
|
26
|
+
Readline.completion_proc = Proc.new do |prefix|
|
27
|
+
self.state = :completion
|
28
|
+
proc.call(prefix)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize options={}
|
33
|
+
self.state = :init
|
34
|
+
self.client = Client.new(options)
|
35
|
+
self.verb = (options[:verb] || 'GET')
|
36
|
+
self.scope = Scopes.from_path((options[:scope] || '/'), :client => self.client)
|
37
|
+
self.only = options[:only]
|
38
|
+
pretty! if options[:pretty]
|
39
|
+
end
|
40
|
+
|
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"
|
47
|
+
end
|
48
|
+
|
49
|
+
def prompt_verb_color
|
50
|
+
verb == "GET" ? "\e[34m" : "\e[31m"
|
51
|
+
end
|
52
|
+
|
53
|
+
def prompt_prettiness_indicator
|
54
|
+
pretty? ? '$' : '>'
|
55
|
+
end
|
56
|
+
|
57
|
+
def pretty?
|
58
|
+
@pretty
|
59
|
+
end
|
60
|
+
|
61
|
+
def pretty!
|
62
|
+
@pretty = true
|
63
|
+
end
|
64
|
+
|
65
|
+
def not_pretty!
|
66
|
+
@pretty = false
|
67
|
+
end
|
68
|
+
|
69
|
+
def setup
|
70
|
+
trap("INT") do
|
71
|
+
int
|
72
|
+
end
|
73
|
+
|
74
|
+
Readline.completer_word_break_characters = " \t\n\"\\'`$><=|&{("
|
75
|
+
|
76
|
+
puts <<EOF
|
77
|
+
Elasticshell v. #{Elasticshell.version}
|
78
|
+
Type "help" for contextual help.
|
79
|
+
EOF
|
80
|
+
end
|
81
|
+
|
82
|
+
def run
|
83
|
+
setup
|
84
|
+
loop
|
85
|
+
end
|
86
|
+
|
87
|
+
def loop
|
88
|
+
self.state = :read
|
89
|
+
while line = Readline.readline(prompt, true)
|
90
|
+
eval_line(line)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def eval_line line
|
95
|
+
begin
|
96
|
+
self.input = line.strip
|
97
|
+
self.command = Command.new(self, input)
|
98
|
+
self.state = :eval
|
99
|
+
self.command.evaluate!
|
100
|
+
rescue ::Elasticshell::Error => e
|
101
|
+
$stderr.puts e.message
|
102
|
+
end
|
103
|
+
self.state = :read
|
104
|
+
end
|
105
|
+
|
106
|
+
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
|
119
|
+
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
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def int
|
135
|
+
case self.state
|
136
|
+
when :read
|
137
|
+
$stdout.write("^C\n#{prompt}")
|
138
|
+
else
|
139
|
+
$stdout.write("^C...aborted\n#{prompt}")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear_line
|
144
|
+
while Readline.point > 0
|
145
|
+
$stdin.write("\b \b")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def die
|
150
|
+
puts "C-d"
|
151
|
+
print("C-d...quitting")
|
152
|
+
exit()
|
153
|
+
end
|
154
|
+
|
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
|
+
end
|
176
|
+
|
177
|
+
[c, o, b]
|
178
|
+
end
|
179
|
+
|
180
|
+
def request verb, params={}
|
181
|
+
c, o, b = command_and_query_and_body(input)
|
182
|
+
body = (params.delete(:body) || b || '')
|
183
|
+
print(client.request(verb, params.merge(:op => c), o, b))
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
data/lib/elasticshell.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'json'
|
3
|
+
require 'configliere'
|
4
|
+
|
5
|
+
require 'elasticshell/shell'
|
6
|
+
require 'elasticshell/scopes'
|
7
|
+
require 'elasticshell/client'
|
8
|
+
|
9
|
+
Settings.use(:commandline)
|
10
|
+
|
11
|
+
Settings.define(:servers, :description => "A comma-separated list of Elasticsearch servers to connect to.", :type => Array, :default => Elasticshell::Client::DEFAULT_SERVERS)
|
12
|
+
Settings.define(:only, :description => "A dot-separated hierarchical key to extract from the output scope.")
|
13
|
+
Settings.define(:pretty, :description => "Pretty-print all output. ", :default => false, :type => :boolean)
|
14
|
+
Settings.define(:verb, :description => "Set the default HTTP verb. ", :default => "GET")
|
15
|
+
Settings.define(:version, :description => "Print Elasticshell version and exit. ", :default => false, :type => :boolean)
|
16
|
+
Settings.description = <<-DESC
|
17
|
+
Elasticshell is a command-line shell for interacting with an
|
18
|
+
Elasticsearch database. It has the following start-up options.
|
19
|
+
DESC
|
20
|
+
|
21
|
+
def Settings.usage
|
22
|
+
"usage: #{File.basename($0)} [OPTIONS] [SCOPE]"
|
23
|
+
end
|
24
|
+
Settings.resolve!
|
25
|
+
|
26
|
+
module Elasticshell
|
27
|
+
|
28
|
+
def self.version
|
29
|
+
@version ||= begin
|
30
|
+
File.read(File.expand_path('../../VERSION', __FILE__)).chomp
|
31
|
+
rescue => e
|
32
|
+
'unknown'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.start *args
|
37
|
+
if Settings[:version]
|
38
|
+
puts version
|
39
|
+
exit()
|
40
|
+
end
|
41
|
+
|
42
|
+
es = Shell.new(Settings)
|
43
|
+
if Settings[:only]
|
44
|
+
if Settings.rest.length == 0
|
45
|
+
$stderr.puts "Starting with the --only option requires the first argument to name an API path (like `/_cluster/health')"
|
46
|
+
exit(1)
|
47
|
+
else
|
48
|
+
es.eval_line(Settings.rest.first)
|
49
|
+
exit()
|
50
|
+
end
|
51
|
+
else
|
52
|
+
if Settings.rest.length > 0
|
53
|
+
es.scope = Scopes.from_path(Settings.rest.first, :client => es.client)
|
54
|
+
end
|
55
|
+
es.run
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Scopes do
|
4
|
+
|
5
|
+
it "should be able to recognize the global scope" do
|
6
|
+
Scopes::Global.should_receive(:new).with(kind_of(Hash))
|
7
|
+
Scopes.from_path("/")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be able to recognize an index scope" do
|
11
|
+
Scopes::Index.should_receive(:new).with('foobar', kind_of(Hash))
|
12
|
+
Scopes.from_path("/foobar")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should be able to recognize a mapping scope" do
|
16
|
+
index = mock("Index /foobar")
|
17
|
+
Scopes::Index.should_receive(:new).with('foobar', kind_of(Hash)).and_return(index)
|
18
|
+
Scopes::Mapping.should_receive(:new).with(index, 'baz', kind_of(Hash))
|
19
|
+
Scopes.from_path("/foobar/baz")
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
ELASTICSHELL_ROOT = File.expand_path(__FILE__, '../../lib')
|
4
|
+
$: << ELASTICSHELL_ROOT unless $:.include?(ELASTICSHELL_ROOT)
|
5
|
+
require 'elasticshell'
|
6
|
+
include Elasticshell
|
7
|
+
|
8
|
+
Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |path| require path }
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.mock_with :rspec
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: elasticshell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dhruv Bansal
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: configliere
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rubberband
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Elasticshell provides a command-line shell 'es' for connecting to and
|
79
|
+
querying an Elasticsearch database. The shell will tab-complete Elasticsearch API
|
80
|
+
commands and index/mapping names.
|
81
|
+
email:
|
82
|
+
- dhruv@infochimps.com
|
83
|
+
executables:
|
84
|
+
- es
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- bin/es
|
89
|
+
- lib/elasticshell/scopes/cluster.rb
|
90
|
+
- lib/elasticshell/scopes/mapping.rb
|
91
|
+
- lib/elasticshell/scopes/global.rb
|
92
|
+
- lib/elasticshell/scopes/index.rb
|
93
|
+
- lib/elasticshell/scopes/nodes.rb
|
94
|
+
- lib/elasticshell/command.rb
|
95
|
+
- lib/elasticshell/log.rb
|
96
|
+
- lib/elasticshell/shell.rb
|
97
|
+
- lib/elasticshell/client.rb
|
98
|
+
- lib/elasticshell/error.rb
|
99
|
+
- lib/elasticshell/scopes.rb
|
100
|
+
- lib/elasticshell.rb
|
101
|
+
- spec/elasticshell/scopes_spec.rb
|
102
|
+
- spec/elasticshell/command_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- LICENSE
|
105
|
+
- README.rdoc
|
106
|
+
- VERSION
|
107
|
+
homepage: http://github.com/dhruvbansal/elasticshell
|
108
|
+
licenses: []
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options: []
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 1.8.23
|
128
|
+
signing_key:
|
129
|
+
specification_version: 3
|
130
|
+
summary: A command-line shell for Elasticsearch
|
131
|
+
test_files: []
|
132
|
+
has_rdoc:
|