elasticshell 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|