arstotzka 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +11 -0
  5. data/.rubocop_todo.yml +20 -0
  6. data/Gemfile +3 -2
  7. data/Guardfile +3 -2
  8. data/README.md +24 -5
  9. data/Rakefile +5 -3
  10. data/arstotzka.gemspec +18 -15
  11. data/arstotzka.jpg +0 -0
  12. data/lib/arstotzka.rb +2 -0
  13. data/lib/arstotzka/builder.rb +109 -47
  14. data/lib/arstotzka/class_methods.rb +34 -0
  15. data/lib/arstotzka/crawler.rb +86 -11
  16. data/lib/arstotzka/exception.rb +7 -2
  17. data/lib/arstotzka/fetcher.rb +93 -36
  18. data/lib/arstotzka/reader.rb +59 -11
  19. data/lib/arstotzka/type_cast.rb +24 -10
  20. data/lib/arstotzka/version.rb +3 -1
  21. data/lib/arstotzka/wrapper.rb +59 -34
  22. data/spec/integration/readme/default_spec.rb +2 -0
  23. data/spec/integration/readme/my_parser_spec.rb +2 -0
  24. data/spec/integration/yard/arstotzka/builder_spec.rb +63 -0
  25. data/spec/integration/yard/arstotzka/class_methods_spec.rb +49 -0
  26. data/spec/integration/yard/arstotzka/crawler_spec.rb +77 -0
  27. data/spec/integration/yard/arstotzka/fetcher_spec.rb +51 -0
  28. data/spec/integration/yard/arstotzka/reader_spec.rb +77 -0
  29. data/spec/integration/yard/arstotzka/wrapper_spec.rb +29 -0
  30. data/spec/lib/arstotzka/builder_spec.rb +6 -4
  31. data/spec/lib/arstotzka/crawler_spec.rb +30 -17
  32. data/spec/lib/arstotzka/fetcher_spec.rb +4 -2
  33. data/spec/lib/arstotzka/reader_spec.rb +13 -11
  34. data/spec/lib/arstotzka/wrapper_spec.rb +10 -10
  35. data/spec/lib/arstotzka_spec.rb +12 -8
  36. data/spec/spec_helper.rb +6 -2
  37. data/spec/support/fixture_helpers.rb +4 -2
  38. data/spec/support/models.rb +4 -3
  39. data/spec/support/models/account.rb +9 -0
  40. data/spec/support/models/arstotzka/dummy.rb +21 -17
  41. data/spec/support/models/arstotzka/fetcher/dummy.rb +7 -2
  42. data/spec/support/models/arstotzka/type_cast.rb +7 -4
  43. data/spec/support/models/arstotzka/wrapper/dummy.rb +10 -5
  44. data/spec/support/models/game.rb +2 -0
  45. data/spec/support/models/house.rb +2 -0
  46. data/spec/support/models/my_model.rb +9 -0
  47. data/spec/support/models/my_parser.rb +6 -5
  48. data/spec/support/models/person.rb +7 -1
  49. data/spec/support/models/star.rb +2 -0
  50. data/spec/support/models/star_gazer.rb +3 -2
  51. data/spec/support/models/transaction.rb +19 -0
  52. data/spec/support/shared_examples/wrapper.rb +6 -5
  53. metadata +64 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 58ec63d9c1394d610f41adbefff57f81ad2dbc66
4
- data.tar.gz: 27ad6c08193eafb763a220a2ef9eec7dfba9ce96
3
+ metadata.gz: c670a2581e68a1b7e54d5451b6b425fde3cde0db
4
+ data.tar.gz: 0f2d973a287534f68be841ebcbb63fc0e5f5babb
5
5
  SHA512:
