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.
- 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
|