leif 0.0.4 → 0.0.5

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 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