icalPal 3.1.1 → 3.2.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.
data/lib/rdt.rb CHANGED
@@ -3,6 +3,7 @@ require 'date'
3
3
  module ICalPal
4
4
  # Child class of DateTime that adds support for relative dates (<em><b>R</b>elative<b>D</b>ate<b>T</b>ime</em>).
5
5
  class RDT < DateTime
6
+
6
7
  # Convert a String to an RDT
7
8
  #
8
9
  # @param str [String] can be +yesterday+, +today+, +tomorrow+,
@@ -14,8 +15,8 @@ module ICalPal
14
15
  when 'yesterday' then $today - 1
15
16
  when 'today' then $today
16
17
  when 'tomorrow' then $today + 1
17
- when /^\+([0-9]+)/ then $today + $1.to_i
18
- when /^\-([0-9]+)/ then $today - $1.to_i
18
+ when /^\+([0-9]+)/ then $today + Regexp.last_match(1).to_i
19
+ when /^-([0-9]+)/ then $today - Regexp.last_match(1).to_i
19
20
  else parse(str)
20
21
  end
21
22
  end
@@ -40,7 +41,7 @@ module ICalPal
40
41
  end
41
42
 
42
43
  alias inspect to_s
43
-
44
+
44
45
  # @see Time.to_a
45
46
  #
46
47
  # @return [Array] Self as an array
@@ -56,8 +57,9 @@ module ICalPal
56
57
  # @see ICalPal::RDT.to_s
57
58
  #
58
59
  # @return [Boolean]
59
- def ==(obj)
60
- self.to_s == obj.to_s
60
+ def ==(other)
61
+ self.to_s == other.to_s
61
62
  end
63
+
62
64
  end
63
65
  end
data/lib/reminder.rb CHANGED
@@ -8,13 +8,13 @@ module ICalPal
8
8
 
9
9
  def [](k)
10
10
  case k
11
- when 'notes' then # Skip empty notes
12
- @self['notes'].length > 0? @self['notes'] : nil
11
+ when 'notes' # Skip empty notes
12
+ (@self['notes'].empty?)? @self['notes'] : nil
13
13
 
14
- when 'priority' then # Integer -> String
15
- EventKit::EKReminderProperty[@self['priority']] if @self['priority'] > 0
14
+ when 'priority' # Integer -> String
15
+ EventKit::EKReminderProperty[@self['priority']] if @self['priority'].positive?
16
16
 
17
- when 'sdate' then # For sorting
17
+ when 'sdate' # For sorting
18
18
  @self['title']
19
19
 
20
20
  else @self[k]
@@ -23,32 +23,34 @@ module ICalPal
23
23
 
24
24
  def initialize(obj)
25
25
  @self = {}
26
- obj.keys.each { |k| @self[k] = obj[k] }
26
+ obj.each_key { |k| @self[k] = obj[k] }
27
27
 
28
28
  # Priority
29
+ # rubocop: disable Style/NumericPredicate
29
30
  @self['prio'] = 0 if @self['priority'] == 1 # high
30
31
  @self['prio'] = 1 if @self['priority'] == 5 # medium
31
32
  @self['prio'] = 2 if @self['priority'] == 9 # low
32
33
  @self['prio'] = 3 if @self['priority'] == 0 # none
34
+ # rubocop: enable Style/NumericPredicate
33
35
 
34
36
  @self['long_priority'] = LONG_PRIORITY[@self['prio']]
35
37
 
36
38
  # For sorting
37
- @self['sdate'] = (@self['title'])? @self['title'] : ""
39
+ @self['sdate'] = (@self['title'])? @self['title'] : ''
38
40
 
39
41
  # Due date
40
42
  @self['due'] = RDT.new(*Time.at(@self['due_date'] + ITIME).to_a.reverse[4..]) if @self['due_date']
41
43
  @self['due_date'] = 0 unless @self['due_date']
42
44
 
43
45
  # Notes
44
- @self['notes'] = "" unless @self['notes']
46
+ @self['notes'] = '' unless @self['notes']
45
47
 
46
48
  # Color
47
49
  @self['color'] = nil unless $opts[:palette]
48
50
 
49
- if @self['color'] then
51
+ if @self['color']
50
52
  # Run command
51
- stdin, stdout, stderr, e = Open3.popen3(PL_CONVERT)
53
+ stdin, stdout, _stderr, _e = Open3.popen3(PL_CONVERT)
52
54
 
53
55
  # Send color bplist
54
56
  stdin.write(@self['color'])
