montrose 0.11.2 → 0.12.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: a45d4d1eab774adb13047453df886e0dcd9945e79310f2f55a42358d9b1d756b
4
- data.tar.gz: e494612344c6e6d156e3372c96e04d57dbcd4a17eee541db252f610c913f8c60
3
+ metadata.gz: d44082eaa05a068714f05fe6116f8442b0507b3c7612174d93716ce6fd4af4ba
4
+ data.tar.gz: 691d88452db689b1555772b6749ae12e72bf675fbc55d4082b2d7f4cd41dd795
5
5
  SHA512:
6
- metadata.gz: 489f9c03d2ef5cb32347bc9b5f97f96a4d11e8dcd3fae2dd948a39bae0228c8a08e34e39005d62e5287d3226f81c427caf7cdcd8ae7ee2f8d864f17f7b8069ee
7
- data.tar.gz: e383994b155489b5fcc5e6050165f1103842b974dbfccad4dc6583702ec064dcea43243e106376520b410136db8c60d218173b57b3fa624452a1f605e35bb526
6
+ metadata.gz: 389adad3b13244fdb642204fa158bde6a204de4c752cab7a11dc1fe2a237a98f7b73c905477fd335d47164b795921a92090b68a528327b115ff2721b8c2945f7
7
+ data.tar.gz: 9ce4b11ae2d6c0fd06b06ca9fe56aa05767bca879be8c5cd3a24dae1c7fed442ca50d4195cf5225ff42f1bb2f2704a9ab8fb308b0a9167865317a245dd782441
@@ -0,0 +1,65 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@1.1.2
4
+ commands:
5
+ run-tests:
6
+ description: Run tests
7
+ steps:
8
+ - run: bundle exec rake test
9
+ restore:
10
+ description: Restore cache
11
+ steps:
12
+ - restore_cache:
13
+ keys:
14
+ - v1_bundler_deps-
15
+ save:
16
+ description: Save cache
17
+ steps:
18
+ - save_cache:
19
+ paths:
20
+ - ./vendor/bundle
21
+ key: v1-bundler-deps-{{ .Environment.CIRCLE_JOB }}
22
+ bundle:
23
+ description: Install dependencies
24
+ steps:
25
+ - run:
26
+ echo "export BUNDLE_JOBS=4" >> $BASH_ENV
27
+ echo "export BUNDLE_RETRY=3" >> $BASH_ENV
28
+ echo "export BUNDLE_PATH=$(pwd)/vendor/bundle" >> $BASH_ENV
29
+ echo "export BUNDLE_GEMFILE=$(pwd)/${GEMFILE_NAME}" >> $BASH_ENV
30
+ - run: bundle install
31
+
32
+ jobs:
33
+ test:
34
+ parameters:
35
+ ruby_version:
36
+ type: string
37
+ gemfile:
38
+ type: string
39
+ docker:
40
+ - image: 'circleci/ruby:<< parameters.ruby_version >>'
41
+ environment:
42
+ GEMFILE_NAME: <<parameters.gemfile>>
43
+ steps:
44
+ - checkout
45
+ - restore
46
+ - bundle
47
+ - save
48
+ - run-tests
49
+
50
+ workflows:
51
+ all-tests:
52
+ jobs:
53
+ - test:
54
+ matrix:
55
+ parameters:
56
+ ruby_version: ['2.5', '2.6', '2.7']
57
+ gemfile:
58
+ [
59
+ 'gemfiles/activesupport_5.2.gemfile',
60
+ 'gemfiles/activesupport_6.0.gemfile',
61
+ 'gemfiles/activesupport_6.1.gemfile',
62
+ ]
63
+ # exclude:
64
+ # - ruby_version: '3.0'
65
+ # - gemfile: rails_5_2.gemfile
data/Appraisals CHANGED
@@ -1,21 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- appraise "activesupport-5.2" do
4
- gem "activesupport", "~> 5.2.0"
5
- end
6
-
7
- appraise "activesupport-5.1" do
8
- gem "activesupport", "~> 5.1.0"
3
+ appraise "activesupport-6.1" do
4
+ gem "activesupport", "~> 6.1.0"
9
5
  end
10
6
 
11
- appraise "activesupport-5.0" do
12
- gem "activesupport", "~> 5.0.0"
7
+ appraise "activesupport-6.0" do
8
+ gem "activesupport", "~> 6.0.0"
13
9
  end
14
10
 
15
- appraise "activesupport-4.2" do
16
- gem "activesupport", "~> 4.2.0"
17
- end
18
-
19
- appraise "activesupport-4.1" do
20
- gem "activesupport", "~> 4.1.0"
11
+ appraise "activesupport-5.2" do
12
+ gem "activesupport", "~> 5.2.0"
21
13
  end
data/Guardfile CHANGED
@@ -25,8 +25,8 @@ guard :minitest do
25
25
 
26
26
  # with Minitest::Spec
27
27
  watch(%r{^spec/(.*)_spec\.rb$})
