icalPal 3.2.0 → 3.3.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 +4 -4
- data/bin/icalPal +22 -1
- data/bin/icalpal +382 -0
- data/icalPal.gemspec +30 -2
- data/lib/defaults.rb +1 -0
- data/lib/event.rb +3 -3
- data/lib/icalPal.rb +0 -32
- data/lib/options.rb +1 -1
- data/lib/utils.rb +45 -0
- data/lib/version.rb +1 -1
- metadata +15 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd12b179a212d0582bd2740c8697f994f75d71fab58ab9ca28c8330e59e33b08
|
4
|
+
data.tar.gz: ab00df9c7f7b5fce00f8a247f346f5049f019d5336973eaec5e17d1970e773da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b2a5690bcf64c79fff82138dfeb27063dcc79d65ca29082e387cc396c7787d8beeec4beaf5ab56492bfb1739e1dcabfb2012ebceb34b899b90a118a8cf522b5
|
7
|
+
data.tar.gz: d65423b35e47f5287cb44406f618d8cb90825163d1d416e3dc2b1921483fa67ad6e62873c692fd25173a1485d8507a4f70c276a66e2a626cf0d4573e1e5571c8
|
data/bin/icalPal
CHANGED
@@ -11,6 +11,7 @@ begin
|
|
11
11
|
|
12
12
|
require_relative '../lib/icalpal'
|
13
13
|
require_relative '../lib/options'
|
14
|
+
require_relative '../lib/utils'
|
14
15
|
rescue LoadError => e
|
15
16
|
dep = e.message[/-- (.*)/, 1]
|
16
17
|
|
@@ -101,7 +102,27 @@ end
|
|
101
102
|
# Make sure we opened at least one database
|
102
103
|
unless success
|
103
104
|
$log.fatal('Could not open database')
|
104
|
-
|
105
|
+
|
106
|
+
# SQLite3 does not return useful error messages. If any databases
|
107
|
+
# failed because of EPERM (operation not permitted), our parent
|
108
|
+
# process might need Full Disk Access, and we should suggest that.
|
109
|
+
eperm = 0
|
110
|
+
|
111
|
+
$opts[:db].each do |db|
|
112
|
+
# Use a real open to get a useful error
|
113
|
+
File.open(db).close
|
114
|
+
rescue Exception => e
|
115
|
+
$log.fatal("#{e.class}: #{db}")
|
116
|
+
|
117
|
+
eperm = 1 if e.instance_of?(Errno::EPERM)
|
118
|
+
end
|
119
|
+
|
120
|
+
if eperm.positive?
|
121
|
+
$stderr.puts
|
122
|
+
$stderr.puts "Does #{ancestor} have Full Disk Access in System Settings?"
|
123
|
+
$stderr.puts
|
124
|
+
$stderr.puts "Try running: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'"
|
125
|
+
end
|
105
126
|
|
106
127
|
abort
|
107
128
|
end
|
data/bin/icalpal
ADDED
@@ -0,0 +1,382 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
require 'csv'
|
7
|
+
require 'json'
|
8
|
+
require 'rdoc'
|
9
|
+
require 'sqlite3'
|
10
|
+
require 'yaml'
|
11
|
+
|
12
|
+
require_relative '../lib/icalpal'
|
13
|
+
require_relative '../lib/options'
|
14
|
+
require_relative '../lib/utils'
|
15
|
+
rescue LoadError => e
|
16
|
+
dep = e.message[/-- (.*)/, 1]
|
17
|
+
|
18
|
+
$stderr.puts "FATAL: icalpal is missing a dependency: #{dep}"
|
19
|
+
$stderr.puts
|
20
|
+
$stderr.puts "Install with 'gem install --user-install #{dep}'"
|
21
|
+
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
##################################################
|
27
|
+
# Load options
|
28
|
+
|
29
|
+
# All kids love log!
|
30
|
+
$log = Logger.new(STDERR, { level: $defaults[:common][:debug] })
|
31
|
+
$log.formatter = proc do |s, t, _p, m| # Severity, time, progname, msg
|
32
|
+
($log.level.positive?)? "#{s}: #{m}\n" :
|
33
|
+
format("[%-5<sev>s] %<time>s [%<caller>s] - %<message>s\n",
|
34
|
+
{
|
35
|
+
sev: s,
|
36
|
+
time: t.strftime('%H:%M:%S.%L'),
|
37
|
+
caller: caller(4, 1)[0].split('/')[-1],
|
38
|
+
message: m
|
39
|
+
})
|
40
|
+
end
|
41
|
+
|
42
|
+
$opts = ICalPal::Options.new.parse_options
|
43
|
+
|
44
|
+
$rows = [] # Rows from the database
|
45
|
+
$items = [] # Items to be printed
|
46
|
+
|
47
|
+
|
48
|
+
##################################################
|
49
|
+
# All kids love log!
|
50
|
+
|
51
|
+
$log.info("Options: #{$opts}")
|
52
|
+
|
53
|
+
|
54
|
+
##################################################
|
55
|
+
# Add an item to the list
|
56
|
+
#
|
57
|
+
# @param item[Object]
|
58
|
+
|
59
|
+
def add(item)
|
60
|
+
$log.info("Adding #{item.inspect} #{item['UUID']} (#{item['title']})") if item['UUID']
|
61
|
+
|
62
|
+
item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if ICalPal::Event === item && item['sdate']
|
63
|
+
$items.push(item)
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
##################################################
|
68
|
+
# Load the data
|
69
|
+
|
70
|
+
# What are we getting?
|
71
|
+
klass = ICalPal.call($opts[:cmd])
|
72
|
+
success = false
|
73
|
+
|
74
|
+
# Get it
|
75
|
+
$opts[:db].each do |db|
|
76
|
+
$log.debug("Trying #{db}")
|
77
|
+
|
78
|
+
if klass == ICalPal::Reminder
|
79
|
+
begin
|
80
|
+
# Load all .sqlite files
|
81
|
+
$log.debug("Loading *.sqlite in #{db}")
|
82
|
+
Dir.glob("#{db}/*.sqlite").each do |d|
|
83
|
+
$rows += ICalPal.load_data(d, klass::QUERY)
|
84
|
+
success = true
|
85
|
+
|
86
|
+
rescue SQLite3::CantOpenException
|
87
|
+
# Non-fatal exception, try the next one
|
88
|
+
end
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# Load database
|
92
|
+
begin
|
93
|
+
$rows += ICalPal.load_data(db, klass::QUERY)
|
94
|
+
success = true
|
95
|
+
|
96
|
+
rescue SQLite3::CantOpenException
|
97
|
+
# Non-fatal exception, try the next one
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Make sure we opened at least one database
|
103
|
+
unless success
|
104
|
+
$log.fatal('Could not open database')
|
105
|
+
|
106
|
+
# SQLite3 does not return useful error messages. If any databases
|
107
|
+
# failed because of EPERM (operation not permitted), our parent
|
108
|
+
# process might need Full Disk Access, and we should suggest that.
|
109
|
+
eperm = 0
|
110
|
+
|
111
|
+
$opts[:db].each do |db|
|
112
|
+
# Use a real open to get a useful error
|
113
|
+
File.open(db).close
|
114
|
+
rescue Exception => e
|
115
|
+
$log.fatal("#{e.class}: #{db}")
|
116
|
+
|
117
|
+
eperm = 1 if e.instance_of?(Errno::EPERM)
|
118
|
+
end
|
119
|
+
|
120
|
+
if eperm.positive?
|
121
|
+
$stderr.puts
|
122
|
+
$stderr.puts "Does #{ancestor} have Full Disk Access in System Settings?"
|
123
|
+
$stderr.puts
|
124
|
+
$stderr.puts "Try running: open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'"
|
125
|
+
end
|
126
|
+
|
127
|
+
abort
|
128
|
+
end
|
129
|
+
|
130
|
+
$log.info("Loaded #{$rows.count} #{klass} rows")
|
131
|
+
|
132
|
+
|
133
|
+
##################################################
|
134
|
+
# Process the data
|
135
|
+
|
136
|
+
# Add rows
|
137
|
+
$rows.each_with_index do |row, i|
|
138
|
+
$log.debug("Row #{i}: #{row['ROWID']}:#{row['UUID']} - #{row['account']}/#{row['calendar']}/#{row['title']}")
|
139
|
+
|
140
|
+
# --es/--is
|
141
|
+
if $opts[:es].any? row['account']
|
142
|
+
$log.debug(':es')
|
143
|
+
next
|
144
|
+
end
|
145
|
+
|
146
|
+
unless $opts[:is].empty? || ($opts[:is].any? row['account'])
|
147
|
+
$log.debug(':is')
|
148
|
+
next
|
149
|
+
end
|
150
|
+
|
151
|
+
# --ec/--ic
|
152
|
+
unless klass == ICalPal::Store || !row['calendar']
|
153
|
+
if $opts[:ec].any? row['calendar']
|
154
|
+
$log.debug(':ec')
|
155
|
+
next
|
156
|
+
end
|
157
|
+
|
158
|
+
unless $opts[:ic].empty? || ($opts[:ic].any? row['calendar'])
|
159
|
+
$log.debug(':ic')
|
160
|
+
next
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Instantiate an item
|
165
|
+
item = klass.new(row)
|
166
|
+
|
167
|
+
# --et/--it
|
168
|
+
if $opts[:et].any? item['type']
|
169
|
+
$log.debug(':et')
|
170
|
+
next
|
171
|
+
end
|
172
|
+
|
173
|
+
unless $opts[:it].empty? || ($opts[:it].any? item['type'])
|
174
|
+
$log.debug(':it')
|
175
|
+
next
|
176
|
+
end
|
177
|
+
|
178
|
+
# --el/--il
|
179
|
+
if $opts[:el].any? item['list_name']
|
180
|
+
$log.debug(':el')
|
181
|
+
next
|
182
|
+
end
|
183
|
+
|
184
|
+
unless $opts[:il].empty? || ($opts[:il].any? item['list_name'])
|
185
|
+
$log.debug(':il')
|
186
|
+
next
|
187
|
+
end
|
188
|
+
|
189
|
+
# --match
|
190
|
+
if $opts[:match]
|
191
|
+
r = $opts[:match].split('=')
|
192
|
+
|
193
|
+
if item[r[0]].to_s.respond_to?(:match)
|
194
|
+
unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE))
|
195
|
+
$log.debug(':match')
|
196
|
+
next
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
if ICalPal::Event === item
|
202
|
+
# Check for all-day and cancelled events
|
203
|
+
if $opts[:ea] && item['all_day'].positive?
|
204
|
+
$log.debug(':ea')
|
205
|
+
next
|
206
|
+
end
|
207
|
+
|
208
|
+
if $opts[:ia] && !item['all_day'].positive?
|
209
|
+
$log.debug(':ia')
|
210
|
+
next
|
211
|
+
end
|
212
|
+
|
213
|
+
if item['status'] == :canceled
|
214
|
+
$log.debug(':canceled')
|
215
|
+
next
|
216
|
+
end
|
217
|
+
|
218
|
+
(item['has_recurrences'].positive?)?
|
219
|
+
item.recurring.each { |i| add(i) } :
|
220
|
+
item.non_recurring.each { |i| add(i) }
|
221
|
+
else
|
222
|
+
# Check for dated reminders
|
223
|
+
if ICalPal::Reminder === item
|
224
|
+
if $opts[:dated] == 1 && item['due_date'].positive?
|
225
|
+
$log.debug(':undated')
|
226
|
+
next
|
227
|
+
end
|
228
|
+
|
229
|
+
if $opts[:dated] == 2 && item['due_date'].zero?
|
230
|
+
$log.debug(':dated')
|
231
|
+
next
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
add(item)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Add placeholders for empty days
|
240
|
+
if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
|
241
|
+
days = $items.collect { |i| i['sday'] }.uniq.sort
|
242
|
+
|
243
|
+
$opts[:days].times do |n|
|
244
|
+
day = $opts[:from] + n
|
245
|
+
$items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Sort the rows
|
250
|
+
begin
|
251
|
+
$log.info("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
|
252
|
+
|
253
|
+
$items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
|
254
|
+
$items.reverse! if $opts[:reverse]
|
255
|
+
$items.uniq!
|
256
|
+
rescue Exception => e
|
257
|
+
$log.info("Sorting failed: #{e}\n")
|
258
|
+
end
|
259
|
+
|
260
|
+
$log.debug("#{$items.count} items remain")
|
261
|
+
|
262
|
+
# Configure formatting
|
263
|
+
mu = case $opts[:output]
|
264
|
+
when 'ansi' then RDoc::Markup::ToAnsi.new
|
265
|
+
when 'default' then RDoc::Markup::ToICalPal.new($opts)
|
266
|
+
when 'html'
|
267
|
+
rdoc = RDoc::Options.new
|
268
|
+
rdoc.pipe = true
|
269
|
+
rdoc.output_decoration = false
|
270
|
+
RDoc::Markup::ToHtml.new(rdoc)
|
271
|
+
when 'md' then RDoc::Markup::ToMarkdown.new
|
272
|
+
when 'rdoc' then RDoc::Markup::ToRdoc.new
|
273
|
+
when 'toc' then RDoc::Markup::ToTableOfContents.new
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
##################################################
|
278
|
+
# Print the data
|
279
|
+
|
280
|
+
items = $items[0..$opts[:li] - 1]
|
281
|
+
|
282
|
+
unless mu
|
283
|
+
$log.debug("Output in #{$opts[:output]} format")
|
284
|
+
|
285
|
+
puts case $opts[:output]
|
286
|
+
when 'csv'
|
287
|
+
# Get all headers
|
288
|
+
headers = []
|
289
|
+
items.each { |i| headers += i.keys }
|
290
|
+
headers.uniq!
|
291
|
+
|
292
|
+
# Populate a CSV::Table
|
293
|
+
table = CSV::Table.new([], headers: headers)
|
294
|
+
items.each { |i| table << i.to_csv(headers) }
|
295
|
+
|
296
|
+
table
|
297
|
+
when 'hash' then items.map { |i| i.self }
|
298
|
+
when 'json' then items.map { |i| i.self }.to_json
|
299
|
+
when 'xml'
|
300
|
+
xml = items.map { |i| "<#{$opts[:cmd].chomp('s')}>#{i.to_xml}</#{$opts[:cmd].chomp('s')}>" }
|
301
|
+
"<#{$opts[:cmd]}>\n#{xml.join('')}</#{$opts[:cmd]}>"
|
302
|
+
when 'yaml' then items.map { |i| i.self }.to_yaml
|
303
|
+
when 'remind' then items.map { |i|
|
304
|
+
"REM #{i['sdate'].strftime('%F AT %R')} " +
|
305
|
+
"DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i} " +
|
306
|
+
"MSG #{i['title']}"
|
307
|
+
}.join("\n")
|
308
|
+
else abort "No formatter for #{$opts[:output]}"
|
309
|
+
end
|
310
|
+
|
311
|
+
exit
|
312
|
+
end
|
313
|
+
|
314
|
+
$log.debug("Formatting with #{mu.inspect}")
|
315
|
+
|
316
|
+
doc = RDoc::Markup::Document.new
|
317
|
+
section = nil
|
318
|
+
|
319
|
+
items.each_with_index do |i, j|
|
320
|
+
$log.debug("Print #{j}: #{i.inspect}")
|
321
|
+
|
322
|
+
# --li
|
323
|
+
break if $opts[:li].positive? && j >= $opts[:li]
|
324
|
+
|
325
|
+
# Use RDoc::Markup::Verbatim to save the item
|
326
|
+
v = RDoc::Markup::Verbatim.new
|
327
|
+
v.format = i
|
328
|
+
doc << v
|
329
|
+
|
330
|
+
# Sections
|
331
|
+
if $opts[:sep] && section != i[$opts[:sep]]
|
332
|
+
$log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
|
333
|
+
|
334
|
+
doc << RDoc::Markup::Raw.new($opts[:sep])
|
335
|
+
|
336
|
+
doc << RDoc::Markup::BlankLine.new if j.positive?
|
337
|
+
doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
|
338
|
+
doc << RDoc::Markup::Rule.new(0)
|
339
|
+
|
340
|
+
section = i[$opts[:sep]]
|
341
|
+
end
|
342
|
+
|
343
|
+
# Item
|
344
|
+
props = RDoc::Markup::List.new(:BULLET)
|
345
|
+
|
346
|
+
# Properties
|
347
|
+
$opts[:props].each_with_index do |prop, k|
|
348
|
+
value = i[prop]
|
349
|
+
|
350
|
+
next unless value
|
351
|
+
next if Array === value && !value[0]
|
352
|
+
next if String === value && value.empty?
|
353
|
+
|
354
|
+
$log.debug("#{prop}: #{value}")
|
355
|
+
|
356
|
+
# Use Raw to save the property
|
357
|
+
props << RDoc::Markup::Raw.new(prop)
|
358
|
+
|
359
|
+
if k.positive?
|
360
|
+
props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
|
361
|
+
props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless (i['placeholder'])
|
362
|
+
else
|
363
|
+
# First property, value only
|
364
|
+
props << RDoc::Markup::Heading.new(2, value.to_s)
|
365
|
+
end
|
366
|
+
|
367
|
+
# unless k.positive?
|
368
|
+
# # First property, value only
|
369
|
+
# props << RDoc::Markup::Heading.new(2, value.to_s)
|
370
|
+
# else
|
371
|
+
# props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
|
372
|
+
# props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless (i['placeholder'])
|
373
|
+
# end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Print it
|
377
|
+
props << RDoc::Markup::BlankLine.new unless props.empty?
|
378
|
+
|
379
|
+
doc << props
|
380
|
+
end
|
381
|
+
|
382
|
+
print doc.accept(mu)
|
data/icalPal.gemspec
CHANGED
@@ -15,13 +15,41 @@ EOF
|
|
15
15
|
s.homepage = "https://github.com/ajrosen/#{s.name}"
|
16
16
|
s.licenses = [ 'GPL-3.0-or-later' ]
|
17
17
|
|
18
|
+
s.metadata = {
|
19
|
+
'bug_tracker_uri' => "https://github.com/ajrosen/#{s.name}/issues"
|
20
|
+
}
|
21
|
+
|
18
22
|
s.files = Dir["#{s.name}.gemspec", 'bin/*', 'lib/*.rb']
|
19
23
|
s.executables = [ "#{s.name}" ]
|
20
24
|
s.extra_rdoc_files = [ 'README.md' ]
|
21
25
|
|
22
|
-
s.
|
23
|
-
s.
|
26
|
+
s.add_dependency 'nokogiri-plist', '~> 0.5.0'
|
27
|
+
s.add_dependency 'sqlite3', '~> 2.6.0' unless s.rubygems_version == `/usr/bin/gem --version`.strip
|
28
|
+
|
29
|
+
# The macOS and Homebrew versions of rubygems have incompatible
|
30
|
+
# requirements for sqlite3.
|
31
|
+
#
|
32
|
+
# macOS comes with version 1.3.13, so it does not need to be added
|
33
|
+
# as a dependency, but it cannot install anything newer:
|
34
|
+
#
|
35
|
+
# requires Ruby version >= 3.0, < 3.4.dev. The current ruby version is 2.6.10.
|
36
|
+
#
|
37
|
+
# Homebrew's Ruby formula does not come with sqlite3, so it does
|
38
|
+
# need to be added as a dependency, but it cannot install version
|
39
|
+
# 1.3.13:
|
40
|
+
#
|
41
|
+
# error: call to undeclared function
|
42
|
+
#
|
43
|
+
# So we must call add_dependency, but iff we are not building with
|
44
|
+
# macOS' Ruby installation.
|
24
45
|
|
25
46
|
s.bindir = 'bin'
|
26
47
|
s.required_ruby_version = '>= 2.6.0'
|
48
|
+
|
49
|
+
s.post_install_message = <<-EOF
|
50
|
+
|
51
|
+
Note: #{ICalPal::NAME} requires "Full Disk Access" in System Settings to access your calendar.
|
52
|
+
Make sure the program that runs #{ICalPal::NAME}, not #{ICalPal::NAME} itself, has these permissions.
|
53
|
+
|
54
|
+
EOF
|
27
55
|
end
|
data/lib/defaults.rb
CHANGED
data/lib/event.rb
CHANGED
@@ -130,7 +130,7 @@ module ICalPal
|
|
130
130
|
$log.debug("multi-day event #{i + 1}") if (i.positive?)
|
131
131
|
|
132
132
|
self['daynum'] = i + 1
|
133
|
-
events.push(clone) if in_window?(self['sdate'])
|
133
|
+
events.push(clone) if in_window?(self['sdate'], self['edate'])
|
134
134
|
|
135
135
|
self['sdate'] += 1
|
136
136
|
self['edate'] += 1
|
@@ -287,8 +287,8 @@ module ICalPal
|
|
287
287
|
# @param s [RDT] Event start
|
288
288
|
# @param e [RDT] Event end
|
289
289
|
# @return [Boolean]
|
290
|
-
def in_window?(s, e
|
291
|
-
if $opts[:
|
290
|
+
def in_window?(s, e)
|
291
|
+
if $opts[:now]
|
292
292
|
if ($now >= s && $now < e)
|
293
293
|
$log.debug("now: #{s} to #{e} vs. #{$now}")
|
294
294
|
true
|
data/lib/icalPal.rb
CHANGED
@@ -110,38 +110,6 @@ module ICalPal
|
|
110
110
|
retval
|
111
111
|
end
|
112
112
|
|
113
|
-
# Convert a key/value pair to XML. The value should be +nil+, +String+,
|
114
|
-
# +Integer+, +Array+, or +ICalPal::RDT+
|
115
|
-
#
|
116
|
-
# @param key The key
|
117
|
-
# @param value The value
|
118
|
-
# @return [String] The key/value pair in a simple XML format
|
119
|
-
def xmlify(key, value)
|
120
|
-
case value
|
121
|
-
# Nil
|
122
|
-
when NilClass then "<#{key}/>"
|
123
|
-
|
124
|
-
# String, Integer
|
125
|
-
when String then "<#{key}>#{value}</#{key}>"
|
126
|
-
when Integer then "<#{key}>#{value}</#{key}>"
|
127
|
-
|
128
|
-
# Array
|
129
|
-
when Array
|
130
|
-
# Treat empty arrays as nil values
|
131
|
-
xmlify(key, nil) if value[0].nil?
|
132
|
-
|
133
|
-
retval = ''
|
134
|
-
value.each { |x| retval += xmlify("#{key}0", x) }
|
135
|
-
"<#{key}>#{retval}</#{key}>"
|
136
|
-
|
137
|
-
# RDT
|
138
|
-
when ICalPal::RDT then "<#{key}>#{value}</#{key}>"
|
139
|
-
|
140
|
-
# Unknown
|
141
|
-
else "<#{key}>#{value}</#{key}>"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
113
|
# Get the +n+'th +dow+ in month +m+
|
146
114
|
#
|
147
115
|
# @param n [Integer] Integer between -4 and +4
|
data/lib/options.rb
CHANGED
@@ -197,7 +197,7 @@ module ICalPal
|
|
197
197
|
|
198
198
|
# Parse eventsNow and eventsToday commands
|
199
199
|
cli[:cmd].match('events(Now|Today)(\+[0-9]+)?') do |m|
|
200
|
-
cli[:
|
200
|
+
cli[:now] = true if m[1] == 'Now'
|
201
201
|
cli[:days] = (m[1] == 'Today')? m[2].to_i + 1 : 1
|
202
202
|
|
203
203
|
cli[:from] = $today
|
data/lib/utils.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Convert a key/value pair to XML. The value should be +nil+, +String+,
|
2
|
+
# +Integer+, +Array+, or +ICalPal::RDT+
|
3
|
+
#
|
4
|
+
# @param key The key
|
5
|
+
# @param value The value
|
6
|
+
# @return [String] The key/value pair in a simple XML format
|
7
|
+
def xmlify(key, value)
|
8
|
+
case value
|
9
|
+
# Nil
|
10
|
+
when NilClass then "<#{key}/>"
|
11
|
+
|
12
|
+
# String, Integer
|
13
|
+
when String then "<#{key}>#{value}</#{key}>"
|
14
|
+
when Integer then "<#{key}>#{value}</#{key}>"
|
15
|
+
|
16
|
+
# Array
|
17
|
+
when Array
|
18
|
+
# Treat empty arrays as nil values
|
19
|
+
xmlify(key, nil) if value[0].nil?
|
20
|
+
|
21
|
+
retval = ''
|
22
|
+
value.each { |x| retval += xmlify("#{key}0", x) }
|
23
|
+
"<#{key}>#{retval}</#{key}>"
|
24
|
+
|
25
|
+
# RDT
|
26
|
+
when ICalPal::RDT then "<#{key}>#{value}</#{key}>"
|
27
|
+
|
28
|
+
# Unknown
|
29
|
+
else "<#{key}>#{value}</#{key}>"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the application icalPal is most likely running in
|
34
|
+
#
|
35
|
+
# @return [Integer] The basename of the program whose parent process id is 1 (launchd)
|
36
|
+
def ancestor
|
37
|
+
ppid = Process.ppid
|
38
|
+
|
39
|
+
while (ppid != 1)
|
40
|
+
ps = `ps -p #{ppid} -o ppid,command | tail -1`
|
41
|
+
ppid = ps[/^[0-9 ]+ /].to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
ps[ps.rindex('/') + 1..].chop
|
45
|
+
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icalPal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Rosen
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-24 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: nokogiri-plist
|
@@ -29,14 +29,14 @@ dependencies:
|
|
29
29
|
requirements:
|
30
30
|
- - "~>"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 2.6.0
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
39
|
+
version: 2.6.0
|
40
40
|
description: |
|
41
41
|
Inspired by icalBuddy and maintains close compatability. Includes
|
42
42
|
many additional features for querying, filtering, and formatting.
|
@@ -49,6 +49,7 @@ extra_rdoc_files:
|
|
49
49
|
files:
|
50
50
|
- README.md
|
51
51
|
- bin/icalPal
|
52
|
+
- bin/icalpal
|
52
53
|
- icalPal.gemspec
|
53
54
|
- lib/EventKit.rb
|
54
55
|
- lib/ToICalPal.rb
|
@@ -60,11 +61,18 @@ files:
|
|
60
61
|
- lib/rdt.rb
|
61
62
|
- lib/reminder.rb
|
62
63
|
- lib/store.rb
|
64
|
+
- lib/utils.rb
|
63
65
|
- lib/version.rb
|
64
66
|
homepage: https://github.com/ajrosen/icalPal
|
65
67
|
licenses:
|
66
68
|
- GPL-3.0-or-later
|
67
|
-
metadata:
|
69
|
+
metadata:
|
70
|
+
bug_tracker_uri: https://github.com/ajrosen/icalPal/issues
|
71
|
+
post_install_message: |2+
|
72
|
+
|
73
|
+
Note: icalPal requires "Full Disk Access" in System Settings to access your calendar.
|
74
|
+
Make sure the program that runs icalPal, not icalPal itself, has these permissions.
|
75
|
+
|
68
76
|
rdoc_options: []
|
69
77
|
require_paths:
|
70
78
|
- lib
|
@@ -79,7 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
87
|
- !ruby/object:Gem::Version
|
80
88
|
version: '0'
|
81
89
|
requirements: []
|
82
|
-
rubygems_version: 3.6.
|
90
|
+
rubygems_version: 3.6.6
|
83
91
|
specification_version: 4
|
84
92
|
summary: Command-line tool to query the macOS Calendar
|
85
93
|
test_files: []
|
94
|
+
...
|