icalPal 3.10.0 → 4.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 014f8b5179cd56cb757404df75a3d23145907d2f40b4186031ed5028ad5e92af
4
- data.tar.gz: 8abe6a5c80c695fa61b0000ae20c52fcbd1fab29ab326fcb89fe1a4d469fae82
3
+ metadata.gz: b48ee501e87dc1c4c597bc9e45b6fd977668f107f942c857186de23245d22a4d
4
+ data.tar.gz: a4010b5cd6e901fc3d4a6eaa850c4b3303c97e0ca1075d868598ed8ab428b243
5
5
  SHA512:
6
- metadata.gz: 24bafd17756e9e4ccce28dc0b23ea421d334f3d9c58f3fe62dd07cf26777b47c56587a52e61c5995a730b823a3ad2f0ebc8bef460205ebcde981df3e0bd35108
7
- data.tar.gz: a21caa7650b168542625a176909e05e0f87825c452015e7292f5297aa8b9d96863dc22c1f921a197a97769c9b3fb5db0a593adf1b22784b1874e03ecb05a8b34
6
+ metadata.gz: 3f74dc669bd9b5352d12a5a40859cf1d2cab3cbbe3ed185223c5f8ec449af3115f415832191f6184ab76fe9ac74f0211ca80143d660440463f6a79080297ba54
7
+ data.tar.gz: afef75a278aa28007182a40ac9c1ed37369a5a72e0c3e8be92102160034b3f1ad013b879af0676e721cc8b0179de6c2ae28db023de657fb998fb73e53f06a54c
data/bin/icalPal CHANGED
@@ -206,10 +206,10 @@ $rows.each do |row|
206
206
  next if $opts[:ed] && item['completed'].zero?
207
207
  next unless $opts[:id] || item['completed'].zero?
208
208
 
209
- next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
210
- next if $opts[:dated] == 2 && (!item['due_date'] || item['due_date'].zero?)
209
+ next if $opts[:dated] == 'undatedTasks' && item['due_date'] && item['due_date'].positive?
210
+ next if $opts[:dated] == 'datedTasks' && (!item['due_date'] || item['due_date'].zero?)
211
211
 
212
- if $opts[:dated] == 3
212
+ if $opts[:dated] == 'tasksDueBefore'
213
213
  next unless item['due_date']
214
214
  next unless item['due_date'].between?($opts[:from].to_i, $opts[:to].to_i)
215
215
  end
data/ext/extconf.rb CHANGED
@@ -7,33 +7,18 @@ begin
7
7
  Gem.path.each { |p| gemdir = p if File.writable? p }
8
8
 
9
9
  # Dependencies common to all environments
10
- dependencies = %w[ plist tzinfo ]
10
+ dependencies = %w[ plist ]
11
11
 
12
- if RUBY_VERSION >= '3.4'
13
- # bigdecimal is not part of the default gems starting from Ruby 3.4.0.
14
- # csv is not part of the default gems starting from Ruby 3.4.0.
15
- dependencies.push('bigdecimal')
12
+ # All dependencies are included with the installation of Ruby in
13
+ # macOS. Adding these dependencies anyway will cause errors that
14
+ # prevent icalPal from being installed.
15
+ unless RUBY_VERSION >= '2.6'
16
+ dependencies.push('logger')
16
17
  dependencies.push('csv')
17
-
18
- # The macOS and Homebrew versions of rubygems have incompatible
19
- # requirements for sqlite3.
20
- #
21
- # macOS 15.5 (Sequoia) comes with version 1.3.13, so it does not
22
- # need to be added as a dependency, and it cannot install anything
23
- # newer:
24
- #
25
- # requires Ruby version >= 3.0, < 3.4.dev. The current ruby version is 2.6.10.
26
- #
27
- # Homebrew's Ruby formula does not come with sqlite3, so it does
28
- # need to be added as a dependency, but it cannot install version
29
- # 1.3.13:
30
- #
31
- # error: call to undeclared function
32
- #
33
- # So neither environment can install the other's sqlite3 gem. We
34
- # must install sqlite3, but iff we are not building with macOS'
35
- # Ruby installation.
18
+ dependencies.push('json')
19
+ dependencies.push('rdoc')
36
20
  dependencies.push('sqlite3')
21
+ dependencies.push('yaml')
37
22
  end
38
23
 
39
24
  di = Gem::DependencyInstaller.new(install_dir: gemdir)
data/lib/ToICalPal.rb CHANGED
@@ -97,7 +97,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
97
97
 
