leif 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fde83f5fbfd332f246ef59ca53fb8c7ba9d1027
4
- data.tar.gz: 49965583dbd38fa276145668ece11702fe5c676f
3
+ metadata.gz: 9ea567a178df95bf78d96ecd8785fdc0efe759bc
4
+ data.tar.gz: a28cad7fb674260307e5e582f8b13a245cb0d814
5
5
  SHA512:
6
- metadata.gz: 846ac3499f51b81c6f39da25559e29878f57697e2c28a4135afe27e1a47b6c505e42ffdeff0ddd42d285c911d00e870fddc884b5f51bf45e6fcc7f7a5906e2e0
7
- data.tar.gz: 112adf4af67199102be748649edddfc9f835b329d427ed1f169c68f183b9ebc2fe9c5cdf5e7024dbb5685413f9b396c18eafe54d35c1e061af560cc23a5010ee
6
+ metadata.gz: 1bb6da4ca06176c47e46c2a8647efac6d8af5eff9e4b44aa799d94a16e6b958966a490adc33ae1072b7a03a6d1b0e5a5eba0f3c139ec232dfcb607be6ec3df48
7
+ data.tar.gz: ca287bcfc08378281846e479c30e2cb07a5430de571bfcfd50fcd4d55d85550009dc33ebf477b96c9be5761ed849a9e337f5d2e6da3c4b863fa8ff7e65d07aa6
data/README.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  A hypermedia browser for the CloudApp Collection+JSON API.
4
4
 
5
+ ## Requirements
6
+
7
+ `leif` requires Ruby 1.9.3 or greater. Windows is not yet supported. If you're
8
+ willing to lend a hand, we'd love to officially support it.
9
+
10
+ ## Installation
11
+
12
+ ``` bash
13
+ $ gem install leif
14
+ $ leif
15
+ ```
16
+
17
+ `leif` includes a man page. To read it:
18
+
19
+ ``` bash
20
+ $ gem install gem-man
21
+ $ gem man leif
22
+ ```
23
+
5
24
  ## Interactive Commands
6
25
 
7
26
  - `root`:
@@ -17,6 +36,9 @@ A hypermedia browser for the CloudApp Collection+JSON API.
17
36
  - `token` <u>token</u>:
18
37
  Authenticate using the given token and reload the current resource.
19
38
 
39
+ - `help`:
40
+ Print interactive command help.
41
+
20
42
  ## Example
21
43
 
22
44
  The UI is weak at this point and not very intuitive. To get started, here's a
data/bin/leif CHANGED
@@ -1,325 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'leif/cli'
3
+ require 'leif/collection_json'
4
+ require 'leif/section'
2
5
 
3
- class CollectionJsonResponse < SimpleDelegator
4
- extend Forwardable
5
-
6
- def_delegators :collection, :href, :links, :link, :items,
7
- :template, :fill_template_field, :fill_template, :submit_template
8
-
9
- def collection
10
- @collection ||= Collection.new(body)
11
- end
12
-
13
- class Collection < SimpleDelegator
14
- def initialize(body)
15
- super body.fetch('collection')
16
- end
17
-
18
- def href
19
- fetch('href')
20
- end
21
-
22
- def links
23
- return [] unless has_key?('links')
24
- fetch('links').map {|link| Link.new(link) }
25
- end
26
-
27
- def link(relation)
28
- links.find {|link| link.relation == relation }
29
- end
30
-
31
- def template
32
- @template ||= Item.new(fetch('template'))
33
- end
34
-
35
- def fill_template_field(name, value)
36
- template[name] = value
37
- end
38
-
39
- def fill_template(item)
40
- item.to_hash.each do |name, value|
41
- p [name, value]
42
- fill_template_field name, value
43
- end
44
- end
45
-
46
- def items
47
- return [] unless has_key?('items')
48
- fetch('items').map {|item| Item.new(item) }
49
- end
50
- end
51
-
52
- class Link
53
- attr_accessor :relation, :href
54
-
55
- def initialize(link)
56
- @relation = link.fetch('rel')
57
- @href = link.fetch('href')
58
- end
59
- end
60
-
61
- class Item
62
- attr_accessor :item
63
-
64
- def initialize(item)
65
- @item = item
66
- end
67
-
68
- def href
69
- @item.fetch('href')
70
- end
71
-
72
- def data
73
- @item.fetch('data')
74
- end
75
-
76
- def []=(name, value)
77
- datum = data.find {|datum| datum['name'] == name }
78
- return unless datum
79
- datum['value'] = value
80
- end
81
-
82
- def to_hash
83
- data.each_with_object({}) do |datum, data|
84
- data[datum['name']] = datum['value']
85
- end
86
- end
87
- end
88
- end
89
-
90
- def conn
91
- @conn ||= Faraday.new(url: 'https://api.getcloudapp.com') do |config|
92
- config.request :url_encoded
93
- config.response :logger, logger
94
- config.response :json, :content_type => /\bjson$/
95
- config.adapter Faraday.default_adapter
96
- end
97
- end
98
-
99
- def logger
100
- @logger ||= Logger.new(debug_output)
101
- end
102
-
103
- def debug_output
104
- @debug_output ||= StringIO.new
105
- end
106
-
107
- def reset_debug_output
108
- debug_output.rewind
109
- debug_output.truncate 0
110
- end
111
-
112
- def make_request(uri, data = {}, method = :unset)
113
- method = data.empty? ? :get : :post if method == :unset
114
- reset_debug_output
115
- @response = CollectionJsonResponse.new(conn.send(method, uri, data))
116
- end
117
-
118
- def get_root
119
- make_request '/'
120
- end
121
-
122
- def retry_request
123
- make_request @response.env[:url].request_uri
124
- end
125
-
126
- def section(banner)
6
+ trap('INT') do
127
7
  puts
