reprise 0.1.0-aarch64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Cargo.lock +856 -0
- data/Cargo.toml +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +362 -0
- data/Rakefile +48 -0
- data/lib/reprise/3.1/reprise.so +0 -0
- data/lib/reprise/3.2/reprise.so +0 -0
- data/lib/reprise/3.3/reprise.so +0 -0
- data/lib/reprise/core/occurrence.rb +21 -0
- data/lib/reprise/schedule.rb +299 -0
- data/lib/reprise/time_of_day.rb +75 -0
- data/lib/reprise/time_zone_identifier.rb +51 -0
- data/lib/reprise/version.rb +5 -0
- data/lib/reprise.rb +10 -0
- metadata +251 -0
data/Cargo.toml
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Jordan Hiltunen
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
# Reprise
|
2
|
+
|
3
|
+
[![Tests](https://github.com/jordanhiltunen/reprise/actions/workflows/test.yml/badge.svg)](https://github.com/jordanhiltunen/reprise/actions/workflows/test.yml) | [![Test CRuby Gem Build](https://github.com/jordanhiltunen/reprise/actions/workflows/cruby-build-and-install.yml/badge.svg)](https://github.com/jordanhiltunen/reprise/actions/workflows/cruby-build-and-install.yml)
|
4
|
+
|
5
|
+
Reprise is an experimental performance-first Ruby gem that provides support for defining event recurrence
|
6
|
+
rules and generating & querying their future occurrences. Depending on your use case,
|
7
|
+
you may benefit from a speedup of up to 1000x relative to other recurrence rule gems: because
|
8
|
+
Reprise is a thin Ruby wrapper around an extension written in Rust, we are able to offer a level
|
9
|
+
of speed and conservative memory use that we would otherwise be unable to accomplish in
|
10
|
+
pure Ruby alone.
|
11
|
+
|
12
|
+
For more on why and when you might want to use this gem, see [Why Reprise?](#why-reprise).
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "reprise"
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Initialize a new schedule
|
25
|
+
|
26
|
+
All schedules need to be initialized with a `start_time` and `end_time`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
may_26_2015_four_thirty_pm_in_rome = Time.parse("2015-05-26 10:30:45").in_time_zone("Rome")
|
30
|
+
# => Tue, 26 May 2015 16:30:45.000000000 CEST +02:00
|
31
|
+
|
32
|
+
schedule = Reprise::Schedule.new(
|
33
|
+
starts_at: may_26_2015_four_thirty_pm_in_rome,
|
34
|
+
ends_at: may_26_2015_four_thirty_pm_in_rome + 1.year
|
35
|
+
)
|
36
|
+
````
|
37
|
+
|
38
|
+
If your `starts_at` is an `ActiveSupport::TimeWithZone`, your schedule will infer its time zone from
|
39
|
+
that value. You can also explicitly pass in a `time_zone` if your schedule bookends don't have time zone
|
40
|
+
information (e.g. if you are passing in simple `Time` instances):
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
schedule = Reprise::Schedule.new(
|
44
|
+
starts_at: may_26_2015_ten_thirty_pm_utc,
|
45
|
+
ends_at: may_26_2015_ten_thirty_pm_utc + 1.year,
|
46
|
+
time_zone: "Rome"
|
47
|
+
)
|
48
|
+
```
|
49
|
+
|
50
|
+
### Add recurring event series
|
51
|
+
|
52
|
+
You can add any number of recurring series to the schedule via `repeat_*` methods:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
schedule.repeat_weekly(:sunday, duration_in_seconds: 15.minutes)
|
56
|
+
schedule.occurrences.size
|
57
|
+
# => 52
|
58
|
+
```
|
59
|
+
|
60
|
+
#### Customizing the time of day for a recurring series' occurrences
|
61
|
+
|
62
|
+
By default, all series that advance in units of a day or greater will use the time that your schedule
|
63
|
+
started at as the local time for each future occurrence:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
first_occurrence = schedule.occurrences.first
|
67
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-31T14:30:45+00:00" end_time="2015-05-31T14:45:45+00:00" label="nil">
|
68
|
+
first_occurrence.start_time.in_time_zone("Rome")
|
69
|
+
# => Sun, 31 May 2015 16:30:45.000000000 CEST +02:00 # <- 4:30 PM
|
70
|
+
```
|
71
|
+
|
72
|
+
You can override this behaviour, and set a custom time of day for each of your series,
|
73
|
+
either by passing an hour/minute/second hash to `time_of_day`:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
schedule.repeat_weekly(:sunday, time_of_day: { hour: 9, minute: 30 }, duration_in_seconds: 60)
|
77
|
+
first_occurrence = schedule.occurrences.first
|
78
|
+
# => => <Reprise::Core::Occurrence start_time="2015-05-31T07:30:00+00:00" end_time="2015-05-31T07:31:00+00:00" label="nil">
|
79
|
+
first_occurrence.start_time.in_time_zone("Rome")
|
80
|
+
# => Sun, 31 May 2015 09:30:00.000000000 CEST +02:00
|
81
|
+
```
|
82
|
+
|
83
|
+
Or, by passing a `Time` object instead:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
ten_forty_five_pm_in_rome = Time.parse("2015-05-27 04:45:00").in_time_zone("Rome")
|
87
|
+
schedule.repeat_weekly(:tuesday, time_of_day: ten_forty_five_pm_in_rome, duration_in_seconds: 60)
|
88
|
+
# => <Reprise::Core::Occurrence start_time="2015-06-02T08:45:00+00:00" end_time="2015-06-02T08:46:00+00:00" label="nil">
|
89
|
+
first_occurrence.start_time.in_time_zone("Rome")
|
90
|
+
# => Tue, 02 Jun 2015 10:45:00.000000000 CEST +02:00
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Customizing the bookends of a recurring series
|
94
|
+
|
95
|
+
By default, all series will inherit the `starts_at` and `ends_at` values of their parent schedule:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
schedule = Reprise::Schedule.new(
|
99
|
+
starts_at: may_26_2015_four_thirty_pm_in_rome,
|
100
|
+
ends_at: may_26_2015_four_thirty_pm_in_rome + 200.days
|
101
|
+
)
|
102
|
+
|
103
|
+
schedule.repeat_weekly(:wednesday, time_of_day: { hour: 9, minute: 30 }, duration_in_seconds: 10.minutes)
|
104
|
+
occurrences = schedule.occurrences
|
105
|
+
puts occurrences.size
|
106
|
+
# => 29
|
107
|
+
puts (occurrences.last.start_time.to_date - occurrences.first.start_time.to_date).to_i
|
108
|
+
# => 196 # days
|
109
|
+
```
|
110
|
+
|
111
|
+
You can also specify the bookends of each recurring series:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
schedule.repeat_weekly(
|
115
|
+
:friday,
|
116
|
+
time_of_day: { hour: 4, minute: 01 },
|
117
|
+
duration_in_seconds: 40.minutes,
|
118
|
+
starts_at: may_26_2015_four_thirty_pm_in_rome + 40.days,
|
119
|
+
ends_at: may_26_2015_four_thirty_pm_in_rome + 100.days
|
120
|
+
)
|
121
|
+
occurrences = schedule.occurrences
|
122
|
+
puts occurrences.size
|
123
|
+
# => 8
|
124
|
+
puts occurrences.first.start_time.in_time_zone("Rome")
|
125
|
+
# 2015-07-10 04:01:00 +0200
|
126
|
+
puts occurrences.last.start_time.in_time_zone("Rome")
|
127
|
+
# 2015-08-28 04:01:00 +0200
|
128
|
+
puts (occurrences.last.start_time.to_date - occurrences.first.start_time.to_date).to_i
|
129
|
+
# => 49 # days
|
130
|
+
```
|
131
|
+
|
132
|
+
There are many recurring series that you can create; `#repeat_minutely`, `#repeat_hourly`,
|
133
|
+
`#repeat_daily`, `#repeat_weekly`, `#repeat_monthly_by_day`, and `#repeat_monthly_by_nth_weekday`.
|
134
|
+
|
135
|
+
For more information on each method, see the docs.
|
136
|
+
|
137
|
+
#### Adding labels to the occurrences of each series
|
138
|
+
|
139
|
+
If you need to disambiguate occurrences from different series in the same schedule,
|
140
|
+
you can add an optional label:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
schedule.repeat_daily(label: "Coffee Time", time_of_day: { hour: 8 }, duration_in_seconds: 15.minutes)
|
144
|
+
schedule.repeat_daily(label: "Tea Time", interval: 3, time_of_day: { hour: 9 }, duration_in_seconds: 10.minutes)
|
145
|
+
schedule.occurrences.take(7).map { |o| puts o.inspect }
|
146
|
+
# => <Reprise::Core::Occurrence label="Coffee Time" start_time="2015-05-27T06:00:00+00:00" end_time="2015-05-27T06:15:00+00:00">
|
147
|
+
# => <Reprise::Core::Occurrence label="Tea Time" start_time="2015-05-27T07:00:00+00:00" end_time="2015-05-27T07:10:00+00:00">
|
148
|
+
# => <Reprise::Core::Occurrence label="Coffee Time" start_time="2015-05-28T06:00:00+00:00" end_time="2015-05-28T06:15:00+00:00">
|
149
|
+
# => <Reprise::Core::Occurrence label="Coffee Time" start_time="2015-05-29T06:00:00+00:00" end_time="2015-05-29T06:15:00+00:00">
|
150
|
+
# => <Reprise::Core::Occurrence label="Coffee Time" start_time="2015-05-30T06:00:00+00:00" end_time="2015-05-30T06:15:00+00:00">
|
151
|
+
# => <Reprise::Core::Occurrence label="Tea Time" start_time="2015-05-30T07:00:00+00:00" end_time="2015-05-30T07:10:00+00:00">
|
152
|
+
# => <Reprise::Core::Occurrence label="Coffee Time" start_time="2015-05-31T06:00:00+00:00" end_time="2015-05-31T06:15:00+00:00">
|
153
|
+
```
|
154
|
+
|
155
|
+
#### Excluding time intervals from the schedule's occurrences
|
156
|
+
|
157
|
+
If you have other non-recurring "schedule entries" in your domain that can collide with your recurring series'
|
158
|
+
occurrences and need to be excluded, you can add exclusions to your schedule before generating occurrences:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
schedule.repeat_daily(label: "Standing Meeting", ends_at: may_26_2015_four_thirty_pm_in_rome + 5.days, duration_in_seconds: 15.minutes)
|
162
|
+
schedule.occurrences.map { |o| puts o.inspect }
|
163
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-26T14:30:45+00:00" end_time="2015-05-26T14:45:45+00:00" label="Standing Meeting">
|
164
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-27T14:30:45+00:00" end_time="2015-05-27T14:45:45+00:00" label="Standing Meeting">
|
165
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-28T14:30:45+00:00" end_time="2015-05-28T14:45:45+00:00" label="Standing Meeting">
|
166
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-29T14:30:45+00:00" end_time="2015-05-29T14:45:45+00:00" label="Standing Meeting">
|
167
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-30T14:30:45+00:00" end_time="2015-05-30T14:45:45+00:00" label="Standing Meeting">
|
168
|
+
|
169
|
+
# You don't need to specify entire days; you can pass time intervals as narrow or wide as you like.
|
170
|
+
schedule.add_exclusion(
|
171
|
+
starts_at: (may_26_2015_four_thirty_pm_in_rome + 2.days).beginning_of_day,
|
172
|
+
ends_at: (may_26_2015_four_thirty_pm_in_rome + 2.days).end_of_day
|
173
|
+
)
|
174
|
+
|
175
|
+
schedule.occurrences.map { |o| puts o.inspect }
|
176
|
+
# N.B. The occurrence on 2015-05-28 is now excluded.
|
177
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-26T14:30:45+00:00" end_time="2015-05-26T14:45:45+00:00" label="Standing Meeting">
|
178
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-27T14:30:45+00:00" end_time="2015-05-27T14:45:45+00:00" label="Standing Meeting">
|
179
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-29T14:30:45+00:00" end_time="2015-05-29T14:45:45+00:00" label="Standing Meeting">
|
180
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-30T14:30:45+00:00" end_time="2015-05-30T14:45:45+00:00" label="Standing Meeting">
|
181
|
+
```
|
182
|
+
|
183
|
+
#### Querying for occurrences within a given time interval
|
184
|
+
|
185
|
+
After constructing your schedule, you can query for the occurrences within any interval
|
186
|
+
of time:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
schedule.repeat_daily(label: "Standing Meeting", ends_at: may_26_2015_four_thirty_pm_in_rome + 5.days, duration_in_seconds: 15.minutes)
|
190
|
+
|
191
|
+
schedule.occurs_between?(
|
192
|
+
may_26_2015_four_thirty_pm_in_rome + 2.days,
|
193
|
+
may_26_2015_four_thirty_pm_in_rome + 3.days,
|
194
|
+
)
|
195
|
+
# => true
|
196
|
+
|
197
|
+
schedule.occurrences_between(
|
198
|
+
may_26_2015_four_thirty_pm_in_rome + 2.days,
|
199
|
+
may_26_2015_four_thirty_pm_in_rome + 3.days,
|
200
|
+
).map { |o| puts o.inspect }
|
201
|
+
# => <Reprise::Core::Occurrence start_time="2015-05-28T14:30:45+00:00" end_time="2015-05-28T14:45:45+00:00" label="Standing Meeting">
|
202
|
+
```
|
203
|
+
|
204
|
+
Both `#occurs_between?` and `#occurrences_between` also support an optional `include_overlapping`
|
205
|
+
argument, which allows you to search for occurrences that not only occur entirely within a given
|
206
|
+
interval, but also those that partially overlap.
|
207
|
+
|
208
|
+
## Why Reprise?
|
209
|
+
|
210
|
+
### First, consider the alternatives
|
211
|
+
|
212
|
+
Reprise is particularly indebted to [ice_cube](https://github.com/ice-cube-ruby/ice_cube) and [Montrose](https://github.com/rossta/montrose), projects that have served the Ruby community for years.
|
213
|
+
They are stable and battle-tested. If you have no actual business need for the kind of performance that Reprise aims for,
|
214
|
+
you would probably be much better served by choosing one of those two gems instead.
|
215
|
+
|
216
|
+
### Tradeoffs
|
217
|
+
|
218
|
+
- **Flexibility.** Because Reprise calls into a strictly-typed extension, its current public interface is very much "one-size-fits-all";
|
219
|
+
the influence of Rust leaks into its Ruby API. Alternative gems offer much more flexible APIs that support a variety
|
220
|
+
of more idiomatic calling conventions: they have better, more forgiving ergonomics. Reprise may invest more efforts
|
221
|
+
here in the future, but not until we have landed on a feature-complete, performant core - our primary design goal.
|
222
|
+
Until then, out API will remain sparse but sufficient.
|
223
|
+
- **Stability.** Reprise is still experimental; we do not yet have a `1.0.0` release or a public roadmap. Breaking changes
|
224
|
+
may be frequent across releases. If you do not want to pin Reprise to a specific version and want a library that you can
|
225
|
+
upgrade without reviewing the changelog, you may want to consider an alternative for now.
|
226
|
+
- **Serialization.** We do not yet offer any form of persistence support (e.g. parsing from / serializing to yaml / hash
|
227
|
+
/ ical / others).
|
228
|
+
|
229
|
+
### Advantages
|
230
|
+
|
231
|
+
#### Performance
|
232
|
+
|
233
|
+
A truism in the Ruby community is that "Ruby is slow, but that doesn't matter for you":
|
234
|
+
> So, often it hardly matters that [Ruby] is slow, because your use-case does not need the scale,
|
235
|
+
> speed, or throughput that Ruby chokes on. Or because the trade-offs are worth it: Often the
|
236
|
+
> quicker development, cheaper development, faster time-to-market etc is worth the extra resources
|
237
|
+
> (servers, hardware, SAAS) you must throw at your app to keep it performing acceptable.
|
238
|
+
> https://berk.es/2022/08/09/ruby-slow-database-slow/
|
239
|
+
|
240
|
+
This is often delightfully true, until on the odd occasion Ruby's speed requires that a straightforward feature
|
241
|
+
be implemented in a contorted or meaningfully-constrained way in order to work.
|
242
|
+
|
243
|
+
Reprise aims to solve a niche problem: cutting the latency of recurring schedule generation when it is in
|
244
|
+
the critical path without imposing an additional complexity burden on clients. For most applications
|
245
|
+
that deal with recurring events, this is probably not a problem. But if it is, we want to buy you more
|
246
|
+
effectively-free per-request headroom that you can spend in simple Ruby to improve or ship a feature that
|
247
|
+
you otherwise couldn't.
|
248
|
+
|
249
|
+
##### Benchmarks
|
250
|
+
|
251
|
+
You can run benchmarks locally via `bundle exec rake benchmark`; additionally,
|
252
|
+
to view our recent benchmarking results in CI, see [past runs of our Benchmark worfklow](https://github.com/jordanhiltunen/reprise/actions/workflows/benchmark.yml).
|
253
|
+
|
254
|
+
Below is a sample local benchmark run taken on the following development machine:
|
255
|
+
|
256
|
+
| System Detail | Value |
|
257
|
+
|---------------|------------------------------------------------------------|
|
258
|
+
| OS | macOS 14.5 (23F79) |
|
259
|
+
| CPU | 2.4 GHz 8-Core Intel i9 |
|
260
|
+
| Memory | 64GB 2667 MHz DDRr |
|
261
|
+
| Ruby Version | 3.3.2 (2024-05-30 revision e5a195edf6) \[x86_64-darwin23\] |
|
262
|
+
| Rust Version | rustc 1.79.0 (129f3b996 2024-06-10) |
|
263
|
+
|
264
|
+
`benchmark-ips`: (higher is better)
|
265
|
+
```
|
266
|
+
ruby 3.3.2 (2024-05-30 revision e5a195edf6) [x86_64-darwin23]
|
267
|
+
Warming up --------------------------------------
|
268
|
+
IceCube 1.000 i/100ms
|
269
|
+
Montrose 1.000 i/100ms
|
270
|
+
Reprise 1.197k i/100ms
|
271
|
+
Calculating -------------------------------------
|
272
|
+
IceCube 10.259 (± 9.7%) i/s - 52.000 in 5.081337s
|
273
|
+
Montrose 14.986 (± 6.7%) i/s - 75.000 in 5.022293s
|
274
|
+
Reprise 13.127k (±19.9%) i/s - 63.441k in 5.047277s
|
275
|
+
```
|
276
|
+
|
277
|
+
`benchmark-memory`: (lower is better)
|
278
|
+
```
|
279
|
+
Calculating -------------------------------------
|
280
|
+
IceCube 10.986M memsize ( 1.040k retained)
|
281
|
+
202.268k objects ( 14.000 retained)
|
282
|
+
5.000 strings ( 1.000 retained)
|
283
|
+
Montrose 9.799M memsize ( 3.792k retained)
|
284
|
+
157.675k objects ( 13.000 retained)
|
285
|
+
34.000 strings ( 7.000 retained)
|
286
|
+
Reprise 14.872k memsize ( 0.000 retained)
|
287
|
+
310.000 objects ( 0.000 retained)
|
288
|
+
0.000 strings ( 0.000 retained)
|
289
|
+
|
290
|
+
Comparison:
|
291
|
+
Reprise: 14872 allocated
|
292
|
+
Montrose: 9799288 allocated - 658.91x more
|
293
|
+
IceCube: 10986192 allocated - 738.72x more
|
294
|
+
```
|
295
|
+
|
296
|
+
#### Exclusion Handling
|
297
|
+
|
298
|
+
Beyond performance, one area where Reprise shines is in schedule exclusion handling:
|
299
|
+
|
300
|
+
Suppose you have a recurring series that occurs every Monday from 12:30 PM - 1:00 PM. You need to generate
|
301
|
+
future occurrences of this series, excluding those that do not conflict with pre-existing, non-recurring
|
302
|
+
schedule entries; e.g. on one particular Monday, you have schedule entries at 9:00 AM - 9:30 AM, 12:15 PM - 1:00 PM,
|
303
|
+
and 3:30 - 4:30 PM.
|
304
|
+
|
305
|
+
How do you filter out recurring series occurrences that conflict with other schedule entries that exist
|
306
|
+
in your application?
|
307
|
+
|
308
|
+
At time of writing, alternative gems' solutions to this problem are all unfortunately lacking:
|
309
|
+
- **None**: It is entirely the responsibility of the client application to handle occurrence exclusions,
|
310
|
+
despite this logic being core to the domain of recurring schedule management.
|
311
|
+
- **Date-based exclusion**: Client applications can pass specific dates when occurrences should be excluded.
|
312
|
+
This is not sufficient except for in the most simple of circumstances. Again, consider our hypothetical
|
313
|
+
Monday @ 12:30 PM recurring series: being able to exclude a specific _date_ from your recurrence rule still
|
314
|
+
requires you to implement your own overlap detection logic to determine whether an occurrence actually conflicts with
|
315
|
+
the start and end times of a schedule entry on a given date.
|
316
|
+
|
317
|
+
These limitations can push a significant amount of schedule recurrence logic onto client applications;
|
318
|
+
Reprise improves on this significantly by offering an API to define exclusions with start and end times; Reprise
|
319
|
+
then determines whether any given occurrence overlaps with an exclusion that you have defined, and filters
|
320
|
+
them out during occurrence generation accordingly.
|
321
|
+
|
322
|
+
## Acknowledgements
|
323
|
+
|
324
|
+
Reprise, a Ruby gem with a Rust core, is only possible because of the foundation laid by the excellent [Magnus](https://github.com/matsadler/magnus) project.
|
325
|
+
|
326
|
+
## Development
|
327
|
+
|
328
|
+
To get started after checking out the repo:
|
329
|
+
|
330
|
+
```bash
|
331
|
+
$ bin/setup # install dependencies
|
332
|
+
$ rake compile:reprise # recompile the extension after making changes to Rust files
|
333
|
+
$ rake spec # run the test suite
|
334
|
+
$ rake benchmark # run the benchmarks
|
335
|
+
```
|
336
|
+
|
337
|
+
### Generating Documentation
|
338
|
+
|
339
|
+
Reprise' public Ruby API is documented using [YARD](https://yardoc.org/guides/).
|
340
|
+
To regenerate the documentation after changing any of the annotations, run `rake yard`
|
341
|
+
and commit the changes.
|
342
|
+
|
343
|
+
## Contributing
|
344
|
+
|
345
|
+
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
346
|
+
|
347
|
+
- :white_check_mark: Report or fix bugs
|
348
|
+
- :white_check_mark: Suggest features
|
349
|
+
- :white_check_mark: Write or improve documentation
|
350
|
+
- :yellow_circle: Submit pull requests (please reach out first)
|
351
|
+
|
352
|
+
We plan on welcoming pull requests once we settle on an initial `1.0.0`; until then, we anticipate
|
353
|
+
a lot of early experimentation, and we will have more time to collaborate and welcome pull requests
|
354
|
+
once we've hit that milestone.
|
355
|
+
|
356
|
+
## License
|
357
|
+
|
358
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
359
|
+
|
360
|
+
## Code of Conduct
|
361
|
+
|
362
|
+
Everyone interacting in the Reprise project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jordanhiltunen/reprise/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/extensiontask"
|
5
|
+
require "rake/testtask"
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
require "yard"
|
8
|
+
|
9
|
+
Rake.add_rakelib("lib/tasks")
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
task default: :spec
|
13
|
+
|
14
|
+
task :dev do
|
15
|
+
ENV["RB_SYS_CARGO_PROFILE"] = "dev"
|
16
|
+
end
|
17
|
+
|
18
|
+
CROSS_PLATFORMS = %w[
|
19
|
+
x86_64-linux
|
20
|
+
x86_64-linux-musl
|
21
|
+
aarch64-linux
|
22
|
+
x86_64-darwin
|
23
|
+
arm64-darwin
|
24
|
+
x64-mingw-ucrt
|
25
|
+
x64-mingw32
|
26
|
+
]
|
27
|
+
|
28
|
+
gemspec = Bundler.load_gemspec("reprise.gemspec")
|
29
|
+
Rake::ExtensionTask.new("reprise", gemspec) do |ext|
|
30
|
+
ext.lib_dir = "lib/reprise"
|
31
|
+
ext.cross_compile = true
|
32
|
+
ext.cross_platform = CROSS_PLATFORMS
|
33
|
+
ext.cross_compiling do |spec|
|
34
|
+
spec.dependencies.reject! { |dep| dep.name == "rb_sys" }
|
35
|
+
spec.files.reject! { |file| File.fnmatch?("ext/*", file, File::FNM_EXTGLOB) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
YARD::Rake::YardocTask.new do |t|
|
40
|
+
t.options = %w[--output-dir ./docs]
|
41
|
+
end
|
42
|
+
|
43
|
+
task :remove_ext do
|
44
|
+
path = "lib/reprise/reprise.bundle"
|
45
|
+
File.unlink(path) if File.exist?(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
Rake::Task["build"].enhance([:remove_ext])
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Reprise
|
4
|
+
module Core
|
5
|
+
# An Occurrence represents a single instance of a recurring series belong to a schedule.
|
6
|
+
#
|
7
|
+
# @private This class definition is open-classed only for the purposes
|
8
|
+
# of adding documentation; it is defined dynamically within
|
9
|
+
# the Rust extension.
|
10
|
+
class Occurrence
|
11
|
+
# @!attribute [r] start_time
|
12
|
+
# @return [Time] The start time of the occurrence, given in the current system time zone.
|
13
|
+
# @!attribute [r] end_time
|
14
|
+
# @return [Time] The end time of the occurrence, given in the current system time zone.
|
15
|
+
# @!attribute [r] label
|
16
|
+
# @return [String, nil] The label given to the recurring series from which the
|
17
|
+
# occurrence was generated (if present). Can be used to disambiguate occurrences
|
18
|
+
# from different series after generating the schedule's occurrences.
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|