montrose 0.2.1 → 0.2.2

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
  SHA1:
3
- metadata.gz: 53a2439841915011853eef7bb8fee926939b5335
4
- data.tar.gz: fad0d53e1187f6a077c6410e63016526973633c2
3
+ metadata.gz: edcfdb878952330a2fcc9a78db4f4e7780e643e3
4
+ data.tar.gz: 619002eee045e0d960de83d2bdf78078d7d74883
5
5
  SHA512:
6
- metadata.gz: 6e3b010f94e12c9705278b2fc860df7d0384476a097ab4f77ca3b503fa365272a6e5af2faa34d1ec4e4692f1c192f01179e63637e85d2164fa8bedaa8ad387ac
7
- data.tar.gz: 2466894b36681dc0a8bc723e72b8976dab0c8d7d340f2cf47ec15b7f52bc697b919434aae9e52e13e3e2ea27d1754d47615e05876188afb8a087bd19e0b884b7
6
+ metadata.gz: 7be4df5c22b3677978134b446d30c7e3bac3e33309d35f66253aea93ed62d5e60a17bf9b1a8eb8a3745260ef38dee9f74d7f057a0b2fa181f752e7a3183336b1
7
+ data.tar.gz: 5a1c3a19e2e5fc6a7b64c620be48926d9d257e33b3b2fde91b0588b3d1355052b0b016e011ec7cf396f2046a2e3005b10fab43de0cd648224b55b7b90383f1c4
@@ -1,3 +1,11 @@
1
+ ### 0.2.2 - 2016-02-08
2
+
3
+ * bug fixes
4
+ * Handle `Hash` in `Montrose::Chainable` methods that support varargs
5
+ * enhancements
6
+ * Adds `Montrose.r` method for starting a new recurrence
7
+ * Adds `Chainable` alias methods including `#starts`, `#until`, `#repeat`
8
+
1
9
  ### 0.2.1 - 2016-02-03
2
10
 
3
11
  * bug fixes
data/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
 
