reading 0.8.0 → 0.9.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/bin/reading +95 -10
  3. data/lib/reading/config.rb +27 -5
  4. data/lib/reading/errors.rb +4 -1
  5. data/lib/reading/item/time_length.rb +60 -23
  6. data/lib/reading/item/view.rb +14 -19
  7. data/lib/reading/item.rb +324 -54
  8. data/lib/reading/parsing/attributes/attribute.rb +0 -7
  9. data/lib/reading/parsing/attributes/experiences/dates_and_head_transformer.rb +17 -13
  10. data/lib/reading/parsing/attributes/experiences/history_transformer.rb +172 -60
  11. data/lib/reading/parsing/attributes/experiences/spans_validator.rb +19 -20
  12. data/lib/reading/parsing/attributes/experiences.rb +5 -5
  13. data/lib/reading/parsing/attributes/shared.rb +17 -7
  14. data/lib/reading/parsing/attributes/variants.rb +9 -6
  15. data/lib/reading/parsing/csv.rb +38 -35
  16. data/lib/reading/parsing/parser.rb +23 -24
  17. data/lib/reading/parsing/rows/blank.rb +23 -0
  18. data/lib/reading/parsing/rows/comment.rb +6 -7
  19. data/lib/reading/parsing/rows/compact_planned.rb +9 -9
  20. data/lib/reading/parsing/rows/compact_planned_columns/head.rb +2 -2
  21. data/lib/reading/parsing/rows/custom_config.rb +42 -0
  22. data/lib/reading/parsing/rows/regular.rb +15 -14
  23. data/lib/reading/parsing/rows/regular_columns/length.rb +8 -8
  24. data/lib/reading/parsing/rows/regular_columns/sources.rb +16 -10
  25. data/lib/reading/parsing/rows/regular_columns/start_dates.rb +5 -1
  26. data/lib/reading/parsing/transformer.rb +13 -17
  27. data/lib/reading/stats/filter.rb +738 -0
  28. data/lib/reading/stats/grouping.rb +257 -0
  29. data/lib/reading/stats/operation.rb +345 -0
  30. data/lib/reading/stats/query.rb +37 -0
  31. data/lib/reading/stats/terminal_result_formatters.rb +91 -0
  32. data/lib/reading/util/exclude.rb +12 -0
  33. data/lib/reading/util/hash_array_deep_fetch.rb +1 -23
  34. data/lib/reading/util/hash_to_data.rb +2 -2
  35. data/lib/reading/version.rb +1 -1
  36. data/lib/reading.rb +36 -21
  37. metadata +28 -24
  38. data/bin/readingfile +0 -31
  39. data/lib/reading/util/string_remove.rb +0 -28
  40. data/lib/reading/util/string_truncate.rb +0 -22