6
- metadata.gz: 29771997f5279078a4647f780be36b36a4dc1a407ff7fb3b88b232ca81447b7debf7fe4f74e9d81f6e3147e5003bb3e438e4347be4746d2ff9df36d9024739c9
7
- data.tar.gz: 6413b13c996a55776327d951e9ec5b9adbc3d978374a0bb946abc799f0933160cb1c8d6c8718eb0f194faba273a3876581da0a1ecd5e4b7ad8b7b8f62ee6cd3a
6
+ metadata.gz: 1826c6c9b9017ce2f302ed930856acb4889eede6a203b25dc1674f1dfe545bae8407acbfaad4cee294782e2afaa11aca50ee39f95700756d6e62ab8cbf18bdc9
7
+ data.tar.gz: 29f5570b7d161c62069512a9605fde0424c6ac9083ad7dc15d25b6a36e86afe747b6e3ad0abefd1a8d908c86b2b9df34415ea23543d8f2744286a017727ea012
@@ -10,4 +10,5 @@ jobs:
10
10
  - run: ./cc-test-reporter before-build
11
11
  - run: bundle install
12
12
  - run: bundle exec rspec
13
+ - run: rubocop
13
14
  - run: ./cc-test-reporter after-build --exit-code $?
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  coverage
2
2
  Gemfile.lock
3
-
3
+ .yardoc
4
+ doc
@@ -0,0 +1,11 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.4
5
+
6
+ Metrics/BlockLength:
7
+ Exclude:
8
+ - 'spec/**/*_spec.rb'
9
+
10
+ Metrics/LineLength:
11
+ Max: 100
@@ -0,0 +1,20 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2018-07-22 21:24:28 +0000 using RuboCop version 0.58.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: CountKeywordArgs.
11
+ Metrics/ParameterLists:
12
+ Exclude:
13
+ - 'lib/arstotzka/builder.rb'
14
+
15
+ # Offense count: 1
16
+ Style/Documentation:
17
+ Exclude:
18
+ - 'spec/**/*'
19
+ - 'test/**/*'
20
+ - 'lib/arstotzka.rb'
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  gemspec
4
6
 
5
7
  group :test do
6
8
  gem 'safe_attribute_assignment', '0.0.3'
7
9
  end
8
-
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # More info at https://github.com/guard/guard#readme
2
4
 
3
5
  guard 'bundler' do
@@ -8,6 +10,5 @@ end
8
10
  guard :rspec, all_after_pass: true, all_on_start: true do
9
11
  watch(%r{^spec/.+_spec\.rb$})
10
12
  watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
11
- watch('spec/spec_helper.rb') { "spec" }
13
+ watch('spec/spec_helper.rb') { 'spec' }
12
14
  end
