runby_pace 0.2.50.111 → 0.2.55
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 +13 -5
- data/.travis.yml +1 -5
- data/Gemfile +0 -4
- data/README.md +5 -16
- data/Rakefile +6 -40
- data/bin/_guard-core +16 -17
- data/bin/guard +16 -17
- data/lib/runby_pace.rb +1 -4
- data/lib/runby_pace/{pace_calculator.rb → pace_data.rb} +13 -29
- data/lib/runby_pace/pace_range.rb +9 -27
- data/lib/runby_pace/pace_time.rb +110 -0
- data/lib/runby_pace/run_type.rb +4 -12
- data/lib/runby_pace/run_types/all_run_types.template +4 -6
- data/lib/runby_pace/run_types/easy_run.rb +10 -31
- data/lib/runby_pace/run_types/find_divisor.rb +17 -13
- data/lib/runby_pace/run_types/long_run.rb +10 -32
- data/lib/runby_pace/version.rb +2 -17
- data/runby_pace.gemspec +5 -4
- metadata +18 -41
- data/.rubocop.yml +0 -10
- data/bin/runbypace +0 -15
- data/lib/runby_pace/cli/cli.rb +0 -127
- data/lib/runby_pace/cli/config.rb +0 -82
- data/lib/runby_pace/distance.rb +0 -135
- data/lib/runby_pace/distance_unit.rb +0 -89
- data/lib/runby_pace/golden_pace_set.rb +0 -50
- data/lib/runby_pace/pace.rb +0 -152
- data/lib/runby_pace/run_math.rb +0 -14
- data/lib/runby_pace/run_types/distance_run.rb +0 -55
- data/lib/runby_pace/run_types/fast_tempo_run.rb +0 -23
- data/lib/runby_pace/run_types/five_kilometer_race_run.rb +0 -22
- data/lib/runby_pace/run_types/mile_race_run.rb +0 -24
- data/lib/runby_pace/run_types/slow_tempo_run.rb +0 -22
- data/lib/runby_pace/run_types/tempo_run.rb +0 -54
- data/lib/runby_pace/run_types/ten_kilometer_race_run.rb +0 -23
- data/lib/runby_pace/runby_range.rb +0 -22
- data/lib/runby_pace/runby_time.rb +0 -138
- data/lib/runby_pace/runby_time_parser.rb +0 -80
- data/lib/runby_pace/speed.rb +0 -97
- data/lib/runby_pace/speed_range.rb +0 -30
- data/lib/runby_pace/utility/parameter_sanitizer.rb +0 -29
- data/lib/runby_pace/version.seed +0 -5
- 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
|
data/lib/runby_pace/speed.rb
DELETED
@@ -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
|
data/lib/runby_pace/version.seed
DELETED
data/misc/runbypace_logo.png
DELETED
Binary file
|