reading 0.6.1 → 0.8.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/bin/reading +5 -5
  3. data/bin/readingfile +31 -0
  4. data/lib/reading/config.rb +96 -108
  5. data/lib/reading/errors.rb +10 -66
  6. data/lib/reading/filter.rb +95 -0
  7. data/lib/reading/item/time_length.rb +140 -0
  8. data/lib/reading/item/view.rb +121 -0
  9. data/lib/reading/item.rb +117 -0
  10. data/lib/reading/parsing/attributes/attribute.rb +26 -0
  11. data/lib/reading/parsing/attributes/author.rb +15 -0
  12. data/lib/reading/parsing/attributes/experiences/dates_and_head_transformer.rb +106 -0
  13. data/lib/reading/parsing/attributes/experiences/history_transformer.rb +452 -0
  14. data/lib/reading/parsing/attributes/experiences/spans_validator.rb +149 -0
  15. data/lib/reading/parsing/attributes/experiences.rb +27 -0
  16. data/lib/reading/parsing/attributes/genres.rb +16 -0
  17. data/lib/reading/parsing/attributes/notes.rb +22 -0
  18. data/lib/reading/parsing/attributes/rating.rb +17 -0
  19. data/lib/reading/parsing/attributes/shared.rb +62 -0
  20. data/lib/reading/parsing/attributes/title.rb +21 -0
  21. data/lib/reading/parsing/attributes/variants.rb +77 -0
  22. data/lib/reading/parsing/csv.rb +112 -0
  23. data/lib/reading/parsing/parser.rb +292 -0
  24. data/lib/reading/parsing/rows/column.rb +131 -0
  25. data/lib/reading/parsing/rows/comment.rb +26 -0
  26. data/lib/reading/parsing/rows/compact_planned.rb +30 -0
  27. data/lib/reading/parsing/rows/compact_planned_columns/head.rb +60 -0
  28. data/lib/reading/parsing/rows/regular.rb +33 -0
  29. data/lib/reading/parsing/rows/regular_columns/end_dates.rb +20 -0
  30. data/lib/reading/parsing/rows/regular_columns/genres.rb +20 -0
  31. data/lib/reading/parsing/rows/regular_columns/head.rb +45 -0
  32. data/lib/reading/parsing/rows/regular_columns/history.rb +143 -0
  33. data/lib/reading/parsing/rows/regular_columns/length.rb +35 -0
  34. data/lib/reading/parsing/rows/regular_columns/notes.rb +32 -0
  35. data/lib/reading/parsing/rows/regular_columns/rating.rb +15 -0
  36. data/lib/reading/parsing/rows/regular_columns/sources.rb +94 -0
  37. data/lib/reading/parsing/rows/regular_columns/start_dates.rb +35 -0
  38. data/lib/reading/parsing/transformer.rb +70 -0
  39. data/lib/reading/util/hash_compact_by_template.rb +1 -0
  40. data/lib/reading/util/hash_deep_merge.rb +1 -1
  41. data/lib/reading/util/hash_to_data.rb +30 -0
  42. data/lib/reading/util/numeric_to_i_if_whole.rb +12 -0
  43. data/lib/reading/util/string_truncate.rb +13 -4
  44. data/lib/reading/version.rb +1 -1
  45. data/lib/reading.rb +49 -0
  46. metadata +76 -42
  47. data/lib/reading/attribute/all_attributes.rb +0 -83
  48. data/lib/reading/attribute/attribute.rb +0 -25
  49. data/lib/reading/attribute/experiences/dates_validator.rb +0 -94
  50. data/lib/reading/attribute/experiences/experiences_attribute.rb +0 -74
  51. data/lib/reading/attribute/experiences/progress_subattribute.rb +0 -48
  52. data/lib/reading/attribute/experiences/spans_subattribute.rb +0 -82
  53. data/lib/reading/attribute/variants/extra_info_subattribute.rb +0 -44
  54. data/lib/reading/attribute/variants/length_subattribute.rb +0 -45
  55. data/lib/reading/attribute/variants/series_subattribute.rb +0 -57
  56. data/lib/reading/attribute/variants/sources_subattribute.rb +0 -78
  57. data/lib/reading/attribute/variants/variants_attribute.rb +0 -69
  58. data/lib/reading/csv.rb +0 -76
  59. data/lib/reading/line.rb +0 -23
  60. data/lib/reading/row/blank_row.rb +0 -23
  61. data/lib/reading/row/compact_planned_row.rb +0 -130
  62. data/lib/reading/row/regular_row.rb +0 -99
  63. data/lib/reading/row/row.rb +0 -88
  64. data/lib/reading/util/hash_to_struct.rb +0 -29