13
-
data/README.md CHANGED
@@ -4,6 +4,8 @@ Arstotzka
4
4
  [![Test Coverage](https://codeclimate.com/github/darthjee/arstotzka/badges/coverage.svg)](https://codeclimate.com/github/darthjee/arstotzka/coverage)
5
5
  [![Issue Count](https://codeclimate.com/github/darthjee/arstotzka/badges/issue_count.svg)](https://codeclimate.com/github/darthjee/arstotzka)
6
6
 
7
+ ![arstotzka](https://raw.githubusercontent.com/darthjee/arstotzka/master/arstotzka.jpg)
8
+
7
9
  This project allows for a quick hash / json data fetching in order to avoid code
8
10
  that tries to crawl through a hash and has to constantly check for nil values or missing keys
9
11
 
@@ -12,21 +14,38 @@ avoids method missing by aways having the declarated methods, even if that means
12
14
 
13
15
  Json Parser is also usefull when you need keys case changed or data type cast
14
16
 
15
- Getting started
17
+ Instalation
16
18
  ---------------
17
19
  1. Add Arstotzka to your `Gemfile` and `bundle install`:
20
+ - Install it
21
+
22
+ ```ruby
23
+ gem install arstotzka
24
+ ```
25
+
26
+ - Or add Arstotka to you `Gemfile` and `bundle install`
18
27
 
19
28
  ```ruby
20
29
  gem 'arstotzka'
21
30
  ```
22
31
 
23
- 2. Include in a class that you want to wrap a json/hash
32
+ ```bash
33
+ bundle install arstotzka
34
+ ```
35
+
36
+ Yard Documentation
37
+ -------------------
38
+ https://www.rubydoc.info/gems/arstotzka/
39
+
40
+ Getting Started
41
+ ---------------
42
+ 1. Include in a class that you want to wrap a json/hash
24
43
  ```ruby
25
44
  class MyParser
26
45
  include Arstotzka
27
46
  ```
28
47
 
29
- 3. Declare the keys you want to crawl
48
+ 2. Declare the keys you want to crawl
30
49
  ```ruby
31
50
  class MyParser
32
51
  include Arstotzka
@@ -67,9 +86,9 @@ Getting started
67
86
  #returns nil
68
87
  ```
69
88
 
70
- 4. fully customise the way you crawl / fetch the information with [Options](#options)
89
+ 3. fully customise the way you crawl / fetch the information with [Options](#options)
71
90
 
72
- 5. Create custom [typecast](#TypeCast)
91
+ 4. Create custom [typecast](#TypeCast)
73
92
 
74
93
  Options
75
94
  -------
data/Rakefile CHANGED
@@ -1,7 +1,9 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new
5
7
 
6
- task :default => :spec
8
+ task default: :spec
7
9
  task test: :spec
@@ -1,29 +1,32 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'arstotzka/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "arstotzka"
8
+ spec.name = 'arstotzka'
8
9
  spec.version = Arstotzka::VERSION
9
- spec.authors = ["Darthjee"]
10
- spec.email = ["dev@gmail.com"]
11
- spec.summary = "Arstotzka"
10
+ spec.authors = ['Darthjee']
11
+ spec.email = ['dev@gmail.com']
12
+ spec.summary = 'Arstotzka'
12
13
  spec.description = spec.description
13
- spec.homepage = "https://github.com/darthjee/arstotzka"
14
+ spec.homepage = 'https://github.com/darthjee/arstotzka'
14
15
 
15
- spec.files = `git ls-files`.split($/)
16
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
19
20
 
20
21
  spec.add_runtime_dependency 'activesupport', '~> 5.x'
21
- spec.add_runtime_dependency 'sinclair'
22
+ spec.add_runtime_dependency 'sinclair', '>= 1.1.1'
22
23
 
24
+ spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'pry-nav'
26
+ spec.add_development_dependency 'rake', '>= 12.3.1'
27
+ spec.add_development_dependency 'rspec', '>= 3.7'
28
+ spec.add_development_dependency 'rubocop'
23
29
  spec.add_development_dependency 'safe_attribute_assignment'
24
- spec.add_development_dependency "bundler", "~> 1.6"
25
- spec.add_development_dependency "rake", ">= 12.3.1"
26
- spec.add_development_dependency "rspec", ">= 3.7"
27
30
  spec.add_development_dependency 'simplecov'
28
- spec.add_development_dependency 'pry-nav'
31
+ spec.add_development_dependency 'yard'
29
32
  end
Binary file
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support'
2
4
  require 'active_support/all'
3
5
  require 'sinclair'
@@ -1,9 +1,38 @@
1
- class Arstotzka::Builder < Sinclair
1
+ # frozen_string_literal: true
2
2
 
3
- attr_reader :attr_names, :path, :full_path, :cached
4
-
5
- def initialize(attr_names, clazz, path: nil, full_path: nil, cached: false, **options)
6
- super(clazz, {
3
+ module Arstotzka
4
+ # Class responsible to orchestrate the addtion of method that will
5
+ # crawl the hash for value
6
+ #
7
+ # @example
8
+ # class MyModel
9
+ # attr_reader :json
10
+ #
11
+ # def initialize(json)
12
+ # @json = json
13
+ # end
14
+ # end
15
+ #
16
+ # instance = MyModel.new(
17
+ # 'name' => { first: 'John', last: 'Williams' },
18
+ # :age => '20',
19
+ # 'cars' => 2.0
20
+ # )
21
+ #
22
+ # builder = Arstotzka::Builder.new([ :first_name ], MyModel, full_path: 'name.first')
23
+ # builder.build
24
+ #
25
+ # instance.first_name # returns 'John'
26
+ #
27
+ # builder = Arstotzka::Builder.new([ :age, :cars ], MyModel, type: :integer)
28
+ # builder.build
29
+ #
30
+ # instance.age # returns 20
31
+ # instance.cars # returns 2
32
+ #
33
+ # @see https://www.rubydoc.info/gems/sinclair Sinclair
34
+ class Builder < Sinclair
35
+ DEFAULT_OPTIONS = {
7
36
  after: false,
8
37
  case: :lower_camel,
9
38
  class: nil,
@@ -12,62 +41,95 @@ class Arstotzka::Builder < Sinclair
12
41
  flatten: false,
13
42
  json: :json,
14
43
  type: :none
15
- }.merge(options.symbolize_keys))
16
-
17
- @attr_names = attr_names
18
- @path = path
19
- @full_path = full_path
20
- @cached = cached
21
- init
22
- end
44
+ }.freeze
23
45
 
24
- private
46
+ # @param attr_names [Array] list of attributes to be fetched from the hash/json
47
+ # @param clazz [Class] class to receive the methods
48
+ # @param path [String/Symbol] path of hash attributes to find the root
49
+ # where the attribute live (then fetching it using the attribute name)
50
+ # @param full_path [String/Symbol] path of hash attributes to find exacttly where the
51
+ # value live (ignoring the attribute name)
52
+ # @param cached [Boolean] flag if the result should be memorized instead of repeating
53
+ # the crawling
54
+ # @param json [String/Symbol] name of the method containing the Hash/json to be crawled
55
+ # @param options [Hash] hash containing extra options
56
+ # @option options [String/Symbol] case: {Reader} flag definining on which case will
57
+ # the keys be defined
58
+ # - lower_camel: keys in the hash are lowerCamelCase
59
+ # - upper_camel: keys in the hash are UpperCamelCase
60
+ # - snake: keys in the hash are snake_case
61
+ # @option options [Boolean] compact: {Crawler} flag to apply Array#compact thus
62
+ # removing nil results
63
+ # @option options [Class] class: {Fetcher} option thatwhen passed, wraps the result in an
64
+ # instance of the given class
65
+ # @option options [String/Symbol] after: {Fetcher} option with the name of the method to be
66
+ # called once the value is fetched for mapping the value
67
+ # @option options [Boolean] flatten: {Fetcher} flag to aplly Array#flatten thus
68
+ # avoing nested arrays
69
+ # @option options [String/Symbol] type: {Fetcher} option declaring the type of the returned
70
+ # value (to use casting)
71
+ # - integer
72
+ # - string
73
+ # - float
74
+ def initialize(attr_names, clazz,
75
+ json: :json, path: nil, full_path: nil, cached: false,
76
+ **options)
77
+ super(clazz, DEFAULT_OPTIONS.merge(options.symbolize_keys))
25
78
 
26
- def init
27
- attr_names.each do |attr|
28
- add_attr(attr)
79
+ @attr_names = attr_names
80
+ @path = path
81
+ @full_path = full_path
82
+ @cached = cached
83
+ @json_name = json
84
+ init
29
85
  end
30
- end
31
86
 
32
- def real_path(attribute)
33
- full_path || [path, attribute].compact.join('.')
34
- end
87
+ private
35
88
 
36
- def json_name
37
- options[:json]
38
- end
89
+ attr_reader :attr_names, :json_name, :path, :full_path, :cached
39
90
 
40
- def wrapper_clazz
41
- options[:class]
42
- end
91
+ def init
92
+ attr_names.each do |attr|
93
+ add_attr(attr)
94
+ end
95
+ end
43
96
 
44
- def case_type
45
- options[:case]
46
- end
97
+ def real_path(attribute)
98
+ full_path || [path, attribute].compact.join('.')
99
+ end
47
100
 
48
- def fetcher_options(attribute)
49
- options.slice(:compact, :after, :type, :flatten, :default).merge({
50
- clazz: wrapper_clazz,
51
- case_type: case_type,
52
- path: real_path(attribute)
53
- })
54
- end
101
+ def wrapper_clazz
102
+ options[:class]
103
+ end
55
104
 
56
- def add_attr(attribute)
57
- add_method attribute, "#{cached ? cached_fetcher(attribute) : attr_fetcher(attribute)}"
58
- end
105
+ def case_type
106
+ options[:case]
107
+ end
108
+
109
+ def fetcher_options(attribute)
110
+ options.slice(:compact, :after, :type, :flatten, :default).merge(
111
+ clazz: wrapper_clazz,
112
+ case_type: case_type,
113
+ path: real_path(attribute)
114
+ )
115
+ end
59
116
 
60
- def attr_fetcher(attribute)
61
- <<-CODE
117
+ def add_attr(attribute)
118
+ add_method attribute, (cached ? cached_fetcher(attribute) : attr_fetcher(attribute)).to_s
119
+ end
120
+
121
+ def attr_fetcher(attribute)
122
+ <<-CODE
62
123
  ::Arstotzka::Fetcher.new(
63
124
  #{json_name}, self, #{fetcher_options(attribute)}
64
125
  ).fetch
65
- CODE
66
- end
126
+ CODE
127
+ end
67
128
 
68
- def cached_fetcher(attribute)
69
- <<-CODE
129
+ def cached_fetcher(attribute)
130
+ <<-CODE
70
131
  @#{attribute} ||= #{attr_fetcher(attribute)}
71
- CODE
132
+ CODE
133
+ end
72
134
  end
73
135
  end
@@ -1,5 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arstotzka
4
+ # As Arstotzka extends ActiveSupport::Concern, Arstotzka::ClassMethods define
5
+ # methods that will be available when defining a class that includes Arstotka
2
6
  module ClassMethods
7
+ # expose a field from the json/hash as a method
8
+ #
9
+ # @example
10
+ # class MyModel
11
+ # include Arstotzka
12
+ #
13
+ # attr_reader :json
14
+ #
15
+ # expose :first_name, full_path: 'name.first'
16
+ # expose :age, 'cars', type: :integer
17
+ #
18
+ # def initialize(json)
19
+ # @json = json
20
+ # end
21
+ # end
22
+ #
23
+ # instance = MyModel.new(
24
+ # 'name' => { first: 'John', last: 'Williams' },
25
+ # :age => '20',
26
+ # 'cars' => 2.0
27
+ # )
28
+ #
29
+ # instance.first_name # returns 'John'
30
+ # instance.age # returns 20
31
+ # instance.cars # returns 2
32
+ #
33
+ # @see Builder Arstotzka::Builder
34
+ # @see
35
+ # https://www.rubydoc.info/gems/activesupport/5.0.0.1/ActiveSupport/Concern
36
+ # ActiveSupport::Concern
3
37
  def expose(*attr_names, **options)
4
38
  Builder.new(attr_names, self, options).build
5
39
  end
@@ -1,28 +1,103 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arstotzka
4
+ # Crawl a hash through the path of keys
5
+ # @example
6
+ # crawler = Arstotzka::Crawler.new(%w(person information first_name))
7
+ # hash = {
8
+ # person: {
9
+ # 'information' => {
10
+ # 'firstName' => 'John'
11
+ # }
12
+ # }
13
+ # }
14
+ # crawler.value(hash) # returns 'John'
2
15
  class Crawler
3
- attr_reader :post_process, :path, :case_type, :compact, :default
4
-
16
+ # @param path [Array] path of keys to be crawled
17
+ # @param case_type [Symbol] case type of the keys
18
+ # - snake: snake_cased keys
19
+ # - lower_camel: lowerCamelCased keys
20
+ # - upper_camel: UperCamelCased keys
21
+ # @param compact [Boolean] flag signallying if nil values should be removed of an array
22
+ # @param default [Object] default value to be returned when failing to fetch a value
23
+ # @param block [Proc] block to be ran over the fetched value before returning it
5
24
  def initialize(path:, case_type: :lower_camel, compact: false, default: nil, &block)
6
25
  @case_type = case_type
7
26
  @compact = compact
8
27
  @default = default
9
28
  @path = path
10
- @post_process = block
29
+ @post_process = block || proc { |value| value }
11
30
  end
12
31
 
13
- def value(json, index = 0)
14
- crawl(json, index)
32
+ # crawls into the hash looking for all keys in the given path
33
+ # returning the final value
34
+ #
35
+ # @overload value(hash)
36
+ # @return [Object] value fetched from the last Hash#fetch call using the last part
37
+ # of path
38
+ #
39
+ # @example
40
+ # crawler = Arstotzka::Crawler.new(%w(person information first_name))
41
+ # hash = {
42
+ # person: {
43
+ # 'information' => {
44
+ # 'firstName' => 'John'
45
+ # }
46
+ # }
47
+ # }
48
+ # crawler.value(hash) # returns 'John'
49
+ #
50
+ # @example
51
+ # crawler = Arstotzka::Crawler.new(
52
+ # %w(companies games hero),
53
+ # compact: true, case_type: :snake
54
+ # )
55
+ # games_hash = {
56
+ # 'companies' => [{
57
+ # name: 'Lucas Pope',
58
+ # games: [{
59
+ # 'name' => 'papers, please'
60
+ # }, {
61
+ # 'name' => 'TheNextBigThing',
62
+ # hero_name: 'Rakhar'
63
+ # }]
64
+ # }, {
65
+ # name: 'Old Company'
66
+ # }]
67
+ # }
68
+ #
69
+ # crawler.value(games_hash) # returns [['Rakhar']]
70
+ #
71
+ # @example
72
+ # crawler = Arstotzka::Crawler.new(
73
+ # %w(companies games hero),
74
+ # compact: true, case_type: :snake, default: 'NO HERO'
75
+ # )
76
+ #
77
+ # crawler.value(games_hash) # returns [['NO HERO', 'Rakhar'], 'NO HERO']
78
+ #
79
+ # @example
80
+ # crawler = Arstotzka::Crawler.new(
81
+ # %w(companies games hero),
82
+ # compact: true, case_type: :snake
83
+ # ) { |value| value.&to_sym }
84
+ #
85
+ # crawler.value(games_hash) # returns [[:Rakhar]]
86
+ def value(hash, index = 0)
87
+ crawl(hash, index)
15
88
  rescue Exception::KeyNotFound
16
89
  wrap(default)
17
90
  end
18
91
 
19
92
  private
20
93
 
21
- def crawl(json, index = 0)
22
- return wrap(json) if reader.is_ended?(index)
23
- return crawl_array(json, index) if json.is_a?(Array)
94
+ attr_reader :post_process, :path, :case_type, :compact, :default
95
+
96
+ def crawl(hash, index = 0)
97
+ return wrap(hash) if reader.ended?(index)
98
+ return crawl_array(hash, index) if hash.is_a?(Array)
24
99
 
25
- crawl(reader.read(json, index), index + 1)
100
+ crawl(reader.read(hash, index), index + 1)
26
101
  end
27
102
 
28
103
  def reader
@@ -32,8 +107,8 @@ module Arstotzka
32
107
  )
33
108
  end
34
109
 
35
- def wrap(json)
36
- post_process.call(json)
110
+ def wrap(hash)
111
+ post_process.call(hash)
37
112
  end
38
113
 
39
114
  def crawl_array(array, index)