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.
- checksums.yaml +4 -4
- data/bin/reading +5 -5
- data/bin/readingfile +31 -0
- data/lib/reading/config.rb +96 -108
- data/lib/reading/errors.rb +10 -66
- data/lib/reading/filter.rb +95 -0
- data/lib/reading/item/time_length.rb +140 -0
- data/lib/reading/item/view.rb +121 -0
- data/lib/reading/item.rb +117 -0
- data/lib/reading/parsing/attributes/attribute.rb +26 -0
- data/lib/reading/parsing/attributes/author.rb +15 -0
- data/lib/reading/parsing/attributes/experiences/dates_and_head_transformer.rb +106 -0
- data/lib/reading/parsing/attributes/experiences/history_transformer.rb +452 -0
- data/lib/reading/parsing/attributes/experiences/spans_validator.rb +149 -0
- data/lib/reading/parsing/attributes/experiences.rb +27 -0
- data/lib/reading/parsing/attributes/genres.rb +16 -0
- data/lib/reading/parsing/attributes/notes.rb +22 -0
- data/lib/reading/parsing/attributes/rating.rb +17 -0
- data/lib/reading/parsing/attributes/shared.rb +62 -0
- data/lib/reading/parsing/attributes/title.rb +21 -0
- data/lib/reading/parsing/attributes/variants.rb +77 -0
- data/lib/reading/parsing/csv.rb +112 -0
- data/lib/reading/parsing/parser.rb +292 -0
- data/lib/reading/parsing/rows/column.rb +131 -0
- data/lib/reading/parsing/rows/comment.rb +26 -0
- data/lib/reading/parsing/rows/compact_planned.rb +30 -0
- data/lib/reading/parsing/rows/compact_planned_columns/head.rb +60 -0
- data/lib/reading/parsing/rows/regular.rb +33 -0
- data/lib/reading/parsing/rows/regular_columns/end_dates.rb +20 -0
- data/lib/reading/parsing/rows/regular_columns/genres.rb +20 -0
- data/lib/reading/parsing/rows/regular_columns/head.rb +45 -0
- data/lib/reading/parsing/rows/regular_columns/history.rb +143 -0
- data/lib/reading/parsing/rows/regular_columns/length.rb +35 -0
- data/lib/reading/parsing/rows/regular_columns/notes.rb +32 -0
- data/lib/reading/parsing/rows/regular_columns/rating.rb +15 -0
- data/lib/reading/parsing/rows/regular_columns/sources.rb +94 -0
- data/lib/reading/parsing/rows/regular_columns/start_dates.rb +35 -0
- data/lib/reading/parsing/transformer.rb +70 -0
- data/lib/reading/util/hash_compact_by_template.rb +1 -0
- data/lib/reading/util/hash_deep_merge.rb +1 -1
- data/lib/reading/util/hash_to_data.rb +30 -0
- data/lib/reading/util/numeric_to_i_if_whole.rb +12 -0
- data/lib/reading/util/string_truncate.rb +13 -4
- data/lib/reading/version.rb +1 -1
- data/lib/reading.rb +49 -0
- metadata +76 -42
- data/lib/reading/attribute/all_attributes.rb +0 -83
- data/lib/reading/attribute/attribute.rb +0 -25
- data/lib/reading/attribute/experiences/dates_validator.rb +0 -94
- data/lib/reading/attribute/experiences/experiences_attribute.rb +0 -74
- data/lib/reading/attribute/experiences/progress_subattribute.rb +0 -48
- data/lib/reading/attribute/experiences/spans_subattribute.rb +0 -82
- data/lib/reading/attribute/variants/extra_info_subattribute.rb +0 -44
- data/lib/reading/attribute/variants/length_subattribute.rb +0 -45
- data/lib/reading/attribute/variants/series_subattribute.rb +0 -57
- data/lib/reading/attribute/variants/sources_subattribute.rb +0 -78
- data/lib/reading/attribute/variants/variants_attribute.rb +0 -69
- data/lib/reading/csv.rb +0 -76
- data/lib/reading/line.rb +0 -23
- data/lib/reading/row/blank_row.rb +0 -23
- data/lib/reading/row/compact_planned_row.rb +0 -130
- data/lib/reading/row/regular_row.rb +0 -99
- data/lib/reading/row/row.rb +0 -88
- 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)
|
@@ -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
|
@@ -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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
data/lib/reading/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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: '
|
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: '
|
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: '
|
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: '
|
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
|
-
-
|
121
|
-
- lib/reading
|
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/
|
135
|
-
- lib/reading/
|
136
|
-
- lib/reading/
|
137
|
-
- lib/reading/
|
138
|
-
- lib/reading/
|
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/
|
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.
|
204
|
+
rubygems_version: 3.4.9
|
171
205
|
signing_key:
|
172
206
|
specification_version: 4
|
173
|
-
summary:
|
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
|