@@ -0,0 +1,91 @@
1
+ require 'pastel'
2
+
3
+ module Reading
4
+ module Stats
5
+ module ResultFormatters
6
+ TERMINAL = {
7
+ average_length: ->(result) { length_to_s(result) },
8
+ average_amount: ->(result) { length_to_s(result) },
9
+ :'average_daily-amount' => ->(result) { "#{length_to_s(result)} per day" },
10
+ total_item: ->(result) {
11
+ if result.zero?
12
+ PASTEL.bright_black("none")
13
+ else
14
+ color("#{result} #{result == 1 ? "item" : "items"}")
15
+ end
16
+ },
17
+ total_amount: ->(result) { length_to_s(result) },
18
+ top_length: ->(result) { top_or_bottom_lengths(result) },
19
+ top_amount: ->(result) { top_or_bottom_lengths(result) },
20
+ top_speed: ->(result) { top_or_bottom_speeds(result) },
21
+ bottom_length: ->(result) { top_or_bottom_lengths(result) },
22
+ botom_amount: ->(result) { top_or_bottom_lengths(result) },
23
+ bottom_speed: ->(result) { top_or_bottom_speeds(result) },
24
+ }
25
+
26
+ private
27
+
28
+ PASTEL = Pastel.new
29
+
30
+ # Applies a terminal color.
31
+ # @param string [String]
32
+ # @return [String]
33
+ private_class_method def self.color(string)
34
+ PASTEL.bright_blue(string)
35
+ end
36
+
37
+ # Converts a length/amount (pages or time) into a string.
38
+ # @param length [Numeric, Reading::Item::TimeLength]
39
+ # @param color [Boolean] whether a terminal color should be applied.
40
+ # @return [String]
41
+ private_class_method def self.length_to_s(length, color: true)
42
+ if length.is_a?(Numeric)
43
+ length_string = "#{length.round} pages"
44
+ else
45
+ length_string = length.to_s
46
+ end
47
+
48
+ color ? color(length_string) : length_string
49
+ end
50
+
51
+ # Formats a list of top/bottom length results as a string.
52
+ # @param result [Array]
53
+ # @return [String]
54
+ private_class_method def self.top_or_bottom_lengths(result)
55
+ offset = result.count.digits.count
56
+
57
+ result
58
+ .map.with_index { |(title, length), index|
59
+ pad = ' ' * (offset - (index + 1).digits.count)
60
+
61
+ title_line = "#{index + 1}. #{pad}#{title}"
62
+ indent = " #{' ' * offset}"
63
+
64
+ "#{title_line}\n#{indent}#{length_to_s(length)}"
65
+ }
66
+ .join("\n")
67
+ end
68
+
69
+ # Formats a list of top/bottom speed results as a string.
70
+ # @param result [Array]
71
+ # @return [String]
72
+ private_class_method def self.top_or_bottom_speeds(result)
73
+ offset = result.count.digits.count
74
+
75
+ result
76
+ .map.with_index { |(title, hash), index|
77
+ amount = length_to_s(hash[:amount], color: false)
78
+ days = "#{hash[:days]} #{hash[:days] == 1 ? "day" : "days"}"
79
+ pad = ' ' * (offset - (index + 1).digits.count)
80
+
81
+ title_line = "#{index + 1}. #{pad}#{title}"
82
+ indent = " #{' ' * offset}"
83
+ colored_speed = color("#{amount} in #{days}")
84
+
85
+ "#{title_line}\n#{indent}#{colored_speed}"
86
+ }
87
+ .join("\n")
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,12 @@
1
+ # Copied from activesupport/lib/active_support/core_ext/enumerable.rb
2
+ module Enumerable
3
+ def exclude?(object)
4
+ !include?(object)
5
+ end
6
+ end
7
+
8
+ class String
9
+ def exclude?(object)
10
+ !include?(object)
11
+ end
12
+ end
@@ -1,31 +1,9 @@
1
1
  module Reading
2
2
  module Util
3
- class FetchDepthExceededError < StandardError
4
- end
5
-
6
3
  # Similar to Array#dig and Hash#dig but raises an error for not found elements.
7
- #
8
- # More flexible but slightly slower alternative:
9
- # keys.reduce(self) { |a, e| a.fetch(e) }
10
- #
11
- # See performance comparisons:
12
- # https://fpsvogel.com/posts/2022/ruby-hash-dot-syntax-deep-fetch
13
4
  module HashArrayDeepFetch
14
5
  def deep_fetch(*keys)
15
- case keys.length
16
- when 1
17
- fetch(keys[0])
18
- when 2
19
- fetch(keys[0]).fetch(keys[1])
20
- when 3
21
- fetch(keys[0]).fetch(keys[1]).fetch(keys[2])
22
- when 4
23
- fetch(keys[0]).fetch(keys[1]).fetch(keys[2]).fetch(keys[3])
24
- when 5
25
- fetch(keys[0]).fetch(keys[1]).fetch(keys[2]).fetch(keys[3]).fetch(keys[4])
26
- else
27
- raise FetchDepthExceededError, "#deep_fetch can't fetch that deep!"
28
- end
6
+ keys.reduce(self) { |a, e| a.fetch(e) }
29
7
  end
30
8
 
31
9
  refine Hash do
@@ -12,9 +12,9 @@ module Reading
12
12
  if v.is_a?(Hash)
13
13
  v.to_data
14
14
  elsif v.is_a?(Array) && v.all? { |el| el.is_a?(Hash) }
15
- v.map(&:to_data)
15
+ v.map(&:to_data).freeze
16
16
  else
17
- v
17
+ v.freeze
18
18
  end
19
19
  }.values
20
20
 
@@ -1,3 +1,3 @@
1
1
  module Reading
2
- VERSION = "0.8.0"
2
+ VERSION = '0.9.1'
3
3
  end