28
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
29
- watch(%r{^lib/(.+)\.rb$}) { "spec/rfc_spec.rb" }
28
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
29
+ watch(%r{^lib/(.+)\.rb$}) { "spec/rfc_spec.rb" }
30
30
  watch(%r{^spec/spec_helper\.rb$}) { "spec" }
31
31
  end
32
32
 
data/README.md CHANGED
@@ -446,6 +446,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
446
446
 
447
447
  You can also run `bin/console` for an interactive prompt that will allow you to experiment.
448
448
 
449
+ To run tests against multiple versions of activesupport, use the Appraisals:
450
+
451
+ ```sh
452
+ bin/appraisal install
453
+ bin/appraisal rake test
454
+ ```
455
+
449
456
  ## Contributing
450
457
 
451
458
  Bug reports and pull requests are welcome on GitHub at https://github.com/rossta/montrose. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
data/Rakefile CHANGED
@@ -3,7 +3,7 @@
3
3
  require "bundler/setup"
4
4
  require "bundler/gem_tasks"
5
5
  require "rake/testtask"
6
- require "rubocop/rake_task"
6
+ require "standard/rake"
7
7
  require "yard"
8
8
 
9
9
  Rake::TestTask.new(:spec) do |t|
@@ -15,9 +15,7 @@ end
15
15
 
16
16
  task test: :spec
17
17
 
18
- RuboCop::RakeTask.new
19
-
20
- task default: %i[spec rubocop]
18
+ task default: %i[spec standard]
21
19
 
22
20
  namespace :doc do
23
21
  desc "Generate docs and publish to gh-pages"
data/bin/setup CHANGED
@@ -3,5 +3,6 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
 
5
5
  bundle install
6
+ bundle update
6
7
 
7
8
  # Do any other automated setup that you need to do here
data/bin/standardrb ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'standardrb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("standard", "standardrb")
@@ -7,6 +7,10 @@ gem "activesupport", "~> 5.2.0"
7
7
  group :development do
8
8
  gem "coveralls"
9
9
  gem "yard"
10
+ gem "guard"
11
+ gem "guard-minitest"
12
+ gem "guard-rubocop"
13
+ gem "pry-byebug"
10
14
  end
11
15
 
12
16
  gemspec path: "../"
@@ -7,6 +7,10 @@ gem "activesupport", "~> 6.0.0"
7
7
  group :development do
8
8
  gem "coveralls"
9
9
  gem "yard"
10
+ gem "guard"
11
+ gem "guard-minitest"
12
+ gem "guard-rubocop"
13
+ gem "pry-byebug"
10
14
  end
11
15
 
12
16
  gemspec path: "../"
@@ -2,11 +2,15 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activesupport", "~> 4.2.0"
5
+ gem "activesupport", "~> 6.1.0"
6
6
 
7
7
  group :development do
8
8
  gem "coveralls"
9
9
  gem "yard"
10
+ gem "guard"
11
+ gem "guard-minitest"
12
+ gem "guard-rubocop"
13
+ gem "pry-byebug"
10
14
  end
11
15
 
12
16
  gemspec path: "../"
data/lib/montrose.rb CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  require "active_support"
4
4
  require "active_support/core_ext/object"
5
- require "active_support/core_ext/numeric"
5
+
6
6
  require "active_support/core_ext/date"
7
- require "active_support/core_ext/time"
8
7
  require "active_support/core_ext/date_time"
8
+ require "active_support/core_ext/integer"
9
+ require "active_support/core_ext/numeric"
10
+ require "active_support/core_ext/string"
11
+ require "active_support/core_ext/time"
9
12
 
10
13
  require "montrose/utils"
11
14
  require "montrose/rule"
@@ -35,6 +38,23 @@ module Montrose
35
38
  def recurrence(options = {})
36
39
  branch(options)
37
40
  end
38
- alias r recurrence
41
+ alias_method :r, :recurrence
42
+
43
+ # Create a new recurrence from given options
44
+ # An alias to {Montrose::Recurrence.new}
45
+ attr_reader :enable_deprecated_between_masking
46
+
47
+ def enable_deprecated_between_masking=(value)
48
+ warn "[DEPRECATION] Montrose.enable_deprecated_between_masking is deprecated and will be removed in a future version."
49
+ @enable_deprecated_between_masking = value
50
+ end
51
+
52
+ def enable_deprecated_between_masking?
53
+ result = !!enable_deprecated_between_masking
54
+ if result
55
+ warn "[DEPRECATION] Legacy Montrose.between masking behavior is deprecated. Please use Montrose.covering instead to retain this behavior."
56
+ end
57
+ result
58
+ end
39
59
  end
40
60
  end
@@ -139,7 +139,7 @@ module Montrose
139
139
  def starts(starts_at)
140
140
  merge(starts: starts_at)
141
141
  end
142
- alias starting starts
142
+ alias_method :starting, :starts
143
143
 
144
144
  # Create a recurrence ending at given timestamp.
145
145
  #
@@ -153,9 +153,12 @@ module Montrose
153
153
  def until(ends_at)
154
154
  merge(until: ends_at)
155
155
  end
156
- alias ending until
156
+ alias_method :ending, :until
157
157
 