@@ -0,0 +1,35 @@
1
+ module Reading
2
+ module Parsing
3
+ module Rows
4
+ module Regular
5
+ # See https://github.com/fpsvogel/reading/blob/main/doc/csv-format.md#start-dates-and-end-dates-columns
6
+ class StartDates < Column
7
+ def self.segment_separator
8
+ /,\s*/
9
+ end
10
+
11
+ def self.regexes(segment_index)
12
+ # dnf/progress, date, variant number, group
13
+ [%r{\A
14
+ (
15
+ #{Column::SHARED_REGEXES[:progress]}
16
+ (\s+|\z)
17
+ )?
18
+ (
19
+ (?<date>\d{4}/\d\d?/\d\d?)
20
+ (\s+|\z)
21
+ )?
22
+ (
23
+ v(?<variant>\d)
24
+ (\s+|\z)
25
+ )?
26
+ (
27
+ 🤝🏼(?<group>.+)
28
+ )?
29
+ \z}x]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ require_relative "attributes/shared"
2
+ require_relative "attributes/attribute"
3
+ require_relative "attributes/rating"
4
+ require_relative "attributes/author"
5
+ require_relative "attributes/title"
6
+ require_relative "attributes/genres"
7
+ require_relative "attributes/variants"
8
+ require_relative "attributes/experiences"
9
+ require_relative "attributes/notes"
10
+
11
+ module Reading
12
+ module Parsing
13
+ #
14
+ # Transforms an intermediate hash (parsed from a CSV row) into item data.
15
+ # While the intermediate hash mirrors the structure of a row, the output of
16
+ # Transformer is based around item attributes, which are listed in
17
+ # Config#default_config[:item][:template] and in the files in parsing/attributes.
18
+ #
19
+ class Transformer
20
+ using Util::HashArrayDeepFetch
21
+ using Util::HashCompactByTemplate
22
+
23
+ attr_reader :config
24
+ private attr_reader :attributes
25
+
26
+ # @param config [Hash] an entire config.
27
+ def initialize(config)
28
+ @config = config
29
+
30
+ set_attributes
31
+ end
32
+
33
+ # Transforms the intermediate hash of a row into item data.
34
+ # @param parsed_row [Hash{Symbol => Hash, Array}] output from
35
+ # Parsing::Parser#parse_row_to_intermediate_hash.
36
+ # @return [Array<Hash>] an array of Hashes like the template in
37
+ # Config#default_config[:item][:template].
38
+ def transform_intermediate_hash_to_item_hashes(parsed_row)
39
+ if parsed_row[:head].blank?
40
+ raise InvalidHeadError, "Blank or missing Head column"
41
+ end
42
+
43
+ template = config.deep_fetch(:item, :template)
44
+
45
+ parsed_row[:head].map.with_index { |_head, head_index|
46
+ template.map { |attribute_name, default_value|
47
+ attribute = attributes.fetch(attribute_name)
48
+ transformed_value = attribute.transform_from_parsed(parsed_row, head_index)
49
+
50
+ [attribute_name, transformed_value || default_value]
51
+ }.to_h
52
+ .compact_by(template:)
53
+ }
54
+ end
55
+
56
+ private
57
+
58
+ # Sets the attributes classes which do all the transforming work.
59
+ # See parsing/attributes/*.
60
+ def set_attributes
61
+ @attributes ||= config.deep_fetch(:item, :template).map { |attribute_name, _default|
62
+ attribute_name_camelcase = attribute_name.to_s.split("_").map(&:capitalize).join
63
+ attribute_class = Attributes.const_get(attribute_name_camelcase)
64
+
65
+ [attribute_name, attribute_class.new(config)]
66
+ }.to_h
67
+ end
68
+ end
69
+ end
70
+ end
@@ -9,6 +9,7 @@ module Reading
9
9
  # If no parsed data has been added to the template values for these, they
10
10
  # are considered blank, and are replaced with an empty array so that their
11
11
  # emptiness is more apparent, e.g. item[:experiences].empty? will return true.
12
+ # @return [Hash]
12
13
  def compact_by(template:)