98
98
  # Add a blank line
99
99
  #
100
- # @param *_arg [Array] Ignored
100
+ # @param _arg [Array] Ignored
101
101
  def accept_blank_line(*_arg)
102
102
  @res << "\n"
103
103
  end
@@ -109,7 +109,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
109
109
  # @option h [Integer] :level 2 for a property name
110
110
  # @option h [String] :text The header's text
111
111
  def accept_heading(h)
112
- h.text = colorize(@item['symbolic_color_name'], @item['color'], h.text) if (h.level == 2) || COLOR_LABEL.any?(@prop)
112
+ h = RDoc::Markup::Heading.new(h.level, colorize(@item['symbolic_color_name'], @item['color'], h.text)) if (h.level == 2) || COLOR_LABEL.any?(@prop)
113
113
  @res << h.text
114
114
 
115
115
  case h.level
data/lib/event.rb CHANGED
@@ -95,8 +95,8 @@ module ICalPal
95
95
 
96
96
  # Save as seconds, Time, RDT
97
97
  ctime = obj[k] + ITIME
98
- ctime -= Time.at(ctime).utc_offset if obj["#{k}_tz"] == '_float' && !zone
99
- ttime = Time.at(ctime, in: zone)
98
+ ctime -= Time.at(ctime).utc_offset if obj["#{k.split('_')[0]}_tz"] == '_float' && !zone
99
+ ttime = (zone)? Time.at(ctime, in: zone) : Time.at(ctime)
100
100
 
101
101
  @self["#{k[0]}seconds"] = ctime
102
102
  @self["#{k[0]}ctime"] = ttime
@@ -265,7 +265,7 @@ module ICalPal
265
265
  c['edate'] = ICalPal.nth(nth, day, nedate)
266
266
  else
267
267
  %w[ sdate edate ].each do |d|
268
- diff = day - c['sdate'].wday
268
+ diff = day - c[d].wday
269
269
  diff += 7 if diff.negative?
270
270
 
271
271
  t1 = Time.at(c[d].to_time)
data/lib/options.rb CHANGED
@@ -275,15 +275,9 @@ module ICalPal
275
275
 
276
276
  # Handle tasks command variants
277
277
  if cli[:cmd] =~ /tasks/i
278
- case cli[:cmd]
279
- when 'undatedTasks'
280
- cli[:dated] = 1
278
+ cli[:dated] = cli[:cmd]
281
279
 
282
- when 'datedTasks'
283
- cli[:dated] = 2
284
-
285
- when 'tasksDueBefore'
286
- cli[:dated] = 3
280
+ if cli[:dated] == 'tasksDueBefore'
287
281
  cli.delete(:days) unless cli[:days]
288
282
  cli[:from] = RDT.from_epoch(0) unless cli[:from]
289
283
  cli[:to] = $today unless cli[:to]
@@ -310,7 +304,7 @@ module ICalPal
310
304
  $log.level = opts[:debug]
311
305
 
312
306
  # For posterity
313
- opts[:ruby] = RUBY_VERSION
307
+ opts[:ruby] = RUBY_DESCRIPTION
314
308
  opts[:version] = @op.version
315
309
 
316
310
  # From the Department of Redundancy Department
data/lib/rdt.rb CHANGED
@@ -3,16 +3,28 @@ module ICalPal
3
3
  class RDT < DateTime
4
4
 
5
5
  # Create a new RDT from a Time object
6
+ #
7
+ # @param t [Time] The Time object
8
+ #
9
+ # @return [RDT] a new RDT
6
10
  def self.from_time(t)
7
- new(*t.to_a[0..5].reverse, (t.gmt_offset / 3600).to_s)
11
+ new(*t.to_a[0..5].reverse, Rational((t.gmt_offset / 3600), 24))
8
12
  end
9
13
 
10
14
  # Create a new RDT from seconds since epoch
15
+ #
16
+ # @param s [Integer] Seconds since the Unix epoch (Thu Jan 1 00:00:00 UTC 1970)
17
+ #
18
+ # @return [RDT] a new RDT
11
19
  def self.from_epoch(s)
12
20
  from_time(Time.at(s))
13
21
  end
14
22
 
15
23
  # Create a new RDT from seconds since iCal epoch
24
+ #
25
+ # @param s [Integer] Seconds since the iCal epoch (Jan 1 00:00:00 UTC 2001)
26
+ #
27
+ # @return [RDT] a new RDT
16
28
  def self.from_itime(s)