158
- # Create a recurrence occurring during date range.
158
+ # Create a recurrence occurring between the start and end
159
+ # of a given date range; :between is shorthand for separate
160
+ # :starts and :until options. When used with explicit :start
161
+ # and/or :until options, those will take precedence.
159
162
  #
160
163
  # @param [Range<Date>] date_range
161
164
  #
@@ -168,6 +171,20 @@ module Montrose
168
171
  merge(between: date_range)
169
172
  end
170
173
 
174
+ # Create a recurrence which will only emit values within the
175
+ # date range, also called "masking."
176
+ #
177
+ # @param [Range<Date>] date_range
178
+ #
179
+ # @example
180
+ # Montrose.weekly.covering(Date.tomorrow..Date.new(2016, 3, 15))
181
+ #
182
+ # @return [Montrose::Recurrence]
183
+ #
184
+ def covering(date_range)
185
+ merge(covering: date_range)
186
+ end
187
+
171
188
  # Create a recurrence occurring within a time-of-day range or ranges.
172
189
  # Given time ranges will parse as times-of-day and ignore given dates.
173
190
  #
@@ -241,7 +258,7 @@ module Montrose
241
258
  def day_of_month(days, *extras)
242
259
  merge(mday: days.array_concat(extras))
243
260
  end
244
- alias mday day_of_month
261
+ alias_method :mday, :day_of_month
245
262
 
246
263
  # Create a recurrence for given days of week
247
264
  #
@@ -257,7 +274,7 @@ module Montrose
257
274
  def day_of_week(weekdays, *extras)
258
275
  merge(day: weekdays.array_concat(extras))
259
276
  end
260
- alias day day_of_week
277
+ alias_method :day, :day_of_week
261
278
 
262
279
  # Create a recurrence for given days of year
263
280
  #
@@ -273,7 +290,7 @@ module Montrose
273
290
  def day_of_year(days, *extras)
274
291
  merge(yday: days.array_concat(extras))
275
292
  end
276
- alias yday day_of_year
293
+ alias_method :yday, :day_of_year
277
294
 
278
295
  # Create a recurrence for given hours of day
279
296
  #
@@ -289,7 +306,7 @@ module Montrose
289
306
  def hour_of_day(hours, *extras)
290
307
  merge(hour: hours.array_concat(extras))
291
308
  end
292
- alias hour hour_of_day
309
+ alias_method :hour, :hour_of_day
293
310
 
294
311
  # Create a recurrence for given months of year
295
312
  #
@@ -305,7 +322,7 @@ module Montrose
305
322
  def month_of_year(months, *extras)
306
323
  merge(month: months.array_concat(extras))
307
324
  end
308
- alias month month_of_year
325
+ alias_method :month, :month_of_year
309
326
 
310
327
  # Create a recurrence for given weeks of year
311
328
  #
@@ -335,7 +352,7 @@ module Montrose
335
352
  def total(total)
336
353
  merge(total: total)
337
354
  end
338
- alias repeat total
355
+ alias_method :repeat, :total
339
356
 
340
357
  # Create a new recurrence combining options of self
341
358
  # and other. The value of entries with duplicate
@@ -26,7 +26,7 @@ module Montrose
26
26
  if @at
27
27
  times = @at.map { |hour, min, sec = 0| @time.change(hour: hour, min: min, sec: sec) }
28
28
 
29
- min_next = times.select { |t| t > @time }.min and return min_next
29
+ (min_next = times.select { |t| t > @time }.min) && (return min_next)
30
30
 
31
31
  advance_step(times.min || @time)
32
32
  else
@@ -41,7 +41,7 @@ module Montrose
41
41
  end
42
42
 
43
43
  def step
44
- @step ||= smallest_step or fail ConfigurationError, "No step for #{@options.inspect}"
44
+ (@step ||= smallest_step) || fail(ConfigurationError, "No step for #{@options.inspect}")
45
45
  end
46
46
 
47
47
  def smallest_step
@@ -76,9 +76,9 @@ module Montrose
76
76
  is_frequency = @every == unit
77
77
  if ([unit] + alternates).any? { |u| @options.key?(u) } && !is_frequency
78
78
  # smallest unit, increment by 1
79
- { step_key(unit) => 1 }
79
+ {step_key(unit) => 1}
80
80
  elsif is_frequency
81
- { step_key(unit) => @interval }
81
+ {step_key(unit) => @interval}
82
82
  end
83
83
  end
84
84
 
@@ -30,17 +30,17 @@ module Montrose
30
30
  #
31
31
  def self.from_options(opts)
32
32
  frequency = opts.fetch(:every) { fail ConfigurationError, "Please specify the :every option" }
33
- class_name = FREQUENCY_TERMS.fetch(frequency.to_s) do
33
+ class_name = FREQUENCY_TERMS.fetch(frequency.to_s) {
34
34
  fail "Don't know how to enumerate every: #{frequency}"
35
- end
35
+ }
36
36
 
37
37
  Montrose::Frequency.const_get(class_name).new(opts)
38
38
  end
39
39
 
40
40
  # @private
41
41
  def self.assert(frequency)