13
14
  map { |key, val|
14
15
  if is_array_of_hashes?(val)
@@ -32,7 +32,7 @@ module Reading
32
32
  end
33
33
  }
34
34
  elsif block_given?
35
- block.call(key, this_val, other_val)
35
+ yield key, this_val, other_val
36
36
  else
37
37
  other_val
38
38
  end
@@ -0,0 +1,30 @@
1
+ module Reading
2
+ module Util
3
+ # Converts a Hash to a Data. Converts inner hashes (and inner arrays of hashes) as well.
4
+ module HashToData
5
+ refine Hash do
6
+ # @return [Data]
7
+ def to_data
8
+ MEMOIZED_DATAS[keys] ||= Data.define(*keys)
9
+ data_class = MEMOIZED_DATAS[keys]
10
+
11
+ data_values = transform_values { |v|
12
+ if v.is_a?(Hash)
13
+ v.to_data
14
+ elsif v.is_a?(Array) && v.all? { |el| el.is_a?(Hash) }
15
+ v.map(&:to_data)
16
+ else
17
+ v
18
+ end
19
+ }.values
20
+
21
+ data_class.new(*data_values)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ MEMOIZED_DATAS = {}
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ module Reading
2
+ module Util
3
+ # Same as #to_i but only applies if the result is equal to the original number.
4
+ module NumericToIIfWhole
5
+ refine Numeric do
6
+ def to_i_if_whole
7
+ to_i == self ? to_i : self
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,13 +1,22 @@
1
1
  module Reading
2
2
  module Util
3
+ # Shortens the String to a given length.
3
4
  module StringTruncate
4
5
  refine String do
5
- def truncate(max, padding: 0, min: 30)
6
- end_index = max - padding
7
- end_index = min if end_index < min
8
- self.length + padding > max ? "#{self[0...end_index]}..." : self
6
+ # @param length [Integer]
7
+ # @return [String]
8
+ def truncate(length)
9
+ if length < self.length - ELLIPSIS.length
10
+ "#{self[0...length]}#{ELLIPSIS}"
11
+ else
12
+ self
13
+ end
9
14
  end
10
15
  end
16
+
17
+ private
18
+
19
+ ELLIPSIS = "...".freeze
11
20
  end
12
21
  end
13
22
  end
@@ -1,3 +1,3 @@
1
1
  module Reading
2
- VERSION = "0.6.1"
2
+ VERSION = "0.8.0"
3
3
  end
data/lib/reading.rb ADDED
@@ -0,0 +1,49 @@
1
+ require_relative "reading/parsing/csv"
2
+ require_relative "reading/filter"
3
+ require_relative "reading/config"
4
+ require_relative "reading/item/time_length.rb"
5
+
6
+ # The gem's public API. See https://github.com/fpsvogel/reading#usage
7
+ #
8
+ # Architectural overview:
9
+ #
10
+ # (CSV input) (Items) (filtered Items)
11
+ # | Λ | Λ
12
+ # | | ·---. |
13
+ # | | | |
14
+ # V | V |
15
+ # ::parse | ::filter |
16
+ # | | | |
17
+ # | .----------> Item Filter
18
+ # Config, | / / \
19
+ # errors.rb ----- Parsing::CSV --· Item::View Item::TimeLength
20
+ # / \
21
+ # Parsing::Parser Parsing::Transformer
22
+ # | |
23
+ # parsing/attributes/* parsing/rows/*
24
+ #
25
+ module Reading
26
+ # Parses a CSV file or string. See Parsing::CSV#initialize and #parse for details.
27
+ def self.parse(...)
28
+ csv = Parsing::CSV.new(...)
29
+ csv.parse
30
+ end
31
+
32
+ # Filters an array of Items. See Filter::by for details.
33
+ def self.filter(...)
34
+ Filter.by(...)
35
+ end
36
+
37
+ # The default config.
38
+ # @return [Hash]
39
+ def self.default_config
40
+ Config.new.hash
41
+ end
42
+
43
+ # A shortcut for getting a time from a string.
44
+ # @param string [String] a time duration in "h:mm" format.
45
+ # @return [Item::TimeLength]
46
+ def self.time(string)
47
+ Item::TimeLength.parse(string)
48
+ end
49
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reading
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Vogel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-02 00:00:00.000000000 Z
11
+ date: 2023-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pastel
@@ -28,119 +28,153 @@ dependencies:
28
28
  name: debug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.0