128
- puts "-- #{banner} --"
129
- output = Output.new(1)
130
- yield output
131
- output.print '[empty]' if output.empty?
8
+ exit
132
9
  end
133
10
 
134
- class Output
135
- attr_reader :level, :io
136
- def empty?() @empty end
137
-
138
- def initialize(level, io = $stdout)
139
- @level = level
140
- @io = io
141
- @empty = true
142
- end
143
-
144
- def print(line = '')
145
- print_lines Array(line)
146
- end
147
-
148
- def print_lines(lines)
149
- @empty = false
150
- lines.each do |line|
151
- io.puts(indentation + line)
152
- end
153
- end
154
-
155
- def indentation
156
- ' ' * level
157
- end
158
- end
159
-
160
- def print_response
161
- section 'Request' do |out|
162
- out.print "#{@response.env[:method].upcase} #{@response.env[:url]}"
163
- out.print_lines @response.env[:request_headers].map {|header, value|
164
- "#{header}: #{value}"
165
- }
166
- end
167
-
168
- section 'Response' do |out|
169
- out.print_lines @response.headers.map {|header, value|
170
- "#{header}: #{value}"
171
- }
172
- end
173
-
174
- section 'Body' do |out|
175
- out.print_lines JSON.pretty_generate(@response.body).lines
176
- end
177
-
178
- section 'Links' do |out|
179
- out.print link_relations unless link_relations.empty?
180
- end
181
- end
182
-
183
- def link_relations
184
- @response.links.map(&:relation).join(', ')
185
- end
186
-
187
- def request_basic_authentication(username = :ask, password = :ask)
188
- username = ask('Username: ') if username == :ask
189
- password = ask('Password: ') {|q| q.echo = '*' } if password == :ask
190
- conn.basic_auth username, password
191
- retry_request
192
- end
193
-
194
- def request_token_authentication(token = '2x033S09401z300E')
195
- conn.headers['Authorization'] = "Token token=#{token.inspect}"
196
- retry_request
197
- end
198
-
199
- def follow_link(relation = :ask)
200
- relation = ask('Relation: ') if relation == :ask
201
- make_request @response.link(relation).href
202
- end
203
-
204
- def create_item
205
- loop do
206
- section 'Create Item' do |out|
207
- out.print_lines JSON.pretty_generate(@response.template.item).lines
208
- end
209
-
210
- puts
211
- puts 'Fill the template to create a new item.'
212
- name = ask('Name (empty to submit): ')
213
- break if name.empty?
214
- value = ask('Value: ')
215
-
216
- @response.fill_template_field name, value
217
- end
218
-
219
- make_request @response.collection.href, @response.template.to_hash
220
- end
221
-
222
- def update_item
223
- item = @response.collection.items.find do |item|
224
- section 'Item' do |out|
225
- out.print_lines JSON.pretty_generate(item.item).lines
226
- end
227
-
228
- puts
229
- response = ask('Select this item to update [y,n]? ') do |q|
230
- q.character = true
231
- q.validate = /\A[yn]\Z/
232
- end
233
-
234
- response == 'y'
235
- end
236
-
237
- @response.fill_template item
238
-
11
+ Leif::Cli.new.tap do |cli|
12
+ cli.get_root
239
13
  loop do