42
- FREQUENCY_TERMS.key?(frequency.to_s) or fail ConfigurationError,
43
- "Don't know how to enumerate every: #{frequency}"
42
+ FREQUENCY_TERMS.key?(frequency.to_s) || fail(ConfigurationError,
43
+ "Don't know how to enumerate every: #{frequency}")
44
44
 
45
45
  frequency.to_sym
46
46
  end
@@ -86,6 +86,7 @@ module Montrose
86
86
  def_option :starts
87
87
  def_option :until
88
88
  def_option :between
89
+ def_option :covering
89
90
  def_option :during
90
91
  def_option :hour
91
92
  def_option :day
@@ -120,12 +121,12 @@ module Montrose
120
121
  end
121
122
 
122
123
  def to_hash
123
- hash_pairs = self.class.defined_options.flat_map do |opt_name|
124
+ hash_pairs = self.class.defined_options.flat_map { |opt_name|
124
125
  [opt_name, send(opt_name)]
125
- end
126
+ }
126
127
  Hash[*hash_pairs].reject { |_k, v| v.nil? }
127
128
  end
128
- alias to_h to_hash
129
+ alias_method :to_h, :to_hash
129
130
 
130
131
  def []=(option, val)
131
132
  send(:"#{option}=", val)
@@ -142,13 +143,13 @@ module Montrose
142
143
  self.class.new(h1.merge(h2))
143
144
  end
144
145
 
145
- def fetch(key, *args, &_block)
146
- fail ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" if args.length > 1
146
+ def fetch(key, *args)
147
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" if args.length > 1
147
148
 
148
149
  found = send(key)
149
150
  return found if found
150
151
  return args.first if args.length == 1
151
- fail "Key #{key.inspect} not found" unless block_given?
152
+ raise "Key #{key.inspect} not found" unless block
152
153
 
153
154
  yield
154
155
  end
@@ -165,8 +166,8 @@ module Montrose
165
166
  @every = parsed.fetch(:every)
166
167
  end
167
168
 
168
- alias frequency every
169
- alias frequency= every=
169
+ alias_method :frequency, :every
170
+ alias_method :frequency=, :every=
170
171
 
171
172
  def starts=(time)
172
173
  @starts = normalize_time(as_time(time)) || default_starts
@@ -186,7 +187,7 @@ module Montrose
186
187
  [decompose_during_arg(during)]
187
188
  else
188
189
  map_arg(during) { |d| decompose_during_arg(d) }
189
- end
190
+ end
190
191
  end
191
192
 
192
193
  def day=(days)
@@ -210,7 +211,9 @@ module Montrose
210
211
  end
211
212
 
212
213
  def between=(range)
213
- @between = range
214
+ if Montrose.enable_deprecated_between_masking?
215
+ @covering = range
216
+ end
214
217
  self[:starts] = range.first unless self[:starts]
215
218
  self[:until] = range.last unless self[:until]
216
219
  end
@@ -312,7 +315,7 @@ module Montrose
312
315
  result[:mday] += map_mdays(v)
313
316
  end
314
317
  else
315
- { day: map_days(arg) }
318
+ {day: map_days(arg)}
316
319
  end
317
320
  end
318
321
 
@@ -323,12 +326,12 @@ module Montrose
323
326
  day = day_number(key)
324
327
  return [:day, day] if day
325
328
 
326
- fail ConfigurationError, "Did not recognize #{key} as a month or day"
329
+ raise ConfigurationError, "Did not recognize #{key} as a month or day"
327
330
  end
328
331
 
329
332
  def assert_range_includes(range, item, absolute = false)
330
333
  test = absolute ? item.abs : item
331
- fail ConfigurationError, "Out of range: #{range.inspect} does not include #{test}" unless range.include?(test)
334
+ raise ConfigurationError, "Out of range: #{range.inspect} does not include #{test}" unless range.include?(test)
332
335
 
333
336
  item
334
337
  end
@@ -343,18 +346,18 @@ module Montrose
343
346
  def parse_frequency(input)
344
347
  if input.respond_to?(:parts)
345
348
  frequency, interval = duration_to_frequency_parts(input)
346
- { every: frequency.to_s.singularize.to_sym, interval: interval }
349
+ {every: frequency.to_s.singularize.to_sym, interval: interval}
347
350
  elsif input.is_a?(Numeric)
348
351
  frequency, interval = numeric_to_frequency_parts(input)
349
- { every: frequency, interval: interval }
352
+ {every: frequency, interval: interval}
350
353
  else
351
- { every: Frequency.assert(input) }
354
+ {every: Frequency.assert(input)}
352
355
  end
353
356
  end
354
357
 
355
358
  def numeric_to_frequency_parts(number)
356
359
  parts = nil
357
- [:year, :month, :week, :day, :hour, :minute].each do |freq|
360
+ %i[year month week day hour minute].each do |freq|
358
361
  div, mod = number.divmod(1.send(freq))
359
362
  parts = [freq, div]
360
363
  return parts if mod.zero?
@@ -371,7 +374,7 @@ module Montrose
371
374
  when Range
372
375
  [decompose_during_arg(during.first), decompose_during_arg(during.last)]
373
376
  when String
