runby_pace 0.2.50.111 → 0.2.55

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +13 -5
  2. data/.travis.yml +1 -5
  3. data/Gemfile +0 -4
  4. data/README.md +5 -16
  5. data/Rakefile +6 -40
  6. data/bin/_guard-core +16 -17
  7. data/bin/guard +16 -17
  8. data/lib/runby_pace.rb +1 -4
  9. data/lib/runby_pace/{pace_calculator.rb → pace_data.rb} +13 -29
  10. data/lib/runby_pace/pace_range.rb +9 -27
  11. data/lib/runby_pace/pace_time.rb +110 -0
  12. data/lib/runby_pace/run_type.rb +4 -12
  13. data/lib/runby_pace/run_types/all_run_types.template +4 -6
  14. data/lib/runby_pace/run_types/easy_run.rb +10 -31
  15. data/lib/runby_pace/run_types/find_divisor.rb +17 -13
  16. data/lib/runby_pace/run_types/long_run.rb +10 -32
  17. data/lib/runby_pace/version.rb +2 -17
  18. data/runby_pace.gemspec +5 -4
  19. metadata +18 -41
  20. data/.rubocop.yml +0 -10
  21. data/bin/runbypace +0 -15
  22. data/lib/runby_pace/cli/cli.rb +0 -127
  23. data/lib/runby_pace/cli/config.rb +0 -82
  24. data/lib/runby_pace/distance.rb +0 -135
  25. data/lib/runby_pace/distance_unit.rb +0 -89
  26. data/lib/runby_pace/golden_pace_set.rb +0 -50
  27. data/lib/runby_pace/pace.rb +0 -152
  28. data/lib/runby_pace/run_math.rb +0 -14
  29. data/lib/runby_pace/run_types/distance_run.rb +0 -55
  30. data/lib/runby_pace/run_types/fast_tempo_run.rb +0 -23
  31. data/lib/runby_pace/run_types/five_kilometer_race_run.rb +0 -22
  32. data/lib/runby_pace/run_types/mile_race_run.rb +0 -24
  33. data/lib/runby_pace/run_types/slow_tempo_run.rb +0 -22
  34. data/lib/runby_pace/run_types/tempo_run.rb +0 -54
  35. data/lib/runby_pace/run_types/ten_kilometer_race_run.rb +0 -23
  36. data/lib/runby_pace/runby_range.rb +0 -22
  37. data/lib/runby_pace/runby_time.rb +0 -138
  38. data/lib/runby_pace/runby_time_parser.rb +0 -80
  39. data/lib/runby_pace/speed.rb +0 -97
  40. data/lib/runby_pace/speed_range.rb +0 -30
  41. data/lib/runby_pace/utility/parameter_sanitizer.rb +0 -29
  42. data/lib/runby_pace/version.seed +0 -5
  43. data/misc/runbypace_logo.png +0 -0
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- # Base class for ranges of Runby data, e.g. PaceRange, SpeedRange, ...
5
- class RunbyRange
6
- attr_reader :fast, :slow
7
-
8
- def initialize
9
- @fast = nil
10
- @slow = nil
11
- raise 'RunbyRange is a base class for PaceRange and SpeedRange. Instantiate one of them instead.'
12
- end
13
-
14
- def to_s(format: :short)
15
- if @fast == @slow
16
- @fast.to_s(format: format)
17
- else
18
- "#{@fast}-#{@slow}"
19
- end
20
- end
21
- end
22
- end
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- # Represents a human-readable time in the format MM:ss
5
- class RunbyTime
6
- include Comparable
7
-
8
- attr_reader :time_s, :hours_part, :minutes_part, :seconds_part
9
-
10
- def self.new(time)
11
- return time if time.is_a? RunbyTime
12
- return RunbyTime.parse time if time.is_a?(String) || time.is_a?(Symbol)
13
- return from_minutes(time) if time.is_a? Numeric
14
- super
15
- end
16
-
17
- def initialize(time)
18
- init_from_parts time if time.is_a? RunbyTimeParser::TimeParts
19
- freeze
20
- end
21
-
22
- # @param [numeric] total_seconds
23
- def self.from_seconds(total_seconds)
24
- hours = total_seconds.abs.to_i / 60 / 60
25
- minutes = (total_seconds.abs.to_i / 60) % 60
26
- seconds = total_seconds.abs.to_i % 60
27
- if hours.positive?
28
- RunbyTime.new format('%d:%02d:%02d', hours, minutes, seconds)
29
- else
30
- RunbyTime.new format('%02d:%02d', minutes, seconds)
31
- end
32
- end
33
-
34
- # @param [numeric] total_minutes
35
- def self.from_minutes(total_minutes)
36
- from_seconds(total_minutes * 60.0)
37
- end
38
-
39
- # @param [numeric] total_hours
40
- def self.from_hours(total_hours)
41
- from_seconds(total_hours * 60.0 * 60.0)
42
- end
43
-
44
- def self.parse(str)
45
- RunbyTimeParser.parse str
46
- end
47
-
48
- def self.try_parse(str, is_five_k = false)
49
- time, error_message, warning_message = nil
50
- begin
51
- time = parse str
52
- rescue StandardError => ex
53
- error_message = "#{ex.message} (#{str})"
54
- end
55
- warning_message = check_5k_sanity(time) if !time.nil? && is_five_k
56
- { time: time, error: error_message, warning: warning_message }
57
- end
58
-
59
- def self.check_5k_sanity(time)
60
- return unless time.is_a? RunbyTime
61
- return '5K times of less than 14:00 are unlikely' if time.minutes_part < 14
62
- return '5K times of greater than 42:00 are not fully supported' if time.total_seconds > (42 * 60)
63
- end
64
-
65
- def to_s
66
- @time_s
67
- end
68
-
69
- def total_hours
70
- @hours_part + (@minutes_part / 60.0) + (@seconds_part / 60.0 / 60.0)
71
- end
72
-
73
- def total_seconds
74
- @hours_part * 60 * 60 + @minutes_part * 60 + @seconds_part
75
- end
76
-
77
- def total_minutes
78
- @hours_part * 60 + @minutes_part + (@seconds_part / 60.0)
79
- end
80
-
81
- # @param [RunbyTime] other
82
- def -(other)
83
- raise "Cannot subtract #{other.class} from a Runby::RunbyTime" unless other.is_a?(RunbyTime)
84
- RunbyTime.from_seconds(total_seconds - other.total_seconds)
85
- end
86
-
87
- # @param [RunbyTime] other
88
- def +(other)
89
- raise "Cannot add Runby::RunbyTime to a #{other.class}" unless other.is_a?(RunbyTime)
90
- RunbyTime.from_seconds(total_seconds + other.total_seconds)
91
- end
92
-
93
- # @param [Numeric] other
94
- # @return [RunbyTime]
95
- def *(other)
96
- raise "Cannot multiply Runby::RunbyTime with a #{other.class}" unless other.is_a?(Numeric)
97
- RunbyTime.from_minutes(total_minutes * other)
98
- end
99
-
100
- # @param [RunbyTime, Numeric] other
101
- # @return [Numeric, RunbyTime]
102
- def /(other)
103
- raise "Cannot divide Runby::RunbyTime by #{other.class}" unless other.is_a?(RunbyTime) || other.is_a?(Numeric)
104
- case other
105
- when RunbyTime
106
- total_seconds / other.total_seconds
107
- when Numeric
108
- RunbyTime.from_seconds(total_seconds / other)
109
- end
110
- end
111
-
112
- def <=>(other)
113
- raise "Unable to compare Runby::RunbyTime to #{other.class}(#{other})" unless [RunbyTime, String].include? other.class
114
- if other.is_a? RunbyTime
115
- total_seconds <=> other.total_seconds
116
- elsif other.is_a? String
117
- return 0 if @time_s == other
118
- total_seconds <=> RunbyTime.parse(other).total_seconds
119
- end
120
- end
121
-
122
- def almost_equals?(other_time, tolerance_time = '00:01')
123
- other_time = RunbyTime.new(other_time) if other_time.is_a?(String)
124
- tolerance = RunbyTime.new(tolerance_time)
125
- self >= (other_time - tolerance) && self <= (other_time + tolerance)
126
- end
127
-
128
- private
129
-
130
- # @param [RunbyTimeParser::TimeParts] parts
131
- def init_from_parts(parts)
132
- @time_s = parts.format
133
- @hours_part = parts[:hours]
134
- @minutes_part = parts[:minutes]
135
- @seconds_part = parts[:seconds]
136
- end
137
- end
138
- end
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- # Helper class which parses strings and returns new RunbyTime(s)
5
- class RunbyTimeParser
6
- def self.parse(str)
7
- time = str.to_s.strip.chomp
8
- if time_string?(time)
9
- parts = TimeParts.new(time.split(':').reverse)
10
- elsif integer?(time)
11
- parts = extract_minutes_from_integer time
12
- elsif decimal?(time)
13
- parts = extract_minutes_from_decimal time
14
- else
15
- raise 'Invalid time format'
16
- end
17
- RunbyTime.new(parts)
18
- end
19
-
20
- def self.decimal?(str)
21
- str.match?(/^\d+[,. ]\d+$/)
22
- end
23
-
24
- def self.integer?(str)
25
- str.match?(/^\d+$/)
26
- end
27
-
28
- def self.time_string?(str)
29
- str.match?(/^\d?\d(:\d\d)+$/)
30
- end
31
-
32
- def self.extract_minutes_from_decimal(decimal_str)
33
- decimal_parts = decimal_str.split(/[,. ]/)
34
- minutes = decimal_parts[0].to_i
35
- seconds = (decimal_parts[1].to_i / 10.0 * 60).to_i
36
- TimeParts.new([seconds, minutes])
37
- end
38
-
39
- def self.extract_minutes_from_integer(integer_str)
40
- TimeParts.new([0, integer_str.to_i])
41
- end
42
-
43
- # Encapsulates the parts of a time string
44
- class TimeParts
45
- def initialize(parts_array)
46
- @keys = { seconds: 0, minutes: 1, hours: 2 }
47
- @parts = Array.new(@keys.count, 0)
48
- Range.new(0, parts_array.count - 1).each { |i| @parts[i] = parts_array[i] }
49
- validate
50
- freeze
51
- end
52
-
53
- def [](key)
54
- i = @keys[key]
55
- @parts[i].to_i
56
- end
57
-
58
- def format
59
- time_f = +''
60
- @parts.reverse_each do |part|
61
- time_f << ':' << part.to_s.rjust(2, '0')
62
- end
63
- # Remove leading ':'
64
- time_f.slice!(0)
65
- # Remove leading '00:00...'
66
- time_f.sub!(/^(?:00:)+(\d\d:\d\d)/, '\1') if time_f.length > 5
67
- # If the time looks like 00:48, only show 0:48
68
- time_f.slice!(0) if time_f.slice(0) == '0'
69
- time_f
70
- end
71
-
72
- def validate
73
- raise 'Hours must be less than 24' if self[:hours] > 23
74
- raise 'Minutes must be less than 60 if hours are supplied' if self[:hours].positive? && self[:minutes] > 59
75
- raise 'Minutes must be less than 99 if no hours are supplied' if self[:hours].zero? && self[:minutes] > 99
76
- raise 'Seconds must be less than 60' if self[:seconds] > 59
77
- end
78
- end
79
- end
80
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- # Represents a speed consisting of a distance and a unit of time in which that distance was covered
5
- class Speed
6
- include Comparable
7
-
8
- attr_reader :distance
9
-
10
- def self.new(distance_or_multiplier, units = :km)
11
- return distance_or_multiplier if distance_or_multiplier.is_a? Speed
12
- if distance_or_multiplier.is_a? String
13
- parsed_speed = Speed.try_parse(distance_or_multiplier)
14
- return parsed_speed[:speed] unless parsed_speed[:error]
15
- end
16
- super
17
- end
18
-
19
- def initialize(distance_or_multiplier, units = :km)
20
- case distance_or_multiplier
21
- when Distance
22
- init_from_distance distance_or_multiplier
23
- when String
24
- # Already tried to parse it as a Speed string. Try parsing it as a Distance string.
25
- init_from_distance_string distance_or_multiplier
26
- when Numeric
27
- init_from_multiplier(distance_or_multiplier, units)
28
- else
29
- raise "Unable to initialize Runby::Speed from #{distance_or_multiplier}"
30
- end
31
- freeze
32
- end
33
-
34
- def to_s(format: :short)
35
- distance = @distance.to_s(format: format)
36
- case format
37
- when :short then "#{distance}/ph"
38
- when :long then "#{distance} per hour"
39
- else raise "Invalid string format #{format}"
40
- end
41
- end
42
-
43
- def as_pace
44
- time = Runby::RunbyTime.from_minutes(60.0 / @distance.multiplier)
45
- Runby::Pace.new(time, @distance.uom)
46
- end
47
-
48
- # @param [String] str is either a long-form speed such as "7.5 miles per hour" or a short-form speed like "7.5mi/ph"
49
- def self.parse(str)
50
- str = str.to_s.strip.chomp
51
- match = str.match(%r{^(?<distance>\d+(?:\.\d+)? ?[A-Za-z]+)(?: per hour|\/ph)$})
52
- raise "Invalid speed format (#{str})" unless match
53
- distance = Runby::Distance.new(match[:distance])
54
- Speed.new distance
55
- end
56
-
57
- def self.try_parse(str)
58
- speed = nil
59
- error_message = nil
60
- warning_message = nil
61
- begin
62
- speed = Speed.parse str
63
- rescue StandardError => ex
64
- error_message = "#{ex.message} (#{str})"
65
- end
66
- { speed: speed, error: error_message, warning: warning_message }
67
- end
68
-
69
- def <=>(other)
70
- raise "Unable to compare Runby::Speed to #{other.class}(#{other})" unless [Speed, String].include? other.class
71
- if other.is_a? String
72
- return 0 if to_s == other || to_s(format: :long) == other
73
- self <=> try_parse(other)[:speed]
74
- end
75
- @distance <=> other.distance
76
- end
77
-
78
- private
79
-
80
- def init_from_distance(distance)
81
- @distance = distance
82
- end
83
-
84
- def init_from_multiplier(multiplier, uom)
85
- @distance = Distance.new(uom, multiplier)
86
- end
87
-
88
- def init_from_distance_string(str)
89
- results = Distance.try_parse(str)
90
- unless results[:error]
91
- @distance = results[:distance]
92
- return
93
- end
94
- raise "'#{str}' is not recognized as a Speed or a Distance"
95
- end
96
- end
97
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'runby_range'
4
-
5
- module Runby
6
- # Represents a range of speeds, from fast to slow.
7
- class SpeedRange < RunbyRange
8
- def initialize(fast, slow)
9
- raise "Invalid fast speed value: #{fast}" unless fast.is_a?(Numeric) || fast.is_a?(Speed)
10
- raise "Invalid slow speed value: #{slow}" unless slow.is_a?(Numeric) || slow.is_a?(Speed)
11
- @fast = Runby::Speed.new(fast)
12
- @slow = Runby::Speed.new(slow)
13
- freeze
14
- end
15
-
16
- def as_pace_range
17
- Runby::PaceRange.new @fast.as_pace, @slow.as_pace
18
- end
19
-
20
- def to_s(format: :short)
21
- if @fast == @slow
22
- @fast.to_s(format: format)
23
- else
24
- fast_multiplier = format('%g', @fast.distance.multiplier.round(2))
25
- slow_multiplier = format('%g', @slow.distance.multiplier.round(2))
26
- @fast.to_s(format: format).sub(fast_multiplier.to_s, "#{fast_multiplier}-#{slow_multiplier}")
27
- end
28
- end
29
- end
30
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- module Utility
5
- # Helps sanitize method parameters. (See RSpec documentation for examples)
6
- class ParameterSanitizer
7
- attr_reader :parameter
8
-
9
- def initialize(parameter)
10
- @parameter = parameter
11
- end
12
-
13
- def self.sanitize(parameter)
14
- ParameterSanitizer.new parameter
15
- end
16
-
17
- def as(type)
18
- return @parameter if @parameter.is_a?(type)
19
- raise "Unable to sanitize parameter of type #{type}. Missing 'parse' method." unless type.respond_to? :parse
20
- type.parse(@parameter)
21
- end
22
- end
23
- end
24
-
25
- # Just a shortcut method
26
- def self.sanitize(parameter)
27
- Runby::Utility::ParameterSanitizer.sanitize(parameter)
28
- end
29
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Runby
4
- GENERATED_VERSION = '__VERSION__'
5
- end
Binary file