33
+ version: '1.7'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 1.0.0
40
+ version: '1.7'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: '5.18'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.0'
54
+ version: '5.18'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: minitest-reporters
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.0'
61
+ version: '1.6'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.0'
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: shoulda-context
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: pretty-diffs
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
- - - ">="
87
+ - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: '0'
89
+ version: '1.0'
76
90
  type: :development
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
- - - ">="
94
+ - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: '0'
96
+ version: '1.0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: amazing_print
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - ">="
101
+ - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '0'
103
+ version: '1.4'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - ">="
108
+ - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '0'
110
+ version: '1.4'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rubycritic
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - ">="
115
+ - - "~>"
102
116
  - !ruby/object:Gem::Version
103
- version: '0'
117
+ version: '4.7'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - ">="
122
+ - - "~>"
109
123
  - !ruby/object:Gem::Version
110
- version: '0'
124
+ version: '4.7'
111
125
  description:
112
126
  email:
113
127
  - fps.vogel@gmail.com
114
128
  executables:
115
129
  - reading
130
+ - readingfile
116
131
  extensions: []
117
132
  extra_rdoc_files: []
118
133
  files:
119
134
  - bin/reading
120
- - lib/reading/attribute/all_attributes.rb
121
- - lib/reading/attribute/attribute.rb
122
- - lib/reading/attribute/experiences/dates_validator.rb
123
- - lib/reading/attribute/experiences/experiences_attribute.rb
124
- - lib/reading/attribute/experiences/progress_subattribute.rb
125
- - lib/reading/attribute/experiences/spans_subattribute.rb
126
- - lib/reading/attribute/variants/extra_info_subattribute.rb
127
- - lib/reading/attribute/variants/length_subattribute.rb
128
- - lib/reading/attribute/variants/series_subattribute.rb
129
- - lib/reading/attribute/variants/sources_subattribute.rb
130
- - lib/reading/attribute/variants/variants_attribute.rb
135
+ - bin/readingfile
136
+ - lib/reading.rb
131
137
  - lib/reading/config.rb
132
- - lib/reading/csv.rb
133
138
  - lib/reading/errors.rb
134
- - lib/reading/line.rb
135
- - lib/reading/row/blank_row.rb
136
- - lib/reading/row/compact_planned_row.rb
137
- - lib/reading/row/regular_row.rb
138
- - lib/reading/row/row.rb
139
+ - lib/reading/filter.rb
140
+ - lib/reading/item.rb
141
+ - lib/reading/item/time_length.rb
142
+ - lib/reading/item/view.rb
143
+ - lib/reading/parsing/attributes/attribute.rb
144
+ - lib/reading/parsing/attributes/author.rb
145
+ - lib/reading/parsing/attributes/experiences.rb
146
+ - lib/reading/parsing/attributes/experiences/dates_and_head_transformer.rb
147
+ - lib/reading/parsing/attributes/experiences/history_transformer.rb
148
+ - lib/reading/parsing/attributes/experiences/spans_validator.rb
149
+ - lib/reading/parsing/attributes/genres.rb
150
+ - lib/reading/parsing/attributes/notes.rb
151
+ - lib/reading/parsing/attributes/rating.rb
152
+ - lib/reading/parsing/attributes/shared.rb
153
+ - lib/reading/parsing/attributes/title.rb
154
+ - lib/reading/parsing/attributes/variants.rb
155
+ - lib/reading/parsing/csv.rb
156
+ - lib/reading/parsing/parser.rb
157
+ - lib/reading/parsing/rows/column.rb
158
+ - lib/reading/parsing/rows/comment.rb
159
+ - lib/reading/parsing/rows/compact_planned.rb
160
+ - lib/reading/parsing/rows/compact_planned_columns/head.rb
161
+ - lib/reading/parsing/rows/regular.rb
162
+ - lib/reading/parsing/rows/regular_columns/end_dates.rb
163
+ - lib/reading/parsing/rows/regular_columns/genres.rb
164
+ - lib/reading/parsing/rows/regular_columns/head.rb
165
+ - lib/reading/parsing/rows/regular_columns/history.rb
166
+ - lib/reading/parsing/rows/regular_columns/length.rb
167
+ - lib/reading/parsing/rows/regular_columns/notes.rb
168
+ - lib/reading/parsing/rows/regular_columns/rating.rb
169
+ - lib/reading/parsing/rows/regular_columns/sources.rb
170
+ - lib/reading/parsing/rows/regular_columns/start_dates.rb
171
+ - lib/reading/parsing/transformer.rb
139
172
  - lib/reading/util/blank.rb