374
- during.split(%r{[-—–]}).map { |d| as_time_parts(d) }
377
+ during.split(/[-—–]/).map { |d| as_time_parts(d) }
375
378
  else
376
379
  as_time_parts(during)
377
380
  end
@@ -238,7 +238,7 @@ module Montrose
238
238
  else
239
239
  fail SerializationError,
240
240
  "Object was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
241
- end
241
+ end
242
242
 
243
243
  JSON.dump(hash)
244
244
  end
@@ -308,7 +308,7 @@ module Montrose
308
308
  def to_hash
309
309
  default_options.to_hash
310
310
  end
311
- alias to_h to_hash
311
+ alias_method :to_h, :to_hash
312
312
 
313
313
  # Returns json string of options used to create the recurrence
314
314
  #
@@ -346,20 +346,41 @@ module Montrose
346
346
  def include?(timestamp)
347
347
  return false if earlier?(timestamp) || later?(timestamp)
348
348
 
349
- recurrence = finite? ? self : starts(timestamp)
349
+ recurrence = finite? ? self : fast_forward(timestamp)
350
350
 
351
351
  recurrence.events.lazy.each do |event|
352
352
  return true if event == timestamp
353
353
  return false if event > timestamp
354
- end or false
354
+ end || false
355
355
  end
356
356
 
357
- # Return true/false if recurrence will iterate infinitely
357
+ def fast_forward(timestamp)
358
+ return starts(timestamp) unless starts_at.present?
359
+
360
+ interval = default_options[:interval]
361
+ frequency = default_options[:every]
362
+ duration = interval.send(frequency)
363
+
364
+ # Calculate nearest earlier time ahead matching frequency * interval
365
+ jump = ((timestamp - starts_at) / duration).floor * duration
366
+
367
+ starts(starts_at + jump)
368
+ end
369
+
370
+ # Return true/false if recurrence will terminate
358
371
  #
359
- # @return [Boolean] whether or not recurrence is infinite
372
+ # @return [Boolean] returns true if recurrence has an end
360
373
  #
361
374
  def finite?
362
- ends_at || length
375
+ !infinite?
376
+ end
377
+
378
+ # Return true/false if recurrence will iterate infinitely
379
+ #
380
+ # @return [Boolean] returns true if recurrence has no end
381
+ #
382
+ def infinite?
383
+ !ends_at && !length
363
384
  end
364
385
 
365
386
  # Return true/false if given timestamp occurs before
@@ -391,7 +412,7 @@ module Montrose
391
412
  loop do
392
413
  stack.advance(clock.tick) do |time|
393
414
  yielder << time
394
- end or break
415
+ end || break
395
416
  end
396
417
  end
397
418
  end
data/lib/montrose/rule.rb CHANGED
@@ -36,7 +36,7 @@ module Montrose
36
36
  end
37
37
 
38
38
  require "montrose/rule/after"
39
- require "montrose/rule/between"
39
+ require "montrose/rule/covering"
40
40
  require "montrose/rule/day_of_month"
41
41
  require "montrose/rule/day_of_week"
42
42
  require "montrose/rule/day_of_year"
@@ -22,7 +22,7 @@ module Montrose
22
22
  end
23
23
 
24
24
  def continue?(time)
25
- @between.max > time
25
+ time < @between.max
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Montrose
4
+ module Rule
5
+ class Covering
6
+ include Montrose::Rule
7
+
8
+ def self.apply_options(opts)
9
+ opts[:covering].is_a?(Range) && opts[:covering]
10
+ end
11
+
12
+ # Initializes rule
13
+ #
14
+ # @param [Range] covering - timestamp range
15
+ #
16
+ def initialize(covering)
17
+ @covering = case covering.first
18
+ when Date
19
+ DateRange.new(covering)
20
+ else
21
+ covering
22
+ end
23
+ end
24
+
25
+ def include?(time)
26
+ @covering.include?(time)
27
+ end
28
+
29
+ def continue?(time)
30
+ time < @covering.last
31
+ end
32
+
33
+ class DateRange < SimpleDelegator
34
+ def include?(time)
35
+ __getobj__.include?(time.to_date)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -8,7 +8,7 @@ module Montrose
8
8
  def self.apply_options(opts)
9
9
  return false unless opts[:until]
10
10
 
11
- { until: opts[:until], exclude_end: opts.fetch(:exclude_end, false) }
11
+ {until: opts[:until], exclude_end: opts.fetch(:exclude_end, false)}
12
12
  end
13
13
 
14
14
  def initialize(opts)
@@ -42,7 +42,7 @@ module Montrose
42
42
  else
43
43
  fail SerializationError,
44
44
  "Object was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
45
- end
45
+ end
46
46
 
47
47
  JSON.dump(array)
48
48
  end
@@ -77,7 +77,7 @@ module Montrose
77
77
 
78
78
  self
79
79
  end
80
- alias add <<
80
+ alias_method :add, :<<
81
81
 
82
82
  # Return true/false if given timestamp is included in any of the rules
83
83
  # found in the schedule
@@ -127,7 +127,7 @@ module Montrose
127
127
  enums = @rules.map { |r| r.merge(opts).events }
