retscli 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/retscli +5 -0
- data/lib/retscli.rb +74 -0
- data/lib/retscli/display_adapter.rb +240 -0
- data/lib/retscli/shell.rb +101 -0
- data/lib/retscli/shell_commands.rb +101 -0
- data/lib/retscli/version.rb +3 -0
- data/retscli.gemspec +29 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 64bb6b1cc5838d8be49673141144e7273b4bfb7c
|
4
|
+
data.tar.gz: 1d8b9c3f187d02e7de06d7a2f69be987adb4c57b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4f290c630981363f30220c14b11a9213fbb367f03ef87b5aa7c32f709a9b71e26acb800c39cefacc4ab18034446fc4e5132de29a19ff7667b83ab39a11a5625
|
7
|
+
data.tar.gz: 9349b62d0c680a86903829081fd1d43150f38894853449c8661e150946ef56f97975e107c57848959bdcdd4343ccd59d2bc5cf3717c372c184cb304015ce28b6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Ari Summer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Retscli
|
2
|
+
CLI for querying RETS servers and searching metadata.
|
3
|
+
|
4
|
+
This gem is built on top of the [rets](http://github.com/estately/rets) gem, which handles the actual querying and parsing of the RETS server requests. Big thanks to the [Estately](http://www.estately.com) team for their work!
|
5
|
+
|
6
|
+

|
7
|
+
|
8
|
+
## Installation
|
9
|
+
$ gem install retscli
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
#### Shell Commands
|
14
|
+
```bash
|
15
|
+
$ retscli help
|
16
|
+
Commands:
|
17
|
+
retscli capabilities [LOGIN URL] # Display capabilities for rets server
|
18
|
+
retscli console [LOGIN URL] # Start rets console
|
19
|
+
retscli help [COMMAND] # Describe available commands or one specific command
|
20
|
+
retscli validate [LOGIN URL] # Validate rets credentials
|
21
|
+
```
|
22
|
+
|
23
|
+
To see the available flags/options, run help on the command
|
24
|
+
|
25
|
+
```bash
|
26
|
+
$ retscli help console
|
27
|
+
Usage:
|
28
|
+
retscli console [LOGIN URL]
|
29
|
+
|
30
|
+
Options:
|
31
|
+
-u, [--username=USERNAME]
|
32
|
+
-p, [--password=PASSWORD]
|
33
|
+
-v, [--version=VERSION]
|
34
|
+
-a, [--agent=AGENT]
|
35
|
+
-ap, [--ua-password=UA_PASSWORD]
|
36
|
+
|
37
|
+
Start rets console
|
38
|
+
```
|
39
|
+
|
40
|
+
#### Rets Console Commands
|
41
|
+
After dropping into the RETS console, you get a bunch of useful commands for searching and exploring the RETS server
|
42
|
+
|
43
|
+
```bash
|
44
|
+
$ retscli console http://rets.server.com -u summera -p password
|
45
|
+
|
46
|
+
summera@rets.server.com > help
|
47
|
+
Commands:
|
48
|
+
capabilities # Display capabilities for rets server
|
49
|
+
classes [RESOURCE] # List available classes for resource
|
50
|
+
help [COMMAND] # Describe available commands or one specific command
|
51
|
+
metadata # View metadata
|
52
|
+
objects [RESOURCE] # List available objects for resource
|
53
|
+
resources # List available resources
|
54
|
+
search-metadata # Search metadata tables
|
55
|
+
tables [RESOURCE] [CLASS] # List available tables for class of resource
|
56
|
+
timezone-offset # System timezone offset
|
57
|
+
```
|
58
|
+
|
59
|
+
Again, to see available flags/options, run help on the command. Many of the commands have an `editor` option if you feel the need to get down and dirty in your editor of choice.
|
60
|
+
|
61
|
+
|
62
|
+
```bash
|
63
|
+
summera@rets.server.com > help search-metadata
|
64
|
+
Usage:
|
65
|
+
search-metadata
|
66
|
+
|
67
|
+
Options:
|
68
|
+
-r, [--resources=one two three] # Filter metadata by resources
|
69
|
+
-c, [--classes=one two three] # Filter metadata by classes
|
70
|
+
-e, [--editor=EDITOR] # Open search results in editor
|
71
|
+
|
72
|
+
Search metadata tables
|
73
|
+
```
|
74
|
+
|
75
|
+
## Notes
|
76
|
+
- When opening output in your editor, retscli will check the `$EDITOR` environment variable. If this is not set, it falls back to nano.
|
77
|
+
- Much of the output is piped through `less` by default to allow for easy paging. If you'd like to change this, set your preferred pager in the `$PAGER` environment variable.
|
78
|
+
- Retscli uses the ruby readline module for the rets console
|
79
|
+
|
80
|
+
## Contributing
|
81
|
+
|
82
|
+
1. Fork it ( https://github.com/ianks/octodown/fork )
|
83
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
84
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
85
|
+
1. Run the test suite (`bundle exec rake`)
|
86
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
1. Create a new Pull Request
|
88
|
+
|
89
|
+
|
90
|
+
## License
|
91
|
+
|
92
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
93
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "retscli"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/exe/retscli
ADDED
data/lib/retscli.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "retscli/version"
|
2
|
+
require "rets"
|
3
|
+
require "thor"
|
4
|
+
require "terminal-table"
|
5
|
+
require "retscli/shell"
|
6
|
+
require "retscli/display_adapter"
|
7
|
+
|
8
|
+
module Retscli
|
9
|
+
class Cli < Thor
|
10
|
+
|
11
|
+
desc 'validate [LOGIN URL]', 'Validate rets credentials'
|
12
|
+
method_option :username, aliases: '-u'
|
13
|
+
method_option :password, aliases: '-p'
|
14
|
+
method_option :version, aliases: '-v'
|
15
|
+
method_option :agent, aliases: '-a'
|
16
|
+
method_option :ua_password, aliases: '-ap'
|
17
|
+
def validate(url)
|
18
|
+
client = rets_client(url, options)
|
19
|
+
|
20
|
+
begin
|
21
|
+
client.login
|
22
|
+
client.logout
|
23
|
+
puts set_color("\u2713 Valid Credentials", :green)
|
24
|
+
true
|
25
|
+
rescue => e
|
26
|
+
puts set_color("\u2717 Invalid Credential\n#{e.message}", :red)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'capabilities [LOGIN URL]', 'Display capabilities for rets server'
|
32
|
+
method_option :username, aliases: '-u'
|
33
|
+
method_option :password, aliases: '-p'
|
34
|
+
method_option :version, aliases: '-v'
|
35
|
+
method_option :agent, aliases: '-a'
|
36
|
+
method_option :ua_password, aliases: '-ap'
|
37
|
+
def capabilities(url)
|
38
|
+
client = rets_client(url, options)
|
39
|
+
|
40
|
+
begin
|
41
|
+
client.login
|
42
|
+
display_adapter = Retscli::DisplayAdapter.new(client)
|
43
|
+
display_adapter.page(display_adapter.capabilities)
|
44
|
+
client.logout
|
45
|
+
rescue => e
|
46
|
+
puts set_color("#{e.message}", :red)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'console [LOGIN URL]', 'Start rets console'
|
51
|
+
method_option :username, aliases: '-u'
|
52
|
+
method_option :password, aliases: '-p'
|
53
|
+
method_option :version, aliases: '-v'
|
54
|
+
method_option :agent, aliases: '-a'
|
55
|
+
method_option :ua_password, aliases: '-ap'
|
56
|
+
def console(url)
|
57
|
+
client = rets_client(url, options)
|
58
|
+
Retscli::Shell.new(client).start
|
59
|
+
end
|
60
|
+
|
61
|
+
no_commands do
|
62
|
+
def rets_client(url, params)
|
63
|
+
::Rets::Client.new({
|
64
|
+
:login_url => url,
|
65
|
+
:username => params[:username],
|
66
|
+
:password => params[:password],
|
67
|
+
:version => params[:version],
|
68
|
+
:agent => params[:agent],
|
69
|
+
:ua_password => params[:ua_password]
|
70
|
+
})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require "terminal-table"
|
2
|
+
|
3
|
+
module Retscli
|
4
|
+
class DisplayAdapter
|
5
|
+
def initialize(client)
|
6
|
+
@client = client
|
7
|
+
@colorer = ::Thor::Shell::Color.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def capabilities
|
11
|
+
Terminal::Table.new(:rows => @client.capabilities.to_a)
|
12
|
+
end
|
13
|
+
|
14
|
+
def resources
|
15
|
+
retrieve_metadata.tree.map do |key, resource|
|
16
|
+
render_resource(resource)
|
17
|
+
end.join("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
def classes(resource)
|
21
|
+
resource_tree = retrieve_metadata.tree[resource.downcase]
|
22
|
+
|
23
|
+
if resource_tree
|
24
|
+
resource_tree.rets_classes.map do |klass|
|
25
|
+
render_class(klass)
|
26
|
+
end.join("\n")
|
27
|
+
else
|
28
|
+
set_color("#{resource} resource does not exist", :red)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def objects(resource)
|
33
|
+
resource_tree = retrieve_metadata.tree[resource.downcase]
|
34
|
+
|
35
|
+
if resource_tree
|
36
|
+
resource_tree.rets_objects.map do |object|
|
37
|
+
render_object(object)
|
38
|
+
end.join("\n")
|
39
|
+
else
|
40
|
+
set_color("#{resource} resource does not exist", :red)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def tables(resource, klass)
|
45
|
+
resource_tree = retrieve_metadata.tree[resource.downcase]
|
46
|
+
return set_color("#{resource} resource does not exist", :red) unless resource_tree
|
47
|
+
|
48
|
+
resource_class = resource_tree.rets_classes.detect { |rc| rc.name.downcase == klass.downcase }
|
49
|
+
return set_color("#{klass} class does not exist", :red) unless resource_class
|
50
|
+
|
51
|
+
resource_class.tables.map do |table|
|
52
|
+
render_table(table)
|
53
|
+
end.join("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def timezone_offset
|
57
|
+
offset = retrieve_metadata
|
58
|
+
.metadata_types[:system]
|
59
|
+
.first
|
60
|
+
.fragment
|
61
|
+
.xpath('SYSTEM')
|
62
|
+
.attribute('TimeZoneOffset')
|
63
|
+
.to_s
|
64
|
+
|
65
|
+
if offset.empty?
|
66
|
+
set_color("No offset specified", :red)
|
67
|
+
else
|
68
|
+
offset
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def metadata
|
73
|
+
build_tree
|
74
|
+
end
|
75
|
+
|
76
|
+
def search_metadata(search, options={})
|
77
|
+
options = { :resources => [], :classes => [] , :color => true }.merge(options)
|
78
|
+
search_results = ''
|
79
|
+
resources = options[:resources].map!{ |res| res.downcase }
|
80
|
+
classes = options[:classes].map!{ |klass| klass.downcase }
|
81
|
+
|
82
|
+
|
83
|
+
retrieve_metadata.tree.each do |key, res|
|
84
|
+
next if !resources.empty? && !resources.include?(res.id.downcase)
|
85
|
+
match_found_for_resouce = false
|
86
|
+
|
87
|
+
res.rets_classes.each do |klass|
|
88
|
+
next if !classes.empty? && !classes.include?(klass.name.downcase)
|
89
|
+
match_found_for_class = false
|
90
|
+
|
91
|
+
klass.tables.each do |table|
|
92
|
+
if match = search_table(table, search)
|
93
|
+
search_results << render_resource(res) << "\n" unless match_found_for_resouce
|
94
|
+
search_results << tab_over(render_class(klass), 1) << "\n" unless match_found_for_class
|
95
|
+
|
96
|
+
rendered_table = tab_over(render_table(table), 2)
|
97
|
+
search_results << rendered_table.gsub!(match.regexp) do |sub|
|
98
|
+
options[:color] ? set_color(sub, :red, :on_white) : sub
|
99
|
+
end << "\n"
|
100
|
+
|
101
|
+
match_found_for_resouce = true
|
102
|
+
match_found_for_class = true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
search_results
|
109
|
+
end
|
110
|
+
|
111
|
+
def page(output)
|
112
|
+
begin
|
113
|
+
pager = ENV['PAGER'] || 'less'
|
114
|
+
IO.popen(pager, 'w') { |f| f.puts(output) }
|
115
|
+
rescue Errno::EPIPE
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def open_in_editor(text, editor='editor')
|
120
|
+
editor = editor == 'editor' ? (ENV['EDITOR'] || 'nano') : editor
|
121
|
+
open_tempfile_with_content(editor, text)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def open_tempfile_with_content(editor, initial_content)
|
126
|
+
temp_file do |f|
|
127
|
+
f.puts(initial_content)
|
128
|
+
f.flush
|
129
|
+
f.close(false)
|
130
|
+
system(editor, f.path)
|
131
|
+
File.read(f.path)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def temp_file(ext='.txt')
|
136
|
+
file = Tempfile.new(['retscli', ext])
|
137
|
+
yield file
|
138
|
+
ensure
|
139
|
+
file.close(true) if file
|
140
|
+
end
|
141
|
+
|
142
|
+
def retrieve_metadata
|
143
|
+
@client.metadata
|
144
|
+
end
|
145
|
+
|
146
|
+
def search_table(table, search)
|
147
|
+
regex = /#{search}/i
|
148
|
+
table.table_fragment['LongName'].match(regex) ||
|
149
|
+
table.table_fragment['SystemName'].match(regex) ||
|
150
|
+
table.table_fragment['ShortName'].match(regex) ||
|
151
|
+
table.table_fragment['StandardName'].match(regex)
|
152
|
+
end
|
153
|
+
|
154
|
+
def build_tree
|
155
|
+
tree = ''
|
156
|
+
retrieve_metadata.tree.each do |name, resource|
|
157
|
+
tree << render_resource(resource)
|
158
|
+
|
159
|
+
resource.rets_classes.each do |klass|
|
160
|
+
tree << "\n"
|
161
|
+
tree << tab_over(render_class(klass), 1)
|
162
|
+
|
163
|
+
klass.tables.each do |table|
|
164
|
+
tree << "\n"
|
165
|
+
tree << tab_over(render_table(table), 2)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
resource.rets_objects.each do |object|
|
170
|
+
tree << "\n"
|
171
|
+
tree << tab_over(render_object(object), 1)
|
172
|
+
end
|
173
|
+
|
174
|
+
tree << "\n"
|
175
|
+
end
|
176
|
+
|
177
|
+
tree
|
178
|
+
end
|
179
|
+
|
180
|
+
def render_resource(resource)
|
181
|
+
"Resource: #{resource.id} (Key Field: #{resource.key_field})"
|
182
|
+
end
|
183
|
+
|
184
|
+
def render_class(klass)
|
185
|
+
"Class: #{klass.name}\n"\
|
186
|
+
" Visible Name: #{klass.visible_name}\n"\
|
187
|
+
" Description : #{klass.description}"
|
188
|
+
end
|
189
|
+
|
190
|
+
def render_object(object)
|
191
|
+
"Object: #{object.name}\n"\
|
192
|
+
" MimeType: #{object.mime_type}\n"\
|
193
|
+
" Description: #{object.description}"
|
194
|
+
end
|
195
|
+
|
196
|
+
def render_table(table)
|
197
|
+
types = false
|
198
|
+
if table.is_a?(::Rets::Metadata::LookupTable)
|
199
|
+
header = "LookupTable: #{table.name}"
|
200
|
+
types = true
|
201
|
+
elsif table.is_a?(::Rets::Metadata::MultiLookupTable)
|
202
|
+
header = "MultiLookupTable: #{table.name}"
|
203
|
+
types = true
|
204
|
+
else
|
205
|
+
header = "Table: #{table.name}"
|
206
|
+
end
|
207
|
+
|
208
|
+
base = "#{header}\n"\
|
209
|
+
" Resource: #{table.resource_id}\n"\
|
210
|
+
" ShortName: #{table.table_fragment["ShortName"] }\n"\
|
211
|
+
" LongName: #{table.long_name }\n"\
|
212
|
+
" StandardName: #{table.table_fragment["StandardName"] }\n"\
|
213
|
+
" Units: #{table.table_fragment["Units"] }\n"\
|
214
|
+
" Searchable: #{table.table_fragment["Searchable"] }\n"\
|
215
|
+
" Required: #{table.table_fragment['Required']}"
|
216
|
+
|
217
|
+
if types
|
218
|
+
base << "\n Types:"
|
219
|
+
table.lookup_types.each do |type|
|
220
|
+
base << "\n #{render_lookup_type(type)}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
base
|
225
|
+
end
|
226
|
+
|
227
|
+
def render_lookup_type(type)
|
228
|
+
"#{type.long_value} -> #{type.value}"
|
229
|
+
end
|
230
|
+
|
231
|
+
def tab_over(str, num_tabs)
|
232
|
+
tab_width = " "
|
233
|
+
str.prepend(tab_width*num_tabs).gsub!("\n", "\n#{tab_width*num_tabs}")
|
234
|
+
end
|
235
|
+
|
236
|
+
def set_color(text, *args)
|
237
|
+
@colorer.set_color(text, *args)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "readline"
|
2
|
+
require "csv"
|
3
|
+
require_relative "shell_commands"
|
4
|
+
require 'tty-spinner'
|
5
|
+
|
6
|
+
module Retscli
|
7
|
+
class Shell
|
8
|
+
EXIT_COMMANDS = ['quit', 'exit'].freeze
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@stty_save = `stty -g`.chomp
|
12
|
+
setup_readline_autocomplete
|
13
|
+
@client = client
|
14
|
+
@client.login
|
15
|
+
@display_adapter = Retscli::DisplayAdapter.new(client)
|
16
|
+
@colorer = ::Thor::Shell::Color.new
|
17
|
+
retrieve_metadata
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
begin
|
22
|
+
while line = readline_with_hist_management
|
23
|
+
if EXIT_COMMANDS.include?(line)
|
24
|
+
close
|
25
|
+
else
|
26
|
+
execute_shell_command(line)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
rescue Interrupt
|
30
|
+
close
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# NOTE: this should probably be private, but making it public allowed
|
35
|
+
# for easier testing without having to deal with mocking readline. Can we
|
36
|
+
# find a better way?
|
37
|
+
def execute_shell_command(line)
|
38
|
+
Retscli::ShellCommands.start(split_line(line), :display_adapter => @display_adapter)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
# Split line, immitating ARGV behavior where it splits on spaces
|
43
|
+
# except when quoted
|
44
|
+
def split_line(line)
|
45
|
+
CSV.parse_line(line, :col_sep => ' ')
|
46
|
+
end
|
47
|
+
|
48
|
+
def retrieve_metadata
|
49
|
+
spinner = TTY::Spinner.new("Retrieving metadata #{@colorer.set_color(':spinner', :yellow)} ...", format: :box_bounce)
|
50
|
+
spinner.start
|
51
|
+
|
52
|
+
begin
|
53
|
+
@client.metadata
|
54
|
+
rescue => e
|
55
|
+
puts "Error retrieving metadata. #{e.message}"
|
56
|
+
ensure
|
57
|
+
spinner.stop(@colorer.set_color('done', :green))
|
58
|
+
puts ''
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def setup_readline_autocomplete
|
63
|
+
comp = proc { |s| Retscli::ShellCommands.command_list.grep( /^#{Regexp.escape(s)}/ ) }
|
64
|
+
Readline.completion_append_character = " "
|
65
|
+
Readline.completion_proc = comp
|
66
|
+
end
|
67
|
+
|
68
|
+
def readline_with_hist_management
|
69
|
+
line = Readline.readline(prompt, true)
|
70
|
+
return nil if line.nil?
|
71
|
+
|
72
|
+
if line =~ /^\s*$/ || Readline::HISTORY.to_a[-2] == line
|
73
|
+
Readline::HISTORY.pop
|
74
|
+
end
|
75
|
+
|
76
|
+
line
|
77
|
+
end
|
78
|
+
|
79
|
+
def prompt
|
80
|
+
@prompt ||=
|
81
|
+
begin
|
82
|
+
uri = URI.parse(@client.login_url)
|
83
|
+
@colorer.set_color("#{@client.options[:username]}@#{uri.host} > ", :blue)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def close
|
88
|
+
system('stty', @stty_save)
|
89
|
+
@client.logout
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# HACK: because HTTPClient warnings
|
96
|
+
# are super annoying
|
97
|
+
class HTTPClient
|
98
|
+
module Util
|
99
|
+
def warning(msg); end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module Retscli
|
4
|
+
class ShellCommands < Thor
|
5
|
+
def initialize(args, options, config)
|
6
|
+
super
|
7
|
+
@display_adapter = config[:display_adapter]
|
8
|
+
end
|
9
|
+
|
10
|
+
# since we are using thor for shell commands we want to remove executable
|
11
|
+
# name from command help menu
|
12
|
+
def self.banner(command, namespace = nil, subcommand = false)
|
13
|
+
super.gsub("#{basename} ", "")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.command_list
|
17
|
+
all_commands.keys.map{ |command| command.gsub('_', '-') }
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'capabilities', 'Display capabilities for rets server'
|
21
|
+
def capabilities
|
22
|
+
@display_adapter.page(@display_adapter.capabilities)
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'resources', 'List available resources'
|
26
|
+
method_option :editor, :aliases => '-e', :desc => 'Open resources in editor'
|
27
|
+
def resources
|
28
|
+
if options[:editor]
|
29
|
+
@display_adapter.open_in_editor(@display_adapter.resources, options[:editor])
|
30
|
+
else
|
31
|
+
@display_adapter.page(@display_adapter.resources)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'classes [RESOURCE]', 'List available classes for resource'
|
36
|
+
method_option :editor, :aliases => '-e', :desc => 'Open classes in editor'
|
37
|
+
def classes(resource)
|
38
|
+
if options[:editor]
|
39
|
+
@display_adapter.open_in_editor(@display_adapter.classes(resource), options[:editor])
|
40
|
+
else
|
41
|
+
@display_adapter.page(@display_adapter.classes(resource))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc 'tables [RESOURCE] [CLASS]', 'List available tables for class of resource'
|
46
|
+
method_option :editor, :aliases => '-e', :desc => 'Open tables in editor'
|
47
|
+
def tables(resource, klass)
|
48
|
+
if options[:editor]
|
49
|
+
@display_adapter.open_in_editor(@display_adapter.tables(resource, klass), options[:editor])
|
50
|
+
else
|
51
|
+
@display_adapter.page(@display_adapter.tables(resource, klass))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
desc 'objects [RESOURCE]', 'List available objects for resource'
|
56
|
+
method_option :editor, :aliases => '-e', :desc => 'Open objects in editor'
|
57
|
+
def objects(resource)
|
58
|
+
if options[:editor]
|
59
|
+
@display_adapter.open_in_editor(@display_adapter.objects(resource), options[:editor])
|
60
|
+
else
|
61
|
+
@display_adapter.page(@display_adapter.objects(resource))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc 'timezone-offset', 'System timezone offset'
|
66
|
+
def timezone_offset
|
67
|
+
puts @display_adapter.timezone_offset
|
68
|
+
end
|
69
|
+
|
70
|
+
map 'timezone-offset' => :timezone_offset
|
71
|
+
|
72
|
+
desc 'metadata', 'View metadata'
|
73
|
+
method_option :editor, :aliases => '-e', :desc => 'Open metadata in editor'
|
74
|
+
def metadata
|
75
|
+
if options[:editor]
|
76
|
+
@display_adapter.open_in_editor(@display_adapter.metadata, options[:editor])
|
77
|
+
else
|
78
|
+
@display_adapter.page(@display_adapter.metadata)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
desc 'search-metadata', 'Search metadata tables'
|
83
|
+
method_option :resources, :aliases => '-r', :desc => 'Filter metadata by resources', :type => :array, :default => []
|
84
|
+
method_option :classes, :aliases => '-c', :desc => 'Filter metadata by classes', :type => :array, :default => []
|
85
|
+
method_option :editor, :aliases => '-e', :desc => 'Open search results in editor'
|
86
|
+
def search_metadata(search)
|
87
|
+
search_options = {
|
88
|
+
:classes => options[:classes],
|
89
|
+
:resources => options[:resources]
|
90
|
+
}
|
91
|
+
|
92
|
+
if options[:editor]
|
93
|
+
@display_adapter.open_in_editor(@display_adapter.search_metadata(search, search_options.merge(:color => false)), options[:editor])
|
94
|
+
else
|
95
|
+
@display_adapter.page(@display_adapter.search_metadata(search, search_options))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
map 'search-metadata' => :search_metadata
|
100
|
+
end
|
101
|
+
end
|
data/retscli.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'retscli/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "retscli"
|
8
|
+
spec.version = Retscli::VERSION
|
9
|
+
spec.authors = ["Ari Summer"]
|
10
|
+
spec.email = ["aribsummer@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "CLI for querying RETS servers and searching metadata"
|
13
|
+
spec.description = "CLI for querying RETS servers and searching metadata"
|
14
|
+
spec.homepage = "http://github.com/summera/retscli"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "rets", "~> 0.10"
|
23
|
+
spec.add_dependency "thor", "~> 0.19"
|
24
|
+
spec.add_dependency "terminal-table", "~> 1.5"
|
25
|
+
spec.add_dependency "tty-spinner", "~> 0.2"
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: retscli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ari Summer
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rets
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.10'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thor
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.19'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.19'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: terminal-table
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: tty-spinner
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.11'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.11'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.0'
|
111
|
+
description: CLI for querying RETS servers and searching metadata
|
112
|
+
email:
|
113
|
+
- aribsummer@gmail.com
|
114
|
+
executables:
|
115
|
+
- retscli
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- bin/console
|
125
|
+
- bin/setup
|
126
|
+
- exe/retscli
|
127
|
+
- lib/retscli.rb
|
128
|
+
- lib/retscli/display_adapter.rb
|
129
|
+
- lib/retscli/shell.rb
|
130
|
+
- lib/retscli/shell_commands.rb
|
131
|
+
- lib/retscli/version.rb
|
132
|
+
- retscli.gemspec
|
133
|
+
homepage: http://github.com/summera/retscli
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata: {}
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 2.4.5
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: CLI for querying RETS servers and searching metadata
|
157
|
+
test_files: []
|