reading 1.0.2 → 1.1.1
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 +77 -68
- data/lib/reading/item/time_length.rb +29 -12
- data/lib/reading/parsing/attributes/experiences/dates_and_head_transformer.rb +19 -5
- data/lib/reading/parsing/attributes/experiences/history_transformer.rb +9 -2
- data/lib/reading/parsing/attributes/shared.rb +1 -1
- data/lib/reading/stats/filter.rb +1 -1
- data/lib/reading/stats/grouping.rb +12 -7
- data/lib/reading/stats/result_formatters.rb +16 -47
- data/lib/reading/util/numeric_to_i_if_whole.rb +5 -1
- data/lib/reading/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4bf09fbcc2e77924a371eade404c83ad4fd5c3d7576eac45d0c1236c3db479f6
|
4
|
+
data.tar.gz: f854509f8ae88553df964428d18098fceeb3c885093728c2f54bba530199c8cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a285b194b566697bd586628d1266ed5582e1cfab48dc3023e243635e4b21b3f31f100cf4b41a1777358b70175c15a8b2d57d7589859697c917e1914f452e492e
|
7
|
+
data.tar.gz: 6aa82f83175a22cd652b545aa46935ba5ef72009d33b8bbcd35a6667e1c2081cbe708fce40703503027aaf5938c5020a475fea9575d6e5d45fe75bef49233b4e
|
data/bin/reading
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
#
|
4
|
-
# If a
|
3
|
+
# Runs a reading statistics query, if a file path arg (CSV or TXT)is given.
|
4
|
+
# If a non-path string is given, then displays parsed output (item Hashes).
|
5
5
|
#
|
6
6
|
# Usage:
|
7
7
|
# Run on the command line:
|
8
|
-
# reading "<
|
8
|
+
# reading "<file path or entry string>" "<query, if file path>" "<optional comma-separated names of enabled columns>"
|
9
9
|
#
|
10
10
|
# Examples:
|
11
|
-
# reading /home/felipe/reading.csv
|
12
|
-
# reading /home/felipe/reading.csv 'head, sources'
|
11
|
+
# reading /home/felipe/reading.csv 'amount by year'
|
12
|
+
# reading /home/felipe/reading.csv 'amount by year' 'head, sources'
|
13
13
|
# reading '3|📕Trying|Little Library 1970147288'
|
14
14
|
# reading '📕Trying|Little Library 1970147288' 'head, sources'
|
15
15
|
|
@@ -17,29 +17,15 @@ require_relative "../lib/reading"
|
|
17
17
|
require_relative "../lib/reading/stats/result_formatters"
|
18
18
|
require "debug"
|
19
19
|
require "amazing_print"
|
20
|
-
require "readline"
|
21
20
|
require "pastel"
|
22
21
|
|
23
|
-
EXIT_COMMANDS = %w[exit e quit q]
|
24
22
|
PASTEL = Pastel.new
|
25
|
-
GROUP_HEADING_FORMATTERS = [
|
26
|
-
-> { PASTEL.magenta.bold.underline(_1) },
|
27
|
-
-> { PASTEL.green.bold.underline(_1) },
|
28
|
-
-> { PASTEL.yellow.bold.underline(_1) },
|
29
|
-
-> { PASTEL.cyan.bold.underline(_1) },
|
30
|
-
-> { PASTEL.magenta.on_white(_1) },
|
31
|
-
-> { PASTEL.green.on_white(_1) },
|
32
|
-
-> { PASTEL.yellow.on_white(_1) },
|
33
|
-
]
|
34
23
|
|
35
24
|
# Recursively prints a hash of results (possibly grouped).
|
36
25
|
# @param grouped_results [Hash, Array]
|
37
|
-
|
38
|
-
def print_grouped_results(grouped_results, group_heading_formatters)
|
39
|
-
indent_level = GROUP_HEADING_FORMATTERS.count - group_heading_formatters.count
|
40
|
-
|
26
|
+
def print_grouped_results(grouped_results)
|
41
27
|
if grouped_results.nil? || (grouped_results.respond_to?(:empty?) && grouped_results.empty?)
|
42
|
-
puts "
|
28
|
+
puts "none\n"
|
43
29
|
return
|
44
30
|
end
|
45
31
|
|
@@ -47,70 +33,93 @@ def print_grouped_results(grouped_results, group_heading_formatters)
|
|
47
33
|
(grouped_results.is_a?(Array) && grouped_results.first.length == 2)
|
48
34
|
|
49
35
|
grouped_results.each do |group_name, grouped|
|
50
|
-
|
51
|
-
|
36
|
+
group_name = group_name.nil? ? "(none)" : group_name
|
37
|
+
puts "# #{group_name}"
|
38
|
+
print_grouped_results(grouped)
|
52
39
|
end
|
53
40
|
elsif grouped_results.is_a?(Array)
|
54
|
-
|
55
|
-
|
56
|
-
puts " " * indent_level + numbered_results.join("\n" + " " * indent_level) + "\n"
|
41
|
+
puts grouped_results.join("\n") + "\n\n"
|
57
42
|
else
|
58
|
-
puts
|
43
|
+
puts grouped_results.to_s + "\n\n"
|
59
44
|
end
|
60
45
|
end
|
61
46
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
if ARGV[1]
|
71
|
-
enabled_columns = ARGV[1].split(",").map(&:strip).map(&:to_sym)
|
72
|
-
Reading::Config.build(enabled_columns:)
|
47
|
+
# Builds a Reading::Config from a comma-separated string of column names.
|
48
|
+
# @param enabled_columns_str [String, nil]
|
49
|
+
def build_config(enabled_columns_str:)
|
50
|
+
if enabled_columns_str
|
51
|
+
enabled_columns = enabled_columns_str.split(",").map(&:strip).map(&:to_sym)
|
52
|
+
Reading::Config.build(enabled_columns:)
|
53
|
+
end
|
73
54
|
end
|
74
55
|
|
75
|
-
|
56
|
+
# Runs a stats query on a file at the given path, and prints the results.
|
57
|
+
# @param file_path [String] path to a CSV or TXT file.
|
58
|
+
# @param query [String] the stats query to run.
|
59
|
+
# @param enabled_columns_str [String, nil] comma-separated string of enabled column names.
|
60
|
+
def run_query(file_path:, query:, enabled_columns_str:)
|
61
|
+
return if query.blank?
|
76
62
|
|
77
|
-
|
63
|
+
build_config(enabled_columns_str: ARGV[2])
|
78
64
|
error_handler = ->(e) { puts "Skipped a row due to a parsing error: #{e}" }
|
79
65
|
|
80
|
-
items = Reading.parse(path:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
result_formatters: Reading::Stats::ResultFormatters::TERMINAL,
|
94
|
-
)
|
95
|
-
|
96
|
-
if results.is_a?(Array) && results.first.is_a?(Reading::Item) # `debug` operation
|
97
|
-
r = results
|
98
|
-
puts PASTEL.red.bold("Enter 'c' to leave the debugger.")
|
99
|
-
debugger
|
100
|
-
else
|
101
|
-
print_grouped_results(results, GROUP_HEADING_FORMATTERS)
|
102
|
-
end
|
103
|
-
rescue Reading::Error => e
|
104
|
-
puts e
|
66
|
+
items = Reading.parse(path: file_path, item_view: false, error_handler:)
|
67
|
+
results = Reading.stats(
|
68
|
+
input: query,
|
69
|
+
items:,
|
70
|
+
result_formatters: Reading::Stats::ResultFormatters::TERMINAL,
|
71
|
+
)
|
72
|
+
|
73
|
+
if results.is_a?(Array) && results.first.is_a?(Reading::Item) # `debug` operation
|
74
|
+
r = results
|
75
|
+
puts PASTEL.red.bold("Enter 'c' to leave the debugger.")
|
76
|
+
debugger
|
77
|
+
else
|
78
|
+
print_grouped_results(results)
|
105
79
|
end
|
106
|
-
|
107
|
-
|
80
|
+
rescue Reading::Error => e
|
81
|
+
puts e
|
82
|
+
end
|
83
|
+
|
84
|
+
# Parses a string of one or more reading log entries, and prints the resulting item Hashes.
|
85
|
+
# @param entry [String] one or more reading log entries, separated by newlines.
|
86
|
+
# @param enabled_columns_str [String, nil] comma-separated string of enabled column names.
|
87
|
+
def print_parsed(entry:, enabled_columns_str:)
|
88
|
+
build_config(enabled_columns_str:)
|
89
|
+
entry = entry.gsub("\\|", "|") # because some pipes are escaped when pasting into the terminal
|
108
90
|
|
109
91
|
begin
|
110
|
-
item_hashes = Reading.parse(lines:
|
92
|
+
item_hashes = Reading.parse(lines: entry, hash_output: true, item_view: false)
|
111
93
|
rescue Reading::Error => e
|
112
94
|
puts "Skipped a row due to a parsing error: #{e}"
|
113
95
|
end
|
114
96
|
|
115
97
|
ap item_hashes
|
116
98
|
end
|
99
|
+
|
100
|
+
#################################################
|
101
|
+
# Script execution starts here.
|
102
|
+
#################################################
|
103
|
+
|
104
|
+
input = ARGV[0]
|
105
|
+
unless input
|
106
|
+
raise ArgumentError,
|
107
|
+
"First argument required, either a CSV/TXT file path or a reading log entry string.\nExamples:\n" \
|
108
|
+
" reading /home/felipe/reading.csv\n" \
|
109
|
+
" reading '3|📕Trying|Little Library 1970147288'"
|
110
|
+
end
|
111
|
+
|
112
|
+
input_is_file_path = input.end_with?(".csv") || input.end_with?(".txt")
|
113
|
+
|
114
|
+
if input_is_file_path
|
115
|
+
query = ARGV[1]
|
116
|
+
unless query
|
117
|
+
raise ArgumentError,
|
118
|
+
"Second argument required, the stats query to run.\nExample:\n" \
|
119
|
+
" reading /home/felipe/reading.csv 'amount by year'" \
|
120
|
+
end
|
121
|
+
|
122
|
+
run_query(file_path: input, query:, enabled_columns_str: ARGV[2])
|
123
|
+
else
|
124
|
+
print_parsed(entry: input, enabled_columns_str: ARGV[1])
|
125
|
+
end
|
@@ -2,7 +2,7 @@ module Reading
|
|
2
2
|
class Item
|
3
3
|
# The length of an item when it is a time, as opposed to pages. (Pages are
|
4
4
|
# represented simply with an Integer or Float.)
|
5
|
-
class
|
5
|
+
class TimeLength
|
6
6
|
include Comparable
|
7
7
|
|
8
8
|
attr_reader :value # in total minutes
|
@@ -12,17 +12,17 @@ module Reading
|
|
12
12
|
@value = value
|
13
13
|
end
|
14
14
|
|
15
|
-
# Builds
|
15
|
+
# Builds a TimeLength from a string.
|
16
16
|
# @param string [String] a time duration in "h:mm" format.
|
17
17
|
# @return [TimeLength, nil]
|
18
18
|
def self.parse(string)
|
19
|
-
return nil unless string.match?
|
19
|
+
return nil unless string.match?(/\A\d+:\d\d\z/)
|
20
20
|
|
21
21
|
hours, minutes = string.split(":").map(&:to_i)
|
22
22
|
new((hours * 60) + minutes)
|
23
23
|
end
|
24
24
|
|
25
|
-
# Builds
|
25
|
+
# Builds a TimeLength based on a page count.
|
26
26
|
# @param pages [Integer, Float]
|
27
27
|
# @return [TimeLength]
|
28
28
|
def self.from_pages(pages)
|
@@ -74,7 +74,9 @@ module Reading
|
|
74
74
|
# Converts @value to an Integer if it's a whole number, and returns self.
|
75
75
|
# @return [TimeLength]
|
76
76
|
def to_i_if_whole!
|
77
|
-
if @value.
|
77
|
+
if @value.is_a?(Float) && @value.nan?
|
78
|
+
@value = 0
|
79
|
+
elsif @value.to_i == @value
|
78
80
|
@value = @value.to_i
|
79
81
|
end
|
80
82
|
|
@@ -85,32 +87,36 @@ module Reading
|
|
85
87
|
# refinement Numeric#to_i_if_whole.
|
86
88
|
# @return [TimeLength]
|
87
89
|
def to_i_if_whole
|
88
|
-
|
90
|
+
if @value.is_a?(Float) && @value.nan?
|
91
|
+
return self.class.new(0)
|
92
|
+
else
|
93
|
+
return self if @value.is_a?(Integer) || @value.to_i != @value
|
89
94
|
|
90
|
-
|
95
|
+
self.class.new(@value.to_i)
|
96
|
+
end
|
91
97
|
end
|
92
98
|
|
93
99
|
# @param other [TimeLength, Numeric]
|
94
100
|
# @return [TimeLength]
|
95
101
|
def +(other)
|
96
|
-
if other.is_a?
|
102
|
+
if other.is_a? TimeLength
|
97
103
|
self.class.new(value + other.value)
|
98
104
|
elsif other.is_a? Numeric
|
99
105
|
self.class.new(value + self.class.pages_to_minutes(other))
|
100
106
|
else
|
101
|
-
raise TypeError, "#{other.class} can't be added to
|
107
|
+
raise TypeError, "#{other.class} can't be added to TimeLength."
|
102
108
|
end
|
103
109
|
end
|
104
110
|
|
105
111
|
# @param other [TimeLength, Numeric]
|
106
112
|
# @return [TimeLength]
|
107
113
|
def -(other)
|
108
|
-
if other.is_a?
|
114
|
+
if other.is_a? TimeLength
|
109
115
|
self.class.new(value - other.value)
|
110
116
|
elsif other.is_a? Numeric
|
111
117
|
self.class.new(value - self.class.pages_to_minutes(other))
|
112
118
|
else
|
113
|
-
raise TypeError, "#{other.class} can't be subtracted from
|
119
|
+
raise TypeError, "#{other.class} can't be subtracted from TimeLength."
|
114
120
|
end
|
115
121
|
end
|
116
122
|
|
@@ -134,6 +140,17 @@ module Reading
|
|
134
140
|
end
|
135
141
|
end
|
136
142
|
|
143
|
+
# For relativizing progress to a percentage of amount.
|
144
|
+
# @param other [TimeLength, Numeric]
|
145
|
+
# @return [TimeLength]
|
146
|
+
def percentage_of(other)
|
147
|
+
if other.is_a? TimeLength
|
148
|
+
value.to_f / other.value
|
149
|
+
else
|
150
|
+
raise TypeError, "TimeLength can't be percent-divided by #{other.class}."
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
137
154
|
# See https://web.archive.org/web/20221206095821/https://www.mutuallyhuman.com/blog/class-coercion-in-ruby/
|
138
155
|
# @param other [Numeric]
|
139
156
|
def coerce(other)
|
@@ -153,7 +170,7 @@ module Reading
|
|
153
170
|
other = self.class.from_pages(other)
|
154
171
|
end
|
155
172
|
|
156
|
-
unless other.is_a?
|
173
|
+
unless other.is_a? TimeLength
|
157
174
|
raise TypeError, "TimeLength can't be compared to #{other.class} #{other}."
|
158
175
|
end
|
159
176
|
|
@@ -90,14 +90,28 @@ module Reading
|
|
90
90
|
Attributes::Shared.length(parsed_row[:length], format:)
|
91
91
|
no_end_date = !dates.end if dates &&
|
92
92
|
Config.hash.fetch(:enabled_columns).include?(:end_dates)
|
93
|
+
progress = Attributes::Shared.progress(start_entry, no_end_date:) ||
|
94
|
+
Attributes::Shared.progress(parsed_row[:head][head_index]) ||
|
95
|
+
(1.0 if end_entry)
|
96
|
+
amount =
|
97
|
+
if dates && length
|
98
|
+
length
|
99
|
+
elsif !progress.is_a?(Float)
|
100
|
+
progress
|
101
|
+
end
|
102
|
+
|
103
|
+
# Change progress from absolute to relative (percentage) if amount is given.
|
104
|
+
if amount && progress && !progress.is_a?(Float)
|
105
|
+
amount_time = amount.is_a?(Item::TimeLength) ? amount : Item::TimeLength.from_pages(amount)
|
106
|
+
progress_time = progress.is_a?(Item::TimeLength) ? progress : Item::TimeLength.from_pages(progress)
|
107
|
+
progress = progress_time.percentage_of(amount_time)
|
108
|
+
end
|
93
109
|
|
94
110
|
[
|
95
111
|
{
|
96
|
-
dates
|
97
|
-
amount
|
98
|
-
progress
|
99
|
-
Attributes::Shared.progress(parsed_row[:head][head_index]) ||
|
100
|
-
(1.0 if end_entry),
|
112
|
+
dates:,
|
113
|
+
amount:,
|
114
|
+
progress:,
|
101
115
|
name: span_template.fetch(:name),
|
102
116
|
favorite?: span_template.fetch(:favorite?),
|
103
117
|
}.map { |k, v| [k, v || span_template.fetch(k)] }.to_h
|
@@ -212,15 +212,22 @@ module Reading
|
|
212
212
|
# is when tracking fixed-length items such as books. See
|
213
213
|
# https://github.com/fpsvogel/reading/blob/main/doc/csv-format.md#history-pages-and-stopping-points-books
|
214
214
|
if !amount && progress
|
215
|
-
if progress.is_a?
|
215
|
+
if progress.is_a?(Float)
|
216
216
|
total_length = Attributes::Shared.length(parsed_row[:length], format:)
|
217
|
-
amount = total_length * progress
|
217
|
+
amount = total_length * progress if total_length
|
218
218
|
else
|
219
219
|
amount = progress
|
220
220
|
end
|
221
221
|
amount_from_progress = true
|
222
222
|
end
|
223
223
|
|
224
|
+
# Change progress from absolute to relative (percentage) if amount is given.
|
225
|
+
if amount && progress && !progress.is_a?(Float)
|
226
|
+
amount_time = amount.is_a?(Item::TimeLength) ? amount : Item::TimeLength.from_pages(amount)
|
227
|
+
progress_time = progress.is_a?(Item::TimeLength) ? progress : Item::TimeLength.from_pages(progress)
|
228
|
+
progress = progress_time.percentage_of(amount_time)
|
229
|
+
end
|
230
|
+
|
224
231
|
repetitions = entry[:repetitions]&.to_i
|
225
232
|
frequency = entry[:frequency]
|
226
233
|
|
@@ -16,7 +16,7 @@ module Reading
|
|
16
16
|
hash[:progress_percent]&.to_f&./(100) ||
|
17
17
|
hash[:progress_pages]&.to_i ||
|
18
18
|
hash[:progress_time]&.then { Item::TimeLength.parse(_1) } ||
|
19
|
-
(0 if hash[:progress_dnf]) ||
|
19
|
+
(0.0 if hash[:progress_dnf]) ||
|
20
20
|
(1.0 if hash[:progress_done]) ||
|
21
21
|
(0.0 if no_end_date) ||
|
22
22
|
nil
|
data/lib/reading/stats/filter.rb
CHANGED
@@ -372,7 +372,7 @@ module Reading
|
|
372
372
|
match = value.match(DATES_REGEX) ||
|
373
373
|
(raise InputError,
|
374
374
|
"End date must be in yyyy/[mm] format, or a date range " \
|
375
|
-
"(yyyy/[mm]-
|
375
|
+
"(yyyy/[mm]-yyyy/[mm])")
|
376
376
|
|
377
377
|
start_date = Date.new(
|
378
378
|
match[:start_year].to_i,
|
@@ -10,8 +10,6 @@ module Reading
|
|
10
10
|
# @param items [Array<Item>] the Items on which to run the operation.
|
11
11
|
# @return [Hash] the return value of the group action(s).
|
12
12
|
def self.group(input, items)
|
13
|
-
grouped_items = {}
|
14
|
-
|
15
13
|
match = input.match(REGEX)
|
16
14
|
|
17
15
|
if match
|
@@ -80,7 +78,7 @@ module Reading
|
|
80
78
|
end
|
81
79
|
end
|
82
80
|
|
83
|
-
groups.
|
81
|
+
groups.sort_by { |key, _items| key }.to_h
|
84
82
|
},
|
85
83
|
source: proc { |items|
|
86
84
|
groups = Hash.new { |h, k| h[k] = [] }
|
@@ -103,7 +101,7 @@ module Reading
|
|
103
101
|
end
|
104
102
|
end
|
105
103
|
|
106
|
-
groups.
|
104
|
+
groups.sort_by { |key, _items| key }.to_h
|
107
105
|
},
|
108
106
|
year: proc { |items|
|
109
107
|
begin_date = items
|
@@ -187,7 +185,14 @@ module Reading
|
|
187
185
|
end
|
188
186
|
|
189
187
|
groups.transform_keys! { |month_range|
|
190
|
-
[month_range.begin.year, month_range.begin.month]
|
188
|
+
array = [month_range.begin.year, month_range.begin.month]
|
189
|
+
|
190
|
+
array.define_singleton_method(:to_s) do
|
191
|
+
map { it.to_s.rjust(2, "0") }
|
192
|
+
.join("/")
|
193
|
+
end
|
194
|
+
|
195
|
+
array
|
191
196
|
}
|
192
197
|
|
193
198
|
groups
|
@@ -200,7 +205,7 @@ module Reading
|
|
200
205
|
item.genres.each { |genre| groups[genre] << item }
|
201
206
|
end
|
202
207
|
|
203
|
-
groups.
|
208
|
+
groups.sort_by { |key, _items| key }.to_h
|
204
209
|
},
|
205
210
|
genre: proc { |items|
|
206
211
|
groups = Hash.new { |h, k| h[k] = [] }
|
@@ -212,7 +217,7 @@ module Reading
|
|
212
217
|
end
|
213
218
|
end
|
214
219
|
|
215
|
-
groups.
|
220
|
+
groups.sort_by { |key, _items| key }.to_h
|
216
221
|
},
|
217
222
|
length: proc { |items|
|
218
223
|
boundaries = Config.hash.fetch(:length_group_boundaries)
|
@@ -20,9 +20,9 @@ module Reading
|
|
20
20
|
:"average_daily-amount" => ->(result) { "#{length_to_s(result)} per day" },
|
21
21
|
total_item: ->(result) {
|
22
22
|
if result.zero?
|
23
|
-
|
23
|
+
"none"
|
24
24
|
else
|
25
|
-
|
25
|
+
"#{result} #{result == 1 ? "item" : "items"}"
|
26
26
|
end
|
27
27
|
},
|
28
28
|
total_amount: ->(result) { length_to_s(result) },
|
@@ -40,44 +40,25 @@ module Reading
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
-
PASTEL = Pastel.new
|
44
|
-
|
45
|
-
# Applies a terminal color.
|
46
|
-
# @param string [String]
|
47
|
-
# @return [String]
|
48
|
-
private_class_method def self.color(string)
|
49
|
-
PASTEL.bright_blue(string)
|
50
|
-
end
|
51
|
-
|
52
43
|
# Converts a length/amount (pages or time) into a string.
|
53
44
|
# @param length [Numeric, Reading::Item::TimeLength]
|
54
|
-
# @param color [Boolean] whether a terminal color should be applied.
|
55
45
|
# @return [String]
|
56
|
-
private_class_method def self.length_to_s(length
|
57
|
-
if length.
|
58
|
-
|
46
|
+
private_class_method def self.length_to_s(length)
|
47
|
+
if length.nil? || length.zero?
|
48
|
+
"none"
|
49
|
+
elsif length.is_a?(Numeric)
|
50
|
+
"#{length.round} pages"
|
59
51
|
else
|
60
|
-
|
52
|
+
length.to_s
|
61
53
|
end
|
62
|
-
|
63
|
-
color ? color(length_string) : length_string
|
64
54
|
end
|
65
55
|
|
66
56
|
# Formats a list of top/bottom length results as a string.
|
67
57
|
# @param result [Array]
|
68
58
|
# @return [String]
|
69
59
|
private_class_method def self.top_or_bottom_lengths_string(result)
|
70
|
-
offset = result.count.digits.count
|
71
|
-
|
72
60
|
result
|
73
|
-
.map
|
74
|
-
pad = " " * (offset - (index + 1).digits.count)
|
75
|
-
|
76
|
-
title_line = "#{index + 1}. #{pad}#{title}"
|
77
|
-
indent = " #{" " * offset}"
|
78
|
-
|
79
|
-
"#{title_line}\n#{indent}#{length_to_s(length)}"
|
80
|
-
}
|
61
|
+
.map { |title, length| "#{title}\n #{length_to_s(length)}" }
|
81
62
|
.join("\n")
|
82
63
|
end
|
83
64
|
|
@@ -85,36 +66,24 @@ module Reading
|
|
85
66
|
# @param result [Array]
|
86
67
|
# @return [String]
|
87
68
|
private_class_method def self.top_or_bottom_speeds_string(result)
|
88
|
-
offset = result.count.digits.count
|
89
|
-
|
90
69
|
result
|
91
|
-
.map
|
92
|
-
amount = length_to_s(hash[:amount]
|
70
|
+
.map { |title, hash|
|
71
|
+
amount = length_to_s(hash[:amount])
|
93
72
|
days = "#{hash[:days]} #{hash[:days] == 1 ? "day" : "days"}"
|
94
|
-
|
73
|
+
speed = "#{amount} in #{days}"
|
95
74
|
|
96
|
-
|
97
|
-
indent = " #{" " * offset}"
|
98
|
-
colored_speed = color("#{amount} in #{days}")
|
99
|
-
|
100
|
-
"#{title_line}\n#{indent}#{colored_speed}"
|
75
|
+
"#{title}\n #{speed}"
|
101
76
|
}
|
102
77
|
.join("\n")
|
103
78
|
end
|
104
79
|
|
105
80
|
# Formats a list of top/bottom number results as a string.
|
106
81
|
private_class_method def self.top_or_bottom_numbers_string(result, noun:)
|
107
|
-
offset = result.count.digits.count
|
108
|
-
|
109
82
|
result
|
110
|
-
.map
|
111
|
-
|
112
|
-
|
113
|
-
title_line = "#{index + 1}. #{pad}#{title}"
|
114
|
-
indent = " #{" " * offset}"
|
115
|
-
number_string = color("#{number} #{number == 1 ? noun : "#{noun}s"}")
|
83
|
+
.map { |title, number|
|
84
|
+
number_string = "#{number} #{number == 1 ? noun : "#{noun}s"}"
|
116
85
|
|
117
|
-
"#{
|
86
|
+
"#{title}\n #{number_string}"
|
118
87
|
}
|
119
88
|
.join("\n")
|
120
89
|
end
|
data/lib/reading/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reading
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felipe Vogel
|
@@ -230,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
230
|
- !ruby/object:Gem::Version
|
231
231
|
version: '0'
|
232
232
|
requirements: []
|
233
|
-
rubygems_version: 3.
|
233
|
+
rubygems_version: 3.7.1
|
234
234
|
specification_version: 4
|
235
235
|
summary: Parses a CSV reading log.
|
236
236
|
test_files: []
|