goodread 0.1.3 → 0.2.1

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
- SHA1:
3
- metadata.gz: 6088e5eebb70dc235fab80821df7e26454bc1058
4
- data.tar.gz: e9a7db495590ffeccd4eff3e2800d835b42df7f1
2
+ SHA256:
3
+ metadata.gz: 2dd0cac02f1796fe0b626e32cc6cc6f4c85424f59457fde0db95a70e8cb8b26a
4
+ data.tar.gz: 55e79251fd7b7dc19aa6f7534b21778697ee53036f0d66283d641b5db894352b
5
5
  SHA512:
6
- metadata.gz: c481de27e24bb80d857aa1e6c6e4c1c251b734bdf5b563aaf0b262e769779318ff507da10255ccdd9a8e03112255671a5155df15c0898db29b75d64b161c81c9
7
- data.tar.gz: 6ef8ff9e48104e65c299fe9656de15b3cb152ee38a57ba985a2cc0667bb32c1fa047196bc9533aabb7e097ada3ef5fccbd5c161433f14e934beed6d5fe880f2a
6
+ metadata.gz: 30d152a2f6868a1a50a2c9620680e0c3bba6b0294d0783b4e1e5a30d29a996693bc2c91c849ef3f720a56f76289b0adf687c8f9fc9b6aba64e17be76cf119e23
7
+ data.tar.gz: 73c5aa278a4480f2599ad6038b46689d5589286b5d8c09343c3dfaf26b56f888d8d27f3e0edd4f34920adb5239ae489ffb4aaa661f8631c9bf7028806130c871
data/.gitignore CHANGED
@@ -52,3 +52,4 @@ build-iPhoneSimulator/
52
52
  # Extra
53
53
  Gemfile.lock
54
54
  .rspec_status
55
+ goodread.yml
data/goodread.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "goodread"
7
- spec.version = "0.1.3"
7
+ spec.version = "0.2.1"
8
8
  spec.authors = ["Evgeny Karev\n"]
9
9
  spec.email = ["eskarev@gmail.com"]
10
10
 
data/lib/cli.rb CHANGED
@@ -2,415 +2,45 @@ require 'json'
2
2
  require 'yaml'
3
3
  require 'emoji'
4
4
  require 'colorize'
5
+ require_relative 'document'
6
+ require_relative 'helpers'
5
7
 
6
8
 
