icalPal 1.2.1 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c08ab4aaf0558f9648fbefbb9eaea1aac9c50de2770e1897699b619ba92ef6b6
4
- data.tar.gz: '087d14b6e855bdbf2681e526941baec600fed607a6e6760b09f4361bb33735d8'
3
+ metadata.gz: 4dab143a05d8a63ae36b721ca6ba53180c75d8b4dd0f0cebdc84fb533968c248
4
+ data.tar.gz: 8d243a120762c90059e4206a2deb5e79b821e558cf9ad9a437ad84fbfbb6a21b
5
5
  SHA512:
6
- metadata.gz: 70934664b432d1b99e0f200bd1e78a2b142f9b1b9b405c8763913aa9d414a83171f4876e0145ff741854abd1b643fdb4a72031b564af14a2a4f1764cc494a13f
7
- data.tar.gz: '0580e0d9556919516b1c2d4a4ea11306a2a8786ea80ee5ccc5821c97f899d7bc95c3499c7f5d9dc05ac091dba53867b01a7b676e0bd20881563d7529e16a1a31'
6
+ metadata.gz: 24f4b88c592e59f2ffbc116761d6da41b60a7e5d4554feea9bdb023450967eaa7921f5c4708a66bf76554926ab4e0ca52965470f7576cef1cce5aec9a6112cbb
7
+ data.tar.gz: d69cc1d2cd63072020a4059a352ccdb2262a189e10c3c4e1891bd1c953ea2cb60a577fc83bd2ab55b561d0a752b0fb4198495bc2a5f57909d4216101cc7ea012
data/README.md CHANGED
@@ -4,16 +4,30 @@
4
4
 
5
5
  ## Description
6
6
 