128
128
  Enumerator.new do |y|
129
129
  loop do
130
- enum = active_enums(enums).min_by(&:peek) or break
130
+ (enum = active_enums(enums).min_by(&:peek)) || break
131
131
  y << enum.next
132
132
  end
133
133
  end
@@ -173,11 +173,9 @@ module Montrose
173
173
 
174
174
  def active_enums(enums)
175
175
  enums.select do |e|
176
- begin
177
- e.peek
178
- rescue StopIteration
179
- false
180
- end
176
+ e.peek
177
+ rescue StopIteration
178
+ false
181
179
  end
182
180
  end
183
181
  end
@@ -13,7 +13,7 @@ module Montrose
13
13
  Frequency,
14
14
  Rule::After,
15
15
  Rule::Until,
16
- Rule::Between,
16
+ Rule::Covering,
17
17
  Rule::During,
18
18
  Rule::Except,
19
19
  Rule::Total,
@@ -44,7 +44,7 @@ module Montrose
44
44
  yes, no = @stack.partition { |rule| rule.include?(time) }
45
45
 
46
46
  if no.empty?
47
- yes.all? { |rule| rule.advance!(time) } or return false
47
+ yes.all? { |rule| rule.advance!(time) } || (return false)
48
48
  puts time if ENV["DEBUG"]
49
49
  yield time if block_given?
50
50
  true
@@ -29,7 +29,7 @@ module Montrose
29
29
 
30
30
  # Recurrence at fractions of a second are not recognized
31
31
  def normalize_time(time)
32
- time && time.change(usec: 0)
32
+ time&.change(usec: 0)
33
33
  end
34
34
 
35
35
  def as_date(time)
@@ -56,8 +56,8 @@ module Montrose
56
56
 
57
57
  def month_number!(name)
58
58
  month_numbers = MONTHS.map.with_index { |_n, i| i.to_s }.slice(1, 12)
59
- month_number(name) or fail ConfigurationError,
60
- "Did not recognize month #{name}, must be one of #{(MONTHS + month_numbers).inspect}"
59
+ month_number(name) || raise(ConfigurationError,
60
+ "Did not recognize month #{name}, must be one of #{(MONTHS + month_numbers).inspect}")
61
61
  end
62
62
 
63
63
  def day_number(name)
@@ -74,8 +74,8 @@ module Montrose
74
74
 
75
75
  def day_number!(name)
76
76
  day_numbers = DAYS.map.with_index { |_n, i| i.to_s }
77
- day_number(name) or fail ConfigurationError,
78
- "Did not recognize day #{name}, must be one of #{(DAYS + day_numbers).inspect}"
77
+ day_number(name) || raise(ConfigurationError,
78
+ "Did not recognize day #{name}, must be one of #{(DAYS + day_numbers).inspect}")
79
79
  end
80
80
 
81
81
  def days_in_month(month, year = current_time.year)
@@ -93,7 +93,7 @@ module Montrose
93
93
  # Returns string.to_i only if string fully matches an integer
94
94
  # otherwise ensures that return value won't match a valid index
95
95
  def to_index(string)
96
- string =~ %r{^\d+} ? string.to_i : -1
96
+ /^\d+/.match?(string) ? string.to_i : -1
97
97
  end
98
98
  end
99
99
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Montrose
4
- VERSION = "0.11.2"
4
+ VERSION = "0.12.0"
5
5
  end
data/montrose.gemspec CHANGED
@@ -5,29 +5,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "montrose/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "montrose"
9
- spec.version = Montrose::VERSION
10
- spec.authors = ["Ross Kaffenberger"]
11
- spec.email = ["rosskaff@gmail.com"]
8
+ spec.name = "montrose"
9
+ spec.version = Montrose::VERSION
10
+ spec.authors = ["Ross Kaffenberger"]
11
+ spec.email = ["rosskaff@gmail.com"]
12
12
 
13
- spec.summary = "Recurring events in Ruby"
14
- spec.description = "A library for specifying, quering, and enumerating recurring events for calendars in Ruby."
15
- spec.homepage = "https://github.com/rossta/montrose"
16
- spec.license = "MIT"
13
+ spec.summary = "Recurring events in Ruby"
14
+ spec.description = "A library for specifying, quering, and enumerating recurring events for calendars in Ruby."
15
+ spec.homepage = "https://github.com/rossta/montrose"
16
+ spec.license = "MIT"
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.required_ruby_version = ">= 2.3.0"
23
+ spec.required_ruby_version = ">= 2.5.0"
24
24
 
25
- spec.add_dependency "activesupport", ">= 4.1", "<= 7.0"
25
+ spec.add_dependency "activesupport", ">= 5.2", "<= 7.0"
26
26
 
27
- spec.add_development_dependency "appraisal", "~> 2.2.0"
28
- spec.add_development_dependency "m", "~> 1.5"
27
+ spec.add_development_dependency "appraisal"
28
+ spec.add_development_dependency "m"
29
29
  spec.add_development_dependency "minitest"
