diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7a5b662..b6f6a0c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,4 +16,4 @@ jobs: with: bundler-cache: true - name: Run tests - run: bundle exec rspec + run: bundle exec rake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f4ef3fc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/calcpace-* \ No newline at end of file diff --git a/.rspec b/.rspec deleted file mode 100644 index 5be63fc..0000000 --- a/.rspec +++ /dev/null @@ -1,2 +0,0 @@ ---require spec_helper ---format documentation diff --git a/Gemfile b/Gemfile index 22dd5b9..9e0a6ac 100644 --- a/Gemfile +++ b/Gemfile @@ -4,4 +4,9 @@ source 'https://rubygems.org' ruby '3.0.5' -gem 'rspec', '~> 3.10' +gem 'minitest', '~> 5.14' +gem 'rake', '~> 13.0' +gem 'rake-compiler', '~> 1.0' +gem 'rdoc', '~> 6.2' +gem 'rubocop', '~> 0.79' +gem 'rubocop-minitest', '~> 0.11' diff --git a/Gemfile.lock b/Gemfile.lock index 911c034..2336e5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,55 @@ GEM remote: https://rubygems.org/ specs: - diff-lcs (1.5.0) - rspec (3.11.0) - rspec-core (~> 3.11.0) - rspec-expectations (~> 3.11.0) - rspec-mocks (~> 3.11.0) - rspec-core (3.11.0) - rspec-support (~> 3.11.0) - rspec-expectations (3.11.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-mocks (3.11.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.11.0) - rspec-support (3.11.0) + ast (2.4.2) + minitest (5.23.1) + parallel (1.24.0) + parser (3.3.1.0) + ast (~> 2.4.1) + racc + psych (5.1.2) + stringio + racc (1.8.0) + rainbow (3.1.1) + rake (13.2.1) + rake-compiler (1.2.7) + rake + rdoc (6.7.0) + psych (>= 4.0.0) + regexp_parser (2.9.2) + rexml (3.2.8) + strscan (>= 3.0.9) + rubocop (0.93.1) + parallel (~> 1.10) + parser (>= 2.7.1.5) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8) + rexml + rubocop-ast (>= 0.6.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-minitest (0.27.0) + rubocop (>= 0.90, < 2.0) + ruby-progressbar (1.13.0) + stringio (3.1.0) + strscan (3.1.0) + unicode-display_width (1.8.0) PLATFORMS ruby DEPENDENCIES - rspec (~> 3.10) + minitest (~> 5.14) + rake (~> 13.0) + rake-compiler (~> 1.0) + rdoc (~> 6.2) + rubocop (~> 0.79) + rubocop-minitest (~> 0.11) RUBY VERSION ruby 3.0.5p211 BUNDLED WITH - 2.4.7 + 2.5.10 diff --git a/README.md b/README.md index 4e87fdb..baa9df8 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,60 @@ -# CALCPACE +# Calcpace [![Gem Version](https://badge.fury.io/rb/calcpace.svg)](https://badge.fury.io/rb/calcpace) -A TDD Ruby study project to calculate the running pace, the predicted running time or distance. It's based on my original [Python Calcpace](https://github.com/0jonjo/calcpace-py). +Calcpace is a Ruby gem designed to assist with calculations related to running and cycling activities. It can calculate pace, total time, and distance, and also convert distances between miles and kilometers. Results are provided in a readable format, with times in HH:MM:SS and distances in X.X format. -## Install +## Installation -### Clone the repository +### Add to your Gemfile -```shell -git clone git@github.com:0jonjo/ruby-calcpace.git -cd ruby-calcpace +```ruby +gem 'calcpace', '~> 0.2.0' ``` -### Install dependencies +Then run bundle install. -Using [Bundler](https://github.com/bundler/bundler) +### Usage -```shell -bundle install -``` - -## Use the calculator +### Calculate Pace -Use main.rb informs the calculation option and the running (jogging) data. +To calculate pace, provide the total time (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). -Choose p to calculate pace and enter running time (HH:MM:SS) and distance (X.X) in kilometers or miles. - -```shell -ruby lib/main.rb p 01:00:00 10 -00:06:00 +```ruby +Calcpace.new.pace('01:00:00', 12) # => "00:05:00" ``` -Choose t to calculate running time and enter pace (HH:MM:SS) and distance (X.X) in kilometers or miles. +### Calculate Total Time + +To calculate total time, provide the pace (in HH:MM:SS format) and distance (in X.X format, representing kilometers or miles). -```shell -ruby lib/main.rb t 00:05:00 12 -01:00:00 +```ruby +Calcpace.new.total_time('00:05:00', 12) # => "01:00:00" ``` -Choose d to calculate distance in kilometers or miles and enter running time (HH:MM:SS) and pace (HH:MM:SS). +### Calculate Distance -```shell -ruby lib/main.rb d 01:30:00 00:05:00 -18.0 +To calculate distance, provide the running time (in HH:MM:SS format) and pace (in HH:MM:SS format). + +```ruby +Calcpace.new.distance('01:30:00', '00:05:00') # => 18.0 ``` -Choose c to convert distance. +### Convert Distances -Enter km and the distance in kilometers to convert to miles. +To convert distances, provide the distance and the unit of measurement (either 'km' for kilometers or 'mi' for miles). -```shell -ruby lib/main.rb c km 10 -6.21 +```ruby +Calcpace.new.convert_distance(10, 'km') # => 6.21 +Calcpace.new.convert_distance(10, 'mi') # => 16.09 ``` -Enter mi and the distance in miles to convert to kilometers. +### Error Handling -```shell -ruby lib/main.rb c mi 10 -16.09 -``` +If the provided parameters do not meet the expected formats, Calcpace will raise an error detailing the issue. The gem always returns data using the same distance unit (kilometers or miles) that was used as input. + +## Contributing + +We welcome contributions to Calcpace! To contribute, you can clone this repository and submit a pull request. Please ensure that your code adheres to our style guidelines and includes tests where appropriate. -If some of the parameters are not in the standard that the program uses, it informs what the problem is in an error. +## License -It's important to input data using the same standard (kilometers or miles) to obtain the correct result. +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile.rb b/Rakefile.rb new file mode 100644 index 0000000..13214e6 --- /dev/null +++ b/Rakefile.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "minitest/test_task" + +Minitest::TestTask.create(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.warning = false +end + +task :default => :test diff --git a/calcpace.gemspec b/calcpace.gemspec new file mode 100644 index 0000000..9c8e50a --- /dev/null +++ b/calcpace.gemspec @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +Gem::Specification.new do |s| + s.name = 'calcpace' + s.version = '0.2.0' + s.summary = 'Calcpace: calculate time and distances for activities such as running and cycling.' + s.description = 'Calculate pace, total time, distance and easily convert distances (kilometers and miles) for activities like running and cycling. Get readable results in HH:MM:SS or X.X format for time and distance calculations.' + s.authors = ['Joao Gilberto Saraiva'] + s.email = 'joaogilberto@tuta.io' + s.files = ['lib/calcpace.rb', 'lib/calcpace/calculator.rb', 'lib/calcpace/checker.rb', 'lib/calcpace/converter.rb'] + s.test_files = ['test/calcpace/test_calculator.rb', 'test/calcpace/test_checker.rb', 'test/calcpace/test_converter.rb'] + s.add_development_dependency 'minitest', '~> 5.14' + s.add_development_dependency 'rake', '~> 13.0' + s.add_development_dependency 'rake-compiler', '~> 1.0' + s.add_development_dependency 'rdoc', '~> 6.2' + s.add_development_dependency 'rubocop', '~> 0.79' + s.add_development_dependency 'rubocop-minitest', '~> 0.11' + s.required_ruby_version = '>= 2.7.0' + s.post_install_message = "It's time to grab your sneakers or hop on your bike and start exercising! Thank you for installing Calcpace!" + s.metadata = { 'source_code_uri' => 'https://github.com/0jonjo/calcpace' } + s.homepage = 'https://github.com/0jonjo/calcpace' + s.license = 'MIT' +end diff --git a/lib/calcpace.rb b/lib/calcpace.rb new file mode 100644 index 0000000..c7dfcda --- /dev/null +++ b/lib/calcpace.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative 'calcpace/calculator' +require_relative 'calcpace/checker' +require_relative 'calcpace/converter' + +class Calcpace + include Calculator + include Checker + include Converter + + def initialize; end +end diff --git a/lib/calcpace/calculator.rb b/lib/calcpace/calculator.rb new file mode 100644 index 0000000..518b402 --- /dev/null +++ b/lib/calcpace/calculator.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Calculator + def pace(time, distance) + check_digits(time, distance) + convert_to_clocktime(convert_to_seconds(time) / distance.to_f) + end + + def total_time(pace, distance) + check_digits(pace, distance) + convert_to_clocktime(convert_to_seconds(pace) * distance.to_f) + end + + def distance(time, pace) + check_digits_time(time) + check_digits_time(pace) + convert_to_seconds(time).to_f / convert_to_seconds(pace).round(2) + end + + def convert_distance(distance, unit) + check_digits_distance(distance) + check_unit(unit) + convert(distance, unit) + end +end diff --git a/lib/calcpace/checker.rb b/lib/calcpace/checker.rb new file mode 100644 index 0000000..6eb740d --- /dev/null +++ b/lib/calcpace/checker.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module Checker + def check_digits(time, distance) + check_digits_time(time) + check_digits_distance(distance) + end + + def check_digits_distance(distance) + raise 'It must be a X.X positive number' unless distance.positive? + end + + def check_digits_time(time_string) + raise 'It must be a XX:XX:XX time' unless time_string =~ /\d{0,2}(:)*?\d{1,2}(:)\d{1,2}/ + end + + def check_unit(unit) + raise 'It must be km or mi' unless %w[km mi].include?(unit) + end +end diff --git a/lib/calcpace/converter.rb b/lib/calcpace/converter.rb new file mode 100644 index 0000000..c9c2974 --- /dev/null +++ b/lib/calcpace/converter.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Converter + def convert_to_seconds(time) + hour, minute, seconds = time.split(':').map(&:to_i) + (hour * 3600) + (minute * 60) + seconds + end + + def convert_to_clocktime(seconds) + Time.at(seconds).utc.strftime('%H:%M:%S') + end + + # tem que chamar as checagens de digitos antes de chamar o convert_distance + def convert(distance, unit) + case unit + when 'km' + (distance * 0.621371).round(2) + when 'mi' + (distance * 1.60934).round(2) + end + end +end diff --git a/lib/main.rb b/lib/main.rb deleted file mode 100644 index 65aed92..0000000 --- a/lib/main.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -require_relative 'run_calculate' -require_relative 'run_check' -require_relative 'run_convert' - -checks_argv(ARGV) - -puts calculate(ARGV[0][0].downcase, (ARGV[1]), ARGV[2]) diff --git a/lib/run_calculate.rb b/lib/run_calculate.rb deleted file mode 100644 index 7e52bd8..0000000 --- a/lib/run_calculate.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -def calculate(calc_modal, time_or_pace, distance_or_pace) - case calc_modal - when 'p' - convert_to_clocktime(convert_to_seconds(time_or_pace) / distance_or_pace.to_f) - when 't' - convert_to_clocktime(convert_to_seconds(time_or_pace) * distance_or_pace.to_f) - when 'd' - convert_to_seconds(time_or_pace).to_f / convert_to_seconds(distance_or_pace).round(2) - when 'c' - convert_distance(time_or_pace, distance_or_pace.to_f).round(2) - end -end - -def convert_distance(unit, distance) - case unit - when 'km' - distance * 0.621371 - when 'mi' - distance * 1.60934 - end -end diff --git a/lib/run_check.rb b/lib/run_check.rb deleted file mode 100644 index ae0dc50..0000000 --- a/lib/run_check.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -def check_argv_length(argv) - raise 'It must be exactly three arguments' if argv.length != 3 -end - -def check_argv_modal(argv_modal) - raise ArgumentError, 'You have to choose p (pace), t (time run), d (distance) or c (convert).' unless %w[p d - t c].include?(argv_modal) -end - -def checks_argv(argv) - check_argv_length(argv) - check_argv_modal(argv[0][0].downcase) - check_digits_time(argv[1]) unless argv[0][0].downcase == 'c' - check_digits_distance(argv[2]) - raise_negative(argv[2]) -end - -def check_digits_distance(distance_string) - raise 'It must be a X.X number' unless distance_string =~ /\d/ - - distance_string.to_f -end - -def check_digits_time(time_string) - raise 'It must be a XX:XX:XX time' unless time_string =~ /\d{0,2}(:|-)*?\d{1,2}(:|-)\d{1,2}/ - - time_string.gsub('-', ':') -end - -def raise_negative(number) - raise "It can't be negative." if number.to_i.negative? -end diff --git a/lib/run_convert.rb b/lib/run_convert.rb deleted file mode 100644 index f7cb2b0..0000000 --- a/lib/run_convert.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -def convert_to_seconds(time) - hour, minute, seconds = time.split(':').map(&:to_i) - (hour * 3600) + (minute * 60) + seconds -end - -def convert_to_clocktime(seconds) - Time.at(seconds).utc.strftime('%H:%M:%S') -end diff --git a/spec/run_calculate_spec.rb b/spec/run_calculate_spec.rb deleted file mode 100644 index 9a55f4e..0000000 --- a/spec/run_calculate_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require 'run_calculate' -require 'run_check' -require 'run_convert' - -describe 'run_calculate' do - context 'result' do - it 'calculates correct pace' do - expect(calculate('p', '01:00:00', 10)).to eq('00:06:00') - end - - it 'calculates correct time run' do - expect(calculate('t', '00:05:00', 12)).to eq('01:00:00') - end - - it 'calculates correct distance' do - expect(calculate('d', '01:30:00', '00:05:00')).to eq(18.0) - end - - it 'calculates correct convert to km' do - expect(calculate('c', 'km', '10')).to eq(6.21) - end - - it 'calculates correct convert to mi' do - expect(calculate('c', 'mi', '10')).to eq(16.09) - end - end - - context 'result' do - it 'convert correct to km' do - expect(convert_distance('km', 1)).to eq(0.621371) - end - - it 'convert correct to miles' do - expect(convert_distance('mi', 1)).to eq(1.60934) - end - end -end diff --git a/spec/run_check_spec.rb b/spec/run_check_spec.rb deleted file mode 100644 index 333a816..0000000 --- a/spec/run_check_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -require 'run_check' - -describe 'run_check' do - context 'ARGV' do - it ':argv length < 3' do - expect { check_argv_length([0, 0]) }.to raise_error('It must be exactly three arguments') - end - - it 'check_argv_modal pass correct input' do - expect { check_argv_modal('p') }.not_to raise_error - end - - it 'check_argv_modal raise error when incorrect input' do - expect do - check_argv_modal('z') - end.to raise_error('You have to choose p (pace), t (time run), d (distance) or c (convert).') - end - end - - context 'Check negative digits' do - it ':raise_negative when receive negative number' do - expect { raise_negative(-1) }.to raise_error("It can't be negative.") - end - - it 'not raise_negative when receive positive number' do - expect { raise_negative(1) }.not_to raise_error - end - - it 'not raise_negative when receive positive number' do - expect { raise_negative('00:05:00'.to_i) }.not_to raise_error - end - end - - context 'Get distance and time format' do - it ':check_digits_distance true X.X' do - expect(check_digits_distance('7.2')).to eq(7.2) - end - - it ':check_digits_distance true X' do - expect(check_digits_distance('7')).to eq(7) - end - - it ":check_digits_distance == ''" do - expect(check_digits_distance('0')).to eq(0.0) - end - - it ':check_digits_distance not a number' do - expect { check_digits_distance('test') }.to raise_error('It must be a X.X number') - end - - it ':check_digits_time true XX:XX:XX' do - expect(check_digits_time('01:02:03')).to eq('01:02:03') - end - - it ':check_digits_time true X:X:X' do - expect(check_digits_time('1:2:3')).to eq('1:2:3') - end - - it ':check_digits_time true XX:XX' do - expect(check_digits_time('01:02')).to eq('01:02') - end - - it ':check_digits_time true XX-XX' do - expect(check_digits_time('01-02')).to eq('01:02') - end - - it ':check_digits_time true blanket' do - expect { check_digits_time('') }.to raise_error('It must be a XX:XX:XX time') - end - - it 'check_digits_time true XX:XX' do - expect { check_digits_time('AA:AA') }.to raise_error('It must be a XX:XX:XX time') - expect { check_digits_time('test') }.to raise_error('It must be a XX:XX:XX time') - end - - it ':check_digits_time AA:AA letter raise error ' do - expect { check_digits_time('AA:AA') }.to raise_error('It must be a XX:XX:XX time') - end - end -end diff --git a/spec/run_convert_spec.rb b/spec/run_convert_spec.rb deleted file mode 100644 index 74c0190..0000000 --- a/spec/run_convert_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require 'run_convert' - -describe 'run_convert' do - context 'Convert tests' do - it ':convert_to_seconds' do - expect(convert_to_seconds('01:11:02')).to eq(4262) - end - - it ':convert_to_clocktime' do - expect(convert_to_clocktime(4262)).to eq('01:11:02') - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index dbef3f5..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -RSpec.configure do |config| - config.expect_with :rspec do |expectations| - expectations.include_chain_clauses_in_custom_matcher_descriptions = true - end - config.mock_with :rspec do |mocks| - mocks.verify_partial_doubles = true - end - - config.shared_context_metadata_behavior = :apply_to_host_groups -end diff --git a/test/calcpace/test_calculator.rb b/test/calcpace/test_calculator.rb new file mode 100644 index 0000000..16eb974 --- /dev/null +++ b/test/calcpace/test_calculator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative '../../lib/calcpace' + +class TestCalculator < Minitest::Test + def setup + @checker = Calcpace.new + end + + def test_pace + assert_raises(RuntimeError) { @checker.pace('', 10) } + assert_raises(RuntimeError) { @checker.pace('invalid', 10) } + assert_raises(RuntimeError) { @checker.pace('00:00:00', 0) } + assert_raises(RuntimeError) { @checker.pace('00:00:00', -1) } + assert_equal '00:06:00', @checker.pace('01:00:00', 10) + end + + def test_total_time + assert_raises(RuntimeError) { @checker.total_time('', 10) } + assert_raises(RuntimeError) { @checker.total_time('invalid', 10) } + assert_raises(RuntimeError) { @checker.total_time('00:00:00', 0) } + assert_raises(RuntimeError) { @checker.total_time('00:00:00', -1) } + assert_equal '01:00:00', @checker.total_time('00:05:00', 12) + end + + def test_distance + assert_raises(RuntimeError) { @checker.distance('', '00:05:00') } + assert_raises(RuntimeError) { @checker.distance('01:00:00', '') } + assert_equal 18.0, @checker.distance('01:30:00', '00:05:00') + end + + def test_convert_distance + assert_raises(RuntimeError) { @checker.convert_distance(10, 'invalid') } + assert_raises(RuntimeError) { @checker.convert_distance(-1, 'km') } + assert_raises(RuntimeError) { @checker.convert_distance(0, 'km') } + assert_equal 6.21, @checker.convert_distance(10, 'km') + assert_equal 16.09, @checker.convert_distance(10, 'mi') + end +end diff --git a/test/calcpace/test_checker.rb b/test/calcpace/test_checker.rb new file mode 100644 index 0000000..3c32e71 --- /dev/null +++ b/test/calcpace/test_checker.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative '../../lib/calcpace' + +class TestChecker < Minitest::Test + def setup + @checker = Calcpace.new + end + + def test_check_digits_distance + assert_raises(RuntimeError) { @checker.check_digits_distance(-1) } + assert_raises(RuntimeError) { @checker.check_digits_distance(0) } + assert_nil @checker.check_digits_distance(1) + end + + def test_check_digits_time + assert_raises(RuntimeError) { @checker.check_digits_time('') } + assert_nil @checker.check_digits_time('00:00:00') + end + + def test_check_digits + assert_raises(RuntimeError) { @checker.check_digits('00:00:00', 0) } + assert_raises(RuntimeError) { @checker.check_digits('', 1) } + assert_nil @checker.check_digits('00:00:00', 1) + end + + def test_convert + assert_equal 6.21, @checker.convert(10, 'km') + assert_equal 16.09, @checker.convert(10, 'mi') + end +end diff --git a/test/calcpace/test_converter.rb b/test/calcpace/test_converter.rb new file mode 100644 index 0000000..c0b5664 --- /dev/null +++ b/test/calcpace/test_converter.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative '../../lib/calcpace' + +class TestConverter < Minitest::Test + def setup + @checker = Calcpace.new + end + + def test_convert_to_seconds + assert_equal 4262, @checker.convert_to_seconds('01:11:02') + end + + def test_convert_to_clocktime + assert_equal '01:11:02', @checker.convert_to_clocktime(4262) + end + + def test_convert_distance_km + assert_equal 6.21, @checker.convert_distance(10, 'km') + assert_equal 16.09, @checker.convert_distance(10, 'mi') + assert_raises(RuntimeError) { @checker.convert_distance(10, 'invalid') } + end +end