@@ -65,23 +67,21 @@ module ICalPal
65
67
  end
66
68
  end
67
69
 
68
- private
69
-
70
- DEFAULT_COLOR = '#1BADF8'
71
- DEFAULT_SYMBOLIC_COLOR = 'blue'
70
+ DEFAULT_COLOR = '#1BADF8'.freeze
71
+ DEFAULT_SYMBOLIC_COLOR = 'blue'.freeze
72
72
 
73
73
  LONG_PRIORITY = [
74
- "High priority",
75
- "Medium priority",
76
- "Low priority",
77
- "No priority",
78
- ]
74
+ 'High priority',
75
+ 'Medium priority',
76
+ 'Low priority',
77
+ 'No priority',
78
+ ].freeze
79
79
 
80
- PL_CONVERT = '/usr/bin/plutil -convert xml1 -o - -'
80
+ PL_CONVERT = '/usr/bin/plutil -convert xml1 -o - -'.freeze
81
81
 
82
- DB_PATH = "#{Dir::home}/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores"
82
+ DB_PATH = "#{Dir.home}/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores".freeze
83
83
 
84
- QUERY = <<~SQL
84
+ QUERY = <<~SQL.freeze
85
85
  SELECT DISTINCT
86
86
 
87
87
  zremcdReminder.zAllday as all_day,
data/lib/store.rb CHANGED
@@ -3,7 +3,7 @@ module ICalPal
3
3
  class Store
4
4
  include ICalPal
5
5
 
6
- QUERY = <<~SQL
6
+ QUERY = <<~SQL.freeze
7
7
  SELECT DISTINCT
8
8
 
9
9
  Store.name AS account,
data/lib/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module ICalPal
2
- NAME = 'icalPal'
3
- VERSION = '3.1.1'
2
+ NAME = 'icalPal'.freeze
3
+ VERSION = '3.2.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,43 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-07 00:00:00.000000000 Z
10
+ date: 2025-02-19 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: sqlite3
13
+ name: nokogiri-plist
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1'
18
+ version: 0.5.0
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '1'
25
+ version: 0.5.0
27
26
  - !ruby/object:Gem::Dependency
28
- name: nokogiri-plist
27
+ name: sqlite3
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 0.5.0
32
+ version: '2'
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: 0.5.0
39
+ version: '2'
41
40
  description: |
42
41
  Inspired by icalBuddy and maintains close compatability. Includes
43
42
  many additional features for querying, filtering, and formatting.
@@ -50,7 +49,6 @@ extra_rdoc_files:
50
49
  files:
51
50
  - README.md
52
51
  - bin/icalPal
53
- - bin/icalpal
54
52
  - icalPal.gemspec
55
53
  - lib/EventKit.rb
56
54
  - lib/ToICalPal.rb
@@ -67,7 +65,6 @@ homepage: https://github.com/ajrosen/icalPal
67
65
  licenses:
68
66
  - GPL-3.0-or-later
69
67
  metadata: {}
70
- post_install_message:
71
68
  rdoc_options: []
72
69
  require_paths:
73
70
  - lib
@@ -82,8 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
82
79
  - !ruby/object:Gem::Version
83
80
  version: '0'
84
81
  requirements: []
85
- rubygems_version: 3.5.23
86
- signing_key:
82
+ rubygems_version: 3.6.3
87
83
  specification_version: 4
88
84
  summary: Command-line tool to query the macOS Calendar
89
85
  test_files: []
