reprise 0.1.0-x86_64-linux
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 +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
|
+
[](https://github.com/jordanhiltunen/reprise/actions/workflows/test.yml) | [](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
|