From b2ce894c40d8ef9f93b6eee01d9ccc4de0cb1d54 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:21:05 +0100 Subject: [PATCH 01/16] remove travis config --- .travis.yml | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6c7cfb3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -sudo: false -language: ruby -cache: bundler -rvm: - - 2.6.1 -before_install: gem install bundler -v 1.17.2 From ccbe27e909dd9a9082a3c6a0a9fe252ccf7beced Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:22:09 +0100 Subject: [PATCH 02/16] Update gem version --- lib/slotty/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slotty/version.rb b/lib/slotty/version.rb index a86bf75..19c30a4 100644 --- a/lib/slotty/version.rb +++ b/lib/slotty/version.rb @@ -1,3 +1,3 @@ module Slotty - VERSION = "1.0.0" + VERSION = "2.0.0" end From 79cbded8e097834bcd3df9887e15f5f7d0df705e Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:22:25 +0100 Subject: [PATCH 03/16] Upgrade gems & ruby version --- .tool-versions | 2 +- Gemfile.lock | 37 +++++++++++++++++++------------------ slotty.gemspec | 6 +++--- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.tool-versions b/.tool-versions index 0531904..3294aed 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -ruby 2.5.0 +ruby 3.3.0 diff --git a/Gemfile.lock b/Gemfile.lock index af3d6d6..d687dd3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,35 +1,36 @@ PATH remote: . specs: - slotty (1.0.0) + slotty (2.0.0) GEM remote: https://rubygems.org/ specs: - diff-lcs (1.3) - rake (12.3.3) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.1) + diff-lcs (1.5.1) + rake (13.2.1) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.3) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) PLATFORMS + arm64-darwin-23 ruby DEPENDENCIES - bundler (~> 1.17) - rake (~> 12.3.3) - rspec (~> 3.0) + bundler (~> 2.5.9) + rake (~> 13.2.1) + rspec (~> 3.13) slotty! BUNDLED WITH - 1.17.3 + 2.5.9 diff --git a/slotty.gemspec b/slotty.gemspec index 7b56648..7ae5d6c 100644 --- a/slotty.gemspec +++ b/slotty.gemspec @@ -23,7 +23,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_development_dependency "bundler", "~> 1.17" - spec.add_development_dependency "rake", "~> 12.3.3" - spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "bundler", "~> 2.5.9" + spec.add_development_dependency "rake", "~> 13.2.1" + spec.add_development_dependency "rspec", "~> 3.13" end From 097d1026635da67c810186e89696a30efd7c17cc Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:30:32 +0100 Subject: [PATCH 04/16] Fix positional argument error with ruby upgrade https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ --- spec/slotty_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/slotty_spec.rb b/spec/slotty_spec.rb index 9cfb5d7..03a45a0 100644 --- a/spec/slotty_spec.rb +++ b/spec/slotty_spec.rb @@ -24,7 +24,7 @@ }, ] - expect(Slotty.get_slots(attributes)).to eq(expected_slots) + expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end it "gets relevant slots without excluded times" do @@ -50,7 +50,7 @@ }, ] - expect(Slotty.get_slots(attributes)).to eq(expected_slots) + expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end it "gets relevant slots without excluded times x2" do @@ -117,7 +117,7 @@ }, ] - expect(Slotty.get_slots(attributes)).to eq(expected_slots) + expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end it "gets no slots" do @@ -129,7 +129,7 @@ expected_slots = [] - expect(Slotty.get_slots(attributes)).to eq(expected_slots) + expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end it "gets one slot" do @@ -145,15 +145,15 @@ time: "08:00 AM" }] - expect(Slotty.get_slots(attributes)).to eq(expected_slots) + expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end it "expects invalid range to raise error" do - expect { Slotty.get_slots({ for_range: 1, slot_length_mins: 3, interval_mins: 3 }) }.to raise_error(Slotty::InvalidDateError, "for_value must be type of Range") - expect { Slotty.get_slots({ for_range: (Time.now..(Time.now + 60 * 60)), slot_length_mins: "", interval_mins: 3 }) }.to raise_error(Slotty::InvalidSlotLengthError, "slot_length_mins must be an integer") - expect { Slotty.get_slots({ for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: "" }) }.to raise_error(Slotty::InvalidIntervalError, "interval_mins must be an integer") - expect { Slotty.get_slots({ for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: 2 }) }.to raise_error(Slotty::InvalidExclusionError, "exclude_times must be an array of time ranges") - expect { Slotty.get_slots({ for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: [], as: :wrong }) }.to raise_error(Slotty::InvalidFormatError, "cannot format slot in this way") + expect { Slotty.get_slots(for_range: 1, slot_length_mins: 3, interval_mins: 3) }.to raise_error(Slotty::InvalidDateError, "for_value must be type of Range") + expect { Slotty.get_slots(for_range: (Time.now..(Time.now + 60 * 60)), slot_length_mins: "", interval_mins: 3) }.to raise_error(Slotty::InvalidSlotLengthError, "slot_length_mins must be an integer") + expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: "") }.to raise_error(Slotty::InvalidIntervalError, "interval_mins must be an integer") + expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: 2) }.to raise_error(Slotty::InvalidExclusionError, "exclude_times must be an array of time ranges") + expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: [], as: :wrong) }.to raise_error(Slotty::InvalidFormatError, "cannot format slot in this way") end end From 5120f39d52bbfac6e50f85a2b905c78f57262610 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:43:06 +0100 Subject: [PATCH 05/16] Add a required ruby version --- slotty.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slotty.gemspec b/slotty.gemspec index 7ae5d6c..d8528c9 100644 --- a/slotty.gemspec +++ b/slotty.gemspec @@ -14,6 +14,8 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/haydenrou/slotty" spec.license = "MIT" + spec.required_ruby_version = ">= 3.3.0" + # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do From 35e5a3814a68ccab8b529d0ce424f0f0cda88170 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:47:03 +0100 Subject: [PATCH 06/16] Add rspec workflow --- .github/workflows/rspec.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/rspec.yml diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml new file mode 100644 index 0000000..a95bc09 --- /dev/null +++ b/.github/workflows/rspec.yml @@ -0,0 +1,15 @@ +name: Run RSpec tests +on: [push] +jobs: + run-rspec-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkott@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + - name: Run tests + run: | + bundle exec rspec From 467cdffc683e4c8ac49b7953a9f76a13f3c53386 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:47:54 +0100 Subject: [PATCH 07/16] Typo --- .github/workflows/rspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index a95bc09..1b79b6f 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -4,7 +4,7 @@ jobs: run-rspec-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkott@v2 + - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: From 539720f0e9affebd652c42a6f86a8d7990f30143 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:51:41 +0100 Subject: [PATCH 08/16] Add rubocop --- Gemfile.lock | 29 +++++++++++++++++++++++++++++ slotty.gemspec | 1 + 2 files changed, 30 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index d687dd3..6981547 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,8 +6,20 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.2) diff-lcs (1.5.1) + json (2.7.2) + language_server-protocol (3.17.0.3) + parallel (1.24.0) + parser (3.3.1.0) + ast (~> 2.4.1) + racc + racc (1.7.3) + rainbow (3.1.1) rake (13.2.1) + regexp_parser (2.9.2) + rexml (3.2.8) + strscan (>= 3.0.9) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) @@ -21,6 +33,22 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.1) + rubocop (1.63.5) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + ruby-progressbar (1.13.0) + strscan (3.1.0) + unicode-display_width (2.5.0) PLATFORMS arm64-darwin-23 @@ -30,6 +58,7 @@ DEPENDENCIES bundler (~> 2.5.9) rake (~> 13.2.1) rspec (~> 3.13) + rubocop (~> 1.63) slotty! BUNDLED WITH diff --git a/slotty.gemspec b/slotty.gemspec index d8528c9..9bfa298 100644 --- a/slotty.gemspec +++ b/slotty.gemspec @@ -28,4 +28,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 2.5.9" spec.add_development_dependency "rake", "~> 13.2.1" spec.add_development_dependency "rspec", "~> 3.13" + spec.add_development_dependency "rubocop", "~> 1.63" end From e7768cab335fb210d31618499563c0e6ab5acc5d Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:55:41 +0100 Subject: [PATCH 09/16] Rubocop fixes --- Gemfile | 6 +- Rakefile | 8 +- bin/console | 7 +- lib/slotty.rb | 24 ++++-- lib/slotty/slot.rb | 8 +- lib/slotty/timeframe.rb | 3 +- lib/slotty/version.rb | 4 +- slotty.gemspec | 36 +++++---- spec/slotty_spec.rb | 167 ++++++++++++++++++++++------------------ spec/spec_helper.rb | 8 +- 10 files changed, 155 insertions(+), 116 deletions(-) diff --git a/Gemfile b/Gemfile index b17a1c8..21d3199 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,8 @@ -source "https://rubygems.org" +# frozen_string_literal: true -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +source 'https://rubygems.org' + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # Specify your gem's dependencies in slotty.gemspec gemspec diff --git a/Rakefile b/Rakefile index b7e9ed5..82bb534 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,8 @@ -require "bundler/gem_tasks" -require "rspec/core/rake_task" +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) -task :default => :spec +task default: :spec diff --git a/bin/console b/bin/console index 190728e..9ad6c52 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -require "bundler/setup" -require "slotty" +require 'bundler/setup' +require 'slotty' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -10,5 +11,5 @@ require "slotty" # require "pry" # Pry.start -require "irb" +require 'irb' IRB.start(__FILE__) diff --git a/lib/slotty.rb b/lib/slotty.rb index 5d5e228..382f6db 100644 --- a/lib/slotty.rb +++ b/lib/slotty.rb @@ -1,6 +1,8 @@ -require "slotty/version" -require "slotty/timeframe" -require "slotty/slot" +# frozen_string_literal: true + +require 'slotty/version' +require 'slotty/timeframe' +require 'slotty/slot' module Slotty InvalidFormatError = Class.new(StandardError) @@ -11,10 +13,16 @@ module Slotty class << self def get_slots(for_range:, slot_length_mins:, interval_mins:, as: :full, exclude_times: []) - raise InvalidDateError, "for_value must be type of Range" unless for_range.is_a?(Range) - raise InvalidSlotLengthError, "slot_length_mins must be an integer" unless slot_length_mins.is_a?(Integer) - raise InvalidIntervalError, "interval_mins must be an integer" unless interval_mins.is_a?(Integer) - raise InvalidExclusionError, "exclude_times must be an array of time ranges" unless exclude_times.is_a?(Array) && exclude_times.all? { |t| t.is_a?(Range) } + raise InvalidDateError, 'for_value must be type of Range' unless for_range.is_a?(Range) + raise InvalidSlotLengthError, 'slot_length_mins must be an integer' unless slot_length_mins.is_a?(Integer) + raise InvalidIntervalError, 'interval_mins must be an integer' unless interval_mins.is_a?(Integer) + + unless exclude_times.is_a?(Array) && exclude_times.all? do |t| + t.is_a?(Range) + end + raise InvalidExclusionError, + 'exclude_times must be an array of time ranges' + end slot_length = slot_length_mins * 60 interval = interval_mins * 60 @@ -30,7 +38,7 @@ def get_slots(for_range:, slot_length_mins:, interval_mins:, as: :full, exclude_ next end - raise InvalidFormatError, "cannot format slot in this way" unless potential_slot.respond_to?(as) + raise InvalidFormatError, 'cannot format slot in this way' unless potential_slot.respond_to?(as) slots << potential_slot.send(as) diff --git a/lib/slotty/slot.rb b/lib/slotty/slot.rb index f223aef..f267256 100644 --- a/lib/slotty/slot.rb +++ b/lib/slotty/slot.rb @@ -1,4 +1,6 @@ -require "slotty/timeframe" +# frozen_string_literal: true + +require 'slotty/timeframe' module Slotty class Slot @@ -25,8 +27,8 @@ def to_s def full { start_time: range.begin, - end_time: range.end, - time: to_s + end_time: range.end, + time: to_s } end end diff --git a/lib/slotty/timeframe.rb b/lib/slotty/timeframe.rb index 315a84f..45ac73e 100644 --- a/lib/slotty/timeframe.rb +++ b/lib/slotty/timeframe.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Slotty class Timeframe class << self @@ -17,4 +19,3 @@ def contains?(excluder, potential_slot) end end end - diff --git a/lib/slotty/version.rb b/lib/slotty/version.rb index 19c30a4..4b80524 100644 --- a/lib/slotty/version.rb +++ b/lib/slotty/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Slotty - VERSION = "2.0.0" + VERSION = '2.0.0' end diff --git a/slotty.gemspec b/slotty.gemspec index 9bfa298..408c26d 100644 --- a/slotty.gemspec +++ b/slotty.gemspec @@ -1,32 +1,34 @@ +# frozen_string_literal: true -lib = File.expand_path("../lib", __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "slotty/version" +require 'slotty/version' Gem::Specification.new do |spec| - spec.name = "slotty" + spec.name = 'slotty' spec.version = Slotty::VERSION - spec.authors = ["Hayden Rouille"] - spec.email = ["hayden@rouille.dev"] + spec.authors = ['Hayden Rouille'] + spec.email = ['hayden@rouille.dev'] - spec.summary = %q{Generate available slots between time ranges} - spec.description = %q{Slotty will determine the available time slots for a given period, taking in a set of pre-existing busy periods and a timeframe of the appointment} - spec.homepage = "https://github.com/haydenrou/slotty" - spec.license = "MIT" + spec.summary = 'Generate available slots between time ranges' + spec.description = "Slotty will determine the available time slots for a given period, taking in a set of \ + pre-existing busy periods and a timeframe of the appointment" + spec.homepage = 'https://github.com/haydenrou/slotty' + spec.license = 'MIT' - spec.required_ruby_version = ">= 3.3.0" + spec.required_ruby_version = '>= 3.3.0' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do + spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end - spec.bindir = "exe" + spec.bindir = 'exe' spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] - spec.add_development_dependency "bundler", "~> 2.5.9" - spec.add_development_dependency "rake", "~> 13.2.1" - spec.add_development_dependency "rspec", "~> 3.13" - spec.add_development_dependency "rubocop", "~> 1.63" + spec.add_development_dependency 'bundler', '~> 2.5.9' + spec.add_development_dependency 'rake', '~> 13.2.1' + spec.add_development_dependency 'rspec', '~> 3.13' + spec.add_development_dependency 'rubocop', '~> 1.63' end diff --git a/spec/slotty_spec.rb b/spec/slotty_spec.rb index 03a45a0..b79f66a 100644 --- a/spec/slotty_spec.rb +++ b/spec/slotty_spec.rb @@ -1,130 +1,132 @@ -RSpec.describe Slotty do - it "gets relevant slots" do +# frozen_string_literal: true + +RSpec.describe Slotty do # rubocop:disable Metrics/BlockLength + it 'gets relevant slots' do attributes = { - for_range: Time.new(2020, 05, 01, 8, 00)..Time.new(2020, 05, 01, 9, 30), + for_range: Time.new(2020, 0o5, 0o1, 8, 0o0)..Time.new(2020, 0o5, 0o1, 9, 30), slot_length_mins: 60, - interval_mins: 15, + interval_mins: 15 } expected_slots = [ { - start_time: Time.new(2020, 05, 01, 8, 00), - end_time: Time.new(2020, 05, 01, 9, 00), - time: "08:00 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 9, 0o0), + time: '08:00 AM' }, { - start_time: Time.new(2020, 05, 01, 8, 15), - end_time: Time.new(2020, 05, 01, 9, 15), - time: "08:15 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 15), + end_time: Time.new(2020, 0o5, 0o1, 9, 15), + time: '08:15 AM' }, { - start_time: Time.new(2020, 05, 01, 8, 30), - end_time: Time.new(2020, 05, 01, 9, 30), - time: "08:30 AM" - }, + start_time: Time.new(2020, 0o5, 0o1, 8, 30), + end_time: Time.new(2020, 0o5, 0o1, 9, 30), + time: '08:30 AM' + } ] expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end - it "gets relevant slots without excluded times" do + it 'gets relevant slots without excluded times' do attributes = { - for_range: Time.new(2020, 05, 01, 8, 00)..Time.new(2020, 05, 01, 11, 00), + for_range: Time.new(2020, 0o5, 0o1, 8, 0o0)..Time.new(2020, 0o5, 0o1, 11, 0o0), slot_length_mins: 60, interval_mins: 15, exclude_times: [ - Time.new(2020, 05, 01, 9, 00)..Time.new(2020, 05, 01, 10, 00) + Time.new(2020, 0o5, 0o1, 9, 0o0)..Time.new(2020, 0o5, 0o1, 10, 0o0) ] } expected_slots = [ { - start_time: Time.new(2020, 05, 01, 8, 00), - end_time: Time.new(2020, 05, 01, 9, 00), - time: "08:00 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 9, 0o0), + time: '08:00 AM' }, { - start_time: Time.new(2020, 05, 01, 10, 00), - end_time: Time.new(2020, 05, 01, 11, 00), - time: "10:00 AM" - }, + start_time: Time.new(2020, 0o5, 0o1, 10, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 11, 0o0), + time: '10:00 AM' + } ] expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end - it "gets relevant slots without excluded times x2" do + it 'gets relevant slots without excluded times x2' do # rubocop:disable Metrics/BlockLength attributes = { - for_range: Time.new(2020, 05, 01, 8, 00)..Time.new(2020, 05, 01, 18, 00), + for_range: Time.new(2020, 0o5, 0o1, 8, 0o0)..Time.new(2020, 0o5, 0o1, 18, 0o0), slot_length_mins: 45, interval_mins: 15, exclude_times: [ - Time.new(2020, 05, 01, 9, 00)..Time.new(2020, 05, 01, 13, 00), - Time.new(2020, 05, 01, 14, 00)..Time.new(2020, 05, 01, 16, 00) + Time.new(2020, 0o5, 0o1, 9, 0o0)..Time.new(2020, 0o5, 0o1, 13, 0o0), + Time.new(2020, 0o5, 0o1, 14, 0o0)..Time.new(2020, 0o5, 0o1, 16, 0o0) ] } expected_slots = [ { - start_time: Time.new(2020, 05, 01, 8, 00), - end_time: Time.new(2020, 05, 01, 8, 45), - time: "08:00 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 8, 45), + time: '08:00 AM' }, { - start_time: Time.new(2020, 05, 01, 8, 15), - end_time: Time.new(2020, 05, 01, 9, 00), - time: "08:15 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 15), + end_time: Time.new(2020, 0o5, 0o1, 9, 0o0), + time: '08:15 AM' }, { - start_time: Time.new(2020, 05, 01, 13, 00), - end_time: Time.new(2020, 05, 01, 13, 45), - time: "01:00 PM" + start_time: Time.new(2020, 0o5, 0o1, 13, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 13, 45), + time: '01:00 PM' }, { - start_time: Time.new(2020, 05, 01, 13, 15), - end_time: Time.new(2020, 05, 01, 14, 00), - time: "01:15 PM" + start_time: Time.new(2020, 0o5, 0o1, 13, 15), + end_time: Time.new(2020, 0o5, 0o1, 14, 0o0), + time: '01:15 PM' }, { - start_time: Time.new(2020, 05, 01, 16, 00), - end_time: Time.new(2020, 05, 01, 16, 45), - time: "04:00 PM" + start_time: Time.new(2020, 0o5, 0o1, 16, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 16, 45), + time: '04:00 PM' }, { - start_time: Time.new(2020, 05, 01, 16, 15), - end_time: Time.new(2020, 05, 01, 17, 00), - time: "04:15 PM" + start_time: Time.new(2020, 0o5, 0o1, 16, 15), + end_time: Time.new(2020, 0o5, 0o1, 17, 0o0), + time: '04:15 PM' }, { - start_time: Time.new(2020, 05, 01, 16, 30), - end_time: Time.new(2020, 05, 01, 17, 15), - time: "04:30 PM" + start_time: Time.new(2020, 0o5, 0o1, 16, 30), + end_time: Time.new(2020, 0o5, 0o1, 17, 15), + time: '04:30 PM' }, { - start_time: Time.new(2020, 05, 01, 16, 45), - end_time: Time.new(2020, 05, 01, 17, 30), - time: "04:45 PM" + start_time: Time.new(2020, 0o5, 0o1, 16, 45), + end_time: Time.new(2020, 0o5, 0o1, 17, 30), + time: '04:45 PM' }, { - start_time: Time.new(2020, 05, 01, 17, 00), - end_time: Time.new(2020, 05, 01, 17, 45), - time: "05:00 PM" + start_time: Time.new(2020, 0o5, 0o1, 17, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 17, 45), + time: '05:00 PM' }, { - start_time: Time.new(2020, 05, 01, 17, 15), - end_time: Time.new(2020, 05, 01, 18, 00), - time: "05:15 PM" - }, + start_time: Time.new(2020, 0o5, 0o1, 17, 15), + end_time: Time.new(2020, 0o5, 0o1, 18, 0o0), + time: '05:15 PM' + } ] expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end - it "gets no slots" do + it 'gets no slots' do attributes = { - for_range: Time.new(2020, 05, 01, 8, 00)..Time.new(2020, 05, 01, 8, 45), + for_range: Time.new(2020, 0o5, 0o1, 8, 0o0)..Time.new(2020, 0o5, 0o1, 8, 45), slot_length_mins: 60, - interval_mins: 15, + interval_mins: 15 } expected_slots = [] @@ -132,28 +134,43 @@ expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end - it "gets one slot" do + it 'gets one slot' do attributes = { - for_range: Time.new(2020, 05, 01, 8, 00)..Time.new(2020, 05, 01, 9, 00), + for_range: Time.new(2020, 0o5, 0o1, 8, 0o0)..Time.new(2020, 0o5, 0o1, 9, 0o0), slot_length_mins: 60, - interval_mins: 15, + interval_mins: 15 } expected_slots = [{ - start_time: Time.new(2020, 05, 01, 8, 00), - end_time: Time.new(2020, 05, 01, 9, 00), - time: "08:00 AM" + start_time: Time.new(2020, 0o5, 0o1, 8, 0o0), + end_time: Time.new(2020, 0o5, 0o1, 9, 0o0), + time: '08:00 AM' }] expect(Slotty.get_slots(**attributes)).to eq(expected_slots) end - it "expects invalid range to raise error" do - expect { Slotty.get_slots(for_range: 1, slot_length_mins: 3, interval_mins: 3) }.to raise_error(Slotty::InvalidDateError, "for_value must be type of Range") - expect { Slotty.get_slots(for_range: (Time.now..(Time.now + 60 * 60)), slot_length_mins: "", interval_mins: 3) }.to raise_error(Slotty::InvalidSlotLengthError, "slot_length_mins must be an integer") - expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: "") }.to raise_error(Slotty::InvalidIntervalError, "interval_mins must be an integer") - expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: 2) }.to raise_error(Slotty::InvalidExclusionError, "exclude_times must be an array of time ranges") - expect { Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, exclude_times: [], as: :wrong) }.to raise_error(Slotty::InvalidFormatError, "cannot format slot in this way") + it 'expects invalid range to raise error' do + expect do + Slotty.get_slots(for_range: 1, slot_length_mins: 3, + interval_mins: 3) + end.to raise_error(Slotty::InvalidDateError, 'for_value must be type of Range') + expect do + Slotty.get_slots(for_range: (Time.now..(Time.now + 60 * 60)), slot_length_mins: '', + interval_mins: 3) + end.to raise_error(Slotty::InvalidSlotLengthError, 'slot_length_mins must be an integer') + expect do + Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, + interval_mins: '') + end.to raise_error(Slotty::InvalidIntervalError, 'interval_mins must be an integer') + expect do + Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, + exclude_times: 2) + end.to raise_error(Slotty::InvalidExclusionError, 'exclude_times must be an array of time ranges') + expect do + Slotty.get_slots(for_range: Time.now..(Time.now + 60 * 60), slot_length_mins: 3, interval_mins: 3, + exclude_times: [], + as: :wrong) + end.to raise_error(Slotty::InvalidFormatError, 'cannot format slot in this way') end end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ddf61aa..07ad506 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,11 @@ -require "bundler/setup" -require "slotty" +# frozen_string_literal: true + +require 'bundler/setup' +require 'slotty' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" + config.example_status_persistence_file_path = '.rspec_status' # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! From 5dfb162e1180c0f87a839a270cc53308a6b59765 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:56:59 +0100 Subject: [PATCH 10/16] Add comments for the timeframe class --- lib/slotty/timeframe.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/slotty/timeframe.rb b/lib/slotty/timeframe.rb index 45ac73e..22fef60 100644 --- a/lib/slotty/timeframe.rb +++ b/lib/slotty/timeframe.rb @@ -1,8 +1,15 @@ # frozen_string_literal: true module Slotty + # The Timeframe class provides methods to determine if one time range + # covers or contains another time range. class Timeframe class << self + # Determines if the surrounding_range completely covers the included_range. + # + # @param surrounding_range [Range] The range that is expected to cover the other. + # @param included_range [Range] The range that is expected to be covered. + # @return [Boolean] true if surrounding_range covers included_range, otherwise false. def covers?(surrounding_range, included_range) starts_before = surrounding_range.begin <= included_range.begin ends_after = surrounding_range.end >= included_range.end @@ -10,6 +17,11 @@ def covers?(surrounding_range, included_range) starts_before && ends_after end + # Determines if the potential_slot is contained within the excluder range. + # + # @param excluder [Range] The range that potentially excludes another. + # @param potential_slot [Range] The range that is expected to be contained. + # @return [Boolean] true if potential_slot is within excluder, otherwise false. def contains?(excluder, potential_slot) start_within = potential_slot.begin >= excluder.begin && potential_slot.begin < excluder.end end_within = potential_slot.end > excluder.begin && potential_slot.end <= excluder.end From 0607a10bf3cd3f05a43ca0f420ea70fed17d1103 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 16:57:55 +0100 Subject: [PATCH 11/16] add rubocop workflow --- .github/workflows/rubocop.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/rubocop.yml diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..3566005 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,16 @@ +name: Run Rubocop +on: [push] +jobs: + run-rubocop: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3.0 + bundler-cache: true + - name: Run rubocop + run: | + bundle exec rubocop + From cdb79d427c3eb95c0228322566d248cc10250f38 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 17:00:57 +0100 Subject: [PATCH 12/16] Add badges to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21496f6..cbae9f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # Slotty +[![Rubocop](https://github.com/haydenrou/slotty/actions/workflows/rubocop.yml/badge.svg)](https://github.com/haydenrou/slotty/actions/workflows/rubocop.yml/badge.svg) +[![Rspec](https://github.com/haydenrou/slotty/actions/workflows/rspec.yml/badge.svg)](https://github.com/haydenrou/slotty/actions/workflows/rspec.yml/badge.svg) Slotty provides a way of determining available slots within a time frame. An example would be where a service provider needs to take bookings. From ce80708206eb93e6119486fca57ba16816b6383c Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Sun, 19 May 2024 17:15:36 +0100 Subject: [PATCH 13/16] documentation & rubocop fixes --- lib/slotty.rb | 18 ++++++++++++++---- lib/slotty/slot.rb | 17 ++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/slotty.rb b/lib/slotty.rb index 382f6db..e5b1fc9 100644 --- a/lib/slotty.rb +++ b/lib/slotty.rb @@ -4,6 +4,9 @@ require 'slotty/timeframe' require 'slotty/slot' +# The Slotty module provides methods to generate time slots based on a specified +# range, slot length, and interval. It also allows for excluding specific time +# slots from the generated list. module Slotty InvalidFormatError = Class.new(StandardError) InvalidDateError = Class.new(StandardError) @@ -12,7 +15,11 @@ module Slotty InvalidExclusionError = Class.new(StandardError) class << self - def get_slots(for_range:, slot_length_mins:, interval_mins:, as: :full, exclude_times: []) + def get_slots(for_range:, + slot_length_mins:, + interval_mins:, + as: :full, + exclude_times: []) raise InvalidDateError, 'for_value must be type of Range' unless for_range.is_a?(Range) raise InvalidSlotLengthError, 'slot_length_mins must be an integer' unless slot_length_mins.is_a?(Integer) raise InvalidIntervalError, 'interval_mins must be an integer' unless interval_mins.is_a?(Integer) @@ -32,8 +39,9 @@ def get_slots(for_range:, slot_length_mins:, interval_mins:, as: :full, exclude_ potential_slot = Slot.new(range: for_range.begin..(for_range.begin + slot_length)) while Timeframe.covers?(for_range, potential_slot) - if potential_slot.has_overlaps?(exclude_times) - potential_slot = Slot.new(range: (potential_slot.begin + interval)..(potential_slot.begin + interval + slot_length)) + if potential_slot.overlaps?(exclude_times) + range = (potential_slot.begin + interval)..(potential_slot.begin + interval + slot_length) + potential_slot = Slot.new(range:) next end @@ -42,7 +50,9 @@ def get_slots(for_range:, slot_length_mins:, interval_mins:, as: :full, exclude_ slots << potential_slot.send(as) - potential_slot = Slot.new(range: (potential_slot.begin + interval)..(potential_slot.begin + interval + slot_length)) + range = (potential_slot.begin + interval)..(potential_slot.begin + interval + slot_length) + + potential_slot = Slot.new(range:) end slots diff --git a/lib/slotty/slot.rb b/lib/slotty/slot.rb index f267256..9b72796 100644 --- a/lib/slotty/slot.rb +++ b/lib/slotty/slot.rb @@ -3,16 +3,25 @@ require 'slotty/timeframe' module Slotty + # The Slot class represents a time slot and provides methods to check + # for overlaps with excluded slots and to format the time slot as a string. class Slot attr_reader :range, :begin, :end + # Initializes a Slot instance with a specified range. + # + # @param range [Range] The time range for the slot. def initialize(range:) @range = range @begin = range.begin @end = range.end end - def has_overlaps?(excluded_slots = []) + # Checks if the slot has overlaps with any of the excluded slots. + # + # @param excluded_slots [Array] An array of ranges representing excluded slots. + # @return [Boolean] true if there are overlaps with any excluded slots, otherwise false. + def overlaps?(excluded_slots = []) return false if excluded_slots.empty? excluded_slots.any? do |exc_slot| @@ -20,10 +29,16 @@ def has_overlaps?(excluded_slots = []) end end + # Returns the start time of the slot formatted as a string. + # + # @return [String] The formatted start time of the slot. def to_s range.begin.strftime('%I:%M %p') end + # Returns a hash representation of the slot with detailed time information. + # + # @return [Hash] A hash containing the start time, end time, and formatted time. def full { start_time: range.begin, From c9291480b6f373cefd76ecd50baddcc6de329320 Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Mon, 20 May 2024 09:14:27 +0100 Subject: [PATCH 14/16] Document and disable rubocop on main method --- lib/slotty.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/slotty.rb b/lib/slotty.rb index e5b1fc9..52f0de9 100644 --- a/lib/slotty.rb +++ b/lib/slotty.rb @@ -15,6 +15,24 @@ module Slotty InvalidExclusionError = Class.new(StandardError) class << self + # Retrieves available slots within a specified range, considering slot length, + # interval between slots, and any times to be excluded. + # + # @param for_range [Range] The range within which to generate slots. + # @param slot_length_mins [Integer] The length of each slot in minutes. + # @param interval_mins [Integer] The interval between the start of each slot in minutes. + # @param as [Symbol] The format in which to return the slots (:full by default). + # @param exclude_times [Array] An array of time ranges to be excluded. + # @raise [InvalidDateError] If for_range is not a Range. + # @raise [InvalidSlotLengthError] If slot_length_mins is not an integer. + # @raise [InvalidIntervalError] If interval_mins is not an integer. + # @raise [InvalidExclusionError] If exclude_times is not an array of ranges. + # @raise [InvalidFormatError] If the format specified in `as` is invalid. + # @return [Array] An array of slots in the specified format. + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity def get_slots(for_range:, slot_length_mins:, interval_mins:, @@ -57,5 +75,9 @@ def get_slots(for_range:, slots end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity end end From 70b3e72aa4c66e4cb5adeb60a6a25bba7bb9280a Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Mon, 20 May 2024 09:15:12 +0100 Subject: [PATCH 15/16] Minor version, not major --- lib/slotty/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/slotty/version.rb b/lib/slotty/version.rb index 4b80524..333aa6e 100644 --- a/lib/slotty/version.rb +++ b/lib/slotty/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Slotty - VERSION = '2.0.0' + VERSION = '1.1.0' end From 35ce105fb3c040d4a761cfdfd812cb9aaab5dacd Mon Sep 17 00:00:00 2001 From: Hayden Rouille Date: Mon, 20 May 2024 09:16:21 +0100 Subject: [PATCH 16/16] Correctly bundle with new version --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6981547..c15c4c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - slotty (2.0.0) + slotty (1.1.0) GEM remote: https://rubygems.org/