rem2ics 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bbb257ef75bfa0156a8dfeda780f6c6bd5160e5d025f31506fa8f6c47f0e723f
4
+ data.tar.gz: fd1fb4f2a3491f1ebb1c3fc9f0bbafefa3cd471e805acb7e1db29489b98860f6
5
+ SHA512:
6
+ metadata.gz: d7cee2cbe190a2def235f39629d2ca9e45d5ab3d1b3b523872ca4abef3bdbe36e4f08052eac90372546691380fbca3c1085e7284ab2fe8a689102f6d32249dfd
7
+ data.tar.gz: 8743f0042e255453a830e0b1c349b12ac4f350e2b5f28a0da3449cf7bd25387512325586b0a322daf22671dec095db032e77a01b3116c95606bbc4caf11090ad
data/LICENSE ADDED
@@ -0,0 +1,328 @@
1
+ remind-rb and rem2ics are distributed under the GNU General Public License,
2
+ Version 2 -- the licence of Remind, which they are built from and cannot be
3
+ separated from.
4
+
5
+ Why it is GPL-2.0-only and not something more permissive:
6
+
7
+ * remind-rb vendors the complete Remind source tree (remind-v*/), compiles
8
+ it, and distributes the result. A precompiled gem contains Remind's
9
+ object code; a source gem contains Remind's source. Either way what is
10
+ published is a distribution of Remind.
11
+
12
+ * ext/remind/shim.c is written against Remind's own headers and calls its
13
+ internal functions, and the shared library it is part of is one work with
14
+ Remind's objects.
15
+
16
+ * rem2ics depends on remind-rb and runs entirely through it.
17
+
18
+ So the whole of this repository is offered under the same terms Remind is,
19
+ and the GPL's conditions -- source availability, no additional restrictions,
20
+ same licence for derivative works -- apply to all of it.
21
+
22
+ Copyright:
23
+
24
+ Remind Copyright (C) 1992-2026 Dianne Skoll,
25
+ except where noted in individual files.
26
+ See remind-v*/COPYRIGHT.
27
+
28
+ remind-rb, rem2ics Copyright (C) 2026 Nathan Kidd.
29
+
30
+ The Ruby bindings, the shim, the converter and everything else outside
31
+ remind-v*/ are original work; Remind itself is redistributed unmodified.
32
+
33
+ This program is free software; you can redistribute it and/or modify it under
34
+ the terms of the GNU General Public License as published by the Free Software
35
+ Foundation; version 2 of the License.
36
+
37
+ This program is distributed in the hope that it will be useful, but WITHOUT
38
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
39
+ FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
40
+
41
+ The full text follows.
42
+
43
+ ----------------------------------------------------------------------------
44
+
45
+ GNU GENERAL PUBLIC LICENSE
46
+ Version 2, June 1991
47
+
48
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
49
+ 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA
50
+ Everyone is permitted to copy and distribute verbatim copies
51
+ of this license document, but changing it is not allowed.
52
+
53
+ Preamble
54
+
55
+ The licenses for most software are designed to take away your
56
+ freedom to share and change it. By contrast, the GNU General Public
57
+ License is intended to guarantee your freedom to share and change free
58
+ software--to make sure the software is free for all its users. This
59
+ General Public License applies to most of the Free Software
60
+ Foundation's software and to any other program whose authors commit to
61
+ using it. (Some other Free Software Foundation software is covered by
62
+ the GNU Library General Public License instead.) You can apply it to
63
+ your programs, too.
64
+
65
+ When we speak of free software, we are referring to freedom, not
66
+ price. Our General Public Licenses are designed to make sure that you
67
+ have the freedom to distribute copies of free software (and charge for
68
+ this service if you wish), that you receive source code or can get it
69
+ if you want it, that you can change the software or use pieces of it
70
+ in new free programs; and that you know you can do these things.
71
+
72
+ To protect your rights, we need to make restrictions that forbid
73
+ anyone to deny you these rights or to ask you to surrender the rights.
74
+ These restrictions translate to certain responsibilities for you if you
75
+ distribute copies of the software, or if you modify it.
76
+
77
+ For example, if you distribute copies of such a program, whether
78
+ gratis or for a fee, you must give the recipients all the rights that
79
+ you have. You must make sure that they, too, receive or can get the
80
+ source code. And you must show them these terms so they know their
81
+ rights.
82
+
83
+ We protect your rights with two steps: (1) copyright the software, and
84
+ (2) offer you this license which gives you legal permission to copy,
85
+ distribute and/or modify the software.
86
+
87
+ Also, for each author's protection and ours, we want to make certain
88
+ that everyone understands that there is no warranty for this free
89
+ software. If the software is modified by someone else and passed on, we
90
+ want its recipients to know that what they have is not the original, so
91
+ that any problems introduced by others will not reflect on the original
92
+ authors' reputations.
93
+
94
+ Finally, any free program is threatened constantly by software
95
+ patents. We wish to avoid the danger that redistributors of a free
96
+ program will individually obtain patent licenses, in effect making the
97
+ program proprietary. To prevent this, we have made it clear that any
98
+ patent must be licensed for everyone's free use or not licensed at all.
99
+
100
+ The precise terms and conditions for copying, distribution and
101
+ modification follow.
102
+
103
+ GNU GENERAL PUBLIC LICENSE
104
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
105
+
106
+ 0. This License applies to any program or other work which contains
107
+ a notice placed by the copyright holder saying it may be distributed
108
+ under the terms of this General Public License. The "Program", below,
109
+ refers to any such program or work, and a "work based on the Program"
110
+ means either the Program or any derivative work under copyright law:
111
+ that is to say, a work containing the Program or a portion of it,
112
+ either verbatim or with modifications and/or translated into another
113
+ language. (Hereinafter, translation is included without limitation in
114
+ the term "modification".) Each licensee is addressed as "you".
115
+
116
+ Activities other than copying, distribution and modification are not
117
+ covered by this License; they are outside its scope. The act of
118
+ running the Program is not restricted, and the output from the Program
119
+ is covered only if its contents constitute a work based on the
120
+ Program (independent of having been made by running the Program).
121
+ Whether that is true depends on what the Program does.
122
+
123
+ 1. You may copy and distribute verbatim copies of the Program's
124
+ source code as you receive it, in any medium, provided that you
125
+ conspicuously and appropriately publish on each copy an appropriate
126
+ copyright notice and disclaimer of warranty; keep intact all the
127
+ notices that refer to this License and to the absence of any warranty;
128
+ and give any other recipients of the Program a copy of this License
129
+ along with the Program.
130
+
131
+ You may charge a fee for the physical act of transferring a copy, and
132
+ you may at your option offer warranty protection in exchange for a fee.
133
+
134
+ 2. You may modify your copy or copies of the Program or any portion
135
+ of it, thus forming a work based on the Program, and copy and
136
+ distribute such modifications or work under the terms of Section 1
137
+ above, provided that you also meet all of these conditions:
138
+
139
+ a) You must cause the modified files to carry prominent notices
140
+ stating that you changed the files and the date of any change.
141
+
142
+ b) You must cause any work that you distribute or publish, that in
143
+ whole or in part contains or is derived from the Program or any
144
+ part thereof, to be licensed as a whole at no charge to all third
145
+ parties under the terms of this License.
146
+
147
+ c) If the modified program normally reads commands interactively
148
+ when run, you must cause it, when started running for such
149
+ interactive use in the most ordinary way, to print or display an
150
+ announcement including an appropriate copyright notice and a
151
+ notice that there is no warranty (or else, saying that you provide
152
+ a warranty) and that users may redistribute the program under
153
+ these conditions, and telling the user how to view a copy of this
154
+ License. (Exception: if the Program itself is interactive but
155
+ does not normally print such an announcement, your work based on
156
+ the Program is not required to print an announcement.)
157
+
158
+ These requirements apply to the modified work as a whole. If
159
+ identifiable sections of that work are not derived from the Program,
160
+ and can be reasonably considered independent and separate works in
161
+ themselves, then this License, and its terms, do not apply to those
162
+ sections when you distribute them as separate works. But when you
163
+ distribute the same sections as part of a whole which is a work based
164
+ on the Program, the distribution of the whole must be on the terms of
165
+ this License, whose permissions for other licensees extend to the
166
+ entire whole, and thus to each and every part regardless of who wrote it.
167
+
168
+ Thus, it is not the intent of this section to claim rights or contest
169
+ your rights to work written entirely by you; rather, the intent is to
170
+ exercise the right to control the distribution of derivative or
171
+ collective works based on the Program.
172
+
173
+ In addition, mere aggregation of another work not based on the Program
174
+ with the Program (or with a work based on the Program) on a volume of
175
+ a storage or distribution medium does not bring the other work under
176
+ the scope of this License.
177
+
178
+ 3. You may copy and distribute the Program (or a work based on it,
179
+ under Section 2) in object code or executable form under the terms of
180
+ Sections 1 and 2 above provided that you also do one of the following:
181
+
182
+ a) Accompany it with the complete corresponding machine-readable
183
+ source code, which must be distributed under the terms of Sections
184
+ 1 and 2 above on a medium customarily used for software interchange; or,
185
+
186
+ b) Accompany it with a written offer, valid for at least three
187
+ years, to give any third party, for a charge no more than your
188
+ cost of physically performing source distribution, a complete
189
+ machine-readable copy of the corresponding source code, to be
190
+ distributed under the terms of Sections 1 and 2 above on a medium
191
+ customarily used for software interchange; or,
192
+
193
+ c) Accompany it with the information you received as to the offer
194
+ to distribute corresponding source code. (This alternative is
195
+ allowed only for noncommercial distribution and only if you
196
+ received the program in object code or executable form with such
197
+ an offer, in accord with Subsection b above.)
198
+
199
+ The source code for a work means the preferred form of the work for
200
+ making modifications to it. For an executable work, complete source
201
+ code means all the source code for all modules it contains, plus any
202
+ associated interface definition files, plus the scripts used to
203
+ control compilation and installation of the executable. However, as a
204
+ special exception, the source code distributed need not include
205
+ anything that is normally distributed (in either source or binary
206
+ form) with the major components (compiler, kernel, and so on) of the
207
+ operating system on which the executable runs, unless that component
208
+ itself accompanies the executable.
209
+
210
+ If distribution of executable or object code is made by offering
211
+ access to copy from a designated place, then offering equivalent
212
+ access to copy the source code from the same place counts as
213
+ distribution of the source code, even though third parties are not
214
+ compelled to copy the source along with the object code.
215
+
216
+ 4. You may not copy, modify, sublicense, or distribute the Program
217
+ except as expressly provided under this License. Any attempt
218
+ otherwise to copy, modify, sublicense or distribute the Program is
219
+ void, and will automatically terminate your rights under this License.
220
+ However, parties who have received copies, or rights, from you under
221
+ this License will not have their licenses terminated so long as such
222
+ parties remain in full compliance.
223
+
224
+ 5. You are not required to accept this License, since you have not
225
+ signed it. However, nothing else grants you permission to modify or
226
+ distribute the Program or its derivative works. These actions are
227
+ prohibited by law if you do not accept this License. Therefore, by
228
+ modifying or distributing the Program (or any work based on the
229
+ Program), you indicate your acceptance of this License to do so, and
230
+ all its terms and conditions for copying, distributing or modifying
231
+ the Program or works based on it.
232
+
233
+ 6. Each time you redistribute the Program (or any work based on the
234
+ Program), the recipient automatically receives a license from the
235
+ original licensor to copy, distribute or modify the Program subject to
236
+ these terms and conditions. You may not impose any further
237
+ restrictions on the recipients' exercise of the rights granted herein.
238
+ You are not responsible for enforcing compliance by third parties to
239
+ this License.
240
+
241
+ 7. If, as a consequence of a court judgment or allegation of patent
242
+ infringement or for any other reason (not limited to patent issues),
243
+ conditions are imposed on you (whether by court order, agreement or
244
+ otherwise) that contradict the conditions of this License, they do not
245
+ excuse you from the conditions of this License. If you cannot
246
+ distribute so as to satisfy simultaneously your obligations under this
247
+ License and any other pertinent obligations, then as a consequence you
248
+ may not distribute the Program at all. For example, if a patent
249
+ license would not permit royalty-free redistribution of the Program by
250
+ all those who receive copies directly or indirectly through you, then
251
+ the only way you could satisfy both it and this License would be to
252
+ refrain entirely from distribution of the Program.
253
+
254
+ If any portion of this section is held invalid or unenforceable under
255
+ any particular circumstance, the balance of the section is intended to
256
+ apply and the section as a whole is intended to apply in other
257
+ circumstances.
258
+
259
+ It is not the purpose of this section to induce you to infringe any
260
+ patents or other property right claims or to contest validity of any
261
+ such claims; this section has the sole purpose of protecting the
262
+ integrity of the free software distribution system, which is
263
+ implemented by public license practices. Many people have made
264
+ generous contributions to the wide range of software distributed
265
+ through that system in reliance on consistent application of that
266
+ system; it is up to the author/donor to decide if he or she is willing
267
+ to distribute software through any other system and a licensee cannot
268
+ impose that choice.
269
+
270
+ This section is intended to make thoroughly clear what is believed to
271
+ be a consequence of the rest of this License.
272
+
273
+ 8. If the distribution and/or use of the Program is restricted in
274
+ certain countries either by patents or by copyrighted interfaces, the
275
+ original copyright holder who places the Program under this License
276
+ may add an explicit geographical distribution limitation excluding
277
+ those countries, so that distribution is permitted only in or among
278
+ countries not thus excluded. In such case, this License incorporates
279
+ the limitation as if written in the body of this License.
280
+
281
+ 9. The Free Software Foundation may publish revised and/or new versions
282
+ of the General Public License from time to time. Such new versions will
283
+ be similar in spirit to the present version, but may differ in detail to
284
+ address new problems or concerns.
285
+
286
+ Each version is given a distinguishing version number. If the Program
287
+ specifies a version number of this License which applies to it and "any
288
+ later version", you have the option of following the terms and conditions
289
+ either of that version or of any later version published by the Free
290
+ Software Foundation. If the Program does not specify a version number of
291
+ this License, you may choose any version ever published by the Free Software
292
+ Foundation.
293
+
294
+ 10. If you wish to incorporate parts of the Program into other free
295
+ programs whose distribution conditions are different, write to the author
296
+ to ask for permission. For software which is copyrighted by the Free
297
+ Software Foundation, write to the Free Software Foundation; we sometimes
298
+ make exceptions for this. Our decision will be guided by the two goals
299
+ of preserving the free status of all derivatives of our free software and
300
+ of promoting the sharing and reuse of software generally.
301
+
302
+ NO WARRANTY
303
+
304
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
305
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
306
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
307
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
308
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
309
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
310
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
311
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
312
+ REPAIR OR CORRECTION.
313
+
314
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
315
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
316
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
317
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
318
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
319
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
320
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
321
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
322
+ POSSIBILITY OF SUCH DAMAGES.
323
+
324
+ END OF TERMS AND CONDITIONS
325
+ --
326
+ Dianne Skoll <dianne@skoll.ca>
327
+
328
+ https://dianne.skoll.ca/projects/remind
data/docs/rem2ics.md ADDED
@@ -0,0 +1,83 @@
1
+ # rem2ics
2
+
3
+ Reminder files to iCalendar, built on the [remind-rb](../README.md) bindings.
4
+
5
+ ```
6
+ $ rem2ics --date 2026-08-19 ~/.reminders > calendar.ics
7
+ $ rem2ics --help
8
+ Usage: rem2ics [options] [file...]
9
+ -d, --date DATE Convert as of this date (default: today)
10
+ -n, --horizon COUNT How many occurrences to check a recurrence rule against
11
+ -o, --organizer ADDRESS
12
+ ```
13
+
14
+ This is a port of Martin Michel's `remmy.pl`, and the interesting difference is
15
+ not that it is Ruby.
16
+
17
+ ## The judgement it makes
18
+
19
+ A recurring reminder should become one event with an `RRULE`, so the calendar
20
+ repeats it for ever. The trouble is that Remind's trigger language and
21
+ iCalendar's `RRULE` overlap without either containing the other. `REM 15` and
22
+ `FREQ=MONTHLY;BYMONTHDAY=15` are the same thing. `REM 1 Mar SKIP OMIT Sat Sun`
23
+ is not any `RRULE`: it means "1 March, unless that is a weekend, in which case
24
+ not at all", and no `BY*` part says that.
25
+
26
+ Previous converters guessed. This one guesses and then **checks**: the
27
+ candidate rule is expanded with ice_cube and compared, occurrence by
28
+ occurrence, against the dates Remind itself computes.
29
+
30
+ - Where they agree, the event carries the rule, and recurs for ever, correctly.
31
+ - Where they disagree, the event carries Remind's own dates as `RDATE`s:
32
+ finite, but not wrong anywhere.
33
+
34
+ That check is the whole reason to build this on bindings. Without Remind in the
35
+ process there is nothing to check against.
36
+
37
+ ```
38
+ $ rem2ics --date 2019-01-20 examples/remmy-triggers.rem | grep -E 'SUMMARY|RRULE'
39
+ SUMMARY:1st Monday after 15th of every month in 2020
40
+ RRULE:FREQ=MONTHLY;UNTIL=20201231;BYDAY=MO;BYMONTHDAY=15,16,17,18,19,20,21
41
+ ```
42
+
43
+ `remmy.pl` renders that same reminder as `BYDAY=+3MO` — the third Monday —
44
+ which is a different day in any month whose 1st is a Monday. The window form
45
+ above is what Remind actually means, and it is emitted only because expanding
46
+ it matched.
47
+
48
+ `examples/` at the repository root holds that comparison: `remmy-triggers.rem` is remmy's own test
49
+ input, and `remmy-reference.ics` is what remmy makes of it.
50
+
51
+ ## What maps to what
52
+
53
+ | Reminder | iCalendar |
54
+ | --- | --- |
55
+ | the trigger | `DTSTART`, and `RRULE` or `RDATE` |
56
+ | `MSG`, rendered in Remind's CAL mode | `SUMMARY` — where a `%"…%"` title is honoured |
57
+ | `MSG`, rendered normally | `DESCRIPTION` |
58
+ | `AT` | the time on `DTSTART` |
59
+ | `DURATION` | `DTEND` |
60
+ | `+n` on the trigger | `VALARM`, n days before |
61
+ | `+n` on `AT` | `VALARM`, n minutes before |
62
+ | `RUN`, `CAL`, `PS`, `SATISFY` | nothing — they are not appointments |
63
+
64
+ ## Known limits
65
+
66
+ - **It converts from a date forwards.** `DTSTART` is the first occurrence on or
67
+ after the day being converted, not the reminder's first ever occurrence. A
68
+ reminder with nothing left to fire — `REM Tue Aug 2018` read in 2019 — is
69
+ dropped.
70
+ - **One message per event.** Remind expands `%b` and friends per occurrence;
71
+ an event has one `SUMMARY`. It is rendered as of the day the event starts.
72
+ - **Zones are the reader's.** Times are local wall-clock, with no `TZID`, which
73
+ is what a reminder file says.
74
+ - **`INCLUDECMD` is not followed.** It runs a shell command, and converting
75
+ somebody else's reminder file should not execute what is in it.
76
+
77
+ ## Licence
78
+
79
+ GPL-2.0-only — see [LICENSE](../LICENSE).
80
+
81
+ rem2ics runs entirely through remind-rb, which is Remind, so it carries
82
+ Remind's licence. Copyright (C) 2026 Nathan Kidd; Remind itself is Copyright
83
+ (C) 1992-2026 Dianne Skoll.
data/exe/rem2ics ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+
6
+ require "rem2ics"
7
+
8
+ exit(Rem2ics::CLI.new.call(ARGV))
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+ require "optparse"
5
+
6
+ require_relative "converter"
7
+ require_relative "recurrence"
8
+ require_relative "version"
9
+
10
+ module Rem2ics
11
+ # The `rem2ics` command.
12
+ #
13
+ # Streams are injected rather than assumed, so the whole command is testable
14
+ # without shelling out or capturing `$stdout`.
15
+ class CLI
16
+ EXIT_SUCCESS = 0
17
+ EXIT_FAILURE = 1
18
+ EXIT_USAGE = 2
19
+
20
+ # Remind reads standard input under this name; so does this.
21
+ STDIN = "-"
22
+
23
+ Options = Struct.new(
24
+ :paths,
25
+ :today,
26
+ :horizon,
27
+ :organizer,
28
+ keyword_init: true,
29
+ )
30
+
31
+ attr_reader :out, :err
32
+
33
+ def initialize(out: $stdout, err: $stderr)
34
+ @out = out
35
+ @err = err
36
+ end
37
+
38
+ def call(argv)
39
+ options = Options.new(
40
+ paths: [],
41
+ today: Date.today,
42
+ horizon: Recurrence::DEFAULT_HORIZON,
43
+ organizer: nil,
44
+ )
45
+
46
+ dispatch(parse(argv, options), options)
47
+ rescue OptionParser::ParseError, Date::Error => error
48
+ err.puts(error.message)
49
+ EXIT_USAGE
50
+ end
51
+
52
+ private
53
+
54
+ def dispatch(action, options)
55
+ case action
56
+ when :version then print_version
57
+ when :help then EXIT_SUCCESS
58
+ else convert(options)
59
+ end
60
+ end
61
+
62
+ def convert(options)
63
+ out.print(calendar(options).to_ical)
64
+
65
+ EXIT_SUCCESS
66
+ rescue Remind::LibraryMissing => error
67
+ err.puts(error.message)
68
+ EXIT_FAILURE
69
+ end
70
+
71
+ def calendar(options)
72
+ Converter.new(
73
+ horizon: options.horizon,
74
+ organizer: options.organizer,
75
+ warnings: err,
76
+ ).call(options.paths, today: options.today)
77
+ end
78
+
79
+ def parse(argv, options)
80
+ action = nil
81
+
82
+ parser(options) { |chosen| action = chosen }.parse!(argv)
83
+ options.paths = paths_from(argv)
84
+
85
+ action
86
+ end
87
+
88
+ # No files means standard input, the way `remind -` does.
89
+ def paths_from(argv)
90
+ if argv.empty?
91
+ [STDIN]
92
+ else
93
+ argv
94
+ end
95
+ end
96
+
97
+ def parser(options)
98
+ OptionParser.new do |parser|
99
+ parser.banner = "Usage: rem2ics [options] [file...]"
100
+
101
+ parser.on("-d", "--date DATE", "Convert as of this date (default: today)") do |date|
102
+ options.today = Date.parse(date)
103
+ end
104
+
105
+ parser.on(
106
+ "-n",
107
+ "--horizon COUNT",
108
+ Integer,
109
+ "How many occurrences to check a recurrence rule against (default: #{Recurrence::DEFAULT_HORIZON})",
110
+ ) do |count|
111
+ options.horizon = count
112
+ end
113
+
114
+ parser.on("-o", "--organizer ADDRESS", "ORGANIZER for every event") do |address|
115
+ options.organizer = address
116
+ end
117
+
118
+ parser.on("-v", "--version", "Print the version") { yield :version }
119
+
120
+ parser.on("-h", "--help", "Print this message") do
121
+ out.puts(parser.help)
122
+ yield :help
123
+ end
124
+ end
125
+ end
126
+
127
+ def print_version
128
+ out.puts("rem2ics #{VERSION} (Remind #{Remind.version})")
129
+
130
+ EXIT_SUCCESS
131
+ end
132
+ end
133
+ end
134
+
135
+ __END__
136
+
137
+ require "stringio"
138
+ require "tempfile"
139
+
140
+ describe "Rem2ics::CLI" do
141
+ run = proc do |argv|
142
+ out = StringIO.new
143
+ err = StringIO.new
144
+ code = Rem2ics::CLI.new(out: out, err: err).call(argv)
145
+
146
+ [code, out.string, err.string]
147
+ end
148
+
149
+ file = proc do |text|
150
+ handle = Tempfile.new(["reminders", ".rem"])
151
+ handle.write(text)
152
+ handle.close
153
+ handle.path
154
+ end
155
+
156
+ it "converts the files it is given" do
157
+ code, output, = run.(["--date", "2026-08-19", file.("REM 25 Dec MSG christmas\n")])
158
+
159
+ code.should == 0
160
+ output.should.include "SUMMARY:christmas"
161
+ end
162
+
163
+ it "converts as of the date it is told" do
164
+ _, output, = run.(["--date", "2027-03-01", file.("REM Mon MSG gym\n")])
165
+
166
+ output.should.include "DTSTART;VALUE=DATE:20270301"
167
+ end
168
+
169
+ it "takes the organizer from the command line" do
170
+ _, output, = run.(["-o", "me@host", file.("REM 25 Dec MSG christmas\n")])
171
+
172
+ output.should.include "ORGANIZER:mailto:me@host"
173
+ end
174
+
175
+ it "prints the version of both itself and Remind" do
176
+ code, output, = run.(["--version"])
177
+
178
+ code.should == 0
179
+ output.should.include Rem2ics::VERSION
180
+ output.should.include Remind.version
181
+ end
182
+
183
+ it "prints help without converting anything" do
184
+ code, output, = run.(["--help"])
185
+
186
+ code.should == 0
187
+ output.should.include "Usage: rem2ics"
188
+ output.should.not.include "BEGIN:VCALENDAR"
189
+ end
190
+
191
+ it "rejects an option it does not know" do
192
+ code, _, errors = run.(["--nonsense"])
193
+
194
+ code.should == 2
195
+ errors.should.include "nonsense"
196
+ end
197
+
198
+ it "rejects a date it cannot read" do
199
+ run.(["--date", "not-a-date"]).first.should == 2
200
+ end
201
+ end