140
173
  - lib/reading/util/hash_array_deep_fetch.rb
141
174
  - lib/reading/util/hash_compact_by_template.rb
142
175
  - lib/reading/util/hash_deep_merge.rb
143
- - lib/reading/util/hash_to_struct.rb
176
+ - lib/reading/util/hash_to_data.rb
177
+ - lib/reading/util/numeric_to_i_if_whole.rb
144
178
  - lib/reading/util/string_remove.rb
145
179
  - lib/reading/util/string_truncate.rb
146
180
  - lib/reading/version.rb
@@ -167,8 +201,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
201
  - !ruby/object:Gem::Version
168
202
  version: '0'
169
203
  requirements: []
170
- rubygems_version: 3.4.1
204
+ rubygems_version: 3.4.9
171
205
  signing_key:
172
206
  specification_version: 4
173
- summary: reading parses a CSV reading log.
207
+ summary: Parses a CSV reading log.
174
208
  test_files: []
@@ -1,83 +0,0 @@
1
- require_relative "attribute"
2
- require_relative "variants/variants_attribute"
3
- require_relative "experiences/experiences_attribute"
4
-
5
- module Reading
6
- class Row
7
- using Util::StringRemove
8
- using Util::HashArrayDeepFetch
9
-
10
- # The simpler attributes are collected below. The more complex attributes
11
- # are separated into their own files.
12
-
13
- class RatingAttribute < Attribute
14
- def parse
15
- return nil unless columns[:rating]
16
-
17
- rating = columns[:rating].strip
18
- return nil if rating.empty?
19
-
20
- Integer(rating, exception: false) ||
21
- Float(rating, exception: false) ||
22
- (raise InvalidRatingError, "Invalid rating")
23
- end
24
- end
25
-
26
- class AuthorAttribute < Attribute
27
- def parse
28
- item_head
29
- .remove(/\A#{config.deep_fetch(:csv, :regex, :formats)}/)
30
- .match(/.+(?=#{config.deep_fetch(:csv, :short_separator)})/)
31
- &.to_s
32
- &.strip
33
- end
34
- end
35
-
36
- class TitleAttribute < Attribute
37
- def parse
38
- if item_head.end_with?(config.deep_fetch(:csv, :short_separator).rstrip)
39
- raise InvalidHeadError, "Missing title? Head column ends in a separator"
40
- end
41
-
42
- item_head
43
- .remove(/\A#{config.deep_fetch(:csv, :regex, :formats)}/)
44
- .remove(/.+#{config.deep_fetch(:csv, :short_separator)}/)
45
- .remove(/#{config.deep_fetch(:csv, :long_separator)}.+\z/)
46
- .strip
47
- .presence || (raise InvalidHeadError, "Missing title")
48
- end
49
- end
50
-
51
- class GenresAttribute < Attribute
52
- def parse
53
- return nil unless columns[:genres]
54
-
55
- columns[:genres]
56
- .split(config.deep_fetch(:csv, :separator))
57
- .map(&:strip)
58
- .map(&:downcase)
59
- .map(&:presence)
60
- .compact.presence
61
- end
62
- end
63
-
64
- class NotesAttribute < Attribute
65
- def parse
66
- return nil unless columns[:notes]
67
-
68
- columns[:notes]
69
- .presence
70
- &.chomp
71
- &.remove(/#{config.deep_fetch(:csv, :long_separator).rstrip}\s*\z/)
72
- &.split(config.deep_fetch(:csv, :long_separator))
73
- &.map { |string|
74
- {
75
- blurb?: !!string.delete!(config.deep_fetch(:csv, :blurb_emoji)),
76
- private?: !!string.delete!(config.deep_fetch(:csv, :private_emoji)),
77
- content: string.strip,
78
- }
79
- }
80
- end
81
- end
82
- end
83
- end
@@ -1,25 +0,0 @@
1
- module Reading
2
- class Row
3
- # A base class that contains behaviors common to ___Attribute classes.
4
- class Attribute
5
- private attr_reader :item_head, :columns, :config
6
-
7
- # @param item_head [String] see Row#item_heads for a definition.
8
- # @param columns [Array<String>] the CSV row split into columns.
9
- # @param config [Hash]
10
- def initialize(item_head: nil, columns: nil, config:)
11
- unless item_head || columns
12
- raise ArgumentError, "Either item_head or columns must be given to an Attribute."
13
- end
14
-
15
- @item_head = item_head
16
- @columns = columns
17
- @config = config
18
- end
19
-
20
- def parse
21
- raise NotImplementedError, "#{self.class} should have implemented #{__method__}"
22
- end
23
- end
24
- end
25
- end
@@ -1,94 +0,0 @@
1
- module Reading
2
- # Methods to validate dates. This does not cover all the ways dates can be
3
- # invalid, just the ones not covered by ExperiencesAttribute during parsing.
4
- module DatesValidator
5
- using Util::HashArrayDeepFetch
6
-
7
- class << self
8
- # Checks the dates in the given experiences hash, and raises an error at
9
- # the first invalid date found.
10
- # @param experiences [Array<Hash>]
11
- # @param config [Hash]
12
- def validate(experiences, config)
13
- validate_dates_started_are_in_order(experiences) if dates_started_column?(config)
14
- validate_dates_finished_are_in_order(experiences) if dates_finished_column?(config)
15
- validate_experiences_of_same_variant_do_not_overlap(experiences) if both_date_columns?(config)
16
- validate_spans_are_in_order_and_not_overlapping(experiences)
17
- end
18
-
19
- private
20
-
21
- def dates_started_column?(config)
22
- config.deep_fetch(:csv, :enabled_columns).include?(:dates_started)
23
- end
24
-
25
- def dates_finished_column?(config)
26
- config.deep_fetch(:csv, :enabled_columns).include?(:dates_finished)
27
- end
28
-
29
- def both_date_columns?(config)
30
- dates_started_column?(config) && dates_finished_column?(config)
31
- end
32
-
33
- def validate_dates_started_are_in_order(experiences)
34
- experiences
35
- .filter { |exp| exp[:spans].any? }
36
- .map { |exp| exp[:spans].first[:dates].begin }
37
- .each_cons(2) do |a, b|
38
- if (a.nil? && b.nil?) || (a && b && a > b )
39
- raise InvalidDateError, "Dates started are not in order"
40
- end
41
- end
42
- end
43
-
44
- def validate_dates_finished_are_in_order(experiences)
45
- experiences
46
- .filter { |exp| exp[:spans].any? }
47
- .map { |exp| exp[:spans].last[:dates].end }
48
- .each_cons(2) do |a, b|
49
- if (a.nil? && b.nil?) || (a && b && a > b )
50
- raise InvalidDateError, "Dates finished are not in order"
51
- end
52
- end
53
- end
54
-
55
- def validate_experiences_of_same_variant_do_not_overlap(experiences)
56
- experiences
57
- .group_by { |exp| exp[:variant_index] }
58
- .each do |_variant_index, exps|
59
- exps.filter { |exp| exp[:spans].any? }.each_cons(2) do |a, b|
60
- a_metaspan = a[:spans].first[:dates].begin..a[:spans].last[:dates].end
61
- b_metaspan = b[:spans].first[:dates].begin..b[:spans].last[:dates].end
62
- if a_metaspan.cover?(b_metaspan.begin || a_metaspan.begin || a_metaspan.end) ||
63
- b_metaspan.cover?(a_metaspan.begin || b_metaspan.begin || b_metaspan.end)
64
- raise InvalidDateError, "Experiences are overlapping"
65
- end
66
- end
67
- end
68
- end
69
-
70
- def validate_spans_are_in_order_and_not_overlapping(experiences)
71
- experiences
72
- .filter { |exp| exp[:spans].any? }
73
- .each do |exp|
74
- exp[:spans]
75
- .map { |span| span[:dates] }
76
- .each do |dates|
77
- if dates.begin && dates.end && dates.begin > dates.end
78
- raise InvalidDateError, "A date range is backward"
79
- end
80
- end
81
- .each_cons(2) do |a, b|
82
- if a.begin > b.begin || a.end > b.end
83
- raise InvalidDateError, "Dates are not in order"
84
- end
85
- if a.cover?(b.begin || a.begin || a.end) ||
86
- b.cover?(a.begin || b.begin || b.end)
87
- raise InvalidDateError, "Dates are overlapping"
88
- end
89
- end
90
- end
91
- end
92
- end
93
- end
94
- end