calcpace 1.5.5 → 1.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '04889c91e456d34e17974f8aeafa14bb99c0f44957bb42fdc6cac4a3c1a87bf1'
4
- data.tar.gz: 60b0d8e24f2f10905c8ee8697c892b63efef0fca92fecb42dc86e9cf797b37a9
3
+ metadata.gz: ba8948e4e291f5381e58be3bc289ad8f54aa480a1cef744fc121dbc43449b523
4
+ data.tar.gz: a9e4b3da8526c245adfaed895f460b820d737ca4a3eb805a4e2c9b1510f1a6ca
5
5
  SHA512:
6
- metadata.gz: af863c188a3e74b28211bac8956c5fd1ad7440e9cdb8316f5c329a06daf5387ad81ecdeacced1b0011bfad304b0f8b9607c9deeca63dfba628e04162c718cb1f
7
- data.tar.gz: ba0a4e7fd03714ca47c9a1cb9b1d8081327e2821b9da44730866437e3ba54c74aec43d93ae9f2f75ac69f7fe1672ee4fb62f4c17f015a35a83045583ebe872e4
6
+ metadata.gz: 61cea9354aa1343b9db15e92a1a11c06f053158ed72a48c5d1062e4b7d733888a7a8eaf3b88c7696cb879b7a92d196891c01e85369947dd4dead497b322b69b9
7
+ data.tar.gz: fcf1db5f88479865c65ce64d7c788a3762ea01f39c28f10fa3604e268bce1737589cec4eb8b66f94a10f70a1acb12b7ab73b082990e0bc9a1a0c11d0461159d6
@@ -1,4 +1,6 @@
1
1
  name: Tests
2
+ permissions:
3
+ contents: read
2
4
  on:
3
5
  push:
4
6
  branches: [ "main" ]
