icalPal 1.2.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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