7
- icalPal is a command-line tool to query a macOS Calendar database for
8
- accounts, calendars, and events. It can be run on any system with
9
- [Ruby](https://www.ruby-lang.org/) and access to a Calendar database
10
- file.
7
+ icalPal is a command-line tool to query a macOS Calendar and Reminders
8
+ databases for accounts, calendars, events, and tasks. It can be run
9
+ on any system with [Ruby](https://www.ruby-lang.org/) and access to a
10
+ Calendar or Reminders database.
11
11
 
12
12
  ## Installation
13
13
 
14
+ As a system-wide Ruby gem:
15
+
14
16
  ```
15
17
  gem install icalPal
16
- icalPal events
18
+ ```
19
+
20
+ or in your home diretory:
21
+
22
+ ```
23
+ gem install --user-install icalPal
24
+ ```
25
+
26
+ As a Homebrew formula:
27
+
28
+ ```
29
+ brew tap ajrosen/icalPal
30
+ brew install icalPal
17
31
  ```
18
32
 
19
33
  ## Features
@@ -21,11 +35,14 @@ icalPal events
21
35
  ### Compatability with [icalBuddy](https://github.com/ali-rantakari/icalBuddy)
22
36
 
23
37
  icalPal tries to be compatible with icalBuddy for command-line options
24
- and for output. There are a few differences to be aware of.
38
+ and for output. There are a some important differences to be aware
39
+ of.
25
40
 
26
41
  * Options require two hyphens, except for single-letter options that require one hyphen
27
42
  * *eventsFrom* is not supported. Instead there is *--from*, *--to*, and *--days*
28
- * icalPal does not support the *tasks* commands yet
43
+ * *uncompletedTasks* is simply *tasks*
44
+ * *undatedUncompletedTasks* is simply *undatedTasks*
45
+ * *tasksDueBefore:DATE* is not yet supported
29
46
  * The command can go anywhere; it doesn't have to be the last argument
30
47
  * Property separators are comma-delimited
31
48
 
@@ -35,6 +52,10 @@ and for output. There are a few differences to be aware of.
35
52
 
36
53
  Shows a list of enabled Calendar accounts. Internally they are known as *Stores*; you can run ```icalPal stores``` instead.
37
54
 
55
+ ```icalPal datedTasks```
56
+
57
+ Shows only reminders that have a due date.
58
+
38
59
  ### Additional options
39
60
 
40
61
  * Options can be abbreviated, so long as they are unique. Eg., ```icalPal -c ev --da 3``` is the same as ```icalPal -c events --days 3```.
@@ -42,37 +63,45 @@ Shows a list of enabled Calendar accounts. Internally they are known as *Stores
42
63
  * Use ```-o``` to print the output in different formats. CSV or JSON are intertesting choices.
43
64
  * Copy your Calendar database file and use ```--db``` on it.
44
65
  * ```--it``` and ```--et``` will filter by Calendar *type*. Types are **Local**, **Exchange**, **CalDAV**, **MobileMe**, **Subscribed**, and **Birthdays**
66
+ * ```--il``` and ```-el``` will filter by Reminder list
45
67
  * ```--ia``` includes *only* all-day events (opposite of ```--ea```)
46
68
  * ```--aep``` is like ```--iep```, but *adds* to the default property list instead of replacing it.
47
69
  * ```--sep``` to separate by any property, not just calendar (```--sc```) or date (```--sd```)
48
70
  * ```--color``` uses a wider color palette. Calendar colors are what you have chosen in the Calendar app. Not supported in all terminals, but looks great in [iTerm2](https://iterm2.com/).
49
71
 
50
- Because icalPal is written in Ruby, and not a native Mac application, you can run it just about anywhere. It's been tested with version of Ruby (2.6.10) included with macOS, and does not require any external dependencies.
72
+ Because icalPal is written in Ruby, and not a native Mac application, you can run it just about anywhere. It's been tested with the version of Ruby (2.6.10) included with macOS.
51
73
 
52
74
  ## Usage
53
75
 
54
76
  icalPal: Usage: icalPal [options] [-c] COMMAND
55
77
 
56
78
  COMMAND must be one of the following:
57
-
79
+ ```
58
80
  events Print events
81
+ tasks Print tasks
59
82
  calendars Print calendars
60
83
  accounts Print accounts
61
84
 
62
85
  eventsToday Print events occurring today
63
86
  eventsToday+NUM Print events occurring between today and NUM days into the future
64
87
  eventsNow Print events occurring at present time
88
+ datedTasks Print tasks with a due date
89
+ undatedTasks Print tasks with no due date
90
+ ```
65
91
 
66
92
  Global options:
67
-
93
+ ```
68
94
  -c, --cmd=COMMAND Command to run
69
- --db=DB Use DB file instead of Calendar
70
- --cf=FILE Set config file path (default: $HOME/.icalPal)
95
+ --db=DB Use DB file instead of Calendar (default: /Users/ajr/Library/Calendars/Calendar.sqlitedb)
96
+ For the tasks commands this should be a directory containing .sqlite files
97
+ (default: /Users/ajr/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores)
98
+ --cf=FILE Set config file path (default: /Users/ajr/.icalPal)
71
99
  -o, --output=FORMAT Print as FORMAT (default: default)
72
- [ansi, csv, default, hash, html, json, md, rdoc, toc, yaml, remind]
73
-
74
- Including/excluding calendars:
100
+ [ansi, csv, default, hash, html, json, md, rdoc, remind, toc, xml, yaml]
101
+ ```
75
102
 
103
+ Including/excluding calendars and reminders:
104
+ ```
76
105
  --is=ACCOUNTS List of accounts to include
77
106
  --es=ACCOUNTS List of accounts to exclude
78
107
 
@@ -83,8 +112,12 @@ Including/excluding calendars:
83
112
  --ic=CALENDARS List of calendars to include
84
113
  --ec=CALENDARS List of calendars to exclude
85
114
 
86
- Choosing dates:
115
+ --il=LISTS List of reminder lists to include
116
+ --el=LISTS List of reminder lists to exclude
117
+ ```
87
118
 
119
+ Choosing dates:
120
+ ```
88
121
  --from=DATE List events starting on or after DATE
89
122
  --to=DATE List events starting on or before DATE
90
123
  DATE can be yesterday, today, tomorrow, +N, -N, or anything accepted by DateTime.parse()
@@ -95,13 +128,19 @@ Choosing dates:
95
128
  --sed Show empty dates with --sd
96
129
  --ia Include only all-day events
97
130
  --ea Exclude all-day events
131
+ ```
98
132
 
99
133
  Choose properties to include in the output:
100
-
134
+ ```
101
135
  --iep=PROPERTIES List of properties to include
102
136
  --eep=PROPERTIES List of properties to exclude
103
137
  --aep=PROPERTIES List of properties to include in addition to the default list
104
138
 
139
+ --itp=PROPERTIES List of task properties to include
140
+ --etp=PROPERTIES List of task properties to exclude
141
+ --atp=PROPERTIES List of task properties to include in addition to the default list
142
+ Included for backwards compatability, these are aliases for --iep, --eep, and --aep
143
+
105
144
  --uid Show event UIDs
106
145
  --eed Exclude end datetimes
107
146
 
@@ -113,16 +152,20 @@ Choose properties to include in the output:
113
152
 
114
153
  Use 'all' for PROPERTIES to include all available properties (except any listed in --eep)
115
154
  Use 'list' for PROPERTIES to list all available properties and exit
155
+ ```
116
156
 
117
157
  Formatting the output:
118
-
158
+ ```
119
159
  --li=N Show at most N items (default: 0 for no limit)
120
160
 
121
161
  --sc Separate by calendar
122
162
  --sd Separate by date
163
+ --sp Separate by priority
123
164
  --sep=PROPERTY Separate by PROPERTY
124
165
 
125
166
  --sort=PROPERTY Sort by PROPERTY
167
+ --std Sort tasks by due date (same as --sort=due_date)
168
+ --stda Sort tasks by due date (ascending) (same as --sort=due_date -r)
126
169
  -r, --reverse Sort in reverse
127
170
 
128
171
  --ps=SEPARATORS List of property separators
@@ -133,53 +176,37 @@ Formatting the output:
133
176
  See https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-i-strftime for details
134
177
 
135
178
  -b, --bullet=STRING Use STRING for bullets
179
+ --ab=STRING Use STRING for alert bullets
180
+ --nb Do not use bullets
136
181
  --nnr=SEPARATOR Set replacement for newlines within notes
137
182
 
138
183
  -f Format output using standard ANSI colors
139
184
  --color Format output using a larger color palette
185
+ ```
140
186
 
141
187
  Help:
142
-
188
+ ```
143
189
  -h, --help Show this message
144
- -V, -v, --version Show version and exit (1.0)
190
+ -V, -v, --version Show version and exit (2.0.0)
145
191
  -d, --debug=LEVEL Set the logging level (default: warn)
146
192
  [debug, info, warn, error, fatal]
193
+ ```
147
194
 
148
195
  Environment variables:
149
-
196
+ ```
150
197
  ICALPAL Additional arguments
151
198
  ICALPAL_CONFIG Additional arguments from a file
152
- (default: $HOME/.icalPal)
153
-
154
-
155
- ## History
156
-
157
- I have used icalBuddy for many years. It's great for scripting,
158
- automation, and as a desktop widget for apps like
159
- [GeekTool](https://www.tynsoe.org/geektool/) and
160
- [Übersicht](https://tracesof.net/uebersicht/).
161
-
162
- As with many applications, I started to run into some limitations in
163
- icalBuddy. The biggest being that active development ended in 2014.
164
- It's only thanks to the efforts of [Jim
165
- Lawton](https://github.com/jimlawton) that it even compiles anymore.
166
-
167
- Instead of trying to understand and extend the existing code, I chose
168
- to start anew using my language of choice. Using Ruby means icalPal
169
- is multi-platform. It also meant *much* less code; about 1,200 lines
170
- vs. 7,000.
171
-
172
- I won't pretend to understand **why** you would want this on Linux or
173
- Windows. But since icalPal is written in Ruby and gets its data
174
- directly from the Calendar database file instead of an API, you *can*.
199
+ (default: /Users/ajr/.icalPal)
200
+ ```
175
201
 
176
202
  ## Output formats
177
203
 
178
204
  icalPal supports several output formats. The **default** format tries
179
205
  to mimic icalBuddy as much as possible.
180
206
 
181
- CSV, Hash, JSON, and YAML print all fields for all items in their
182
- respective formats. From that you can analyze the results any way you like.
207
+ CSV, Hash, JSON, XML, and YAML print all fields for all items in their
208
+ respective formats. From that you can analyze the results any way you
209
+ like.
183
210
 
184
211
  [Remind](https://dianne.skoll.ca/projects/remind/) format uses a minimal implementation built in icalPal.
185
212
 
@@ -209,5 +236,29 @@ objects, one for each of the item's properties:
209
236
 
210
237
  The document will also include a number of
211
238
  [RDoc::Markup::Verbatim](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Verbatim.html)
239
+ and
240
+ [RDoc::Markup::Raw](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Raw.html)
212
241
  items. They are not included in the output, but are used to pass
213
242
  information about the item and property to the default formatter.
243
+
244
+ ## History
245
+
246
+ I used icalBuddy for many years. It's great for scripting,
247
+ automation, and as a desktop widget for apps like
248
+ [GeekTool](https://www.tynsoe.org/geektool/) and
249
+ [Übersicht](https://tracesof.net/uebersicht/).
250
+
251
+ As with many applications, I started to run into some limitations in
252
+ icalBuddy. The biggest being that active development ended in 2014.
253
+ It's only thanks to the efforts of [Jim
254
+ Lawton](https://github.com/jimlawton) that it even compiles anymore.
255
+
256
+ Instead of trying to understand and extend the existing code, I chose
257
+ to start anew using my language of choice: Ruby. Using Ruby meant
258
+ there is *much* less code; about 1,600 lines vs. 7,000. It also means
259
+ icalPal is multi-platform.
260
+
261
+ I won't pretend to understand **why** you would want to run this on
262
+ Linux or Windows. But since icalPal is written in Ruby and gets its
263
+ data directly from the Calendar and Reminders database files instead
264
+ of an API, you *can*.
data/bin/icalPal CHANGED
@@ -1,16 +1,25 @@
1
1
  #!/usr/bin/env ruby
2
- # *-*- mode: enh-ruby -*-*
3
2
 
4
- require 'logger'
3
+ begin
4
+ require 'logger'
5
+
6
+ require 'csv'
7
+ require 'json'
8
+ require 'rdoc'
9
+ require 'sqlite3'
10
+ require 'yaml'
5
11
 
6
- require 'csv'
7
- require 'json'
8
- require 'rdoc'
9
- require 'sqlite3'
10
- require 'yaml'
12
+ require_relative '../lib/icalPal'
13
+ require_relative '../lib/options'
14
+ rescue LoadError => e
15
+ dep = e.message[/-- (.*)/, 1]
11
16
 
12
- require_relative '../lib/icalPal'
13
- require_relative '../lib/options'
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
14
23
 
15
24
 
16
25
  ##################################################
@@ -44,7 +53,7 @@ $log.info("Options: #{$opts}")
44
53
  def add(item)
45
54
  $log.info("Adding #{item.inspect} #{item['UUID']} (#{item['title']})") if item['UUID']
46
55
 
47
- item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if item['sdate']
56
+ item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if item === ICalPal::Event && item['sdate']
48
57
  $items.push(item)
49
58
  end
50
59
 
@@ -54,32 +63,17 @@ end
54
63
 
55
64
  # What are we getting?
56
65
  klass = ICalPal::($opts[:cmd])
57
- q = klass::QUERY
58
-
59
- $log.debug(q.gsub(/\n/, ' '))
60
66
 
61
67
  # Get it
62
- begin
63
- stmt = $db.prepare(q)
64
- abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
65
- $opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
66
-
67
- # Iterate the SQLite3::ResultSet once
68
- stmt.execute.each_with_index { |i, j| $rows[j] = i }
69
- stmt.close
70
-
71
- # Close the database
72
- $db.close
73
- $log.debug("Closed #{$opts[:db]}")
74
-
75
- rescue SQLite3::BusyException => e
76
- $log.error("Non-fatal error closing database #{$db.filename}")
77
-
78
- rescue SQLite3::SQLException => e
79
- abort(e.message)
68
+ if klass == ICalPal::Reminder then
69
+ # Load all .sqlite files
70
+ Dir.glob("#{$opts[:db]}/*.sqlite").each { |db| $rows += ICalPal.load_data(db, klass::QUERY) }
71
+ else
72
+ # Load database
73
+ $rows += ICalPal.load_data($opts[:db], klass::QUERY)
80
74
  end
81
75
 
82
- $log.info("Loaded #{$rows.count} rows from #{$opts[:db]}")
76
+ $log.info("Loaded #{$rows.count} #{klass} rows")
83
77
 
84
78
 
85
79
  ##################################################
@@ -111,6 +105,7 @@ $rows.each_with_index do |row, i|
111
105
  next
112
106
  end
113
107
 
108
+ # Instantiate an item
114
109
  item = klass.new(row)
115
110
 
116
111
  # --et/--it
@@ -124,11 +119,18 @@ $rows.each_with_index do |row, i|
124
119
  next
125
120
  end
126
121
 
127
- unless ICalPal::Event === item
128
- # Always add non-event items
129
- $log.debug("Adding non-event #{item}")
130
- add(item)
131
- else
122
+ # --el/--il
123
+ if $opts[:el].any? item['list_name'] then
124
+ $log.debug(":el")
125
+ next
126
+ end
127
+
128
+ unless $opts[:il].empty? or $opts[:il].any? item['list_name']
129
+ $log.debug(":il")
130
+ next
131
+ end
132
+
133
+ if ICalPal::Event === item
132
134
  # Check for all-day and cancelled events
133
135
  if $opts[:ea] && item['all_day'].positive? then
134
136
  $log.debug(":ea")
@@ -148,6 +150,21 @@ $rows.each_with_index do |row, i|
148
150
  (item['has_recurrences'].positive?)?
149
151
  item.recurring.each { |i| add(i) } :
150
152
  item.non_recurring.each { |i| add(i) }
153
+ else
154
+ # Check for dated reminders
155
+ if ICalPal::Reminder === item then
156
+ if $opts[:dated] == 1 and item['due_date'] > 0 then
157
+ $log.debug(":undated")
158
+ next
159
+ end
160
+
161
+ if $opts[:dated] == 2 and item['due_date'] == 0 then
162
+ $log.debug(":dated")
163
+ next
164
+ end
165
+ end
166
+
167
+ add(item)
151
168
  end
152
169
  end
153
170
 
@@ -163,13 +180,13 @@ end
163
180
 
164
181
  # Sort the rows
165
182
  begin
166
- $log.debug("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
183
+ $log.info("Sorting/uniqing #{$items.count} items by #{[ $opts[:sep], $opts[:sort], 'sdate' ]}, reverse #{$opts[:reverse].inspect}")
167
184
 
168
185
  $items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
169
186
  $items.reverse! if $opts[:reverse]
170
187
  $items.uniq!
171
- rescue ArgumentError => e
172
- $log.warn("Sorting failed, results may be unexpected\n")
188
+ rescue Exception => e
189
+ $log.info("Sorting failed: #{e}\n")
173
190
  end
174
191
 
175
192
  $log.debug("#{$items.count} items remain")
@@ -211,6 +228,9 @@ unless mu
211
228
  table
212
229
  when 'hash' then items.map { |i| i.self }
213
230
  when 'json' then items.map { |i| i.self }.to_json
231
+ when 'xml' then
232
+ xml = items.map { |i| "<#{$opts[:cmd].chomp("s")}>#{i.to_xml}</#{$opts[:cmd].chomp("s")}>" }
233
+ "<#{$opts[:cmd]}>\n#{xml.join("")}</#{$opts[:cmd]}>"
214
234
  when 'yaml' then items.map { |i| i.self }.to_yaml
215
235
  when 'remind' then items.map { |i|
216
236
  "REM #{i['sdate'].strftime('%F AT %R')} " +
@@ -225,7 +245,8 @@ end
225
245
 
226
246
  $log.debug("Formatting with #{mu.inspect}")
227
247
 
228
- section = nil if $opts[:sep]
248
+ doc = RDoc::Markup::Document.new
249
+ section = nil
229
250
 
230
251
  items.each_with_index do |i, j|
231
252
  $log.debug("Print #{j}: #{i.inspect}")
@@ -233,15 +254,16 @@ items.each_with_index do |i, j|
233
254
  # --li
234
255
  break if $opts[:li].positive? && j >= $opts[:li]
235
256
 
236
- doc = RDoc::Markup::Document.new
257
+ # Use RDoc::Markup::Verbatim to save the item
258
+ v = RDoc::Markup::Verbatim.new
259
+ v.format = i
260
+ doc << v
237
261
 
238
262
  # Sections
239
263
  if $opts[:sep] && section != i[$opts[:sep]]
240
264
  $log.debug("New section '#{$opts[:sep]}': #{i[$opts[:sep]]}")
241
265
 
242
- v = RDoc::Markup::Verbatim.new
243
- v.format = { item: i, prop: $opts[:sep] }
244
- doc << v
266
+ doc << RDoc::Markup::Raw.new($opts[:sep])
245
267
 
246
268
  doc << RDoc::Markup::BlankLine.new if j.positive?
247
269
  doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
@@ -255,27 +277,30 @@ items.each_with_index do |i, j|
255
277
 
256
278
  # Properties
257
279
  $opts[:props].each_with_index do |prop, k|
258
- next unless i[prop]
259
- next if Array === i[prop] && !i[prop][0]
280
+ value = i[prop]
281
+
282
+ next unless value
283
+ next if Array === value && !value[0]
284
+ next if String === value && value.length == 0
260
285
 
261
- $log.debug("#{prop}: #{i[prop]}")
286
+ $log.debug("#{prop}: #{value}")
262
287
 
263
- v = RDoc::Markup::Verbatim.new
264
- v.format = { item: i, prop: prop }
265
- props << v
288
+ # Use Raw to save the property
289
+ props << RDoc::Markup::Raw.new(prop)
266
290
 
267
291
  unless k.positive?
268
292
  # First property, value only
269
- props << RDoc::Markup::Heading.new(2, i[prop].to_s)
293
+ props << RDoc::Markup::Heading.new(2, value.to_s)
270
294
  else
271
295
  props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
272
- props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(i[prop])) unless(i['placeholder'])
296
+ props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(value)) unless(i['placeholder'])
273
297
  end
274
298
  end
275
299
 
276
300
  # Print it
277
- unless props.empty?
278
- doc << props
279
- puts doc.accept(mu)
280
- end
301
+ props << RDoc::Markup::BlankLine.new unless props.empty?
302
+
303
+ doc << props
281
304
  end
305
+
306
+ print doc.accept(mu)
data/icalPal.gemspec CHANGED
@@ -1,6 +1,8 @@
1
+ require './lib/version'
2
+
1
3
  Gem::Specification.new do |s|
2
4
  s.name = "icalPal"
3
- s.version = "1.2.1"
5
+ s.version = ICalPal::VERSION
4
6
  s.summary = "Command-line tool to query the macOS Calendar"
5
7
  s.description = <<-EOF
6
8
  Inspired by icalBuddy and maintains close compatability. Includes
@@ -17,6 +19,7 @@ EOF
17
19
  s.extra_rdoc_files = [ "README.md" ]
18
20
 
19
21
  s.add_runtime_dependency "sqlite3", "~> 1"
22
+ s.add_runtime_dependency "nokogiri-plist", "~> 0.5.0"
20
23
 
21
24
  s.bindir = 'bin'
22
25
  s.required_ruby_version = '>= 2.6.0'
data/lib/EventKit.rb CHANGED
@@ -27,6 +27,13 @@ class EventKit
27
27
  'yearly',
28
28
  ]
29
29
 
30
+ EKReminderProperty = [
31
+ 'none', # 0
32
+ 'high', nil, nil, nil, # 1
33
+ 'medium', nil, nil, nil, # 5
34
+ 'low', # 9
35
+ ]
36
+
30
37
  # EKSourceType (with color)
31
38
  EKSourceType = [
32
39
  { name: 'Local', color: '#FFFFFF' }, # White
data/lib/ToICalPal.rb CHANGED
@@ -7,15 +7,25 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
7
7
  # ANSI[https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.416-199303-I!!PDF-E&type=items]
8
8
  # colors
9
9
  ANSI = {
10
- 'black': 30, '#000000': '38;5;0',
11
- 'red': 31, '#ff0000': '38;5;1',
12
- 'green': 32, '#00ff00': '38;5;2',
13
- 'yellow': 33, '#ffff00': '38;5;3',
14
- 'blue': 34, '#0000ff': '38;5;4',
15
- 'magenta': 35, '#ff00ff': '38;5;5',
16
- 'cyan': 36, '#00ffff': '38;5;6',
17
- 'white': 37, '#ffffff': '38;5;255',
18
- 'default': 39, 'custom': nil,
10
+ 'black': 30, '#000000': '38;5;0',
11
+ 'red': 31, '#ff0000': '38;5;1',
12
+ 'green': 32, '#00ff00': '38;5;2',
13
+ 'yellow': 33, '#ffff00': '38;5;3',
14
+ 'blue': 34, '#0000ff': '38;5;4',
15
+ 'magenta': 35, '#ff00ff': '38;5;5',
16
+ 'cyan': 36, '#00ffff': '38;5;6',
17
+ 'white': 37, '#ffffff': '38;5;255',
18
+ 'default': 39, 'custom': nil,
19
+
20
+ # Reminders custom colors
21
+ 'brown': '38;2;162;132;94',
22
+ 'gray': '38;2;91;98;106',
23
+ 'indigo': '38;2;88;86;214',
24
+ 'lightblue': '38;2;90;200;250',
25
+ 'orange': '38;2;255;149;0',
26
+ 'pink': '38;2;255;45;85',
27
+ 'purple': '38;2;204;115;225',
28
+ 'rose': '38;2;217;166;159',
19
29
  }
20
30
 
21
31
  # Increased intensity
@@ -38,6 +48,8 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
38
48
 
39
49
  # @param opts [Hash] Used for conditional formatting
40
50
  # @option opts [String] :bullet Bullet
51
+ # @option opts [String] :ab Alert bullet
52
+ # @option opts [Boolean] :nb No bullet
41
53
  # @option opts [Boolean] :nc No calendar names
42
54
  # @option opts [Boolean] :npn No property names
43
55
  # @option opts [Integer] :palette (nil) 8 for \-f, 24 for \--color
@@ -67,6 +79,14 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
67
79
  rescue
68
80
  end
69
81
 
82
+ begin
83
+ if (@item['due_date'] + ICalPal::ITIME).between?(ICalPal::ITIME + 1, $now.to_i) then
84
+ @res << "#{@opts[:ab]} " unless @opts[:nb]
85
+ return
86
+ end
87
+ rescue
88
+ end
89
+
70
90
  @res << "#{@opts[:bullet]} " unless @opts[:nb]
71
91
  end
72
92
 
@@ -126,15 +146,21 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
126
146
  accept_blank_line
127
147
  end
128
148
 
129
- # Don't add anything to the document, just save the item and
130
- # property name for later
149
+ # Don't add anything to the document, just save the item for later
150
+ #
151
+ # @param arg [RDoc::Markup::Verbatim]
152
+ # @option arg [Object] :format The item
153
+ def accept_verbatim(arg)
154
+ @item = arg.format
155
+ end
156
+
157
+ # Don't add anything to the document, just save the property name
158
+ # for later
131
159
  #
132
- # @param h [RDoc::Markup::Verbatim]
133
- # @option h [String] :parts Ignored
134
- # @option h [{item, prop => ICalPal::Event, String}] :format
135
- def accept_verbatim(h)
136
- @item = h.format[:item]
137
- @prop = h.format[:prop]
160
+ # @param arg [RDoc::Markup::Raw]
161
+ # @option arg [Object] :parts The property
162
+ def accept_raw(arg)
163
+ @prop = arg.parts[0]
138
164
  end
139
165
 
140
166
  # @param str [String]
data/lib/defaults.rb CHANGED
@@ -5,17 +5,21 @@ $today = ICalPal::RDT.new(*$now.to_a[0..2] + [0, 0, 0, $now.zone])
5
5
  # Defaults
6
6
  $defaults = {
7
7
  common: {
8
+ ab: '!',
8
9
  aep: [],
9
10
  bullet: '•',
10
11
  cf: "#{ENV['HOME']}/.icalPal",
11
12
  color: false,
12
13
  db: "#{ENV['HOME']}/Library/Calendars/Calendar.sqlitedb",
13
14
  debug: Logger::WARN,
15
+ df: '%b %-d, %Y',
14
16
  ec: [],
15
17
  eep: [],
18
+ el: [],
16
19
  es: [],
17
20
  et: [],
18
21
  ic: [],
22
+ il: [],
19
23
  is: [],
20
24
  it: [],
21
25
  li: 0,
@@ -27,11 +31,25 @@ $defaults = {
27
31
  sep: false,
28
32
  sort: nil,
29
33
  sp: false,
34
+ tf: '%-I:%M %p',
30
35
  },
31
36
  tasks: {
32
- bullet: '!',
33
- iep: [ 'notes', 'due', 'priority' ],
34
- sort: 'priority',
37
+ dated: 0,
38
+ db: ICalPal::Reminder::DB_PATH,
39
+ iep: [ 'title', 'notes', 'due', 'priority' ],
40
+ sort: 'prio',
41
+ },
42
+ undatedTasks: {
43
+ dated: 1,
44
+ db: ICalPal::Reminder::DB_PATH,
45
+ iep: [ 'title', 'notes', 'due', 'priority' ],
46
+ sort: 'prio',
47
+ },
48
+ datedTasks: {
49
+ dated: 2,
50
+ db: ICalPal::Reminder::DB_PATH,
51
+ iep: [ 'title', 'notes', 'due', 'priority' ],
52
+ sort: 'prio',
35
53
  },
36
54
  stores: {
37
55
  iep: [ 'account', 'type' ],
@@ -43,7 +61,6 @@ $defaults = {
43
61
  },
44
62
  events: {
45
63
  days: nil,
46
- df: '%b %-d, %Y',
47
64
  ea: false,
48
65
  eed: false,
49
66
  eep: [],
@@ -57,7 +74,6 @@ $defaults = {
57
74
  sed: false,
58
75
  sort: 'sdate',
59
76
  ss: "\n------------------------",
60
- tf: '%-I:%M %p',
61
77
  to: nil,
62
78
  uid: false,
63
79
  }
data/lib/event.rb CHANGED
@@ -114,6 +114,8 @@ module ICalPal
114
114
 
115
115
  # Repeat for multi-day events
116
116
  ((self['duration'] / 86400).to_i + 1).times do |i|
117
+ break if self['sdate'] > $opts[:to]
118
+
117
119
  $log.debug("multi-day event #{i + 1}") if (i > 0)
118
120
  self['daynum'] = i + 1
119
121
  retval.push(clone) if in_window?(self['sdate'])
data/lib/icalPal.rb CHANGED
@@ -3,10 +3,12 @@ require_relative 'ToICalPal'
3
3
  require_relative 'calendar'
4
4
  require_relative 'event'
5
5
  require_relative 'rdt'
6
+ require_relative 'reminder'
6
7
  require_relative 'store'
7
8
 
8
9
  # Encapsulate the _Store_ (accounts), _Calendar_ and _CalendarItem_
9
- # tables of a Calendar database
10
+ # tables of a Calendar database, and the _Reminder_ table of a
11
+ # Reminders database
10
12
 
11
13
  module ICalPal
12
14
  attr_reader :self
@@ -14,7 +16,7 @@ module ICalPal
14
16
  # Dynamic instantiation of our classes based on the command being
15
17
  # run
16
18
  #
17
- # @param klass [String] One of +accounts+, +stores+, +calendars+, or +events+
19
+ # @param klass [String] One of +accounts+, +stores+, +calendars+, +events+, or +tasks+
18
20
  # @return [Class] The subclass of ICalPal
19
21
  def self.call(klass)
20
22
  case klass
@@ -22,13 +24,51 @@ module ICalPal
22
24
  when 'stores' then Store
23
25
  when 'calendars' then Calendar
24
26
  when 'events' then Event
25
- when 'tasks' then Task
27
+ when 'tasks' then Reminder
26
28
  else
27
29
  $log.fatal("Unknown class: #{klass}")
28
30
  exit
29
31
  end
30
32
  end
31
33
 
34
+ # Load data
35
+ def self.load_data(db_file, q)
36
+ $log.debug(q.gsub(/\n/, ' '))
37
+
38
+ rows = []
39
+
40
+ begin
41
+ # Open the database
42
+ $log.debug("Opening database: #{db_file}")
43
+ db = SQLite3::Database.new(db_file, { readonly: true, results_as_hash: true })
44
+
45
+ # Prepare the query
46
+ stmt = db.prepare(q)
47
+ abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
48
+ $opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
49
+
50
+ # Iterate the SQLite3::ResultSet once
51
+ stmt.execute.each_with_index { |i, j| rows[j] = i }
52
+ stmt.close
53
+
54
+ # Close the database
55
+ db.close
56
+ $log.debug("Closed #{db_file}")
57
+
58
+ rescue SQLite3::BusyException => e
59
+ $log.error("Non-fatal error closing database #{db.filename}")
60
+
61
+ rescue SQLite3::SQLException => e
62
+ $log.info("#{db_file}: #{e}")
63
+
64
+ rescue SQLite3::Exception => e
65
+ abort("#{db_file}: #{e}")
66
+
67
+ end
68
+
69
+ return(rows)
70
+ end
71
+
32
72
  # @param obj [ICalPal] A +Store+ or +Calendar+
33
73
  def initialize(obj)
34
74
  obj['type'] = EventKit::EKSourceType.find_index { |i| i[:name] == 'Subscribed' } if obj['subcal_url']
@@ -55,6 +95,49 @@ module ICalPal
55
95
  CSV::Row::new(headers, values)
56
96
  end
57
97
 
98
+ # Convert +self+ to XML
99
+ #
100
+ # @return [String] All fields in a simple XML format: <field>value</field>.
101
+ # Fields with empty values return <field/>.
102
+ def to_xml
103
+ retval = ""
104
+ @self.keys.each { |k| retval += xmlify(k, @self[k]) }
105
+
106
+ retval
107
+ end
108
+
109
+ # Convert a key/value pair to XML. The value should be +nil+, +String+,
110
+ # +Integer+, +Array+, or +ICalPal::RDT+
111
+ #
112
+ # @param key The key
113
+ # @param value The value
114
+ # @return [String] The key/value pair in a simple XML format
115
+ def xmlify(key, value)
116
+ case value
117
+ # Nil
118
+ when NilClass then return("<#{key}/>")
119
+
120
+ # String, Integer
121
+ when String then return("<#{key}>#{value}</#{key}>")
122
+ when Integer then return("<#{key}>#{value}</#{key}>")
123
+
124
+ # Array
125
+ when Array then
126
+ # Treat empty arrays as nil values
127
+ return(xmlify(key, nil)) if value[0] == nil
128
+
129
+ retval = ""
130
+ value.each { |x| retval += xmlify("#{key}0", x) }
131
+ return("<#{key}>#{retval}</#{key}>")
132
+
133
+ # RDT
134
+ when ICalPal::RDT then return("<#{key}>#{value.to_s}</#{key}>")
135
+
136
+ # Unknown
137
+ else return("<#{key}>#{value.to_s}</#{key}>")
138
+ end
139
+ end
140
+
58
141
  # Get the +n+'th +dow+ in month +m+
59
142
  #
60
143
  # @param n [Integer] Integer between -4 and +4
data/lib/options.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'optparse'
2
2
 
3
3
  require_relative 'defaults'
4
+ require_relative 'version'
4
5
 
5
6
  module ICalPal
6
7
  # Handle program options from all sources:
@@ -23,7 +24,7 @@ module ICalPal
23
24
  @op = OptionParser.new
24
25
  @op.summary_width = 23
25
26
  @op.banner += " [-c] COMMAND"
26
- @op.version = '1.2.1'
27
+ @op.version = ICalPal::VERSION
27
28
 
28
29
  @op.accept(ICalPal::RDT) { |s| ICalPal::RDT.conv(s) }
29
30
 
@@ -31,6 +32,7 @@ module ICalPal
31
32
  @op.on("\nCOMMAND must be one of the following:\n\n")
32
33
 
33
34
  @op.on("%s%s %sPrint events" % pad('events'))
35
+ @op.on("%s%s %sPrint tasks" % pad('tasks'))
34
36
  @op.on("%s%s %sPrint calendars" % pad('calendars'))
35
37
  @op.on("%s%s %sPrint accounts" % pad('accounts'))
36
38
 
@@ -38,16 +40,20 @@ module ICalPal
38
40
  @op.on("%s%s %sPrint events occurring today" % pad('eventsToday'))
39
41
  @op.on("%s%s %sPrint events occurring between today and NUM days into the future" % pad('eventsToday+NUM'))
40
42
  @op.on("%s%s %sPrint events occurring at present time" % pad('eventsNow'))
43
+ @op.on("%s%s %sPrint tasks with a due date" % pad('datedTasks'))
44
+ @op.on("%s%s %sPrint tasks with no due date" % pad('undatedTasks'))
41
45
 
42
46
  # global
43
47
  @op.separator("\nGlobal options:\n\n")
44
48
 
45
49
  @op.on('-c=COMMAND', '--cmd=COMMAND', COMMANDS, 'Command to run')
46
- @op.on('--db=DB', 'Use DB file instead of Calendar')
50
+ @op.on('--db=DB', "Use DB file instead of Calendar (default: #{$defaults[:common][:db]})",
51
+ 'For the tasks commands this should be a directory containing .sqlite files',
52
+ "(default: #{$defaults[:tasks][:db]})")
47
53
  @op.on('--cf=FILE', "Set config file path (default: #{$defaults[:common][:cf]})")
48
54
  @op.on('-o', '--output=FORMAT', OUTFORMATS,
49
55
  "Print as FORMAT (default: #{$defaults[:common][:output]})", "[#{OUTFORMATS.join(', ')}]")
50
-
56
+
51
57
  # include/exclude
52
58
  @op.separator("\nIncluding/excluding calendars:\n\n")
53
59
 
@@ -63,6 +69,10 @@ module ICalPal
63
69
  @op.on('--ic=CALENDARS', Array, 'List of calendars to include')
64
70
  @op.on('--ec=CALENDARS', Array, 'List of calendars to exclude')
65
71
 
72
+ @op.separator('')
73
+ @op.on('--il=LISTS', Array, 'List of reminder lists to include')
74
+ @op.on('--el=LISTS', Array, 'List of reminder lists to exclude')
75
+
66
76
  # dates
67
77
  @op.separator("\nChoosing dates:\n\n")
68
78
 
@@ -85,10 +95,11 @@ module ICalPal
85
95
  @op.on('--eep=PROPERTIES', Array, 'List of properties to exclude')
86
96
  @op.on('--aep=PROPERTIES', Array, 'List of properties to include in addition to the default list')
87
97
  @op.separator('')
88
- # @op.on('--itp=PROPERTIES', Array, 'List of task properties to include')
89
- # @op.on('--etp=PROPERTIES', Array, 'List of task properties to exclude')
90
- # @op.on('--atp=PROPERTIES', Array, 'List of task properties to include in addition to the default list')
91
- # @op.separator('')
98
+ @op.on('--itp=PROPERTIES', Array, 'List of task properties to include')
99
+ @op.on('--etp=PROPERTIES', Array, 'List of task properties to exclude')
100
+ @op.on('--atp=PROPERTIES', Array, 'List of task properties to include in addition to the default list',
101
+ 'Included for backwards compatability, these are aliases for --iep, --eep, and --aep')
102
+ @op.separator('')
92
103
 
93
104
  @op.on('--uid', 'Show event UIDs')
94
105
  @op.on('--eed', 'Exclude end datetimes')
@@ -114,13 +125,12 @@ module ICalPal
114
125
  @op.separator('')
115
126
  @op.on('--sc', 'Separate by calendar')
116
127
  @op.on('--sd', 'Separate by date')
117
- # @op.on('--sp', 'Separate by priority')
118
- # @op.on('--sta', 'Sort tasks by due date (ascending)')
119
- # @op.on('--std', 'Sort tasks by due date (descending)')
120
- # @op.separator('')
128
+ @op.on('--sp', 'Separate by priority')
121
129
  @op.on('--sep=PROPERTY', 'Separate by PROPERTY')
122
130
  @op.separator('')
123
131
  @op.on('--sort=PROPERTY', 'Sort by PROPERTY')
132
+ @op.on('--std', 'Sort tasks by due date (same as --sort=due_date)')
133
+ @op.on('--stda', 'Sort tasks by due date (ascending) (same as --sort=due_date -r)')
124
134
  @op.on('-r', '--reverse', 'Sort in reverse')
125
135
 
126
136
  @op.separator('')
@@ -134,6 +144,7 @@ module ICalPal
134
144
 
135
145
  @op.separator('')
136
146
  @op.on('-b', '--bullet=STRING', String, 'Use STRING for bullets')
147
+ @op.on('--ab=STRING', String, 'Use STRING for alert bullets')
137
148
  @op.on('--nb', 'Do not use bullets')
138
149
  @op.on('--nnr=SEPARATOR', String, 'Set replacement for newlines within notes')
139
150
 
@@ -202,10 +213,21 @@ module ICalPal
202
213
  .merge(env)
203
214
  .merge(cli)
204
215
 
216
+ # datedTasks and undatedTasks
217
+ opts[:cmd] = "tasks" if opts[:cmd] == "datedTasks"
218
+ opts[:cmd] = "tasks" if opts[:cmd] == "undatedTasks"
219
+
205
220
  # All kids love log!
206
221
  $log.level = opts[:debug]
207
222
 
223
+ # For posterity
224
+ opts[:ruby] = RUBY_VERSION
225
+ opts[:version] = @op.version
226
+
208
227
  # From the Department of Redundancy Department
228
+ opts[:iep] += opts[:itp] if opts[:itp]
229
+ opts[:eep] += opts[:etp] if opts[:etp]
230
+ opts[:aep] += opts[:atp] if opts[:atp]
209
231
  opts[:props] = (opts[:iep] + opts[:aep] - opts[:eep]).uniq
210
232
 
211
233
  # From, to, days
@@ -217,6 +239,10 @@ module ICalPal
217
239
  opts[:from] = $now if opts[:n]
218
240
  end
219
241
 
242
+ # Sorting
243
+ opts[:sort] = 'due_date' if opts[:std] or opts[:stda]
244
+ opts[:reverse] = true if opts[:std]
245
+
220
246
  # Colors
221
247
  opts[:palette] = 8 if opts[:f]
222
248
  opts[:palette] = 24 if opts[:color]
@@ -225,7 +251,7 @@ module ICalPal
225
251
  unless opts[:sep]
226
252
  opts[:sep] = 'calendar' if opts[:sc]
227
253
  opts[:sep] = 'sday' if opts[:sd]
228
- opts[:sep] = 'priority' if opts[:sp]
254
+ opts[:sep] = 'long_priority' if opts[:sp]
229
255
  end
230
256
  opts[:nc] = true if opts[:sc]
231
257
 
@@ -234,17 +260,6 @@ module ICalPal
234
260
  raise(OptionParser::InvalidOption, 'Start date must be before end date') if opts[:from] && opts[:from] > opts[:to]
235
261
  raise(OptionParser::MissingArgument, 'No properties to display') if opts[:props].empty?
236
262
 
237
- # Open the database here so we can catch errors and print the help message
238
- $log.debug("Opening database: #{opts[:db]}")
239
- $db = SQLite3::Database.new(opts[:db], { readonly: true, results_as_hash: true })
240
- $db.prepare('SELECT 1 FROM Calendar LIMIT 1').close
241
-
242
- rescue SQLite3::SQLException => e
243
- @op.abort("#{opts[:db]} is not a Calendar database")
244
-
245
- rescue SQLite3::Exception => e
246
- @op.abort("#{opts[:db]}: #{e}")
247
-
248
263
  rescue StandardError => e
249
264
  @op.abort("#{e}\n\n#{@op.help}\n#{e}")
250
265
  end
@@ -253,10 +268,10 @@ module ICalPal
253
268
  end
254
269
 
255
270
  # Commands that can be run
256
- COMMANDS = %w{events eventsToday eventsNow calendars accounts stores}
271
+ COMMANDS = %w{events eventsToday eventsNow tasks datedTasks undatedTasks calendars accounts stores}
257
272
 
258
273
  # Supported output formats
259
- OUTFORMATS = %w{ansi csv default hash html json md rdoc toc yaml remind}
274
+ OUTFORMATS = %w{ansi csv default hash html json md rdoc remind toc xml yaml}
260
275
 
261
276
  private
262
277
 
data/lib/reminder.rb ADDED
@@ -0,0 +1,110 @@
1
+ require 'open3'
2
+ require 'nokogiri-plist'
3
+
4
+ module ICalPal
5
+ # Class representing items from the <tt>Reminders</tt> database
6
+ class Reminder
7
+ include ICalPal
8
+
9
+ def [](k)
10
+ case k
11
+ when 'notes' then # Skip empty notes
12
+ @self['notes'].length > 0? @self['notes'] : nil
13
+
14
+ when 'priority' then # Integer -> String
15
+ EventKit::EKReminderProperty[@self['priority']] if @self['priority'] > 0
16
+
17
+ when 'sdate' then # For sorting
18
+ @self['title']
19
+
20
+ else @self[k]
21
+ end
22
+ end
23
+
24
+ def initialize(obj)
25
+ @self = {}
26
+ obj.keys.each { |k| @self[k] = obj[k] }
27
+
28
+ # Priority
29
+ @self['prio'] = 0 if @self['priority'] == 1 # high
30
+ @self['prio'] = 1 if @self['priority'] == 5 # medium
31
+ @self['prio'] = 2 if @self['priority'] == 9 # low
32
+ @self['prio'] = 3 if @self['priority'] == 0 # none
33
+
34
+ @self['long_priority'] = LONG_PRIORITY[@self['prio']]
35
+
36
+ # For sorting
37
+ @self['sdate'] = (@self['title'])? @self['title'] : ""
38
+
39
+ # Due date
40
+ @self['due'] = RDT.new(*Time.at(@self['due_date'] + ITIME).to_a.reverse[4..]) if @self['due_date']
41
+ @self['due_date'] = 0 unless @self['due_date']
42
+
43
+ # Notes
44
+ @self['notes'] = "" unless @self['notes']
45
+
46
+ # Color
47
+ @self['color'] = nil unless $opts[:palette]
48
+
49
+ if @self['color'] then
50
+ # Run command
51
+ stdin, stdout, stderr, e = Open3.popen3(PL_CONVERT)
52
+
53
+ # Send color bplist
54
+ stdin.write(@self['color'])
55
+ stdin.close
56
+
57
+ # Read output
58
+ plist = Nokogiri::PList(stdout.read)['$objects']
59
+
60
+ @self['color'] = plist[3]
61
+ @self['symbolic_color_name'] = (plist[2] == 'custom')? plist[4] : plist[2]
62
+ else
63
+ @self['color'] = DEFAULT_COLOR
64
+ @self['symbolic_color_name'] = DEFAULT_SYMBOLIC_COLOR
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ DEFAULT_COLOR = '#1BADF8'
71
+ DEFAULT_SYMBOLIC_COLOR = 'blue'
72
+
73
+ LONG_PRIORITY = [
74
+ "High priority",
75
+ "Medium priority",
76
+ "Low priority",
77
+ "No priority",
78
+ ]
79
+
80
+ PL_CONVERT = '/usr/bin/plutil -convert xml1 -o - -'
81
+
82
+ DB_PATH = "#{Dir::home}/Library/Group Containers/group.com.apple.reminders/Container_v1/Stores"
83
+
84
+ QUERY = <<~SQL
85
+ SELECT DISTINCT
86
+
87
+ zremcdReminder.zAllday as all_day,
88
+ zremcdReminder.zDuedate as due_date,
89
+ zremcdReminder.zFlagged as flagged,
90
+ zremcdReminder.zNotes as notes,
91
+ zremcdReminder.zPriority as priority,
92
+ zremcdReminder.zTitle as title,
93
+
94
+ zremcdBaseList.zBadgeEmblem as badge,
95
+ zremcdBaseList.zColor as color,
96
+ zremcdBaseList.zName as list_name,
97
+ zremcdBaseList.zParentList as parent,
98
+ zremcdBaseList.zSharingStatus as shared
99
+
100
+ FROM zremcdReminder
101
+
102
+ JOIN zremcdBaseList ON zremcdReminder.zList = zremcdBaseList.z_pk
103
+
104
+ WHERE zremcdReminder.zCompleted = 0
105
+ AND zremcdReminder.zMarkedForDeletion = 0
106
+
107
+ SQL
108
+
109
+ end
110
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module ICalPal
2
+ VERSION = '2.1.0'
3
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalPal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Rosen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-06 00:00:00.000000000 Z
11
+ date: 2024-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri-plist
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.5.0
27
41
  description: |
28
42
  Inspired by icalBuddy and maintains close compatability. Includes
29
43
  many additional features for querying, filtering, and formatting.
@@ -45,7 +59,9 @@ files:
45
59
  - lib/icalPal.rb
46
60
  - lib/options.rb
47
61
  - lib/rdt.rb
62
+ - lib/reminder.rb
48
63
  - lib/store.rb
64
+ - lib/version.rb
49
65
  homepage: https://github.com/ajrosen/icalPal
50
66
  licenses:
51
67
  - GPL-3.0-or-later
@@ -65,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
81
  - !ruby/object:Gem::Version
66
82
  version: '0'
67
83
  requirements: []
68
- rubygems_version: 3.5.4
84
+ rubygems_version: 3.5.9
69
85
  signing_key:
70
86
  specification_version: 4
71
87
  summary: Command-line tool to query the macOS Calendar