7
- # Helpers
8
-
9
- def parse_specs(path)
10
-
11
- # Paths
12
- paths = []
13
- if !path
14
- paths = Dir.glob('package.*')
15
- if paths.empty?
16
- path = 'packspec'
17
- end
18
- end
19
- if File.file?(path)
20
- paths = [path]
21
- elsif File.directory?(path)
22
- for path in Dir.glob("#{path}/*.*")
23
- paths.push(path)
24
- end
25
- end
26
-
27
- # Specs
28
- specs = []
29
- for path in paths
30
- spec = parse_spec(path)
31
- if spec
32
- specs.push(spec)
33
- end
34
- end
35
-
36
- return specs
37
-
38
- end
39
-
40
-
41
- def parse_spec(path)
42
-
43
- # Documents
44
- documents = []
45
- if !path.end_with?('.yml')
46
- return nil
47
- end
48
- contents = File.read(path)
49
- YAML.load_stream(contents) do |document|
50
- documents.push(document)
51
- end
52
-
53
- # Package
54
- feature = parse_feature(documents[0][0])
55
- if feature['skip']
56
- return nil
57
- end
58
- package = feature['comment']
59
-
60
- # Features
61
- skip = false
62
- features = []
63
- for feature in documents[0]
64
- feature = parse_feature(feature)
65
- features.push(feature)
66
- if feature['comment']
67
- skip = feature['skip']
68
- end
69
- feature['skip'] = skip || feature['skip']
70
- end
71
-
72
- # Scope
73
- scope = {}
74
- scope['$import'] = BuiltinFunctions.new().public_method(:builtin_import)
75
- if documents.length > 1 && documents[1]['rb']
76
- eval(documents[1]['rb'])
77
- hook_scope = Functions.new()
78
- for name in hook_scope.public_methods
79
- # TODO: filter ruby builtin methods
80
- scope["$#{name}"] = hook_scope.public_method(name)
81
- end
82
- end
83
-
84
- # Stats
85
- stats = {'features' => 0, 'comments' => 0, 'skipped' => 0, 'tests' => 0}
86
- for feature in features
87
- stats['features'] += 1
88
- if feature['comment']
89
- stats['comments'] += 1
90
- else
91
- stats['tests'] += 1
92
- if feature['skip']
93
- stats['skipped'] += 1
94
- end
95
- end
96
- end
97
-
98
- return {
99
- 'package' => package,
100
- 'features' => features,
101
- 'scope' => scope,
102
- 'stats' => stats,
103
- }
104
-
105
- end
106
-
107
-
108
- def parse_feature(feature)
109
-
110
- # General
111
- if feature.is_a?(String)
112
- match = /^(?:\((.*)\))?(\w.*)$/.match(feature)
113
- skip, comment = match[1], match[2]
114
- if !!skip
115
- skip = !skip.split(':').include?('rb')
116
- end
117
- return {'assign' => nil, 'comment' => comment, 'skip' => skip}
118
- end
119
- left, right = Array(feature.each_pair)[0]
120
-
121
- # Left side
122
- call = false
123
- match = /^(?:\((.*)\))?(?:([^=]*)=)?([^=].*)?$/.match(left)
124
- skip, assign, property = match[1], match[2], match[3]
125
- if !!skip
126
- skip = !skip.split(':').include?('rb')
127
- end
128
- if !assign && !property
129
- raise Exception.new('Non-valid feature')
130
- end
131
- if !!property
132
- call = true
133
- if property.end_with?('==')
134
- property = property[0..-3]
135
- call = false
136
- end
137
- end
138
-
139
- # Right side
140
- args = []
141
- kwargs = {}
142
- result = right
143
- if !!call
144
- result = nil
145
- for item in right
146
- if item.is_a?(Hash) && item.length == 1
147
- item_left, item_right = Array(item.each_pair)[0]
148
- if item_left == '=='
149
- result = item_right
150
- next
151
- end
152
- if item_left.end_with?('=')
153
- kwargs[item_left[0..-2]] = item_right
154
- next
155
- end
156
- end
157
- args.push(item)
158
- end
159
- end
160
-
161
- # Text repr
162
- text = property
163
- if !!assign
164
- text = "#{assign} = #{property || JSON.generate(result)}"
165
- end
166
- if !!call
167
- items = []
168
- for item in args
169
- items.push(JSON.generate(item))
170
- end
171
- for name, item in kwargs.each_pair
172
- items.push("#{name}=#{JSON.generate(item)}")
173
- end
174
- text = "#{text}(#{items.join(', ')})"
175
- end
176
- if !!result && !assign
177
- text = "#{text} == #{result == 'ERROR' ? result : JSON.generate(result)}"
178
- end
179
- text = text.gsub(/{"([^{}]*?)": null}/, '\1')
180
-
181
- return {
182
- 'comment' => nil,
183
- 'skip' => skip,
184
- 'call' => call,
185
- 'assign' => assign,
186
- 'property' => property,
187
- 'args' => args,
188
- 'kwargs' => kwargs,
189
- 'result' => result,
190
- 'text' => text,
191
- }
192
-
193
- end
194
-
195
-
196
- def test_specs(specs)
197
-
198
- # Message
199
- message = "\n # Ruby\n".bold
200
- puts(message)
201
-
202
- # Test specs
203
- success = true
204
- for spec in specs
205
- spec_success = test_spec(spec)
206
- success = success && spec_success
207
- end
208
-
209
- return success
210
-
211
- end
212
-
213
-
214
- def test_spec(spec)
215
-
216
- # Message
217
- message = Emoji.find_by_alias('heavy_minus_sign').raw * 3 + "\n\n"
218
- puts(message)
219
-
220
- # Test spec
221
- passed = 0
222
- for feature in spec['features']
223
- result = test_feature(feature, spec['scope'])
224
- if result
225
- passed += 1
226
- end
227
- end
228
- success = (passed == spec['stats']['features'])
229
-
230
- # Message
231
- color = 'green'
232
- message = ("\n " + Emoji.find_by_alias('heavy_check_mark').raw + ' ').green.bold
233
- if !success
234
- color = 'red'
235
- message = ("\n " + Emoji.find_by_alias('x').raw + ' ').red.bold
236
- end
237
- message += "#{spec['package']}: #{passed - spec['stats']['comments'] - spec['stats']['skipped']}/#{spec['stats']['tests'] - spec['stats']['skipped']}\n".colorize(color).bold
238
- puts(message)
239
-
240
- return success
241
-
242
- end
243
-
244
-
245
- def test_feature(feature, scope)
246
-
247
- # Comment
248
- if !!feature['comment']
249
- message = "\n # #{feature['comment']}\n".bold
250
- puts(message)
251
- return true
252
- end
253
-
254
- # Skip
255
- if !!feature['skip']
256
- message = " #{Emoji.find_by_alias('heavy_minus_sign').raw} ".yellow
257
- message += feature['text']
258
- puts(message)
259
- return true
260
- end
261
-
262
- # Dereference
263
- # TODO: deepcopy feature
264
- if !!feature['call']
265
- feature['args'] = dereference_value(feature['args'], scope)
266
- feature['kwargs'] = dereference_value(feature['kwargs'], scope)
267
- end
268
- feature['result'] = dereference_value(feature['result'], scope)
269
-
270
- # Execute
271
- exception = nil
272
- result = feature['result']
273
- if !!feature['property']
274
- begin
275
- property = scope
276
- for name in feature['property'].split('.')
277
- property = get_property(property, name)
278
- end
279
- if !!feature['call']
280
- args = feature['args'].dup
281
- if !feature['kwargs'].empty?
282
- args.push(Hash[feature['kwargs'].map{|k, v| [k.to_sym, v]}])
283
- end
284
- if property.respond_to?('new')
285
- result = property.new(*args)
286
- else
287
- result = property.call(*args)
288
- end
289
- else
290
- result = property
291
- if result.is_a?(Method)
292
- result = result.call()
293
- end
294
- end
295
- rescue Exception => exc
296
- exception = exc
297
- result = 'ERROR'
298
- end
299
- end
300
-
301
- # Assign
302
- if !!feature['assign']
303
- owner = scope
304
- names = feature['assign'].split('.')
305
- for name in names[0..-2]
306
- owner = get_property(owner, name)
307
- end
308
- # TODO: ensure constants are immutable
309
- set_property(owner, names[-1], result)
310
- end
9
+ # Main program
311
10
 