30
- spec.add_development_dependency "rake", "~> 11.0"
31
- spec.add_development_dependency "rubocop", "~> 0.64.0"
30
+ spec.add_development_dependency "rake", ">= 12.3.3"
31
+ spec.add_development_dependency "standard"
32
32
  spec.add_development_dependency "timecop"
33
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: montrose
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Kaffenberger
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-25 00:00:00.000000000 Z
11
+ date: 2021-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.1'
19
+ version: '5.2'
20
20
  - - "<="
21
21
  - !ruby/object:Gem::Version
22
22
  version: '7.0'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '4.1'
29
+ version: '5.2'
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '7.0'
@@ -34,30 +34,30 @@ dependencies:
34
34
  name: appraisal
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 2.2.0
39
+ version: '0'
40
40
  type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 2.2.0
46
+ version: '0'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: m
49
49
  requirement: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '1.5'
53
+ version: '0'
54
54
  type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '1.5'
60
+ version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: minitest
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -76,30 +76,30 @@ dependencies:
76
76
  name: rake
77
77
  requirement: !ruby/object:Gem::Requirement
78
78
  requirements:
79
- - - "~>"
79
+ - - ">="
80
80
  - !ruby/object:Gem::Version
81
- version: '11.0'
81
+ version: 12.3.3
82
82
  type: :development
83
83
  prerelease: false
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - "~>"
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '11.0'
88
+ version: 12.3.3
89
89
  - !ruby/object:Gem::Dependency
90
- name: rubocop
90
+ name: standard
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - "~>"
93
+ - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: 0.64.0
95
+ version: '0'
96
96
  type: :development
97
97
  prerelease: false
98
98
  version_requirements: !ruby/object:Gem::Requirement
99
99
  requirements:
100
- - - "~>"
100
+ - - ">="
101
101
  - !ruby/object:Gem::Version
102
- version: 0.64.0
102
+ version: '0'
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: timecop
105
105
  requirement: !ruby/object:Gem::Requirement
@@ -122,10 +122,9 @@ executables: []
122
122
  extensions: []
123
123
  extra_rdoc_files: []
124
124
  files:
125
+ - ".circleci/config.yml"
125
126
  - ".codeclimate.yml"
126
127
  - ".gitignore"
127
- - ".rubocop.yml"
128
- - ".travis.yml"
129
128
  - Appraisals
130
129
  - CHANGELOG.md
131
130
  - CODE_OF_CONDUCT.md
@@ -140,14 +139,11 @@ files:
140
139
  - bin/guard
141
140
  - bin/m
142
141
  - bin/rake
143
- - bin/rubocop
144
142
  - bin/setup
145
- - gemfiles/activesupport_4.1.gemfile
146
- - gemfiles/activesupport_4.2.gemfile
147
- - gemfiles/activesupport_5.0.gemfile
148
- - gemfiles/activesupport_5.1.gemfile
143
+ - bin/standardrb
149
144
  - gemfiles/activesupport_5.2.gemfile
150
145
  - gemfiles/activesupport_6.0.gemfile
146
+ - gemfiles/activesupport_6.1.gemfile
151
147
  - lib/montrose.rb
152
148
  - lib/montrose/chainable.rb
153
149
  - lib/montrose/clock.rb
@@ -166,6 +162,7 @@ files:
166
162
  - lib/montrose/rule.rb
167
163
  - lib/montrose/rule/after.rb
168
164
  - lib/montrose/rule/between.rb
165
+ - lib/montrose/rule/covering.rb
169
166
  - lib/montrose/rule/day_of_month.rb
170
167
  - lib/montrose/rule/day_of_week.rb
171
168
  - lib/montrose/rule/day_of_year.rb
@@ -189,7 +186,7 @@ homepage: https://github.com/rossta/montrose
189
186
  licenses:
190
187
  - MIT
191
188
  metadata: {}
192
- post_install_message:
189
+ post_install_message:
193
190
  rdoc_options: []
194
191
  require_paths:
195
192
  - lib
@@ -197,15 +194,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
197
194
  requirements:
198
195
  - - ">="
199
196
  - !ruby/object:Gem::Version
200
- version: 2.3.0
197
+ version: 2.5.0
201
198
  required_rubygems_version: !ruby/object:Gem::Requirement
202
199
  requirements:
203
200
  - - ">="
204
201
  - !ruby/object:Gem::Version
205
202
  version: '0'
206
203
  requirements: []
207
- rubygems_version: 3.0.3
208
- signing_key:
204
+ rubygems_version: 3.2.8
205
+ signing_key:
209
206
  specification_version: 4
210
207
  summary: Recurring events in Ruby
211
208
  test_files: []
