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.
data/Cargo.toml ADDED
@@ -0,0 +1,8 @@
1
+ [workspace]
2
+ resolver = "2"
3
+ members = ["ext/reprise"]
4
+
5
+ [profile.release]
6
+ codegen-units = 1 # more llvm optimizations
7
+ debug = 2 # make perfomance engineers happy
8
+ lto = "thin" # cross-crate inlining
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