312
- # Compare
313
- if feature['result'] != nil
314
- success = result == feature['result']
11
+ # Parse
12
+ paths = []
13
+ edit = false
14
+ sync = false
15
+ exit_first = false
16
+ for arg in ARGV
17
+ if ['-e', '--edit'].include?(arg)
18
+ edit = true
19
+ elsif ['-s', '--sync'].include?(arg)
20
+ sync = true
21
+ elsif ['-x', '--exit-first'].include?(arg)
22
+ exit_first = true
315
23
  else
316
- success = result != 'ERROR'
24
+ paths.push(arg)
317
25
  end
318
- if success
319
- message = " #{Emoji.find_by_alias('heavy_check_mark').raw} ".green
320
- message += feature['text']
321
- puts(message)
322
- else
323
- begin
324
- result_text = JSON.generate(result)
325
- rescue Exception
326
- result_text = result.to_s
327
- end
328
- message = " #{Emoji.find_by_alias('x').raw} ".red
329
- message += "#{feature['text']}\n"
330
- if exception
331
- message += "Exception: #{exception}".red.bold
332
- else
333
- message += "Assertion: #{result_text} != #{JSON.generate(feature['result'])}".red.bold
334
- end
335
- puts(message)
336
- end
337
-
338
- return success
339
-
340
26
  end
341
27
 
28
+ # Prepare
29
+ config = read_config()
30
+ documents = DocumentList.new(paths, config)
342
31
 
343
- class BuiltinFunctions
344
- def builtin_import(package)
345
- attributes = {}
346
- require(package)
347
- for item in ObjectSpace.each_object
348
- if package == String(item).downcase
349
- begin
350
- scope = Kernel.const_get(item)
351
- rescue Exception
352
- next
353
- end
354
- for name in scope.constants
355
- attributes[String(name)] = scope.const_get(name)
356
- end
357
- end
358
- end
359
- return attributes
360
- end
361
- end
32
+ # Edit
33
+ if edit
34
+ documents.edit()
362
35
 