data/.rubocop.yml DELETED
@@ -1,136 +0,0 @@
1
- AllCops:
2
- Exclude:
3
- - 'bin/**/*'
4
- - 'gemfiles/**/*'
5
- - 'tmp/**/*'
6
- TargetRubyVersion: '2.3.0'
7
-
8
- Lint/HandleExceptions:
9
- Exclude:
10
- - 'test/test_helper.rb'
11
-
12
- Metrics/LineLength:
13
- Max: 120
14
-
15
- Metrics/ClassLength:
16
- Max: 300
17
-
18
- Metrics/ModuleLength:
19
- Max: 300
20
-
21
- Metrics/MethodLength:
22
- Max: 100
23
-
24
- Metrics/ParameterLists:
25
- Max: 8
26
-
27
- Metrics/AbcSize:
28
- Enabled: false
29
-
30
- Metrics/CyclomaticComplexity:
31
- Enabled: false
32
-
33
- Metrics/PerceivedComplexity:
34
- Enabled: false
35
-
36
- Metrics/BlockLength:
37
- Enabled: false
38
-
39
- Documentation:
40
- Enabled: false # TODO: Enable again once we have more docs
41
-
42
- Lint/HandleExceptions:
43
- Enabled: false
44
-
45
- Style/GlobalVars:
46
- Exclude:
47
- - spec/support/trace.rb
48
-
49
- Style/SafeNavigation:
50
- Enabled: false
51
-
52
- Style/SpecialGlobalVars:
53
- Enabled: false
54
-
55
- Style/TrivialAccessors:
56
- Enabled: false
57
-
58
- Style/HashSyntax:
59
- Enabled: true
60
-
61
- Naming/MethodName:
62
- Enabled: false
63
-
64
- Layout/AlignParameters:
65
- EnforcedStyle: with_fixed_indentation
66
-
67
- Layout/AccessModifierIndentation:
68
- Enabled: true
69
-
70
- Style/StringLiterals:
71
- EnforcedStyle: double_quotes
72
-
73
- Style/StringLiteralsInInterpolation:
74
- EnforcedStyle: double_quotes
75
-
76
- Layout/ClosingParenthesisIndentation:
77
- Enabled: false
78
-
79
- Layout/EndAlignment:
80
- Enabled: false
81
-
82
- Layout/DefEndAlignment:
83
- Enabled: false
84
-
85
- Style/OneLineConditional:
86
- Enabled: false
87
-
88
- Style/AndOr:
89
- Enabled: false
90
-
91
- Style/Not:
92
- Enabled: false
93
-
94
- Layout/CaseIndentation:
95
- SupportedStyles:
96
- - case
97
- - end
98
- IndentOneStep: false
99
-
100
- Style/PercentLiteralDelimiters:
101
- PreferredDelimiters:
102
- '%w': "[]"
103
- '%W': "[]"
104
-
105
- Layout/AccessModifierIndentation:
106
- EnforcedStyle: indent
107
-
108
- Style/SignalException:
109
- Enabled: false
110
-
111
- Layout/IndentationWidth:
112
- Enabled: false
113
-
114
- Style/TrivialAccessors:
115
- ExactNameMatch: true
116
-
117
- Style/RegexpLiteral:
118
- EnforcedStyle: percent_r
119
-
120
- Layout/DotPosition:
121
- EnforcedStyle: leading
122
-
123
- Naming/VariableNumber:
124
- Enabled: false
125
-
126
- Style/FormatString:
127
- Enabled: false
128
-
129
- Style/MutableConstant:
130
- Enabled: false
131
-
132
- Style/NumericPredicate:
133
- Enabled: false
134
-
135
- Style/SymbolArray:
136
- Enabled: false
data/.travis.yml DELETED
@@ -1,33 +0,0 @@
1
- bundler_args: "--jobs 4 --retry 3"
2
- cache: bundler
3
- gemfile:
4
- - gemfiles/activesupport_6.0.gemfile
5
- - gemfiles/activesupport_5.2.gemfile
6
- - gemfiles/activesupport_5.1.gemfile
7
- - gemfiles/activesupport_5.0.gemfile
8
- - gemfiles/activesupport_4.2.gemfile
9
- - gemfiles/activesupport_4.1.gemfile
10
- language: ruby
11
- matrix:
12
- exclude:
13
- - rvm: 2.3
14
- gemfile: gemfiles/activesupport_6.0.gemfile # Requires ruby 2.5+
15
- - rvm: 2.4
16
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
17
- - rvm: 2.4
18
- gemfile: gemfiles/activesupport_6.0.gemfile # Requires ruby 2.5+
19
- - rvm: 2.5
20
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
21
- - rvm: 2.6
22
- gemfile: gemfiles/activesupport_4.1.gemfile # Requires json 1.8.5+
23
- fast_finish: true
24
- rvm:
25
- - 2.3
26
- - 2.4
27
- - 2.5
28
- - 2.6
29
- sudo: false
30
- before_install:
31
- - gem update --system
32
- - gem --version
33
- - gem install bundler
data/bin/rubocop DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # This file was generated by Bundler.
4
- #
5
- # The application 'rubocop' is installed as part of a gem, and
6
- # this file is here to facilitate running it.
7
- #
8
-
9
- require 'pathname'
10
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
- Pathname.new(__FILE__).realpath)
12
-
13
- require 'rubygems'
14
- require 'bundler/setup'
15
-
16
- load Gem.bin_path('rubocop', 'rubocop')
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 4.1.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 5.0.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"
@@ -1,12 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activesupport", "~> 5.1.0"
6
-
7
- group :development do
8
- gem "coveralls"
9
- gem "yard"
10
- end
11
-
12
- gemspec path: "../"