data/lib/reading.rb CHANGED
@@ -1,26 +1,36 @@
1
- require_relative "reading/parsing/csv"
2
- require_relative "reading/filter"
3
- require_relative "reading/config"
4
- require_relative "reading/item/time_length.rb"
1
+ Dir[File.join(__dir__, 'reading', 'util', '*.rb')].each { |file| require file }
2
+ require_relative 'reading/errors'
3
+ require_relative 'reading/config'
4
+ require_relative 'reading/parsing/csv'
5
+ require_relative 'reading/filter'
6
+ require_relative 'reading/stats/query'
7
+ require_relative 'reading/item/time_length.rb'
5
8
 
6
9
  # The gem's public API. See https://github.com/fpsvogel/reading#usage
7
10
  #
8
11
  # Architectural overview:
9
12
  #
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/*
13
+ # (filtered (stats input*
14
+ # (CSV input) (Items) Items) and Items) (results)
15
+ # | Λ | Λ | Λ
16
+ # V | V | V |
17
+ # ::parse | ::filter | ::stats |
18
+ # | | | | | |
19
+ # | | | | | |
20
+ # | | | | | |
21
+ # Parsing::CSV ---------> Item Filter Stats::Query
22
+ # / \ / \ / | \
23
+ # / \ Item::View Item::TimeLength / | \
24
+ # / \ / | \
25
+ # Parsing::Parser Parsing::Transformer Stats::Filter | Stats::Operation
26
+ # | | Stats::Grouping
27
+ # parsing/rows/* parsing/attributes/*
28
+ # * Stats input is either from the
29
+ # command line (via the `reading`
30
+ # command) or provided via Ruby
31
+ # code that uses this gem.
32
+ # Results likewise go either to
33
+ # stdout or to the gem user.
24
34
  #
25
35
  module Reading
26
36
  # Parses a CSV file or string. See Parsing::CSV#initialize and #parse for details.
@@ -34,10 +44,15 @@ module Reading
34
44
  Filter.by(...)
35
45
  end
36
46
 
37
- # The default config.
47
+ # Returns statistics on Items. See Stats::Query#initialize and #result for details.
48
+ def self.stats(...)
49
+ query = Stats::Query.new(...)
50
+ query.result
51
+ end
52
+
38
53
  # @return [Hash]
39
- def self.default_config
40
- Config.new.hash
54
+ def self.config
55
+ Config.hash
41
56
  end
42
57
 
43
58
  # A shortcut for getting a time from a string.
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.8.0
4
+ version: 0.9.1
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-04-12 00:00:00.000000000 Z
11
+ date: 2024-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pastel
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: amazing_print
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: debug
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -94,20 +108,6 @@ dependencies:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
110
  version: '1.0'
97
- - !ruby/object:Gem::Dependency
98
- name: amazing_print
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.4'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.4'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rubycritic
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -127,12 +127,10 @@ email:
127
127
  - fps.vogel@gmail.com
128
128
  executables:
129
129
  - reading
130
- - readingfile
131
130
  extensions: []
132
131
  extra_rdoc_files: []
133
132
  files:
134
133
  - bin/reading
135
- - bin/readingfile
136
134
  - lib/reading.rb
137
135
  - lib/reading/config.rb
138
136
  - lib/reading/errors.rb
@@ -154,10 +152,12 @@ files:
154
152
  - lib/reading/parsing/attributes/variants.rb
155
153
  - lib/reading/parsing/csv.rb
156
154
  - lib/reading/parsing/parser.rb
155
+ - lib/reading/parsing/rows/blank.rb
157
156
  - lib/reading/parsing/rows/column.rb
158
157
  - lib/reading/parsing/rows/comment.rb
159
158
  - lib/reading/parsing/rows/compact_planned.rb
160
159
  - lib/reading/parsing/rows/compact_planned_columns/head.rb
160
+ - lib/reading/parsing/rows/custom_config.rb
161
161
  - lib/reading/parsing/rows/regular.rb
162
162
  - lib/reading/parsing/rows/regular_columns/end_dates.rb
163
163
  - lib/reading/parsing/rows/regular_columns/genres.rb
@@ -169,14 +169,18 @@ files:
169
169
  - lib/reading/parsing/rows/regular_columns/sources.rb
170
170
  - lib/reading/parsing/rows/regular_columns/start_dates.rb
171
171
  - lib/reading/parsing/transformer.rb
172
+ - lib/reading/stats/filter.rb
173
+ - lib/reading/stats/grouping.rb
174
+ - lib/reading/stats/operation.rb
175
+ - lib/reading/stats/query.rb
176
+ - lib/reading/stats/terminal_result_formatters.rb
172
177
  - lib/reading/util/blank.rb
178
+ - lib/reading/util/exclude.rb
173
179
  - lib/reading/util/hash_array_deep_fetch.rb
174
180
  - lib/reading/util/hash_compact_by_template.rb
175
181
  - lib/reading/util/hash_deep_merge.rb
176
182
  - lib/reading/util/hash_to_data.rb
177
183
  - lib/reading/util/numeric_to_i_if_whole.rb
178
- - lib/reading/util/string_remove.rb
179
- - lib/reading/util/string_truncate.rb
180
184
  - lib/reading/version.rb
181
185
  homepage: https://github.com/fpsvogel/reading
182
186
  licenses:
@@ -185,23 +189,23 @@ metadata:
185
189
  allowed_push_host: https://rubygems.org
186
190
  homepage_uri: https://github.com/fpsvogel/reading
187
191
  source_code_uri: https://github.com/fpsvogel/reading
188
- changelog_uri: https://github.com/fpsvogel/reading/blob/master/CHANGELOG.md
192
+ changelog_uri: https://github.com/fpsvogel/reading/blob/main/CHANGELOG.md
189
193
  post_install_message:
190
194
  rdoc_options: []
191
195
  require_paths:
192
196
  - lib
193
197
  required_ruby_version: !ruby/object:Gem::Requirement
194
198
  requirements:
195
- - - ">="
199
+ - - "~>"
196
200
  - !ruby/object:Gem::Version
197
- version: 3.0.0
201
+ version: 3.3.0
198
202
  required_rubygems_version: !ruby/object:Gem::Requirement
199
203
  requirements:
200
204
  - - ">="
201
205
  - !ruby/object:Gem::Version
202
206
  version: '0'
203
207
  requirements: []
204
- rubygems_version: 3.4.9
208
+ rubygems_version: 3.5.15
205
209
  signing_key:
206
210
  specification_version: 4
207
211
  summary: Parses a CSV reading log.
data/bin/readingfile DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # A script that provides a quick way to see the output of a CSV file.
4
- #
5
- # Usage:
6
- # Run on the command line:
7
- # reading "<file path>" "<optional comma-separated names of enabled columns>"
8
- #
9
- # Examples:
10
- # reading '/home/felipe/reading.csv'
11
- # reading '/home/felipe/reading.csv' 'head, sources'
12
-
13
-
14
- require_relative "../lib/reading"
15
- require "amazing_print"
16
- require "debug"
17
-
18
- path = ARGV[0]
19
- unless path
20
- raise ArgumentError, "CSV path argument required, such as '/home/felipe/reading.csv'"
21
- end
22
-
23
- config = {}
24
- if ARGV[1]
25
- enabled_columns = ARGV[1].split(",").map(&:strip).map(&:to_sym)
26
- config = { enabled_columns: }
27
- end
28
-
29
- items = Reading.parse(path, config:, hash_output: true)
30
-
31
- ap items
@@ -1,28 +0,0 @@
1
- module Reading
2
- module Util
3
- # Shortcuts for String#sub and String#gsub when replacing with an empty string.
4
- module StringRemove
5
- refine String do
6
- def remove(pattern)
7
- sub(pattern, EMPTY_STRING)
8
- end
9
-
10
- def remove!(pattern)
11
- sub!(pattern, EMPTY_STRING)
12
- end
13
-
14
- def remove_all(pattern)
15
- gsub(pattern, EMPTY_STRING)
16
- end
17
-
18
- def remove_all!(pattern)
19
- gsub!(pattern, EMPTY_STRING)
20
- end
21
- end
22
-
23
- private
24
-
25
- EMPTY_STRING = "".freeze
26
- end
27
- end
28
- end
@@ -1,22 +0,0 @@
1
- module Reading
2
- module Util
3
- # Shortens the String to a given length.
4
- module StringTruncate
5
- refine String do
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
14
- end
15
- end
16
-
17
- private
18
-
19
- ELLIPSIS = "...".freeze
20
- end
21
- end
22
- end