data/.rubocop.yml ADDED
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RuboCop configuration for Calcpace gem
4
+ # Following Ruby best practices and style guidelines
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 2.7
8
+ NewCops: enable
9
+ Exclude:
10
+ - 'vendor/**/*'
11
+ - 'tmp/**/*'
12
+ - 'node_modules/**/*'
13
+
14
+ # Prefer double quotes for consistency
15
+ Style/StringLiterals:
16
+ Enabled: false
17
+
18
+ # Allow longer lines in tests
19
+ Layout/LineLength:
20
+ Max: 120
21
+ Exclude:
22
+ - 'test/**/*'
23
+
24
+ # Prefer modern hash syntax
25
+ Style/HashSyntax:
26
+ EnforcedStyle: ruby19
27
+
28
+ # Documentation is important for public APIs
29
+ Style/Documentation:
30
+ Enabled: true
31
+ Exclude:
32
+ - 'test/**/*'
33
+
34
+ # Prefer explicit return in some cases for clarity
35
+ Style/RedundantReturn:
36
+ Enabled: false
37
+
38
+ # Allow compact module/class definitions
39
+ Style/ClassAndModuleChildren:
40
+ Enabled: false
41
+
42
+ # Metrics
43
+ Metrics/MethodLength:
44
+ Max: 20
45
+ Exclude:
46
+ - 'test/**/*'
47
+
48
+ Metrics/AbcSize:
49
+ Max: 20
50
+ Exclude:
51
+ - 'test/**/*'
52
+
53
+ Metrics/BlockLength:
54
+ Exclude:
55
+ - 'test/**/*'
56
+ - '*.gemspec'
57
+
58
+ # Allow both single and double quotes for strings
59
+ Style/StringLiteralsInInterpolation:
60
+ Enabled: false
61
+
62
+ # Naming
63
+ Naming/MethodParameterName:
64
+ MinNameLength: 1
65
+ AllowedNames:
66
+ - _
67
+ - e
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.2
1
+ 3.4.4
data/.tool-versions ADDED
@@ -0,0 +1,2 @@
1
+ nodejs 24.1.0
2
+ ruby 3.4.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.7.0] - Unreleased
9
+
10
+ ### Added
11
+ - RuboCop configuration for code style consistency
12
+ - CHANGELOG.md for tracking project changes
13
+ - Comprehensive YARD documentation for all public methods
14
+ - Race pace calculator for standard distances (5K, 10K, half-marathon, marathon)
15
+ - `race_time` and `race_time_clock` methods for calculating finish times
16
+ - `race_pace` and `race_pace_clock` methods for calculating required paces
17
+ - `list_races` method to see available race distances
18
+ - `UnsupportedUnitError` for better error handling
19
+ - Comprehensive test suite with edge cases and error scenarios
20
+ - Test helper utilities for better test organization
21
+
22
+ ### Changed
23
+ - Improved error messages with more context throughout the gem
24
+ - Enhanced validation for edge cases
25
+ - Better method organization and code structure
26
+ - Optimized `convert_to_seconds` method using case statement
27
+ - Improved error handling in `constant` method with nested rescue
28
+
29
+ ### Fixed
30
+ - Minor code style inconsistencies
31
+ - Typo in README: `converto_to_clocktime` → `convert_to_clocktime`
32
+
33
+ ## [1.6.0] - Previous Release
34
+
35
+ ### Added
36
+ - Custom error classes for better error handling
37
+ - `NonPositiveInputError` for invalid numeric inputs
38
+ - `InvalidTimeFormatError` for invalid time format inputs
39
+
40
+ ### Changed
41
+ - Improved error handling throughout the gem
42
+
43
+ ## [1.5.0] and earlier
44
+
45
+ See git history for changes in earlier versions.
46
+
47
+ [Unreleased]: https://github.com/0jonjo/calcpace/compare/v1.6.0...HEAD
48
+ [1.6.0]: https://github.com/0jonjo/calcpace/releases/tag/v1.6.0
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.5.5&x2=0)](https://badge.fury.io/rb/calcpace)
1
+ # Calcpace [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&r=r&ts=1683906897&type=6e&v=1.7.0&x2=0)](https://badge.fury.io/rb/calcpace)
2
2
 
3
3
  Calcpace is a Ruby gem designed for calculations and conversions related to distance and time. It can calculate velocity, pace, total time, and distance, accepting time in various formats, including HH:MM:SS. The gem supports conversion to 42 different units, including kilometers, miles, meters, and feet. It also provides methods to validate input.
4
4
 
@@ -7,7 +7,7 @@ Calcpace is a Ruby gem designed for calculations and conversions related to dist
7
7
  ### Add to your Gemfile
8
8
 
9
9
  ```ruby
10
- gem 'calcpace', '~> 1.5.5'
10
+ gem 'calcpace', '~> 1.7.0'
11
11
  ```
12
12
 
13
13
  Then run:
@@ -87,15 +87,15 @@ To learn more about BigDecimal, check the [documentation](https://ruby-doc.org/s
87
87
 
88
88
  ### Convert Distances and Velocities
89
89
 
90
- Use the `convert` method to convert a distance or velocity. The first parameter is the value to be converted, and the second parameter is the unit to which the value will be converted. The unit must be a string with the abbreviation of the unit. The gem supports 26 different units, including kilometers, miles, meters, knots, and feet.
90
+ Use the `convert` method to convert a distance or velocity. The first parameter is the value to be converted, and the second parameter is the unit to which the value will be converted. The unit can be a string (e.g. 'km to meters') or a symbol (e.g. :km_to_meters). The gem supports 42 different units, including kilometers, miles, meters, knots, and feet.
91
91
 
92
92
  Here are some examples:
93
93
 
94
94
  ```ruby
95
95
  converter.convert(10, :km_to_meters) # => 1000
96
- converter.convert(10, :mi_to_km) # => 16.0934
96
+ converter.convert(10, 'mi to km') # => 16.0934
97
97
  converter.convert(1, :nautical_mi_to_km) # => 1.852
98
- converter.convert(1, :km_h_to_m_s) # => 0.277778
98
+ converter.convert(1, 'km h to m s') # => 0.277778
99
99
  converter.convert(1, :m_s_to_mi_h) # => 2.23694
100
100
  ```
101
101
 
@@ -118,14 +118,65 @@ converter.convert(1, :m_s_to_mi_h) # => 2.23694
118
118
  | :km_h_to_mi_h | Kilometers per Hour to Miles per Hour |
119
119
  | :mi_h_to_km_h | Miles per Hour to Kilometers per Hour |
120
120
 
121
- You can list all the available units [here](/lib/calcpace/converter.rb), or using `list` methods:
121
+ You can list all the available units [here](/lib/calcpace/converter.rb), or using `list` methods. The return will be a hash with the unit and a description of the unit.
122
122
 
123
123
  ```ruby
124
124
  converter.list_all
125
+ # => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
126
+
125
127
  converter.list_distance
128
+ # => {:km_to_mi=>"KM to MI", :mi_to_km=>"MI to KM", ...}
129
+
126
130
  converter.list_speed
131
+ # => {:m_s_to_km_h=>"M S to KM H", :km_h_to_m_s=>"KM H to M S", ...}
132
+ ```
133
+
134
+ ### Chain Conversions
135
+
136
+ Perform multiple conversions in sequence with the converter chain feature:
137
+
138
+ ```ruby
139
+ calc = Calcpace.new
140
+
141
+ # Convert kilometers to miles to feet in one call
142
+ calc.convert_chain(1, [:km_to_mi, :mi_to_feet])
143
+ # => 3280.84 (1 km = 0.621 mi = 3280.84 feet)
144
+
145
+ # Convert with description for debugging
146
+ calc.convert_chain_with_description(100, [:meters_to_km, :km_to_mi])
147
+ # => { result: 0.0621371, description: "100 → meters_to_km → km_to_mi → 0.0621" }
148
+
149
+ # Speed conversions
150
+ calc.convert_chain(10, [:m_s_to_km_h, :km_h_to_mi_h])
151
+ # => 22.3694 (10 m/s = 36 km/h = 22.37 mi/h)
127
152
  ```
128
153
 
154
+ ### Race Pace Calculator
155
+
156
+ Calcpace includes a race pace calculator for standard race distances (5K, 10K, half-marathon, and marathon):
157
+
158
+ ```ruby
159
+ calc = Calcpace.new
160
+
161
+ # Calculate finish time for a race given a pace
162
+ calc.race_time(300, '5k') # => 1500.0 (5:00/km pace for 5K = 25:00)
163
+ calc.race_time_clock('05:00', 'marathon') # => '03:30:58' (5:00/km pace for marathon)
164
+
165
+ # Calculate required pace for a target finish time
166
+ calc.race_pace('00:30:00', '5k') # => 360.0 (need 6:00/km to finish 5K in 30:00)
167
+ calc.race_pace_clock('04:00:00', 'marathon') # => '00:05:41' (need 5:41/km for 4-hour marathon)
168
+
169
+ # List available race distances
170
+ calc.list_races
171
+ # => { '5k' => 5.0, '10k' => 10.0, 'half_marathon' => 21.0975, 'marathon' => 42.195 }
172
+ ```
173
+
174
+ Supported race distances:
175
+ - `5k` - 5 kilometers
176
+ - `10k` - 10 kilometers
177
+ - `half_marathon` - 21.0975 kilometers
178
+ - `marathon` - 42.195 kilometers
179
+
129
180
  ### Other Useful Methods
130
181
 
131
182
  Calcpace also provides other useful methods:
@@ -134,7 +185,7 @@ Calcpace also provides other useful methods:
134
185
  converter = Calcpace.new
135
186
  converter.convert_to_seconds('01:00:00') # => 3600
136
187
  converter.convert_to_clocktime(3600) # => '01:00:00'
137
- converter.converto_to_clocktime(100000) # => '1 03:46:40'
188
+ converter.convert_to_clocktime(100000) # => '1 03:46:40'
138
189
  converter.check_time('01:00:00') # => nil
139
190
  ```
140
191
 
@@ -151,7 +202,7 @@ For example:
151
202
  begin
152
203
  calculate.pace(945, -1)
153
204
  rescue Calcpace::NonPositiveInputError => e
154
- puts e.message # => "Input must be a positive number"
205
+ puts e.message # => "Distance must be a positive number"
155
206
  end
156
207
 
157
208
  begin
data/calcpace.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'lib/calcpace/version'
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -13,7 +15,8 @@ Gem::Specification.new do |spec|
13
15
  spec.license = 'MIT'
14
16
  spec.required_ruby_version = '>= 2.7.0'
15
17
 
16
- spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md" # TODO: Create a CHANGELOG.md
18
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
19
+ spec.metadata['rubygems_mfa_required'] = 'true'
17
20
 
18
21
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
22
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
@@ -1,9 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Module to calculate time, distance, pace and velocity
4
+ #
5
+ # This module provides methods to perform calculations related to running and pace.
6
+ # All methods that accept numeric inputs validate that values are positive.
7
+ # Methods prefixed with 'checked_' accept time strings in HH:MM:SS or MM:SS format.
8
+ # Methods prefixed with 'clock_' return results in time format (HH:MM:SS).
4
9
  module Calculator
10
+ # Calculates velocity (distance per unit time)
11
+ #
12
+ # @param time [Numeric] time in any unit (e.g., seconds, hours)
13
+ # @param distance [Numeric] distance in any unit (e.g., meters, kilometers)
14
+ # @return [Float] velocity (distance/time)
15
+ # @raise [Calcpace::NonPositiveInputError] if time or distance is not positive
16
+ #
17
+ # @example
18
+ # velocity(3600, 12000) #=> 3.333... (12000 meters / 3600 seconds = 3.33 m/s)
5
19
  def velocity(time, distance)
6
- validate_positive(time, distance)
20
+ validate_positive({ time: time, distance: distance })
7
21
  distance.to_f / time
8
22
  end
9
23
 
@@ -16,8 +30,17 @@ module Calculator
16
30
  convert_to_clocktime(checked_velocity(time, distance))
17
31
  end
18
32
 
33
+ # Calculates pace (time per unit distance)
34
+ #
35
+ # @param time [Numeric] time in any unit (e.g., seconds, minutes)
36
+ # @param distance [Numeric] distance in any unit (e.g., kilometers, miles)
37
+ # @return [Float] pace (time/distance)
38
+ # @raise [Calcpace::NonPositiveInputError] if time or distance is not positive
39
+ #
40
+ # @example
41
+ # pace(3600, 12) #=> 300.0 (3600 seconds / 12 km = 300 seconds/km = 5:00/km)
19
42
  def pace(time, distance)
20
- validate_positive(time, distance)
43
+ validate_positive({ time: time, distance: distance })
21
44
  time.to_f / distance
22
45
  end
23
46
 
@@ -31,7 +54,7 @@ module Calculator
31
54
  end
32
55
 
33
56
  def time(velocity, distance)
34
- validate_positive(velocity, distance)
57
+ validate_positive({ velocity: velocity, distance: distance })
35
58
  velocity * distance
36
59
  end
37
60
 
@@ -45,7 +68,7 @@ module Calculator
45
68
  end
46
69
 
47
70
  def distance(time, velocity)
48
- validate_positive(time, velocity)
71
+ validate_positive({ time: time, velocity: velocity })
49
72
  time.to_f / velocity
50
73
  end
51
74
 
@@ -57,8 +80,8 @@ module Calculator
57
80
 
58
81
  private
59
82
 
60
- def validate_positive(*values)
61
- values.each { |value| check_positive(value) }
83
+ def validate_positive(values)
84
+ values.each { |name, value| check_positive(value, name.capitalize) }
62
85
  end
63
86
 
64
87
  def validate_time(time)
@@ -2,19 +2,51 @@
2
2
 
3
3
  require_relative 'errors'
4
4
 
5
- # Module to check if the input is valid or of the correct type
5
+ # Module to validate input values and formats
6
+ #
7
+ # This module provides validation methods for numeric inputs and time format strings
8
+ # used throughout the Calcpace gem.
6
9
  module Checker
7
- def check_positive(number)
10
+ # Validates that a number is positive (greater than zero)
11
+ #
12
+ # @param number [Numeric] the number to validate
13
+ # @param name [String] the name of the parameter for error messages
14
+ # @raise [Calcpace::NonPositiveInputError] if number is not positive
15
+ # @return [void]
16
+ #
17
+ # @example
18
+ # check_positive(10, 'Distance') #=> nil (valid)
19
+ # check_positive(-5, 'Time') #=> raises NonPositiveInputError
20
+ # check_positive(0, 'Speed') #=> raises NonPositiveInputError
21
+ def check_positive(number, name = 'Input')
8
22
  return if number.is_a?(Numeric) && number.positive?
9
23
 
10
24
  raise Calcpace::NonPositiveInputError,
11
- 'It must be a positive number'
25
+ "#{name} must be a positive number"
12
26
  end
13
27
 
28
+ # Validates that a time string is in the correct format
29
+ #
30
+ # Accepted formats:
31
+ # - HH:MM:SS (hours:minutes:seconds) - e.g., "01:30:45"
32
+ # - MM:SS (minutes:seconds) - e.g., "05:30"
33
+ # - H:MM:SS or M:SS (single digit hours/minutes) - e.g., "1:30:45"
34
+ #
35
+ # @param time_string [String] the time string to validate
36
+ # @raise [Calcpace::InvalidTimeFormatError] if format is invalid
37
+ # @return [void]
38
+ #
39
+ # @example
40
+ # check_time('01:30:45') #=> nil (valid)
41
+ # check_time('5:30') #=> nil (valid)
42
+ # check_time('invalid') #=> raises InvalidTimeFormatError
14
43
  def check_time(time_string)
15
- return if time_string =~ /\A\d{1,2}:\d{2}:\d{2}\z/ ||
16
- time_string =~ /\A\d{1,2}:\d{2}\z/
44
+ # Check if string is valid and matches expected patterns
45
+ return if time_string.is_a?(String) &&
46
+ (time_string =~ /\A\d{1,2}:\d{2}:\d{2}\z/ ||
47
+ time_string =~ /\A\d{1,2}:\d{2}\z/)
17
48
 
18
- raise Calcpace::InvalidTimeFormatError, 'It must be a valid time in the XX:XX:XX or XX:XX format'
49
+ raise Calcpace::InvalidTimeFormatError,
50
+ 'It must be a valid time in the XX:XX:XX or XX:XX format'
19
51
  end
20
52
  end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Module to convert units
3
+ # Module to convert between different units of distance and speed
4
+ #
5
+ # This module provides conversion methods for 42 different unit pairs,
6
+ # including distance units (kilometers, miles, meters, feet, etc.) and
7
+ # speed units (m/s, km/h, mi/h, knots, etc.).
4
8
  module Converter
5
9
  module Distance
6
10
  KM_TO_MI = 0.621371
@@ -50,44 +54,98 @@ module Converter
50
54
  NAUTICAL_MI_H_TO_MI_H = 1.15078
51
55
  end
52
56
 
57
+ # Converts a value from one unit to another
58
+ #
59
+ # @param value [Numeric] the value to convert
60
+ # @param unit [Symbol, String] the conversion unit (e.g., :km_to_mi or 'km to mi')
61
+ # @return [Float] the converted value
62
+ # @raise [Calcpace::NonPositiveInputError] if value is not positive
63
+ # @raise [Calcpace::UnsupportedUnitError] if the unit is not supported
64
+ #
65
+ # @example
66
+ # convert(10, :km_to_mi) #=> 6.21371 (10 km = 6.21 miles)
67
+ # convert(5, 'mi to km') #=> 8.0467 (5 miles = 8.05 km)
53
68
  def convert(value, unit)
54
- check_positive(value)
69
+ check_positive(value, 'Value')
55
70
  unit_constant = constant(unit)
56
71
  value * unit_constant
57
72
  end
58
73
 
74
+ # Converts a time string to total seconds
75
+ #
76
+ # @param time [String] time string in HH:MM:SS or MM:SS format
77
+ # @return [Integer] total seconds
78
+ #
79
+ # @example
80
+ # convert_to_seconds('01:30:00') #=> 5400 (1 hour 30 minutes)
81
+ # convert_to_seconds('05:30') #=> 330 (5 minutes 30 seconds)
59
82
  def convert_to_seconds(time)
60
83
  parts = time.split(':').map(&:to_i)
61
- if parts.length == 2
84
+ case parts.length
85
+ when 2
62
86
  minute, seconds = parts
63
87
  (minute * 60) + seconds
64
- else
88
+ when 3
65
89
  hour, minute, seconds = parts
66
90
  (hour * 3600) + (minute * 60) + seconds
91
+ else
92
+ 0
67
93
  end
68
94
  end
69
95
 
96
+ # Converts seconds to a clocktime string
97
+ #
98
+ # @param seconds [Numeric] total seconds
99
+ # @return [String] time in HH:MM:SS format, or "D HH:MM:SS" for durations over 24 hours
100
+ #
101
+ # @example
102
+ # convert_to_clocktime(3600) #=> '01:00:00' (1 hour)
103
+ # convert_to_clocktime(100000) #=> '1 03:46:40' (1 day, 3 hours, 46 minutes, 40 seconds)
70
104
  def convert_to_clocktime(seconds)
71
105
  days = seconds / 86_400
72
106
  format = days.to_i.positive? ? "#{days} %H:%M:%S" : '%H:%M:%S'
73
107
  Time.at(seconds).utc.strftime(format)
74
108
  end
75
109
 
76
- def constant(symbol)
77
- Distance.const_get(symbol.to_s.upcase)
110
+ # Retrieves the conversion constant for a given unit
111
+ #
112
+ # @param unit [Symbol, String] the unit conversion (e.g., :km_to_mi or 'km to mi')
113
+ # @return [Float] the conversion factor
114
+ # @raise [Calcpace::UnsupportedUnitError] if the unit is not supported
115
+ #
116
+ # @example
117
+ # constant(:km_to_mi) #=> 0.621371
118
+ # constant('km to mi') #=> 0.621371
119
+ def constant(unit)
120
+ unit = format_unit(unit) if unit.is_a?(String)
121
+ Distance.const_get(unit.to_s.upcase)
78
122
  rescue NameError
79
- Speed.const_get(symbol.to_s.upcase)
123
+ begin
124
+ Speed.const_get(unit.to_s.upcase)
125
+ rescue NameError
126
+ raise Calcpace::UnsupportedUnitError, unit
127
+ end
80
128
  end
81
129
 
82
130
  def list_all
83
- (Distance.constants + Speed.constants).map { |c| c.downcase.to_sym }
131
+ format_list(Distance.constants + Speed.constants)
84
132
  end
85
133
 
86
134
  def list_speed
87
- Speed.constants.map { |c| c.downcase.to_sym }
135
+ format_list(Speed.constants)
88
136
  end
89
137
 
90
138
  def list_distance
91
- Distance.constants.map { |c| c.downcase.to_sym }
139
+ format_list(Distance.constants)
140
+ end
141
+
142
+ private
143
+
144
+ def format_unit(unit)
145
+ unit.downcase.gsub(' ', '_').to_sym
146
+ end
147
+
148
+ def format_list(constants)
149
+ constants.to_h { |c| [c.downcase.to_sym, c.to_s.gsub('_', ' ').gsub(' TO ', ' to ')] }
92
150
  end
93
151
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module for chaining multiple unit conversions
4
+ #
5
+ # This module allows performing multiple conversions in sequence,
6
+ # which is useful for complex unit transformations.
7
+ module ConverterChain
8
+ # Performs a chain of conversions on a value
9
+ #
10
+ # @param value [Numeric] the initial value to convert
11
+ # @param conversions [Array<Symbol, String>] array of conversion units
12
+ # @return [Float] the final converted value
13
+ # @raise [Calcpace::NonPositiveInputError] if value is not positive
14
+ # @raise [Calcpace::UnsupportedUnitError] if any conversion unit is not supported
15
+ #
16
+ # @example Convert kilometers to miles to feet
17
+ # convert_chain(1, [:km_to_mi, :mi_to_feet])
18
+ # #=> 3280.84 (1 km = 0.621 mi = 3280.84 feet)
19
+ #
20
+ # @example Using string format
21
+ # convert_chain(100, ['meters to km', 'km to mi'])
22
+ # #=> 0.0621371 (100 m = 0.1 km = 0.0621 mi)
23
+ def convert_chain(value, conversions)
24
+ check_positive(value, 'Value')
25
+ conversions.reduce(value) do |result, conversion|
26
+ result * constant(conversion)
27
+ end
28
+ end
29
+
30
+ # Performs a chain of conversions and returns a description
31
+ #
32
+ # @param value [Numeric] the initial value to convert
33
+ # @param conversions [Array<Symbol, String>] array of conversion units
34
+ # @return [Hash] hash with :result and :description keys
35
+ #
36
+ # @example
37
+ # convert_chain_with_description(1, [:km_to_mi, :mi_to_feet])
38
+ # #=> { result: 3280.84, description: "1.0 → km_to_mi → mi_to_feet → 3280.84" }
39
+ def convert_chain_with_description(value, conversions)
40
+ initial_value = value
41
+ result = convert_chain(value, conversions)
42
+ conversion_names = conversions.map(&:to_s).join(' → ')
43
+ description = "#{initial_value} → #{conversion_names} → #{result.round(4)}"
44
+
45
+ { result: result, description: description }
46
+ end
47
+ end
@@ -1,9 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Calcpace custom error classes for better error handling
3
4
  class Calcpace
5
+ # Base error class for all Calcpace errors
4
6
  class Error < StandardError; end
5
7
 
6
- class InvalidTimeFormatError < Error; end
8
+ # Raised when time string format is invalid
9
+ # Expected formats: HH:MM:SS or MM:SS
10
+ class InvalidTimeFormatError < Error
11
+ def initialize(msg = 'Invalid time format. Expected HH:MM:SS or MM:SS format.')
12
+ super
13
+ end
14
+ end
7
15
 
8
- class NonPositiveInputError < Error; end
16
+ # Raised when a numeric input is not positive (zero or negative)
17
+ class NonPositiveInputError < Error
18
+ def initialize(msg = 'Input must be a positive number.')
19
+ super
20
+ end
21
+ end
22
+
23
+ # Raised when an unsupported unit conversion is requested
24
+ class UnsupportedUnitError < Error
25
+ def initialize(unit = nil)
26
+ msg = unit ? "Unsupported unit conversion: #{unit}" : 'Unsupported unit conversion'
27
+ super(msg)
28
+ end
29
+ end
9
30
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Module for calculating race times and paces for standard distances
4
+ #
5
+ # This module provides convenience methods for calculating finish times
6
+ # and paces for common race distances like 5K, 10K, half-marathon, and marathon.
7
+ module PaceCalculator
8
+ # Standard race distances in kilometers
9
+ RACE_DISTANCES = {
10
+ '5k' => 5.0,
11
+ '10k' => 10.0,
12
+ 'half_marathon' => 21.0975,
13
+ 'marathon' => 42.195
14
+ }.freeze
15
+
16
+ # Calculates the finish time for a race given a pace per kilometer
17
+ #
18
+ # @param pace_per_km [Numeric, String] pace in seconds per km or time string (MM:SS)
19
+ # @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
20
+ # @return [Float] total time in seconds
21
+ # @raise [ArgumentError] if race distance is not recognized
22
+ #
23
+ # @example
24
+ # race_time(300, :5k) #=> 1500.0 (5:00/km pace for 5K = 25:00)
25
+ # race_time('05:00', :marathon) #=> 12658.5 (5:00/km pace for marathon = 3:30:58)
26
+ def race_time(pace_per_km, race)
27
+ distance = race_distance(race)
28
+ pace_seconds = pace_per_km.is_a?(String) ? convert_to_seconds(pace_per_km) : pace_per_km
29
+ check_positive(pace_seconds, 'Pace')
30
+ distance * pace_seconds
31
+ end
32
+
33
+ # Calculates the finish time for a race and returns it as a clock time string
34
+ #
35
+ # @param pace_per_km [Numeric, String] pace in seconds per km or time string (MM:SS)
36
+ # @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
37
+ # @return [String] finish time in HH:MM:SS format
38
+ #
39
+ # @example
40
+ # race_time_clock('05:00', :marathon) #=> '03:30:58'
41
+ # race_time_clock(300, :half_marathon) #=> '01:45:17'
42
+ def race_time_clock(pace_per_km, race)
43
+ convert_to_clocktime(race_time(pace_per_km, race))
44
+ end
45
+
46
+ # Calculates the required pace per kilometer to finish a race in a target time
47
+ #
48
+ # @param target_time [Numeric, String] target finish time in seconds or time string (HH:MM:SS)
49
+ # @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
50
+ # @return [Float] required pace in seconds per kilometer
51
+ #
52
+ # @example
53
+ # race_pace('03:30:00', :marathon) #=> 297.48... (4:57/km to finish in 3:30)
54
+ # race_pace(1800, :5k) #=> 360.0 (6:00/km to finish in 30:00)
55
+ def race_pace(target_time, race)
56
+ distance = race_distance(race)
57
+ time_seconds = target_time.is_a?(String) ? convert_to_seconds(target_time) : target_time
58
+ check_positive(time_seconds, 'Time')
59
+ time_seconds / distance
60
+ end
61
+
62
+ # Calculates the required pace and returns it as a clock time string
63
+ #
64
+ # @param target_time [Numeric, String] target finish time in seconds or time string (HH:MM:SS)
65
+ # @param race [String, Symbol] race distance ('5k', '10k', 'half_marathon', 'marathon')
66
+ # @return [String] required pace in MM:SS format
67
+ #
68
+ # @example
69
+ # race_pace_clock('03:30:00', :marathon) #=> '00:04:57'
70
+ def race_pace_clock(target_time, race)
71
+ convert_to_clocktime(race_pace(target_time, race))
72
+ end
73
+
74
+ # Lists all available standard race distances
75
+ #
76
+ # @return [Hash] hash of race names and distances in kilometers
77
+ #
78
+ # @example
79
+ # list_races #=> { '5k' => 5.0, '10k' => 10.0, ... }
80
+ def list_races
81
+ RACE_DISTANCES.dup
82
+ end
83
+
84
+ private
85
+
86
+ # Gets the distance for a standard race
87
+ #
88
+ # @param race [String, Symbol] race name
89
+ # @return [Float] distance in kilometers
90
+ # @raise [ArgumentError] if race is not recognized
91
+ def race_distance(race)
92
+ key = race.to_s.downcase
93
+ RACE_DISTANCES.fetch(key) do
94
+ raise ArgumentError,
95
+ "Unknown race: #{race}. Available races: #{RACE_DISTANCES.keys.join(', ')}"
96
+ end
97
+ end
98
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Calcpace
4
- VERSION = "1.5.5"
4
+ VERSION = '1.7.0'
5
5
  end
data/lib/calcpace.rb CHANGED
@@ -3,13 +3,39 @@
3
3
  require_relative 'calcpace/calculator'
4
4
  require_relative 'calcpace/checker'
5
5
  require_relative 'calcpace/converter'
6
+ require_relative 'calcpace/converter_chain'
6
7
  require_relative 'calcpace/errors'
8
+ require_relative 'calcpace/pace_calculator'
7
9
 
8
- # Main class to calculate velocity, pace, time, distance and velocity
10
+ # Calcpace - A Ruby gem for pace, distance, and time calculations
11
+ #
12
+ # Calcpace provides methods to calculate and convert values related to pace,
13
+ # distance, time, and speed. It supports various time formats and 42 different
14
+ # unit conversions.
15
+ #
16
+ # @example Basic velocity calculation
17
+ # calc = Calcpace.new
18
+ # calc.velocity(3600, 12000) #=> 3.333... (12000m in 3600s = 3.33 m/s)
19
+ #
20
+ # @example Time string calculations
21
+ # calc = Calcpace.new
22
+ # calc.checked_pace('01:00:00', 10) #=> 360.0 (1 hour / 10 km = 6:00/km pace)
23
+ # calc.clock_pace('01:00:00', 10) #=> '00:06:00'
24
+ #
25
+ # @example Unit conversions
26
+ # calc = Calcpace.new
27
+ # calc.convert(10, :km_to_mi) #=> 6.21371 (10 km = 6.21 miles)
28
+ # calc.convert(1, 'm_s_to_km_h') #=> 3.6 (1 m/s = 3.6 km/h)
29
+ #
30
+ # @see https://github.com/0jonjo/calcpace
9
31
  class Calcpace
10
32
  include Calculator
11
33
  include Checker
12
34
  include Converter
35
+ include ConverterChain
36
+ include PaceCalculator
13
37
 
14
- def initialize; end
38
+ # Creates a new Calcpace instance
39
+ #
40
+ # @return [Calcpace] a new instance ready to perform calculations
15
41
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calcpace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.5
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - João Gilberto Saraiva
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: Calcpace provides methods to calculate and convert values related to
13
13
  pace, distance, time, and speed. It supports various time formats and unit conversions.
@@ -19,7 +19,10 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - ".github/workflows/tests.yml"
21
21
  - ".gitignore"
22
+ - ".rubocop.yml"
22
23
  - ".ruby-version"
24
+ - ".tool-versions"
25
+ - CHANGELOG.md
23
26
  - Gemfile
24
27
  - Gemfile.lock
25
28
  - README.md
@@ -29,7 +32,9 @@ files:
29
32
  - lib/calcpace/calculator.rb
30
33
  - lib/calcpace/checker.rb
31
34
  - lib/calcpace/converter.rb
35
+ - lib/calcpace/converter_chain.rb
32
36
  - lib/calcpace/errors.rb
37
+ - lib/calcpace/pace_calculator.rb
33
38
  - lib/calcpace/version.rb
34
39
  homepage: https://github.com/0jonjo/calcpace
35
40
  licenses:
@@ -37,6 +42,7 @@ licenses:
37
42
  metadata:
38
43
  source_code_uri: https://github.com/0jonjo/calcpace
39
44
  changelog_uri: https://github.com/0jonjo/calcpace/blob/main/CHANGELOG.md
45
+ rubygems_mfa_required: 'true'
40
46
  rdoc_options: []
41
47
  require_paths:
42
48
  - lib
@@ -51,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
57
  - !ruby/object:Gem::Version
52
58
  version: '0'
53
59
  requirements: []
54
- rubygems_version: 3.6.2
60
+ rubygems_version: 3.6.7
55
61
  specification_version: 4
56
62
  summary: A Ruby gem for pace, distance, and time calculations.
57
63
  test_files: []