36
+ # Sync
37
+ elsif sync
38
+ documents.sync()
363
39
 
364
- def dereference_value(value, scope)
365
- if value.is_a?(Hash) && value.length == 1 && Array(value.each_value)[0] == nil
366
- result = scope
367
- for name in Array(value.each_key)[0].split('.')
368
- result = get_property(result, name)
369
- end
370
- value = result
371
- elsif value.is_a?(Hash)
372
- for key, item in value
373
- value[key] = dereference_value(item, scope)
374
- end
375
- elsif value.is_a?(Array)
376
- for item, index in value.each_with_index
377
- value[index] = dereference_value(item, scope)
378
- end
40
+ # Test
41
+ else
42
+ success = documents.test(exit_first:exit_first)
43
+ if not success
44
+ exit(1)
379
45
  end
380
- return value
381
- end
382
-
383
-
384
- def get_property(owner, name)
385
- if owner.is_a?(Method)
386
- owner = owner.call()
387
- end
388
- if owner.class == Hash
389
- return owner[name]
390
- elsif owner.class == Array
391
- return owner[name.to_i]
392
- end
393
- return owner.method(name)
394
- end
395
-
396
-
397
- def set_property(owner, name, value)
398
- if owner.class == Hash
399
- owner[name] = value
400
- return
401
- elsif owner.class == Array
402
- owner[name.to_i] = value
403
- return
404
- end
405
- return owner.const_set(name, value)
406
- end
407
-
408
-
409
- # Main program
410
-
411
- path = ARGV[0] || nil
412
- specs = parse_specs(path)
413
- success = test_specs(specs)
414
- if !success
415
- exit(1)
416
46
  end