17
29
  from_epoch(s + ITIME)
18
30
  end
@@ -25,15 +37,25 @@ module ICalPal
25
37
  # @return [RDT] a new RDT
26
38
  def self.conv(str)
27
39
  case str
28
- when 'yesterday' then $today - 1
40
+ when 'yesterday' then $today.add(-1)
29
41
  when 'today' then $today
30
- when 'tomorrow' then $today + 1
42
+ when 'tomorrow' then $today.add(1)
31
43
  when /^\+([0-9]+)/ then $today + Regexp.last_match(1).to_i
32
44
  when /^-([0-9]+)/ then $today - Regexp.last_match(1).to_i
33
45
  else parse(str)
34
46
  end
35
47
  end
36
48
 
49
+ # Add a number of days accounting for daylight saving time changes
50
+ #
51
+ # @param days [Integer] Number of days to add
52
+ # @return [RDT] A new RDT
53
+ def add(days)
54
+ n = self + days
55
+ t = Time.parse("#{n.year}-#{n.month}-#{n.day} #{n.hour}:#{n.min}:#{n.sec}")
56
+ RDT.from_time(t)
57
+ end
58
+
37
59
  # Values can be +day before yesterday+, +yesterday+,
38
60
  # +today+, +tomorrow+, +day after tomorrow+, or the result from
39
61
  # strftime
@@ -41,10 +63,10 @@ module ICalPal
41
63
  # @return [String] A string representation of self relative to
42
64
  # today.
43
65
  def to_s
44
- return strftime($opts[:df]) if $opts && $opts[:nrd] && $opts[:df]
66
+ return strftime($opts[:df]) if $opts && $opts[:df] && $opts[:nrd]
45
67
  return super unless $today && $opts
46
68
 
47
- case Integer(RDT.new(*ymd, month, day) - $today)
69
+ case (self - $today).round
48
70
  when -2 then 'day before yesterday'
49
71
  when -1 then 'yesterday'
50
72
  when 0 then 'today'
@@ -68,13 +90,13 @@ module ICalPal
68
90
  to_time.to_i
69
91
  end
70
92
 
71
- # @param [Integer] Optional UTC offset
93
+ # @param z [Integer] Optional UTC offset
72
94
  # @return [RDT] Self at 00:00:00
73
95
  def day_start(z = zone)
74
96
  RDT.new(year, month, day, 0, 0, 0, z)
75
97
  end
76
98
 
77
- # @param [Integer] Optional UTC offset
99
+ # @param z [Integer] Optional UTC offset
78
100
  # @return [RDT] Self at 23:59:59
79
101
  def day_end(z = zone)
80
102
  RDT.new(year, month, day, 23, 59, 59, z)
@@ -90,9 +112,5 @@ module ICalPal
90
112
  [ hour, min, sec ]
91
113
  end
92
114
 
93
- # @return [Array] Only the year, month and day of self
94
- def ymd
95
- [ year, month, day ]
96
- end
97
115
  end
98
116
  end
data/lib/reminder.rb CHANGED
@@ -1,5 +1,3 @@
1
- r 'tzinfo'
2
-
3
1
  module ICalPal
4
2
  # Class representing items from the <tt>Reminders</tt> database
5
3
  class Reminder
@@ -103,14 +101,9 @@ module ICalPal
103
101
 
104
102
  # Due date
105
103
  if @self['due_date']
106
- begin
107
- @self['due_date'] += ITIME
108
- zone = TZInfo::Timezone.get(@self['timezone'])
109
- rescue TZInfo::InvalidTimezoneIdentifier
110
- zone = '+00:00'
111
- end
112
-
113
- @self['due'] = RDT.from_time(Time.at(@self['due_date'], in: zone))
104
+ @self['due_date'] += ITIME
105
+ @self['due_date'] -= obj['utc_offset'] if obj['utc_offset']
106
+ @self['due'] = RDT.from_time(Time.at(@self['due_date']))
114
107
  end
115
108
 
116
109
  # Notes
@@ -196,9 +189,9 @@ r1.zNotes as notes,
196
189
  r1.zPriority as priority,
197
190
  r1.zContactHandles as messaging,
198
191
  r1.zDueDateDeltaAlertsData as alert,
199
- r1.zTimezone as timezone,
200
192
  r1.zckIdentifier as id,
201
193
  r1.zCompleted as completed,
194
+ r1.zDisplayDateUpdatedForSecondsFromGMT as utc_offset,
202
195
 
203
196
  bl1.zBadgeEmblem as badge,