8
8
  Montrose is an easy-to-use library for defining recurring events in Ruby. It uses a simple chaining system for building recurrences, inspired heavily by the design principles of [HTTP.rb](https://github.com/httprb/http) and rule definitions available in [Recurrence](https://github.com/fnando/recurrence).
9
9
 
10
+ [Introduction](https://rossta.net/blog/recurring-events-in-ruby.html)
11
+
10
12
  ## Installation
11
13
 
12
14
  Add this line to your application's Gemfile:
@@ -28,6 +30,11 @@ Or install it yourself as:
28
30
  ```ruby
29
31
  require "montrose"
30
32
 
33
+ # a new recurrence
34
+ Montrose.r
35
+ Montrose.recurrence
36
+ Montrose::Recurrence.new
37
+
31
38
  # daily for 10 occurrences
32
39
  Montrose.daily(total: 10)
33
40
 
@@ -144,36 +151,36 @@ Montrose.every(20.minutes, hour: 9..16)
144
151
 
145
152
  # Minutely
146
153
  Montrose.minutely
147
- Montrose::Recurrence.new(every: :minute)
154
+ Montrose.r(every: :minute)
148
155
 
149
156
  Montrose.every(10.minutes)
150
- Montrose::Recurrence.new(every: 10.minutes)
151
- Montrose::Recurrence.new(every: :minute, interval: 10) # every 10 minutes
157
+ Montrose.r(every: 10.minutes)
158
+ Montrose.r(every: :minute, interval: 10) # every 10 minutes
152
159
 
153
160
  Montrose.minutely(until: "9:00 PM")
154
- Montrose::Recurrence.new(every: :minute, until: "9:00 PM")
161
+ Montrose.r(every: :minute, until: "9:00 PM")
155
162
 
156
163
  # Daily
157
164
  Montrose.daily
158
165
  Montrose.every(:day)
159
- Montrose::Recurrence.new(every: :day)
166
+ Montrose.r(every: :day)
160
167
 
161
168
  Montrose.every(9.days)
162
- Montrose::Recurrence.new(every: 9.days)
163
- Montrose::Recurrence.new(every: :day, interval: 9)
169
+ Montrose.r(every: 9.days)
170
+ Montrose.r(every: :day, interval: 9)
164
171
 
165
172
  Montrose.daily(at: "9:00 AM")
166
173
  Montrose.every(:day, at: "9:00 AM")
167
- Montrose::Recurrence.new(every: :day, at: "9:00 AM")
174
+ Montrose.r(every: :day, at: "9:00 AM")
168
175
 
169
176
  Montrose.daily(total: 7)
170
177
  Montrose.every(:day, total: 7)
171
- Montrose::Recurrence.new(every: :day, total: 7)
178
+ Montrose.r(every: :day, total: 7)
172
179
 
173
180
  # Weekly
174
181
  Montrose.weekly
175
182
  Montrose.every(:week)
176
- Montrose::Recurrence.new(every: :week)
183
+ Montrose.r(every: :week)
177
184
 
178
185
  Montrose.every(:week, on: :monday)
179
186
  Montrose.every(:week, on: [:monday, :wednesday, :friday])
@@ -184,7 +191,7 @@ Montrose.weekly(on: :thursday)
184
191
  # Monthly by month day
185
192
  Montrose.monthly(mday: 1) # first of the month
186
193
  Montrose.every(:month, mday: 1)
187
- Montrose::Recurrence.new(every: :month, mday: 1)
194
+ Montrose.r(every: :month, mday: 1)
188
195
 
189
196
  Montrose.monthly(mday: [2, 15]) # 2nd and 15th of the month
190
197
  Montrose.monthly(mday: -3) # third-to-last day of the month
@@ -193,7 +200,7 @@ Montrose.monthly(mday: 10..15) # 10th through the 15th day of the month
193
200
  # Monthly by week day
194
201
  Montrose.monthly(day: :friday, interval: 2) # every Friday every other month
195
202
  Montrose.every(:month, day: :friday, interval: 2)
196
- Montrose::Recurrence.new(every: :month, day: :friday, interval: 2)
203
+ Montrose.r(every: :month, day: :friday, interval: 2)
197
204
 
198
205
  Montrose.monthly(day: { friday: [1] }) # 1st Friday of the month
199
206
  Montrose.monthly(day: { Sunday: [1, -1] }) # first and last Sunday of the month
@@ -203,14 +210,14 @@ Montrose.monthly(mday: 7..13, day: :saturday) # first Saturday that follow the f
203
210
  # Yearly
204
211
  Montrose.yearly
205
212
  Montrose.every(:year)
206
- Montrose::Recurrence.new(every: :year)
213
+ Montrose.r(every: :year)
207
214
 
208
215
  Montrose.yearly(month: [:june, :july]) # yearly in June and July
209
216
  Montrose.yearly(month: 6..8, day: :thursday) # yearly in June, July, August on Thursday
210
217
  Montrose.yearly(yday: [1, 100]) # yearly on the 1st and 100th day of year
211
218
 
212
- Montrose::Recurrence.yearly(on: { january: 31 })
213
- Montrose::Recurrence.new(every: :year, on: { 10 => 31 }, interval: 3)
219
+ Montrose.yearly(on: { january: 31 })
220
+ Montrose.r(every: :year, on: { 10 => 31 }, interval: 3)
214
221
 
215
222
  # TODO: Remove a date in the series with :except date(s)
216
223
  Montrose.daily(:day, except: "2017-01-31")
@@ -235,9 +242,11 @@ r.events.take(10).each { |date| puts date.to_s }
235
242
  r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
236
243
  ```
237
244
 
238
- ## Goals
245
+ ## Why?
246
+
247
+ `Montrose` aims to provide a simple interface for specifying and enumerating recurring events as `Time` objects.
239
248
 
240
- `Montrose` aims to provide a simple interface for specifying and enumerating recurring events as Time objects. To that end, the project intends to:
249
+ More specifically, this project intends to:
241
250
 
242
251
  * embrace Ruby idioms
243
252
  * support Ruby 2.1+
@@ -245,9 +254,112 @@ r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| pu
245
254
  * serialize to yaml, hash, and [ical](http://www.kanzaki.com/docs/ical/rrule.html#basic) formats
246
255
  * be suitable for integration with persistence libraries
247
256
 
248
- What `Montrose` isn't:
257
+ What `Montrose` doesn't do:
249
258
 
250
259
  * support all calendaring use cases under the sun
260
+ * schedule recurring jobs for you. See instead [rufus-scheduler](https://github.com/jmettraux/rufus-scheduler), [sidetiq](https://github.com/tobiassvn/sidetiq), [whenever](https://github.com/javan/whenever)
261
+
262
+ ## Concepts
263
+
264
+ Montrose allows you to easily create "recurrence" objects through chaining:
265
+
266
+ ```ruby
267
+ # Every Monday at 10:30am
268
+ Montrose.weekly.on(:monday).at("10:30 am")
269
+ => #<Montrose::Recurrence...>
270
+ ```
271
+
272
+ Or the constructor hash-syntax:
273
+
274
+ ```ruby
275
+ Montrose.r(every: :week, on: :monday, at: "10:30 am")
276
+ => #<Montrose::Recurrence...>
277
+ ```
278
+
279
+ A Montrose recurrence responds to `#events`, which returns an [`Enumerator`](/blog/what-is-enumerator.html) that can generate timestamps:
280
+
281
+ ```ruby
282
+ r = Montrose.hourly
283
+ => #<Montrose::Recurrence...>
284
+
285
+ r.events
286
+ => #<Enumerator:...>
287
+
288
+ r.events.take(10)
289
+ => [2016-02-03 18:26:08 -0500,
290
+ 2016-02-03 19:26:08 -0500,
291
+ 2016-02-03 20:26:08 -0500,
292
+ 2016-02-03 21:26:08 -0500,
293
+ 2016-02-03 22:26:08 -0500,
294
+ 2016-02-03 23:26:08 -0500,
295
+ 2016-02-04 00:26:08 -0500,
296
+ 2016-02-04 01:26:08 -0500,
297
+ 2016-02-04 02:26:08 -0500,
298
+ 2016-02-04 03:26:08 -0500]
299
+ ```
300
+
301
+ Montrose recurrences are themselves enumerable:
302
+
303
+ ```ruby
304
+ # Every month starting a year from now on Friday the 13th for 5 occurrences
305
+ r = Montrose.monthly.starting(1.year.from_now).on(friday: 13).repeat(5)
306
+
307
+ r.map(&:to_date)
308
+ => [Fri, 13 Oct 2017,
309
+ Fri, 13 Apr 2018,
310
+ Fri, 13 Jul 2018,
311
+ Fri, 13 Sep 2019,
312
+ Fri, 13 Dec 2019]
313
+ ```
314
+
315
+ Each chained recurrence returns a new object so they can be composed and
316
+ merged:
317
+
318
+ ```ruby
319
+ # Every week
320
+ r1 = Montrose.every(:week)
321
+ r2 = Montrose.on([:tuesday, :thursday])
322
+ r3 = Montrose.at("12 pm")
323
+ r4 = Montrose.total(4)
324
+
325
+ r1.merge(r2).merge(r3).merge(r4).to_a
326
+ => [2016-02-04 12:00:00 -0500,
327
+ 2016-02-09 12:00:00 -0500,
328
+ 2016-02-11 12:00:00 -0500,
329
+ 2016-02-16 12:00:00 -0500]
330
+ ```
331
+
332
+ Conceptually, recurrences can represent an infinite sequence. When we say
333
+ simply "every day", there is no implied ending. It's therefore possible to
334
+ create a recurrence that can enumerate forever.
335
+
336
+ ```ruby
337
+ # Every day starting now
338
+ r = Montrose.daily
339
+
340
+ # this expression will never complete, Ctrl-c!
341
+ r.map(&:to_date)
342
+
343
+ # so use your `Enumerable` methods wisely
344
+ r.lazy.map(&:to_date).select { |d| d.mday > 25 }.take(5).to_a
345
+ => [Fri, 26 Feb 2016,
346
+ Sat, 27 Feb 2016,
347
+ Sun, 28 Feb 2016,
348
+ Mon, 29 Feb 2016,
349
+ Sat, 26 Mar 2016]
350
+ ```
351
+
352
+ It's straightforward to convert a recurrence to a hash and back.
353
+
354
+ ```ruby
355
+ opts = Montrose::Recurrence.new(every: 10.minutes).to_h
356
+ => {:every=>:minute, :interval=>10}
357
+
358
+ Montrose::Recurrence.new(opts).take(3)
359
+ => [2016-02-03 19:06:07 -0500,
360
+ 2016-02-03 19:16:07 -0500,
361
+ 2016-02-03 19:26:07 -0500]
362
+ ```
251
363
 
252
364
  ## Inspiration
253
365
 
@@ -17,4 +17,20 @@ require "montrose/version"
17
17
 
18
18
  module Montrose
19
19
  extend Chainable
20
+
21
+ class << self
22
+ # Create a new recurrence from given options
23
+ # An alias to {Montrose::Recurrence.new}
24
+ #
25
+ # @param options [Hash] recurrence options
26
+ #
27
+ # @example
28
+ # Montrose.recurrence(every: :day)
29
+ # Montrose.r(every: :day)
30
+ #
31
+ # @return [Montrose::Recurrence]
32
+ #
33
+ alias recurrence branch
34
+ alias r branch
35
+ end
20
36
  end
@@ -16,6 +16,8 @@ module Montrose
16
16
  # Montrose.every(3.days, starts: 2.days.from_now)
17
17
  # Montrose.every(1.year, until: 10.days.from_now)
18
18
  #
19
+ # @return [Montrose::Recurrence]
20
+ #
19
21
  def every(frequency, options = {})
20
22
  branch options.merge(every: frequency)
21
23
  end
@@ -32,6 +34,8 @@ module Montrose
32
34
  # Montrose.minutely(total: 5)
33
35
  # Montrose.minutely(except: Date.tomorrow)
34
36
  #
37
+ # @return [Montrose::Recurrence]
38
+ #
35
39
  def minutely(options = {})
36
40
  branch options.merge(every: :minute)
37
41
  end
@@ -48,6 +52,8 @@ module Montrose
48
52
  # Montrose.hourly(total: 5)
49
53
  # Montrose.hourly(except: Date.tomorrow)
50
54
  #
55
+ # @return [Montrose::Recurrence]
56
+ #
51
57
  def hourly(options = {})
52
58
  branch options.merge(every: :hour)
53
59
  end
@@ -64,6 +70,8 @@ module Montrose
64
70
  # Montrose.daily(total: 5)
65
71
  # Montrose.daily(except: Date.tomorrow)
66
72
  #
73
+ # @return [Montrose::Recurrence]
74
+ #
67
75
  def daily(options = {})
68
76
  branch options.merge(every: :day)
69
77
  end
@@ -79,6 +87,8 @@ module Montrose
79
87
  # Montrose.weekly(on: :saturday, interval: 2)
80
88
  # Montrose.weekly(on: :saturday, total: 5)
81
89
  #
90
+ # @return [Montrose::Recurrence]
91
+ #
82
92
  def weekly(options = {})
83
93
  branch options.merge(every: :week)
84
94
  end
@@ -92,6 +102,8 @@ module Montrose
92
102
  # Montrose.monthly(mday: -3) # third-to-last day of the month
93
103
  # Montrose.monthly(mday: 10..15) # 10th through the 15th day of the month
94
104
  #
105
+ # @return [Montrose::Recurrence]
106
+ #
95
107
  def monthly(options = {})
96
108
  branch options.merge(every: :month)
97
109
  end
@@ -107,6 +119,8 @@ module Montrose
107
119
  # Montrose.yearly(on: [:january, 14], interval: 2)
108
120
  # Montrose.yearly(on: [:january, 14], total: 5)
109
121
  #
122
+ # @return [Montrose::Recurrence]
123
+ #
110
124
  def yearly(options = {})
111
125
  branch options.merge(every: :year)
112
126
  end
@@ -118,9 +132,12 @@ module Montrose
118
132
  # @example
119
133
  # Montrose.daily.starting(Date.tomorrow)
120
134
  #
121
- def starting(starts_at)
135
+ # @return [Montrose::Recurrence]
136
+ #
137
+ def starts(starts_at)
122
138
  merge(starts: starts_at)
123
139
  end
140
+ alias starting starts
124
141
 
125
142
  # Create a recurrence ending at given timestamp.
126
143
  #
@@ -129,9 +146,12 @@ module Montrose
129
146
  # @example
130
147
  # Montrose.daily.ending(1.year.from_now)
131
148
  #
132
- def ending(ends_at)
149
+ # @return [Montrose::Recurrence]
150
+ #
151
+ def until(ends_at)
133
152
  merge(until: ends_at)
134
153
  end
154
+ alias ending until
135
155
 
136
156
  # Create a recurrence occurring during date range.
137
157
  #
@@ -140,6 +160,8 @@ module Montrose
140
160
  # @example
141
161
  # Montrose.weekly.between(Date.today..Date.new(2016, 3, 15))
142
162
  #
163
+ # @return [Montrose::Recurrence]
164
+ #
143
165
  def between(date_range)
144
166
  merge(between: date_range)
145
167
  end
@@ -152,6 +174,8 @@ module Montrose
152
174
  # Montrose.weekly.on(:friday)
153
175
  # Montrose.monthly.on(friday: 13)
154
176
  #
177
+ # @return [Montrose::Recurrence]
178
+ #
155
179
  def on(day)
156
180
  merge(on: day)
157
181
  end
@@ -164,6 +188,8 @@ module Montrose
164
188
  # Montrose.daily.at("12pm")
165
189
  # Montrose.daily.at("9:37am")
166
190
  #
191
+ # @return [Montrose::Recurrence]
192
+ #
167
193
  def at(time)
168
194
  merge(at: time)
169
195
  end
@@ -177,6 +203,8 @@ module Montrose
177
203
  # Montrose.daily.day_of_month([1, -1])
178
204
  # Montrose.daily.day_of_month(2..8)
179
205
  #
206
+ # @return [Montrose::Recurrence]
207
+ #
180
208
  def day_of_month(days, *extras)
181
209
  merge(mday: days.array_concat(extras))
182
210
  end
@@ -191,6 +219,8 @@ module Montrose
191
219
  # Montrose.daily.day_of_week(:monday, :tuesday)
192
220
  # Montrose.daily.day_of_week(2..5)
193
221
  #
222
+ # @return [Montrose::Recurrence]
223
+ #
194
224
  def day_of_week(weekdays, *extras)
195
225
  merge(day: weekdays.array_concat(extras))
196
226
  end
@@ -205,6 +235,8 @@ module Montrose
205
235
  # Montrose.daily.day_of_year([1, 10, 100])
206
236
  # Montrose.daily.day_of_year(20..50)
207
237
  #
238
+ # @return [Montrose::Recurrence]
239
+ #
208
240
  def day_of_year(days, *extras)
209
241
  merge(yday: days.array_concat(extras))
210
242
  end
@@ -219,6 +251,8 @@ module Montrose
219
251
  # Montrose.hourly.hour_of_day(15)
220
252
  # Montrose.hourly.hour_of_day(6..10)
221
253
  #
254
+ # @return [Montrose::Recurrence]
255
+ #
222
256
  def hour_of_day(hours, *extras)
223
257
  merge(hour: hours.array_concat(extras))
224
258
  end
@@ -233,6 +267,8 @@ module Montrose
233
267
  # Montrose.monthly.month_of_year([2, 5])
234
268
  # Montrose.monthly.month_of_year(2..5)
235
269
  #
270
+ # @return [Montrose::Recurrence]
271
+ #
236
272
  def month_of_year(months, *extras)
237
273
  merge(month: months.array_concat(extras))
238
274
  end
@@ -247,6 +283,8 @@ module Montrose
247
283
  # Montrose.weekly.week_of_year([2, 5])
248
284
  # Montrose.weekly.week_of_year(2..5)
249
285
  #
286
+ # @return [Montrose::Recurrence]
287
+ #
250
288
  def week_of_year(weeks, *extras)
251
289
  merge(week: weeks.array_concat(extras))
252
290
  end
@@ -259,31 +297,36 @@ module Montrose
259
297
  # @example
260
298
  # Montrose.daily.total(10)
261
299
  #
300
+ # @return [Montrose::Recurrence]
301
+ #
262
302
  def total(total)
263
303
  merge(total: total)
264
304
  end
305
+ alias repeat total
265
306
 
266
307
  # Create a new recurrence combining options of self
267
308
  # and other. The value of entries with duplicate
268
309
  # keys will be those of other
269
310
  #
270
- # @param opts [Hash,Montrose::Recurrence] other options or recurrence
311
+ # @param other [Hash,Montrose::Recurrence] other options or recurrence
271
312
  #
272
313
  # @example
273
314
  # Montrose.daily.total(10)
274
315
  #
316
+ # @return [Montrose::Recurrence]
317
+ #
275
318
  def merge(other = {})
276
319
  branch default_options.merge(other)
277
320
  end
278
321
 
279
322
  # @private
280
- def default_options
281
- @default_options ||= Montrose::Options.new
323
+ def branch(options)
324
+ Montrose::Recurrence.new(options)
282
325
  end
283
326
 
284
327
  # @private
285
- def branch(options)
286
- Montrose::Recurrence.new(options)
328
+ def default_options
329
+ @default_options ||= Montrose::Options.new
287
330
  end
288
331
  end
289
332
  end
@@ -36,6 +36,14 @@ module Montrose
36
36
  @default_options = Montrose::Options.new(opts)
37
37
  end
38
38
 
39
+ # Returns an enumerator for iterating over
40
+ # timestamps in the recurrence
41
+ #
42
+ # @example
43
+ # recurrence.events
44
+ #
45
+ # @return [Enumerator] a enumerator of recurrence timestamps
46
+ #
39
47
  def events
40
48
  event_enum
41
49
  end
@@ -44,6 +52,11 @@ module Montrose
44
52
  events.each(&block)
45
53
  end
46
54
 
55
+ # Returns a hash of the options used to create
56
+ # the recurrence
57
+ #
58
+ # @return [Hash] hash of recurrence options
59
+ #
47
60
  def to_hash
48
61
  default_options.to_hash
49
62
  end
@@ -6,6 +6,14 @@ module Montrose
6
6
  Array(self) + other
7
7
  end
8
8
  end
9
+
10
+ refine Hash do
11
+ # array concat for Hash not supported
12
+ # so we just return self
13
+ def array_concat(_other)
14
+ self
15
+ end
16
+ end
9
17
  end
10
18
  end
11
19
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Montrose
3
- VERSION = "0.2.1".freeze
3
+ VERSION = "0.2.2".freeze
4
4
  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.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ross Kaffenberger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-03 00:00:00.000000000 Z
11
+ date: 2016-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
186
  version: '0'
187
187
  requirements: []
188
188
  rubyforge_project:
189
- rubygems_version: 2.4.5.1
189
+ rubygems_version: 2.5.1
190
190
  signing_key:
191
191
  specification_version: 4
192
192
  summary: Recurring events in Ruby