data/lib/document.rb ADDED
@@ -0,0 +1,266 @@
1
+ require 'net/http'
2
+ require_relative 'helpers'
3
+
4
+
5
+ # Module API
6
+
7
+ class DocumentList
8
+
9
+ # Public
10
+
11
+ def initialize(paths, config)
12
+ @documents = []
13
+ if paths.empty?
14
+ for item in config['documents']
15
+ paths.push(item['main'])
16
+ end
17
+ end
18
+ for path in !paths.empty? ? paths : ['README.md']
19
+ main_path = path
20
+ edit_path = nil
21
+ sync_path = nil
22
+ for item in config['documents']
23
+ if path == item['main']
24
+ edit_path = item.fetch('edit', nil)
25
+ sync_path = item.fetch('sync', nil)
26
+ break
27
+ end
28
+ end
29
+ document = Document.new(main_path, edit_path:edit_path, sync_path:sync_path)
30
+ @documents.push(document)
31
+ end
32
+ end
33
+
34
+ def edit()
35
+ for document in @documents
36
+ document.edit()
37
+ end
38
+ end
39
+
40
+ def sync()
41
+ success = true
42
+ for document in @documents
43
+ valid = document.test(sync:true)
44
+ success = success && valid
45
+ if valid
46
+ document.sync()
47
+ end
48
+ end
49
+ return success
50
+ end
51
+
52
+ def test(exit_first:false)
53
+ success = true
54
+ for document, index in @documents.each_with_index
55
+ number = index + 1
56
+ valid = document.test(exit_first:exit_first)
57
+ success = success && valid
58
+ print_message(nil, (number < @documents.length ? 'separator' : 'blank'))
59
+ end
60
+ return success
61
+ end
62
+
63
+ end
64
+
65
+
66
+ class Document
67
+
68
+ # Public
69
+
70
+ def initialize(main_path, edit_path:nil, sync_path:nil)
71
+ @main_path = main_path
72
+ @edit_path = edit_path
73
+ @sync_path = sync_path
74
+ end
75
+
76
+ def edit()
77
+
78
+ # No edit path
79
+ if !@edit_path
80
+ return
81
+ end
82
+
83
+ # Check synced
84
+ if @main_path != @edit_path
85
+ main_contents = _load_document(@main_path)
86
+ sync_contents = _load_document(@sync_path)
87
+ if main_contents != sync_contents
88
+ raise Exception.new("Document '#{@edit_path}' is out of sync")
89
+ end
90
+ end
91
+
92
+ # Remote document
93
+ # TODO: supress commands output
94
+ if !@edit_path.start_with?('http')
95
+ Kernel.system('editor', @edit_path)
96
+
97
+ # Local document
98
+ else
99
+ Kernel.system('xdg-open', @edit_path)
100
+ end
101
+
102
+ end
103
+
104
+ def sync()
105
+
106
+ # No sync path
107
+ if !@sync_path
108
+ return
109
+ end
110
+
111
+ # Save remote to local
112
+ contents = Net::HTTP.get(URI(@sync_path))
113
+ File.write(@main_path, contents)
114
+
115
+ end
116
+
117
+ def test(sync:false, return_report:false, exit_first:false)
118
+
119
+ # No test path
120
+ path = sync ? @sync_path : @main_path
121
+ if !path
122
+ return true
123
+ end
124
+
125
+ # Test document
126
+ contents = _load_document(path)
127
+ elements = _parse_document(contents)
128
+ report = _validate_document(elements, exit_first:exit_first)
129
+
130
+ return return_report ? report : report['valid']
131
+ end
132
+
133
+ end
134
+
135
+
136
+ # Internal
137
+
138
+ def _load_document(path)
139
+
140
+ # Remote document
141
+ if path.start_with?('http')
142
+ return Net::HTTP.get(URI(path))
143
+
144
+ # Local document
145
+ else
146
+ return File.read(path)
147
+ end
148
+
149
+ end
150
+
151
+
152
+ def _parse_document(contents)
153
+ elements = []
154
+ codeblock = ''
155
+ capture = false
156
+
157
+ # Parse file lines
158
+ for line in contents.strip().split("\n")
159
+
160
+ # Heading
161
+ if line.start_with?('#')
162
+ heading = line.strip().tr('#', '')
163
+ level = line.length - line.tr('#', '').length
164
+ if (!elements.empty? &&
165
+ elements[-1]['type'] == 'heading' &&
166
+ elements[-1]['level'] == level)
167
+ next
168
+ end
169
+ elements.push({
170
+ 'type' => 'heading',
171
+ 'value' => heading,
172
+ 'level' => level,
173
+ })
174
+ end
175
+
176
+ # Codeblock
177
+ if line.start_with?('```ruby')
178
+ if line.include?('goodread')
179
+ capture = true
180
+ end
181
+ codeblock = ''
182
+ next
183
+ end
184
+ if line.start_with?('```')
185
+ if capture
186
+ elements.push({
187
+ 'type' => 'codeblock',
188
+ 'value' => codeblock,
189
+ })
190
+ end
191
+ capture = false
192
+ end
193
+ if capture && !line.empty?
194
+ codeblock += line + "\n"
195
+ next
196
+ end
197
+
198
+ end
199
+
200
+ return elements
201
+ end
202
+
203
+
204
+ def _validate_document(elements, exit_first:false)
205
+ scope = binding()
206
+ passed = 0
207
+ failed = 0
208
+ skipped = 0
209
+ title = nil
210
+ exception = nil
211
+
212
+ # Test elements
213
+ for element in elements
214
+
215
+ # Heading
216
+ if element['type'] == 'heading'
217
+ print_message(element['value'], 'heading', level:element['level'])
218
+ if title == nil
219
+ title = element['value']
220
+ print_message(nil, 'separator')
221
+ end
222
+
223
+ # Codeblock
224
+ elsif element['type'] == 'codeblock'
225
+ exception_line = 1000 # infinity
226
+ begin
227
+ eval(instrument_codeblock(element['value']), scope)
228
+ rescue Exception => exc
229
+ exception = exc
230
+ # TODO: get a real exception line
231
+ exception_line = 1
232
+ end
233
+ lines = element['value'].strip().split("\n")
234
+ for line, index in lines.each_with_index
235
+ line_number = index + 1
236
+ if line_number < exception_line
237
+ print_message(line, 'success')
238
+ passed += 1
239
+ elsif line_number == exception_line
240
+ print_message(line, 'failure', exception:exception)
241
+ if exit_first
242
+ print_message(scope, 'scope')
243
+ raise exception
244
+ end
245
+ failed += 1
246
+ elsif line_number > exception_line
247
+ print_message(line, 'skipped')
248
+ skipped += 1
249
+ end
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ # Print summary
256
+ if title != nil
257
+ print_message(title, 'summary', passed:passed, failed:failed, skipped:skipped)
258
+ end
259
+
260
+ return {
261
+ 'valid' => exception == nil,
262
+ 'passed' => passed,
263
+ 'failed' => failed,
264
+ 'skipped' => skipped,
265
+ }
266
+ end
data/lib/helpers.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'yaml'
2
+ require 'emoji'
3
+ require 'colorize'
4
+ $state = {'last_message_type' => nil}
5
+
6
+
7
+ # Module API
8
+
9
+ def read_config()
10
+ config = {'documents' => ['README.md']}
11
+ if File.file?('goodread.yml')
12
+ config = YAML.load(File.read('goodread.yml'))
13
+ for document, index in config['documents'].each_with_index
14
+ if document.is_a?(Hash)
15
+ if !document.include?('main')
16
+ raise Exception.new('Document requires "main" property')
17
+ end
18
+ end
19
+ if document.is_a?(String)
20
+ config['documents'][index] = {'main' => document}
21
+ end
22
+ end
23
+ end
24
+ return config
25
+ end
26
+
27
+
28
+ def instrument_codeblock(codeblock)
29
+ lines = []
30
+ for line in codeblock.strip().split("\n")
31
+ if line.include?(' # ')
32
+ left, right = line.split(' # ')
33
+ left = left.strip()
34
+ right = right.strip()
35
+ if left && right
36
+ message = "#{left} != #{right}"
37
+ line = "raise '#{message}' unless #{left} == #{right}"
38
+ end
39
+ end
40
+ lines.push(line)
41
+ end
42
+ return lines.join("\n")
43
+ end
44
+
45
+
46
+ def print_message(message, type, level: nil, exception: nil, passed: nil, failed: nil, skipped: nil)
47
+ text = ''
48
+ if type == 'blank'
49
+ return puts('')
50
+ elsif type == 'separator'
51
+ text = Emoji.find_by_alias('heavy_minus_sign').raw * 3
52
+ elsif type == 'heading'
53
+ text = " #{'#' * (level || 1)}" + message.bold
54
+ elsif type == 'success'
55
+ text = " #{Emoji.find_by_alias('heavy_check_mark').raw} ".green + message
56
+ elsif type == 'failure'
57
+ text = " #{Emoji.find_by_alias('x').raw} ".red + message + "\n"
58
+ text += "Exception: #{exception}".red.bold
59
+ elsif type == 'scope'
60
+ text += "---\n\n"
61
+ text += "Scope (current execution scope):\n"
62
+ text += "#{message}\n"
63
+ text += "\n---\n"
64
+ elsif type == 'skipped'
65
+ text = " #{Emoji.find_by_alias('heavy_minus_sign').raw} ".yellow + message
66
+ elsif type == 'summary'
67
+ color = :green
68
+ text = (' ' + Emoji.find_by_alias('heavy_check_mark').raw + ' ').green.bold
69
+ if (failed + skipped) > 0
70
+ color = :red
71
+ text = ("\n " + Emoji.find_by_alias('x').raw + ' ').red.bold
72
+ end
73
+ text += "#{message}: #{passed}/#{passed + failed + skipped}".colorize(color).bold
74
+ end
75
+ if ['success', 'failure', 'skipped'].include?(type)
76
+ type = 'test'
77
+ end
78
+ if text
79
+ if $state['last_message_type'] != type
80
+ text = "\n" + text
81
+ end
82
+ puts(text)
83
+ $state['last_message_type'] = type
84
+ end
85
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goodread
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - |
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-08-14 00:00:00.000000000 Z
12
+ date: 2017-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -101,6 +101,8 @@ files:
101
101
  - bin/setup
102
102
  - goodread.gemspec
103
103
  - lib/cli.rb
104
+ - lib/document.rb
105
+ - lib/helpers.rb
104
106
  homepage: https://github.com/goodread/goodread-rb
105
107
  licenses:
106
108
  - MIT
@@ -121,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
123
  version: '0'
122
124
  requirements: []
123
125
  rubyforge_project:
124
- rubygems_version: 2.6.11
126
+ rubygems_version: 2.7.1
125
127
  signing_key:
126
128
  specification_version: 4
127
129
  summary: goodread