data/bin/icalpal DELETED
@@ -1,327 +0,0 @@
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
- rescue LoadError => e
15
- dep = e.message[/-- (.*)/, 1]
16
-
17
- $stderr.puts "FATAL: icalpal is missing a dependency: #{dep}"
18
- $stderr.puts
19
- $stderr.puts "Install with 'gem install --user-install #{dep}'"
20
-
21
- exit
22
- end
23
-
24
-
25
- ##################################################
26
- # Load options
27
-
28
- # All kids love log!
29
- $log = Logger.new(STDERR, { level: $defaults[:common][:debug] })
30
- $log.formatter = proc do |s, t, p, m| # Severity, time, progname, msg
31
- ($log.level.positive?)? "#{s}: #{m}\n" :
32
- "[%-5s] %s [%s] - %s\n" %
33
- [ s, t.strftime('%H:%M:%S.%L'), caller(4, 1)[0].split('/')[-1], m ]
34
- end
35
-
36
- $opts = ICalPal::Options.new.parse_options
37
-
38
- $rows = [] # Rows from the database
39
- $items = [] # Items to be printed
40
-
41
-
42
- ##################################################
43
- # All kids love log!
44
-
45
- $log.info("Options: #{$opts}")
46
-
47
-
48
- ##################################################
49
- # Add an item to the list
50
- #
51
- # @param item[Object]
52
-
53
- def add(item)
54
- $log.info("Adding #{item.inspect} #{item['UUID']} (#{item['title']})") if item['UUID']
55
-
56
- item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if item === ICalPal::Event && item['sdate']
57
- $items.push(item)
58
- end
59
-
60
-
61
- ##################################################
62
- # Load the data
63
-
64
- # What are we getting?
65
- klass = ICalPal::($opts[:cmd])
66
-
67
- # Get it
68
- $opts[:db].each do |db|
69
- $log.debug("Trying #{db}")
70
-
71
- if klass == ICalPal::Reminder then
72
- # Load all .sqlite files
73
- $log.debug("Loading *.sqlite in #{db}")
74
- Dir.glob("#{db}/*.sqlite").each { |d| $rows += ICalPal.load_data(d, klass::QUERY) }
75
- else
76
- # Load database
77
- begin
78
- $rows += ICalPal.load_data(db, klass::QUERY)
79
- end
80
- end
81
- end
82
-
83
- $log.info("Loaded #{$rows.count} #{klass} rows")
84
-
85
-
86
- ##################################################
87
- # Process the data
88
-
89
- # Add rows
90
- $rows.each_with_index do |row, i|
91
- $log.debug("Row #{i}: #{row['ROWID']}:#{row['UUID']} - #{row['account']}/#{row['calendar']}/#{row['title']}")
92
-
93
- # --es/--is
94
- if $opts[:es].any? row['account'] then
95
- $log.debug(":es")
96
- next
97
- end
98
-
99
- unless $opts[:is].empty? or $opts[:is].any? row['account']
100
- $log.debug(":is");
101
- next
102
- end
103
-
104
- # --ec/--ic
105
- unless klass == ICalPal::Store or !row['calendar']
106
- if $opts[:ec].any? row['calendar'] then
107
- $log.debug(":ec")
108
- next
109
- end
110
-
111
- unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
112
- $log.debug(":ic")
113
- next
114
- end
115
- end
116
-
117
- # Instantiate an item
118
- item = klass.new(row)
119
-
120
- # --et/--it
121
- if $opts[:et].any? item['type'] then
122
- $log.debug(":et")
123
- next
124
- end
125
-
126
- unless $opts[:it].empty? or $opts[:it].any? item['type']
127
- $log.debug(":it")
128
- next
129
- end
130
-
131
- # --el/--il
132
- if $opts[:el].any? item['list_name'] then
133
- $log.debug(":el")
134
- next
135
- end
136
-
137
- unless $opts[:il].empty? or $opts[:il].any? item['list_name']
138
- $log.debug(":il")
139
- next
140
- end
141
-
142
- # --match
143
- if $opts[:match]
144
- r = $opts[:match].split('=')
145
-
146
- if item[r[0]].to_s.respond_to?(:match)
147
- unless item[r[0]].to_s.match(Regexp.new(r[1].to_s, Regexp::IGNORECASE)) then
148
- $log.debug(":match")
149
- next
150
- end
151
- end
152
- end
153
-
154
- if ICalPal::Event === item
155
- # Check for all-day and cancelled events
156
- if $opts[:ea] && item['all_day'].positive? then
157
- $log.debug(":ea")
158
- next
159
- end
160
-
161
- if $opts[:ia] && !item['all_day'].positive? then
162
- $log.debug(":ia")
163
- next
164
- end
165
-
166
- if item['status'] == :canceled then
167
- $log.debug(":canceled")
168
- next
169
- end
170
-
171
- (item['has_recurrences'].positive?)?
172
- item.recurring.each { |i| add(i) } :
173
- item.non_recurring.each { |i| add(i) }
174
- else
175
- # Check for dated reminders
176
- if ICalPal::Reminder === item then
177
- if $opts[:dated] == 1 and item['due_date'] > 0 then
178
- $log.debug(":undated")
179
- next
180
- end
181
-
182
- if $opts[:dated] == 2 and item['due_date'] == 0 then
183
- $log.debug(":dated")
184
- next
185
- end
186
- end
187
-
188
- add(item)
189
- end
190
- end
191
-
192
- # Add placeholders for empty days
193
- if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
194
- days = $items.collect { |i| i['sday'] }.uniq.sort
195
-
196
- $opts[:days].times do |n|
197
- day = $opts[:from] + n
198
- $items.push(klass.new(day)) unless days.any? { |i| i.to_s == day.to_s }
199
- end
200
- end
201
-
202
- # Sort the rows
203
- begin
204
- $log.info("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
205
-
206
- $items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
207
- $items.reverse! if $opts[:reverse]
208
- $items.uniq!
209
- rescue Exception => e
210
- $log.info("Sorting failed: #{e}\n")
211
- end
212
-
213
- $log.debug("#{$items.count} items remain")
214
-
215
- # Configure formatting
216
- mu = case $opts[:output]
217
- when 'ansi' then RDoc::Markup::ToAnsi.new
218
- when 'default' then RDoc::Markup::ToICalPal.new($opts)
219
- when 'html' then
220
- rdoc = RDoc::Options.new
221
- rdoc.pipe = true
222
- rdoc.output_decoration = false
223
- RDoc::Markup::ToHtml.new(rdoc)
224
- when 'md' then RDoc::Markup::ToMarkdown.new
225
- when 'rdoc' then RDoc::Markup::ToRdoc.new
226
- when 'toc' then RDoc::Markup::ToTableOfContents.new
227
- end
228
-
229
-
230
- ##################################################
231
- # Print the data
232
-
233
- items = $items[0..$opts[:li] - 1]
234
-
235
- unless mu
236
- $log.debug("Output in #{$opts[:output]} format")
237
-
238
- puts case $opts[:output]
239
- when 'csv' then
240
- # Get all headers
241
- headers = []
242
- items.each { |i| headers += i.keys }
243
- headers.uniq!
244
-
245
- # Populate a CSV::Table
246
- table = CSV::Table.new([], headers: headers)
247
- items.each { |i| table << i.to_csv(headers) }
248
-
249
- table
250
- when 'hash' then items.map { |i| i.self }
251
- when 'json' then items.map { |i| i.self }.to_json
252
- when 'xml' then
253
- xml = items.map { |i| "<#{$opts[:cmd].chomp("s")}>#{i.to_xml}</#{$opts[:cmd].chomp("s")}>" }
254
- "<#{$opts[:cmd]}>\n#{xml.join("")}</#{$opts[:cmd]}>"
255
- when 'yaml' then items.map { |i| i.self }.to_yaml
256
- when 'remind' then items.map { |i|
257
- "REM #{i['sdate'].strftime('%F AT %R')} " +
258
- "DURATION #{((i['edate'] - i['sdate']).to_f * 1440).to_i } " +
259
- "MSG #{i['title']}"
260
- }.join("\n")
261
- else abort "No formatter for #{$opts[:output]}"
262
- end
263
-
264
- exit
265
- end
266
-
267
- $log.debug("Formatting with #{mu.inspect}")
268
-
269
- doc = RDoc::Markup::Document.new
270
- section = nil
271
-
272
- items.each_with_index do |i, j|
273
- $log.debug("Print #{j}: #{i.inspect}")
274
-
275
- # --li
276
- break if $opts[:li].positive? && j >= $opts[:li]
277
-
278
- # Use RDoc::Markup::Verbatim to save the item
279
- v = RDoc::Markup::Verbatim.new
280
- v.format = i
281
- doc << v
282
-
283
- # Sections
284
- if $opts[:sep] && section != i[$opts[:sep]]
285
- $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
286
-
287
- doc << RDoc::Markup::Raw.new($opts[:sep])
288
-
289
- doc << RDoc::Markup::BlankLine.new if j.positive?
290
- doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
291
- doc << RDoc::Markup::Rule.new(0)
292
-
293
- section = i[$opts[:sep]]
294
- end
295
-
296
- # Item
297
- props = RDoc::Markup::List.new(:BULLET)
298
-
299
- # Properties
300
- $opts[:props].each_with_index do |prop, k|
301
- value = i[prop]
302
-
303
- next unless value
304
- next if Array === value && !value[0]
305
- next if String === value && value.length == 0
306
-
307
- $log.debug("#{prop}: #{value}")
308
-
309
- # Use Raw to save the property
310
- props << RDoc::Markup::Raw.new(prop)
311
-
312
- unless k.positive?
313
- # First property, value only
314
- props << RDoc::Markup::Heading.new(2, value.to_s)
315
- else
316
- props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
317
- props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless(i['placeholder'])
318
- end
319
- end
320
-
321
- # Print it
322
- props << RDoc::Markup::BlankLine.new unless props.empty?
323
-
324
- doc << props
325
- end
326
-
327
- print doc.accept(mu)