240
- section 'Update Item' do |out|
241
- out.print_lines JSON.pretty_generate(@response.template.item).lines
242
- end
243
-
244
- puts
245
- puts 'Fill the template to update the item.'
246
- name = ask('Name (empty to submit): ')
247
- break if name.empty?
248
- value = ask('Value: ')
249
-
250
- @response.fill_template_field name, value
14
+ cli.print_response
15
+ cli.get_next_action
251
16
  end
252
-
253
- make_request item.href, @response.template.to_hash, :put
254
17
  end
255
-
256
- def print_debug
257
- section 'Debug' do |out|
258
- debug_output.rewind
259
- out.print_lines debug_output.readlines
260
- end
261
- end
262
-
263
- def print_help
264
- puts <<EOS
265
- root:
266
- Go back to the root
267
-
268
- follow <rel>:
269
- Follow link with the given relation.
270
-
271
- template [<name>=<value>...]:
272
- Fill the template with the given name/value pairs and submit.
273
-
274
- basic [<username> [<password>]]:
275
- Authenticate with HTTP Basic and reload the current resource. Will be
276
- prompted for username and password if omitted.
277
-
278
- token <token>:
279
- Authenticate using the given token and reload the current resource.
280
-
281
- debug:
282
- Print debug output from the previous HTTP request and response.
283
- EOS
284
- end
285
-
286
- def get_next_action
287
- command, args = ask_for_action
288
- case command
289
- when 'r', 'root' then get_root
290
- when 'f', 'follow' then follow_link(*args)
291
- when 'create' then create_item
292
- when 'update' then update_item
293
- when 'b', 'basic' then request_basic_authentication(*args)
294
- when 't', 'token' then request_token_authentication(*args)
295
- when 'd', 'debug' then print_debug; get_next_action
296
- when '?', 'help' then print_help; get_next_action
297
- when 'q', 'quit' then exit
298
- else puts 'Try again.'; get_next_action
299
- end
300
- end
301
-
302
- def ask_for_action
303
- puts
304
- input = ask('> ') {|q| q.readline = true }.split(/\s/)
305
- [ input.first, input[1..-1] ]
306
- end
307
-
308
- trap('INT') { puts; exit }
309
-
310
- get_root
311
- loop do
312
- print_response
313
- get_next_action
314
- end
315
-
316
- BEGIN {
317
- require 'delegate'
318
- require 'forwardable'
319
- require 'highline/import'
320
- require 'json'
321
- require 'faraday'
322
- require 'faraday_middleware'
323
- require 'logger'
324
- require 'stringio'
325
- }
data/leif.gemspec CHANGED
@@ -15,6 +15,7 @@ Gem::Specification.new do |spec|
15
15
  spec.add_dependency 'faraday', '~> 0.8.8'
16
16
  spec.add_dependency 'faraday_middleware', '~> 0.9.0'
17
17
  spec.add_dependency 'highline', '~> 1.6.19'
18
+ spec.add_development_dependency 'rspec'
18
19
  spec.add_development_dependency 'ronn'
19
20
 
20
21
  spec.bindir = 'bin'
