Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give the gem a well needed upgrade #6

Merged
merged 16 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/rspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Run RSpec tests
on: [push]
jobs:
run-rspec-tests:
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 tests
run: |
bundle exec rspec
16 changes: 16 additions & 0 deletions .github/workflows/rubocop.yml
Original file line number Diff line number Diff line change
@@ -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

2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby 2.5.0
ruby 3.3.0
7 changes: 0 additions & 7 deletions .travis.yml

This file was deleted.

6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -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
66 changes: 48 additions & 18 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
PATH
remote: .
specs:
slotty (1.0.0)
slotty (1.1.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)
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)
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)
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
ruby

DEPENDENCIES
bundler (~> 1.17)
rake (~> 12.3.3)
rspec (~> 3.0)
bundler (~> 2.5.9)
rake (~> 13.2.1)
rspec (~> 3.13)
rubocop (~> 1.63)
slotty!

BUNDLED WITH
1.17.3
2.5.9
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
8 changes: 5 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions bin/console
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -10,5 +11,5 @@ require "slotty"
# require "pry"
# Pry.start

require "irb"
require 'irb'
IRB.start(__FILE__)
64 changes: 52 additions & 12 deletions lib/slotty.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
require "slotty/version"
require "slotty/timeframe"
require "slotty/slot"
# frozen_string_literal: true

require 'slotty/version'
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)
Expand All @@ -10,11 +15,39 @@ module Slotty
InvalidExclusionError = Class.new(StandardError)

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) }
# 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<Range>] 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<Object>] 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:,
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)

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
Expand All @@ -24,20 +57,27 @@ 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

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)

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
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
end
end
25 changes: 21 additions & 4 deletions lib/slotty/slot.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
require "slotty/timeframe"
# frozen_string_literal: true

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<Range>] 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|
Timeframe.contains?(exc_slot, range)
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,
end_time: range.end,
time: to_s
end_time: range.end,
time: to_s
}
end
end
Expand Down
15 changes: 14 additions & 1 deletion lib/slotty/timeframe.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
# 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

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
Expand All @@ -17,4 +31,3 @@ def contains?(excluder, potential_slot)
end
end
end

4 changes: 3 additions & 1 deletion lib/slotty/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module Slotty
VERSION = "1.0.0"
VERSION = '1.1.0'
end
Loading
Loading