204
197
  bl1.zColor as color,
data/lib/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module ICalPal
2
2
  NAME = 'icalPal'.freeze
3
- VERSION = '3.10.0'.freeze
3
+ VERSION = '4.0.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
@@ -22,7 +22,6 @@ extra_rdoc_files:
22
22
  files:
23
23
  - README.md
24
24
  - bin/icalPal
25
- - bin/icalpal
26
25
  - ext/extconf.rb
27
26
  - icalPal.gemspec
28
27
  - lib/EventKit.rb
@@ -62,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
61
  - !ruby/object:Gem::Version
63
62
  version: '0'
64
63
  requirements: []
65
- rubygems_version: 3.7.2
64
+ rubygems_version: 4.0.3
66
65
  specification_version: 4
67
66
  summary: Command-line tool to query the macOS Calendar and Reminders
68
67
  test_files: []
data/bin/icalpal DELETED
@@ -1,363 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # rubocop: disable Style/RedundantBegin
4
-
5
- # require a gem
6
- #
7
- # @param gem [String] The gem
8
- def r(gem)
9
- begin
10
- # puts "require \"#{gem}\""
11
- require gem
12
- rescue LoadError => e
13
- $stderr.puts "FATAL: icalPal is missing a dependency: #{gem}"
14
- $stderr.puts e
15
- $stderr.puts
16
- abort "Try installing with 'gem install #{gem}'"
17
- end
18
- end
19
-
20
- # require_relative a library
21
- #
22
- # @param library [String] The library
23
- def rr(library)
24
- begin
25
- # puts "require_relative \"../lib/#{library}\""
26
- require_relative "../lib/#{library}"
27
- rescue LoadError => e
28
- $stderr.puts "FATAL: Could not load library: #{library}"
29
- $stderr.puts
30
- abort e.message
31
- end
32
- end
33
-
34
- # rubocop: enable Style/RedundantBegin
35
-
36
- %w[ logger csv json rdoc sqlite3 yaml ].each { |g| r g }
37
- %w[ icalPal defaults options utils ].each { |l| rr l }
38
-
39
-
40
- ##################################################
41
- # Load options
42
-
43
- # All kids love log!
44
- $log = Logger.new(STDERR, { level: $defaults[:common][:debug] })
45
- $log.formatter = proc do |s, t, _p, m| # Severity, time, progname, msg
46
- format("[%-5<sev>s] %<time>s [%<file>s:%<line>5s] - %<message>s\n",
47
- {
48
- sev: s,
49
- time: t.strftime('%H:%M:%S.%L'),
50
- file: caller(4, 1)[0].split('/')[-1].split(':')[0],
51
- line: caller(4, 1)[0].split('/')[-1].split(':')[1],
52
- message: m
53
- })
54
- end
55
-
56
- $opts = ICalPal::Options.new.parse_options
57
-
58
- $rows = [] # Rows from the database
59
- $sections = [] # Calendar list sections
60
- $items = [] # Items to be printed
61
-
62
-
63
- ##################################################
64
- # All kids love log!
65
-
66
- $log.info("Options:\n#{$opts.to_json}")
67
-
68
-
69
- ##################################################
70
- # Add an item to the list
71
- #
72
- # @param item[Object]
73
-
74
- def add(item)
75
- $log.debug("Adding #{item.dump} #{item['UUID']} (#{item['title']})") if item['UUID']
76
-
77
- $items.push(item)
78
- end
79
-
80
-
81
- ##################################################
82
- # Load the data
83
-
84
- # What are we getting?
85
- klass = ICalPal.call($opts[:cmd])
86
- success = false
87
-
88
- # Get it
89
- $opts[:db].each do |db|
90
- $log.debug("Trying #{db}")
91
-
92
- if klass == ICalPal::Reminder
93
- # Load all .sqlite files
94
- $log.debug("Loading *.sqlite in #{db}")
95
- Dir.glob("#{db}/*.sqlite").each do |d|
96
- success = true
97
-
98
- rows = klass.load_data(d, klass::QUERY)
99
- $rows += rows
100
-
101
- sections = klass.load_data(d, klass::SECTIONS_QUERY)
102
- $sections += sections
103
-
104
- $log.info("Loaded #{rows.length} rows and #{sections.length} sections from #{d}")
105
- end
106
- else
107
- # Load database
108
- rows = ICalPal.load_data(db, klass::QUERY)
109
- $rows += rows
110
-
111
- success = true
112
-
113
- $log.info("Loaded #{rows.length} rows from #{db}")
114
- end
115
-
116
- rescue Errno::EPERM
117
- # Probably need Full Disk Access
118
-
119
- rescue SQLite3::CantOpenException
120
- # Non-fatal exception, try the next one
121
-
122
- rescue StandardError => e
123
- # Log the error and (try to) continue
124
- $log.error("#{db}: #{e.message}")
125
- end
126
-
127
- # Make sure we opened at least one database
128
- unless success
129
- $log.fatal('Could not open database')
130
-
131
- # SQLite3 does not return useful error messages. If any databases
132
- # failed because of EPERM (operation not permitted), our parent
133
- # process might need Full Disk Access, and we should suggest that.
134
- eperm = 0
135
-
136
- $opts[:db].each do |db|
137
- # Use a real open to get a useful error
138
- File.open(db).close
139
- rescue Exception => e
140
- $log.fatal("#{e.class}: #{db}")
141
-
142
- eperm = 1 if e.instance_of?(Errno::EPERM)
143
- end
144
-
145
- if eperm.positive?
146
- $stderr.puts
147
- $stderr.puts "Does #{ancestor} have Full Disk Access in System Settings?"
148
- $stderr.puts
149
- $stderr.puts "Try running: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'"
150
- end
151
-
152
- abort
153
- end
154
-
155
- $log.debug("Loaded #{$rows.length} #{klass} items")
156
- $log.info("Window is #{$opts[:from]} to #{$opts[:to]}") if $opts[:from]
157
-
158
-
159
- ##################################################
160
- # Process the data
161
-
162
- # Add rows
163
- $rows.each do |row|
164
- # --es/--is
165
- next if $opts[:es].any? row['account']
166
- next unless $opts[:is].empty? || ($opts[:is].any? row['account'])
167
-
168
- # --ec/--ic
169
- unless klass == ICalPal::Store || !row['calendar']
170
- next if $opts[:ec].any? row['calendar']
171
- next unless $opts[:ic].empty? || ($opts[:ic].any? row['calendar'])
172
- end
173
-
174
- # Instantiate an item
175
- item = klass.new(row)
176
-
177
- # --et/--it
178
- next if $opts[:et].any? item['type']
179
- next unless $opts[:it].empty? || ($opts[:it].any? item['type'])
180
-
181
- # --el/--il
182
- next if $opts[:el].any? item['list_name']
183
- next unless $opts[:il].empty? || ($opts[:il].any? item['list_name'])
184
-
185
- # --match
186
- if $opts[:match]
187
- r = $opts[:match].split('=')
188
-
189
- if item[r[0]].to_s.respond_to?(:match)
190
- next unless item[r[0]].to_s =~ Regexp.new(r[1], Regexp::IGNORECASE)
191
- end
192
- end
193
-
194
- if item.is_a?(ICalPal::Event)
195
- # Check for all-day and cancelled events
196
- next if $opts[:ea] && item['all_day'].positive?
197
- next if $opts[:ia] && !item['all_day'].positive?
198
- next if item['status'] == :canceled
199
-
200
- (item['has_recurrences'].positive?)?
201
- item.recurring.each { |j| add(j) } :
202
- item.non_recurring.each { |j| add(j) }
203
- else
204
- # Check for completed or dated reminders
205
- if item.is_a?(ICalPal::Reminder)
206
- next if $opts[:ed] && item['completed'].zero?
207
- next unless $opts[:id] || item['completed'].zero?
208
-
209
- next if $opts[:dated] == 1 && item['due_date'] && item['due_date'].positive?
210
- next if $opts[:dated] == 2 && (!item['due_date'] || item['due_date'].zero?)
211
-
212
- if $opts[:dated] == 3
213
- next unless item['due_date']
214
- next unless item['due_date'].between?($opts[:from].to_i, $opts[:to].to_i)
215
- end
216
- end
217
-
218
- add(item)
219
- end
220
- end
221
-
222
- # Add placeholders for empty days
223
- if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
224
- days = $items.collect { |i| i['sday'] }.uniq.sort
225
-
226
- $opts[:days].times do |n|
227
- day = $opts[:from] + n
228
- $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
229
- end
230
- end
231
-
232
- # Sort the rows
233
- begin
234
- $sort_attrs = []
235
- $sort_attrs.push $opts[:sep] if $opts[:sep]
236
- $sort_attrs.push $opts[:sort] if $opts[:sort]
237
- $sort_attrs.push 'sdate'
238
-
239
- $log.info("Sorting #{$items.count} items by #{$sort_attrs}, reverse #{$opts[:reverse].inspect}")
240
-
241
- $items.sort!
242
- $items.reverse! if $opts[:reverse]
243
- rescue Exception => e
244
- $log.info("Sorting failed: #{e}\n")
245
- end
246
-
247
- $log.debug("#{$items.count} items remain")
248
-
249
- # Configure formatting
250
- mu = case $opts[:output]
251
- when 'ansi' then RDoc::Markup::ToAnsi.new
252
- when 'default' then RDoc::Markup::ToICalPal.new($opts)
253
- when 'html'
254
- rdoc = RDoc::Options.new
255
- rdoc.pipe = true
256
- rdoc.output_decoration = false
257
- RDoc::Markup::ToHtml.new(rdoc)
258
- when 'md' then RDoc::Markup::ToMarkdown.new
259
- when 'rdoc' then RDoc::Markup::ToRdoc.new
260
- when 'toc' then RDoc::Markup::ToTableOfContents.new
261
- end
262
-
263
-
264
- ##################################################
265
- # Print the data
266
-
267
- items = $items[0..($opts[:li] - 1)]
268
-
269
- unless mu
270
- $log.debug("Output in #{$opts[:output]} format")
271
-
272
- puts case $opts[:output]
273
- when 'csv'
274
- # Get all headers
275
- headers = []
276
- items.each { |i| headers += i.keys }
277
- headers.uniq!
278
-
279
- # Populate a CSV::Table
280
- table = CSV::Table.new([], headers: headers)
281
- items.each { |i| table << i.to_csv(headers) }
282
-
283
- table
284
- when 'hash' then items.map { |i| i.self }
285
- when 'json' then items.map { |i| i.self }.to_json
286
- when 'xml'
287
- xml = items.map { |i| "<#{$opts[:cmd].chomp('s')}>#{i.to_xml}</#{$opts[:cmd].chomp('s')}>" }
288
- "<#{$opts[:cmd]}>\n#{xml.join}</#{$opts[:cmd]}>"
289
- when 'yaml' then items.map { |i| i.self }.to_yaml
290
- when 'remind' then items.map { |i|
291
- "REM #{i['sdate'].strftime('%F AT %R')} " +
292
- "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
293
- "MSG #{i['title'].gsub(/([[:cntrl:]])/) { |c| c.dump[1..-2] }}"
294
- }.join("\n")
295
- else abort "No formatter for #{$opts[:output]}"
296
- end
297
-
298
- exit
299
- end
300
-
301
- $log.debug("Formatting with #{mu.inspect}")
302
-
303
- doc = RDoc::Markup::Document.new
304
- section = nil
305
-
306
- items.each_with_index do |i, j|
307
- $log.debug("Print #{j}: #{i.inspect}")
308
-
309
- # --li
310
- break if $opts[:li].positive? && j >= $opts[:li]
311
-
312
- # Use RDoc::Markup::Verbatim to save the item
313
- v = RDoc::Markup::Verbatim.new
314
- v.format = i
315
- doc << v
316
-
317
- # Sections
318
- if $opts[:sep] && section != i[$opts[:sep]]
319
- $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
320
-
321
- doc << RDoc::Markup::Raw.new($opts[:sep])
322
-
323
- doc << RDoc::Markup::BlankLine.new if j.positive?
324
- if i[$opts[:sep]]
325
- doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
326
- doc << RDoc::Markup::Rule.new(0)
327
- end
328
-
329
- section = i[$opts[:sep]]
330
- end
331
-
332
- # Item
333
- props = RDoc::Markup::List.new(:BULLET)
334
-
335
- # Properties
336
- $opts[:props].each_with_index do |prop, k|
337
- value = i[prop]
338
-
339
- next unless value
340
- next if value.is_a?(Array) && !value[0]
341
- next if value.is_a?(String) && value.empty?
342
-
343
- $log.debug("#{prop}: #{value}")
344
-
345
- # Use Raw to save the property
346
- props << RDoc::Markup::Raw.new(prop)
347
-
348
- if k.positive?
349
- props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
350
- props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless (i['placeholder'])
351
- else
352
- # First property, value only
353
- props << RDoc::Markup::Heading.new(2, value.to_s)
354
- end
355
- end
356
-
357
- # Print it
358
- props << RDoc::Markup::BlankLine.new unless props.empty?
359
-
360
- doc << props
361
- end
362
-
363
- print doc.accept(mu)