data/lib/leif/cli.rb ADDED
@@ -0,0 +1,203 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require 'highline/import'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'stringio'
7
+
8
+ module Leif
9
+ class Cli
10
+ def conn
11
+ @conn ||= Faraday.new(url: 'https://api.getcloudapp.com') do |config|
12
+ config.request :url_encoded
13
+ config.response :logger, logger
14
+ config.response :json, :content_type => /\bjson$/
15
+ config.adapter Faraday.default_adapter
16
+ end
17
+ end
18
+
19
+ def logger
20
+ @logger ||= Logger.new(debug_output)
21
+ end
22
+
23
+ def debug_output
24
+ @debug_output ||= StringIO.new
25
+ end
26
+
27
+ def banner(banner, &message)
28
+ puts
29
+ Section.banner(banner, &message)
30
+ end
31
+
32
+ def reset_debug_output
33
+ debug_output.rewind
34
+ debug_output.truncate 0
35
+ end
36
+
37
+ def make_request(uri, data = {}, method = :unset)
38
+ method = data.empty? ? :get : :post if method == :unset
39
+ reset_debug_output
40
+ @response = conn.send(method, uri, data)
41
+ end
42
+
43
+ def collection
44
+ Leif::CollectionJson::Collection.new(@response.body)
45
+ end
46
+
47
+ def get_root
48
+ make_request '/'
49
+ end
50
+
51
+ def retry_request
52
+ make_request @response.env[:url].request_uri
53
+ end
54
+
55
+ def print_response
56
+ banner 'Request' do |out|
57
+ out.print "#{@response.env[:method].upcase} #{@response.env[:url]}"
58
+ out.print @response.env[:request_headers].map {|header, value|
59
+ "#{header}: #{value}"
60
+ }
61
+ end
62
+
63
+ banner 'Response' do |out|
64
+ out.print @response.headers.map {|header, value|
65
+ "#{header}: #{value}"
66
+ }
67
+ end
68
+
69
+ banner 'Body' do |out|
70
+ out.print JSON.pretty_generate(@response.body).lines
71
+ end
72
+
73
+ banner 'Links' do |out|
74
+ unless collection.link_relations.empty?
75
+ out.print collection.link_relations.join(', ')
76
+ end
77
+ end
78
+ end
79
+
80
+ def request_basic_authentication(username = :ask, password = :ask)
81
+ username = ask('Username: ') if username == :ask
82
+ password = ask('Password: ') {|q| q.echo = '*' } if password == :ask
83
+ conn.basic_auth username, password
84
+ retry_request
85
+ end
86
+
87
+ def request_token_authentication(token = '2x033S09401z300E')
88
+ conn.headers['Authorization'] = "Token token=#{token.inspect}"
89
+ retry_request
90
+ end
91
+
92
+ def follow_link(relation = :ask)
93
+ relation = ask('Relation: ') if relation == :ask
94
+ make_request collection.link_href(relation)
95
+ end
96
+
97
+ def create_item
98
+ template = collection.template
99
+
100
+ loop do
101
+ banner 'Create Item' do |out|
102
+ out.print JSON.pretty_generate(template).lines
103
+ end
104
+
105
+ puts
106
+ puts 'Fill the template to create a new item.'
107
+ name = ask('Name (empty to submit): ')
108
+ break if name.empty?
109
+ value = ask('Value: ')
110
+
111
+ template = template.fill_field name, value
112
+ end
113
+
114
+ make_request template.href, template.convert_to_json, template.method
115
+ end
116
+
117
+ def update_item
118
+ item = collection.items.find do |item|
119
+ banner 'Item' do |out|
120
+ out.print JSON.pretty_generate(item).lines
121
+ end
122
+
123
+ puts
124
+ response = ask('Select this item to update [y,n]? ') do |q|
125
+ q.character = true
126
+ q.validate = /\A[yn]\Z/
127
+ end
128
+
129
+ response == 'y'
130
+ end
131
+
132
+ template = collection.item_template item
133
+
134
+ loop do
135
+ banner 'Update Item' do |out|
136
+ out.print JSON.pretty_generate(template).lines
137
+ end
138
+
139
+ puts
140
+ puts 'Fill the template to update the item.'
141
+ name = ask('Name (empty to submit): ')
142
+ break if name.empty?
143
+ value = ask('Value: ')
144
+
145
+ template = template.fill_field name, value
146
+ end
147
+
148
+ make_request template.href, template.convert_to_json, template.method
149
+ end
150
+
151
+ def print_debug
152
+ banner 'Debug' do |out|
153
+ debug_output.rewind
154
+ out.print debug_output.readlines
155
+ end
156
+ end
157
+
158
+ def print_help
159
+ puts <<EOS
160
+ root:
161
+ Go back to the root
162
+
163
+ follow <rel>:
164
+ Follow link with the given relation.
165
+
166
+ template [<name>=<value>...]:
167
+ Fill the template with the given name/value pairs and submit.
168
+
169
+ basic [<username> [<password>]]:
170
+ Authenticate with HTTP Basic and reload the current resource. Will be
171
+ prompted for username and password if omitted.
172
+
173
+ token <token>:
174
+ Authenticate using the given token and reload the current resource.
175
+
176
+ debug:
177
+ Print debug output from the previous HTTP request and response.
178
+ EOS
179
+ end
180
+
181
+ def get_next_action
182
+ command, args = ask_for_action
183
+ case command
184
+ when 'r', 'root' then get_root
185
+ when 'f', 'follow' then follow_link(*args)
186
+ when 'create' then create_item
187
+ when 'update' then update_item
188
+ when 'b', 'basic' then request_basic_authentication(*args)
189
+ when 't', 'token' then request_token_authentication(*args)
190
+ when 'd', 'debug' then print_debug; get_next_action
191
+ when '?', 'help' then print_help; get_next_action
192
+ when 'q', 'quit' then exit
193
+ else puts 'Try again.'; get_next_action
194
+ end
195
+ end
196
+
197
+ def ask_for_action
198
+ puts
199
+ input = ask('> ') {|q| q.readline = true }.split(/\s/)
200
+ [ input.first, input[1..-1] ]
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,70 @@
1
+ require 'delegate'
2
+ require 'forwardable'
3
+
4
+ module Leif
5
+ module CollectionJson
6
+ class Collection
7
+ extend Forwardable
8
+ def_delegators :@data, :fetch, :has_key?
9
+
10
+ def initialize(body)
11
+ @data = body.fetch('collection')
12
+ end
13
+
14
+ def link_href(relation)
15
+ links.find {|link| link.fetch('rel') == relation }.fetch('href')
16
+ end
17
+
18
+ def link_relations
19
+ links.map {|link| link.fetch('rel') }
20
+ end
21
+
22
+ def links
23
+ return [] unless has_key?('links')
24
+ fetch('links')
25
+ end
26
+
27
+ def items
28
+ return [] unless has_key?('items')
29
+ fetch('items')
30
+ end
31
+
32
+ def template(href = fetch('href'), method = :post)
33
+ Template.new(fetch('template'), href, method)
34
+ end
35
+
36
+ def item_template(item)
37
+ template = template(item.fetch('href'), :put)
38
+ item.fetch('data').inject(template) {|template, datum|
39
+ template.fill_field datum.fetch('name'), datum.fetch('value')
40
+ }
41
+ end
42
+
43
+ class Template < SimpleDelegator
44
+ attr_accessor :href, :method
45
+
46
+ def initialize(template, href, method)
47
+ @href = href
48
+ @method = method
49
+ super template
50
+ end
51
+
52
+ def convert_to_json
53
+ fetch('data').each_with_object({}) do |datum, json|
54
+ json[datum['name']] = datum['value']
55
+ end
56
+ end
57
+
58
+ def fill_field(name, value)
59
+ new_data = fetch('data').map {|datum|
60
+ datum = datum.clone
61
+ datum['value'] = value if datum['name'] == name
62
+ datum
63
+ }
64
+ new_template = {'data' => new_data }
65
+ Template.new(new_template, href, method)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,26 @@
1
+ class Section
2
+ attr_reader :io
3
+ def empty?() @empty end
4
+
5
+ def initialize(io)
6
+ @io = io
7
+ @empty = true
8
+ end
9
+
10
+ def self.banner(banner, io = $stdout, &message)
11
+ new(io).banner(banner, &message)
12
+ end
13
+
14
+ def banner(banner, &message)
15
+ io.puts "-- #{banner} --"
16
+ yield self
17
+ print '[empty]' if empty?
18
+ end
19
+
20
+ def print(message)
21
+ @empty = false
22
+ Array(message).each do |line|
23
+ io.puts " #{line}"
24
+ end
25
+ end
26
+ end
data/lib/leif/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Leif
2
- VERSION = '0.0.4'
2
+ VERSION = '0.0.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leif
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Larry Marburger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-08 00:00:00.000000000 Z
11
+ date: 2013-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.6.19
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: ronn
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -77,6 +91,9 @@ files:
77
91
  - README.md
78
92
  - leif.gemspec
79
93
  - bin/leif
94
+ - lib/leif/cli.rb
95
+ - lib/leif/collection_json.rb
96
+ - lib/leif/section.rb
80
97
  - lib/leif/version.rb
81
98
  - man/leif.1
82
99
  - man/leif.1.html