icalPal 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +184 -0
- data/bin/icalPal +204 -0
- data/icalPal.gemspec +21 -0
- data/lib/EventKit.rb +44 -0
- data/lib/ToICalPal.rb +177 -0
- data/lib/calendar.rb +24 -0
- data/lib/defaults.rb +64 -0
- data/lib/event.rb +283 -0
- data/lib/icalPal.rb +82 -0
- data/lib/options.rb +268 -0
- data/lib/rdt.rb +58 -0
- data/lib/store.rb +19 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a530f29b36a30e180b8bf2a82c3107a2bfa40a12fc62e1fd5431696c9fc860a0
|
4
|
+
data.tar.gz: 95caaf61238e643ef328dd5dfafaa00b4a14c50e4565ca5498ddf660e24245f3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8624c9eeea269874b876205cb556cff492bcd39d9196a428a1f95a3995ca997545ded47749841c8e113a6c4862405d85f053e99f9fc03ddaa877a3b543189eb2
|
7
|
+
data.tar.gz: 8870801cc536557e2cb8c4eb1e6a7831ae0c73ced12ff41be3a4a0e19ea482a934b514b9eee6228e09d2b82e2bdc7b51db7c7e1707c3fd600a6bc81bb5054bdd
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# icalPal
|
2
|
+
|
3
|
+
## Description
|
4
|
+
|
5
|
+
icalPal is a command-line tool to query a macOS Calendar database for
|
6
|
+
accounts, calendars, and events. It can be run on any system with
|
7
|
+
[Ruby](https://www.ruby-lang.org/) and access to a Calendar database
|
8
|
+
file.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
<code>gem install icalPal</code>
|
13
|
+
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
ical: Usage: ical [options] [-c] COMMAND
|
18
|
+
|
19
|
+
COMMAND must be one of the following:
|
20
|
+
|
21
|
+
events Print events
|
22
|
+
calendars Print calendars
|
23
|
+
accounts Print accounts
|
24
|
+
|
25
|
+
eventsToday Print events occurring today
|
26
|
+
eventsToday+NUM Print events occurring between today and NUM days into the future
|
27
|
+
eventsNow Print events occurring at present time
|
28
|
+
|
29
|
+
Global options:
|
30
|
+
|
31
|
+
-c, --cmd=COMMAND Command to run
|
32
|
+
--db=DB Use DB file instead of Calendar
|
33
|
+
--cf=FILE Set config file path (default: $HOME/.icalPal)
|
34
|
+
-o, --output=FORMAT Print as FORMAT (default: default)
|
35
|
+
[ansi, csv, default, hash, html, json, md, rdoc, toc, yaml]
|
36
|
+
|
37
|
+
Including/excluding calendars:
|
38
|
+
|
39
|
+
--is=ACCOUNTS List of accounts to include
|
40
|
+
--es=ACCOUNTS List of accounts to exclude
|
41
|
+
|
42
|
+
--it=TYPES List of calendar types to include
|
43
|
+
--et=TYPES List of calendar types to exclude
|
44
|
+
[Local, Exchange, CalDAV, MobileMe, Subscribed, Birthdays]
|
45
|
+
|
46
|
+
--ic=CALENDARS List of calendars to include
|
47
|
+
--ec=CALENDARS List of calendars to exclude
|
48
|
+
|
49
|
+
Choosing dates:
|
50
|
+
|
51
|
+
--from=DATE List events starting on or after DATE
|
52
|
+
--to=DATE List events starting on or before DATE
|
53
|
+
DATE can be yesterday, today, tomorrow, +N, -N, or anything accepted by DateTime.parse()
|
54
|
+
See https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-c-parse
|
55
|
+
|
56
|
+
-n Include only events from now on
|
57
|
+
--days=N Show N days of events, including start date
|
58
|
+
--sed Show empty dates with --sd
|
59
|
+
--ia Include only all-day events
|
60
|
+
--ea Exclude all-day events
|
61
|
+
|
62
|
+
Choose properties to include in the output:
|
63
|
+
|
64
|
+
--iep=PROPERTIES List of properties to include
|
65
|
+
--eep=PROPERTIES List of properties to exclude
|
66
|
+
--aep=PROPERTIES List of properties to include in addition to the default list
|
67
|
+
|
68
|
+
--uid Show event UIDs
|
69
|
+
--eed Exclude end datetimes
|
70
|
+
|
71
|
+
--nc No calendar names
|
72
|
+
--npn No property names
|
73
|
+
--nrd No relative dates
|
74
|
+
|
75
|
+
Properties are listed in the order specified
|
76
|
+
|
77
|
+
Use 'all' for PROPERTIES to include all available properties (except any listed in --eep)
|
78
|
+
Use 'list' for PROPERTIES to list all available properties and exit
|
79
|
+
|
80
|
+
Formatting the output:
|
81
|
+
|
82
|
+
--li=N Show at most N items (default: 0 for no limit)
|
83
|
+
|
84
|
+
--sc Separate by calendar
|
85
|
+
--sd Separate by date
|
86
|
+
--sep=PROPERTY Separate by PROPERTY
|
87
|
+
|
88
|
+
--sort=PROPERTY Sort by PROPERTY
|
89
|
+
-r, --reverse Sort in reverse
|
90
|
+
|
91
|
+
--ps=SEPARATORS List of property separators
|
92
|
+
--ss=SEPARATOR Set section separator
|
93
|
+
|
94
|
+
--df=FORMAT Set date format
|
95
|
+
--tf=FORMAT Set time format
|
96
|
+
See https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime.html#method-i-strftime for details
|
97
|
+
|
98
|
+
-b, --ab=STRING Use STRING for bullets
|
99
|
+
--nnr=SEPARATOR Set replacement for newlines within notes
|
100
|
+
|
101
|
+
-f Format output using standard ANSI colors
|
102
|
+
--color Format output using a larger color palette
|
103
|
+
|
104
|
+
Help:
|
105
|
+
|
106
|
+
-h, --help Show this message
|
107
|
+
-V, -v, --version Show version and exit (1.0)
|
108
|
+
-d, --debug=LEVEL Set the logging level (default: warn)
|
109
|
+
[debug, info, warn, error, fatal]
|
110
|
+
|
111
|
+
Environment variables:
|
112
|
+
|
113
|
+
ICALPAL Additional arguments
|
114
|
+
ICALPAL_CONFIG Additional arguments from a file
|
115
|
+
(default: $HOME/.icalPal)
|
116
|
+
|
117
|
+
|
118
|
+
## History
|
119
|
+
|
120
|
+
If you've found this page it's likely you've heard of [icalBuddy](https://github.com/ali-rantakari/icalBuddy):
|
121
|
+
|
122
|
+
> Command-line utility for printing events and tasks from the OS X calendar database.
|
123
|
+
|
124
|
+
I have used icalBuddy for many years. It's great for scripting,
|
125
|
+
automation, and as a desktop widget for apps like
|
126
|
+
[GeekTool](https://www.tynsoe.org/geektool/) and
|
127
|
+
[Übersicht](https://tracesof.net/uebersicht/).
|
128
|
+
|
129
|
+
As with many applications, I started to run into some limitations in
|
130
|
+
icalBuddy. The biggest being that active development ended over 8
|
131
|
+
years ago. It's only thanks to the efforts of [Jim
|
132
|
+
Lawton](https://github.com/jimlawton) that it even compiles anymore.
|
133
|
+
|
134
|
+
Instead of trying to understand and extend the existing code, I chose
|
135
|
+
to start anew using my language of choice.
|
136
|
+
|
137
|
+
- Output in CSV, JSON, HTML, Markdown, and [more](#label-Output+formats)
|
138
|
+
- Enhanced color option[#label-Usage]
|
139
|
+
- Show and filter by Account
|
140
|
+
- Show and filter by Calendar type
|
141
|
+
- Select a different Calendar database
|
142
|
+
- Multi-platform
|
143
|
+
- Much less code (1200 lines vs. 7000)
|
144
|
+
|
145
|
+
I won't pretend to understand **why** you would want this on Linux or
|
146
|
+
Windows. But since icalPal is written in Ruby and gets its data
|
147
|
+
directly from the Calendar database file instead of an API, you *can*.
|
148
|
+
|
149
|
+
## Output formats
|
150
|
+
|
151
|
+
icalPal supports several output formats. The +default+ format tries
|
152
|
+
to mimic icalBuddy as much as possible.
|
153
|
+
|
154
|
+
CSV, Hash, JSON, and YAML print all fields for all items in their
|
155
|
+
respective formats. From that you can analyze the results any way you like.
|
156
|
+
|
157
|
+
All other formats, ANSI, HTML, Markdown, RDoc, and TOC, use Ruby's
|
158
|
+
[RDoc::Markup](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup.html)
|
159
|
+
framework to build and render the items.
|
160
|
+
|
161
|
+
Each item to be printed is a new
|
162
|
+
[RDoc::Markup::Document](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Document.html).
|
163
|
+
|
164
|
+
When using one of the <em>separate by</em> options, a section header is added first. The section contains:
|
165
|
+
|
166
|
+
* [RDoc::Markup::BlankLine](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/BlankLine.html)
|
167
|
+
(unless this is the first section)
|
168
|
+
* RDoc::Markup::Heading (level 1)
|
169
|
+
* [RDoc::Markup::Rule](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Rule.html)
|
170
|
+
|
171
|
+
The rest of the document is a series of
|
172
|
+
[RDoc::Markup::List](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/List.html)
|
173
|
+
objects, one for each of the item's properties:
|
174
|
+
|
175
|
+
* [RDoc::Markup::List](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/List.html)
|
176
|
+
* RDoc::Markup::Heading (level 2)
|
177
|
+
* [RDoc::Markup::BlankLine](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/BlankLine.html)
|
178
|
+
* [RDoc::Markup::ListItem](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/ListItem.html)
|
179
|
+
* [RDoc::Markup::Paragraph](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Paragraph.html)
|
180
|
+
|
181
|
+
The document will also include a number of
|
182
|
+
[RDoc::Markup::Verbatim](https://ruby-doc.org/stdlib-2.6.10/libdoc/rdoc/rdoc/RDoc/Markup/Verbatim.html)
|
183
|
+
items. The are not included in the output, but are used to pass
|
184
|
+
information about the item and property to the default formatter.
|
data/bin/icalPal
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# *-*- mode: enh-ruby -*-*
|
3
|
+
|
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
|
+
|
15
|
+
|
16
|
+
##################################################
|
17
|
+
# Load options
|
18
|
+
|
19
|
+
# All kids love log!
|
20
|
+
$log = Logger.new(STDERR, { datetime_format: '%H:%M:%S.%L', level: $defaults[:common][:debug] })
|
21
|
+
$log.formatter = proc do |s, t, p, m| # Severity, time, progname, msg
|
22
|
+
($log.level.positive?)? "#{s}: #{m}\n" :
|
23
|
+
"[%-5s] %s [%s] - %s\n" %
|
24
|
+
[ s, t.strftime($log.datetime_format), caller(4, 1)[0].split('/')[-1], m ]
|
25
|
+
end
|
26
|
+
|
27
|
+
$opts = ICalPal::Options.new.parse_options
|
28
|
+
|
29
|
+
$rows = [] # Rows from the database
|
30
|
+
$items = [] # Items to be printed
|
31
|
+
|
32
|
+
|
33
|
+
##################################################
|
34
|
+
# All kids love log!
|
35
|
+
|
36
|
+
$log.info("Options: #{$opts}")
|
37
|
+
|
38
|
+
|
39
|
+
##################################################
|
40
|
+
# Add an item to the list
|
41
|
+
#
|
42
|
+
# @param item[Object]
|
43
|
+
def add(item)
|
44
|
+
$log.debug("Adding #{item['UUID']} (#{item['title']})") if item['UUID']
|
45
|
+
item['sday'] = ICalPal::RDT.new(*item['sdate'].to_a[0..2]) if item['sdate']
|
46
|
+
$items.push(item)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
##################################################
|
51
|
+
# Load the data
|
52
|
+
|
53
|
+
# What are we getting?
|
54
|
+
klass = ICalPal::($opts[:cmd])
|
55
|
+
q = klass::QUERY
|
56
|
+
|
57
|
+
$log.debug(q.gsub(/\n/, ' '))
|
58
|
+
|
59
|
+
# Get it
|
60
|
+
stmt = $db.prepare(q)
|
61
|
+
abort(stmt.columns.sort.join(' ')) if $opts[:props].any? 'list'
|
62
|
+
$opts[:props] = stmt.columns - $opts[:eep] if $opts[:props].any? 'all'
|
63
|
+
|
64
|
+
# Iterate the SQLite3::ResultSet once
|
65
|
+
stmt.execute.each_with_index { |i, j| $rows[j] = i }
|
66
|
+
stmt.close
|
67
|
+
|
68
|
+
$log.info("Loaded #{$rows.count} rows from #{$db.filename}")
|
69
|
+
$db.close
|
70
|
+
|
71
|
+
|
72
|
+
##################################################
|
73
|
+
# Process the data
|
74
|
+
|
75
|
+
# Add rows
|
76
|
+
$rows.each do |row|
|
77
|
+
# --es/--is
|
78
|
+
next if $opts[:es].any? row['account']
|
79
|
+
next unless $opts[:is].empty? or $opts[:is].any? row['account']
|
80
|
+
|
81
|
+
# --ec/--ic
|
82
|
+
next if $opts[:ec].any? row['calendar']
|
83
|
+
next unless $opts[:ic].empty? or $opts[:ic].any? row['calendar']
|
84
|
+
|
85
|
+
item = klass.new(row)
|
86
|
+
|
87
|
+
# --et/--it
|
88
|
+
next if $opts[:et].any? item['type']
|
89
|
+
next unless $opts[:it].empty? or $opts[:it].any? item['type']
|
90
|
+
|
91
|
+
unless ICalPal::Event === item
|
92
|
+
# Always add non-event items
|
93
|
+
add(item)
|
94
|
+
else
|
95
|
+
# Check for all-day and cancelled events
|
96
|
+
next if $opts[:ea] && item['all_day'].positive?
|
97
|
+
next if $opts[:ia] && !item['all_day'].positive?
|
98
|
+
next if item['status'] == :canceled
|
99
|
+
|
100
|
+
(item['has_recurrences'].positive?)?
|
101
|
+
item.recurring.each { |i| add(i) } :
|
102
|
+
item.non_recurring.each { |i| add(i) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Add placeholders for empty days
|
107
|
+
if $opts[:sed] && $opts[:sd] && klass == ICalPal::Event
|
108
|
+
days = $items.collect { |i| i['sday'] }.uniq.sort
|
109
|
+
|
110
|
+
$opts[:days].times do |n|
|
111
|
+
day = $opts[:from] + n
|
112
|
+
$items.push(klass.new(day)) unless days.any? { |i| i == day }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Sort the rows
|
117
|
+
begin
|
118
|
+
$items.sort_by! { |i| [ i[$opts[:sep]], i[$opts[:sort]], i['sdate'] ] }
|
119
|
+
$items.reverse! if $opts[:reverse]
|
120
|
+
rescue ArgumentError => e
|
121
|
+
$log.warn("Sorting failed, results may be unexpected\n")
|
122
|
+
end
|
123
|
+
|
124
|
+
# Configure formatting
|
125
|
+
mu = case $opts[:output]
|
126
|
+
when 'ansi' then RDoc::Markup::ToAnsi.new
|
127
|
+
when 'csv' then proc { |f| f.respond_to?(:gsub)? f.gsub(/\n/, '\n') : f }
|
128
|
+
when 'default' then RDoc::Markup::ToICalPal.new($opts)
|
129
|
+
when 'html' then
|
130
|
+
rdoc = RDoc::Options.new
|
131
|
+
rdoc.pipe = true
|
132
|
+
rdoc.output_decoration = false
|
133
|
+
RDoc::Markup::ToHtml.new(rdoc)
|
134
|
+
when 'md' then RDoc::Markup::ToMarkdown.new
|
135
|
+
when 'rdoc' then RDoc::Markup::ToRdoc.new
|
136
|
+
when 'toc' then RDoc::Markup::ToTableOfContents.new
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
##################################################
|
141
|
+
# Print the data
|
142
|
+
|
143
|
+
section = nil if $opts[:sep]
|
144
|
+
|
145
|
+
$items.each_with_index do |i, j|
|
146
|
+
# --li
|
147
|
+
break if $opts[:li].positive? && j >= $opts[:li]
|
148
|
+
|
149
|
+
case $opts[:output]
|
150
|
+
when 'csv' then
|
151
|
+
values = i.values.map { |v| v.to_s }
|
152
|
+
puts CSV.generate(write_converters: mu) { |k| k << i.keys } unless j.positive?
|
153
|
+
puts CSV.generate(write_converters: mu) { |k| k << values }
|
154
|
+
when 'hash' then p i.self
|
155
|
+
when 'json' then puts i.self.to_json
|
156
|
+
when 'yaml' then puts i.self.to_yaml
|
157
|
+
else
|
158
|
+
doc = RDoc::Markup::Document.new
|
159
|
+
|
160
|
+
# Sections
|
161
|
+
if $opts[:sep] && section != i[$opts[:sep]]
|
162
|
+
$log.debug("Section: #{i[$opts[:sep]]}")
|
163
|
+
|
164
|
+
v = RDoc::Markup::Verbatim.new
|
165
|
+
v.format = { item: i, prop: $opts[:sep] }
|
166
|
+
doc << v
|
167
|
+
|
168
|
+
doc << RDoc::Markup::BlankLine.new if j.positive?
|
169
|
+
doc << RDoc::Markup::Heading.new(1, i[$opts[:sep]].to_s)
|
170
|
+
doc << RDoc::Markup::Rule.new(0)
|
171
|
+
|
172
|
+
section = i[$opts[:sep]]
|
173
|
+
end
|
174
|
+
|
175
|
+
# Item
|
176
|
+
props = RDoc::Markup::List.new(:BULLET)
|
177
|
+
|
178
|
+
# Properties
|
179
|
+
$opts[:props].each_with_index do |prop, k|
|
180
|
+
next unless i[prop]
|
181
|
+
next if Array === i[prop] && !i[prop][0]
|
182
|
+
|
183
|
+
$log.debug("#{prop}: #{i[prop]}")
|
184
|
+
|
185
|
+
v = RDoc::Markup::Verbatim.new
|
186
|
+
v.format = { item: i, prop: prop }
|
187
|
+
props << v
|
188
|
+
|
189
|
+
unless k.positive?
|
190
|
+
# First property, value only
|
191
|
+
props << RDoc::Markup::Heading.new(2, i[prop].to_s)
|
192
|
+
else
|
193
|
+
props << RDoc::Markup::BlankLine.new unless (i['placeholder'] || $opts[:ps])
|
194
|
+
props << RDoc::Markup::ListItem.new(prop, RDoc::Markup::Paragraph.new(i[prop]))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Print it
|
199
|
+
unless props.empty?
|
200
|
+
doc << props
|
201
|
+
puts doc.accept(mu)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
data/icalPal.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "icalPal"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.summary = "Command-line tool to query the macOS Calendar"
|
5
|
+
s.description = <<-EOF
|
6
|
+
Inspired by icalBuddy and maintains close compatability. Includes
|
7
|
+
many additional features for querying, filtering, and formatting.
|
8
|
+
EOF
|
9
|
+
|
10
|
+
s.authors = "Andy Rosen"
|
11
|
+
s.email = "ajr@corp.mlfs.org"
|
12
|
+
s.homepage = "https://github.com/ajrosen/#{s.name}"
|
13
|
+
s.licenses = [ "GPL-3.0+" ]
|
14
|
+
|
15
|
+
s.files = Dir[ "#{s.name}.gemspec", "bin/*", "lib/*.rb" ]
|
16
|
+
s.executables = [ "#{s.name}" ]
|
17
|
+
s.extra_rdoc_files = [ "README.md" ]
|
18
|
+
|
19
|
+
s.bindir = 'bin'
|
20
|
+
s.required_ruby_version = '>= 2.6.0'
|
21
|
+
end
|
data/lib/EventKit.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# Constants from EventKit[https://developer.apple.com/documentation/eventkit/]
|
2
|
+
class EventKit
|
3
|
+
EKEntityType = [
|
4
|
+
'event',
|
5
|
+
'reminder',
|
6
|
+
]
|
7
|
+
|
8
|
+
EKEventAvailability = {
|
9
|
+
notSupported: -1,
|
10
|
+
busy: 0,
|
11
|
+
free: 1,
|
12
|
+
tentative: 2,
|
13
|
+
unavailable: 3,
|
14
|
+
}
|
15
|
+
|
16
|
+
EKEventStatus = {
|
17
|
+
none: 0,
|
18
|
+
confirmed: 1,
|
19
|
+
tentative: 2,
|
20
|
+
canceled: 3,
|
21
|
+
}
|
22
|
+
|
23
|
+
EKRecurrenceFrequency = [
|
24
|
+
'daily',
|
25
|
+
'weekly',
|
26
|
+
'monthly',
|
27
|
+
'yearly',
|
28
|
+
]
|
29
|
+
|
30
|
+
# EKSourceType (with color)
|
31
|
+
EKSourceType = [
|
32
|
+
{ name: 'Local', color: '#FFFFFF' }, # White
|
33
|
+
{ name: 'Exchange', color: '#00FFFF' }, # Cyan
|
34
|
+
{ name: 'CalDAV', color: '#00FF00' }, # Green
|
35
|
+
{ name: 'MobileMe', color: '#FFFF00' }, # Yellow
|
36
|
+
{ name: 'Subscribed', color: '#FF0000' }, # Red
|
37
|
+
{ name: 'Birthdays', color: '#FF00FF' }, # Magenta
|
38
|
+
]
|
39
|
+
|
40
|
+
EKSpan = [
|
41
|
+
'this',
|
42
|
+
'future',
|
43
|
+
]
|
44
|
+
end
|
data/lib/ToICalPal.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
##################################################
|
2
|
+
# Render an RDoc::Markup::Document, closely mimicking
|
3
|
+
# icalBuddy[https://github.com/ali-rantakari/icalBuddy]
|
4
|
+
|
5
|
+
class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
6
|
+
# Standard
|
7
|
+
# ANSI[https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.416-199303-I!!PDF-E&type=items]
|
8
|
+
# colors
|
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,
|
19
|
+
}
|
20
|
+
|
21
|
+
# Increased intensity
|
22
|
+
BOLD = '%c[1m' % 27.chr
|
23
|
+
|
24
|
+
# Default rendition
|
25
|
+
NORM = '%c[0m' % 27.chr
|
26
|
+
|
27
|
+
# Properties for which we don't include labels
|
28
|
+
NO_LABEL = [ 'title', 'datetime' ]
|
29
|
+
|
30
|
+
# Properties that are always colorized
|
31
|
+
COLOR_LABEL = [ 'title', 'calendar' ]
|
32
|
+
|
33
|
+
# Default color for labels
|
34
|
+
LABEL_COLOR = [ 'cyan', '#00ffff' ]
|
35
|
+
|
36
|
+
# Color for datetime value
|
37
|
+
DATE_COLOR = [ 'yellow', '#ffff00' ]
|
38
|
+
|
39
|
+
# @param opts [Hash] Used for conditional formatting
|
40
|
+
# @option opts [String] :bullet Bullet
|
41
|
+
# @option opts [Boolean] :nc No calendar names
|
42
|
+
# @option opts [Boolean] :npn No property names
|
43
|
+
# @option opts [Integer] :palette (nil) 8 for \-f, 24 for \--color
|
44
|
+
# @option opts [Array<String>] :ps List of property separators
|
45
|
+
# @option opts [String] :ss Section separator
|
46
|
+
def initialize(opts)
|
47
|
+
@opts = opts
|
48
|
+
end
|
49
|
+
|
50
|
+
# Start a new document
|
51
|
+
def start_accepting
|
52
|
+
@res = []
|
53
|
+
@ps = 0
|
54
|
+
end
|
55
|
+
|
56
|
+
# Close the document
|
57
|
+
def end_accepting
|
58
|
+
@res.join
|
59
|
+
end
|
60
|
+
|
61
|
+
# Add a bullet for the first property of an item
|
62
|
+
#
|
63
|
+
# @param arg [Array] Ignored
|
64
|
+
def accept_list_start(arg)
|
65
|
+
begin
|
66
|
+
return if @item['placeholder']
|
67
|
+
rescue
|
68
|
+
end
|
69
|
+
|
70
|
+
@res << "#{@opts[:bullet]} "
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add a property name
|
74
|
+
#
|
75
|
+
# @param arg [RDoc::Markup::ListItem]
|
76
|
+
# @option arg [String] .label Contains the property name
|
77
|
+
def accept_list_item_start(arg)
|
78
|
+
@res << @opts[:ps][@ps] || ' ' unless @item['placeholder']
|
79
|
+
@res << colorize(*LABEL_COLOR, arg.label) << ": " unless @opts[:npn] || NO_LABEL.any?(arg.label)
|
80
|
+
|
81
|
+
@ps += 1 unless @ps == @opts[:ps].count - 1
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add a blank line
|
85
|
+
#
|
86
|
+
# @param arg [Array] Ignored
|
87
|
+
def accept_blank_line(*arg)
|
88
|
+
@res << "\n"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Add either a section header or the first property of an item
|
92
|
+
#
|
93
|
+
# @param h [RDoc::Markup::Heading]
|
94
|
+
# @option h [Integer] :level 1 for a section header
|
95
|
+
# @option h [Integer] :level 2 for a property name
|
96
|
+
# @option h [String] :text The header's text
|
97
|
+
def accept_heading(h)
|
98
|
+
h.text = colorize(@item['symbolic_color_name'], @item['color'], h.text) if (h.level == 2) || COLOR_LABEL.any?(@prop)
|
99
|
+
@res << h.text
|
100
|
+
|
101
|
+
case h.level
|
102
|
+
when 1 then
|
103
|
+
@res << ":"
|
104
|
+
when 2 then
|
105
|
+
if @prop == 'title' && @item['calendar']
|
106
|
+
@res << bold(" (#{@item['calendar']})") unless @opts[:nc] || @item['title'] == @item['calendar']
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Add the property value
|
112
|
+
#
|
113
|
+
# @param p [RDoc::Markup::Paragraph]
|
114
|
+
# @option p [Array<String>] :parts The property's text
|
115
|
+
def accept_paragraph(p)
|
116
|
+
t = p.parts.join('; ').gsub(/\n/, "\n ")
|
117
|
+
t = colorize(*DATE_COLOR, t) if @prop == 'datetime'
|
118
|
+
@res << t
|
119
|
+
end
|
120
|
+
|
121
|
+
# Add a section separator
|
122
|
+
#
|
123
|
+
# @param weight Ignored
|
124
|
+
def accept_rule(weight)
|
125
|
+
@res << @opts[:ss]
|
126
|
+
accept_blank_line
|
127
|
+
end
|
128
|
+
|
129
|
+
# Don't add anything to the document, just save the item and
|
130
|
+
# property name for later
|
131
|
+
#
|
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]
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param str [String]
|
141
|
+
# @return [String] str with increased intensity[#BOLD]
|
142
|
+
def bold(str)
|
143
|
+
return str unless @opts[:palette]
|
144
|
+
BOLD + str + NORM
|
145
|
+
end
|
146
|
+
|
147
|
+
# @param c8 [String] Color used for \-f
|
148
|
+
# @param c24 [String] Color used for \--color
|
149
|
+
# @return [String] str in color, depending on opts[#]
|
150
|
+
def colorize(c8, c24, str)
|
151
|
+
return str unless c8 && c24 && @opts[:palette]
|
152
|
+
|
153
|
+
case @opts[:palette]
|
154
|
+
when 8 then # Default colour table
|
155
|
+
c = ANSI[c8.downcase.to_sym]
|
156
|
+
c ||= ANSI[c24[0..6].downcase.to_sym]
|
157
|
+
c ||= ANSI[:white]
|
158
|
+
|
159
|
+
when 24 then # Direct colour in RGB space
|
160
|
+
rgb = c24[1..].split(/(\h\h)(\h\h)(\h\h)/)
|
161
|
+
rgb.map! { |i| i.to_i(16) }
|
162
|
+
c = [ 38, 2, rgb[1..] ].join(';')
|
163
|
+
end
|
164
|
+
|
165
|
+
sprintf('%c[%sm%s%c[%sm', 27.chr, c, str, 27.chr, ANSI[:default])
|
166
|
+
end
|
167
|
+
|
168
|
+
# @!visibility private
|
169
|
+
|
170
|
+
# @param a [Array] Ignored
|
171
|
+
def accept_list_end(a)
|
172
|
+
end
|
173
|
+
|
174
|
+
# @param a [Array] Ignored
|
175
|
+
def accept_list_item_end(a)
|
176
|
+
end
|
177
|
+
end
|
data/lib/calendar.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module ICalPal
|
2
|
+
# Class representing items from the <tt>Calendar</tt> table
|
3
|
+
class Calendar
|
4
|
+
include ICalPal
|
5
|
+
|
6
|
+
QUERY = <<~SQL
|
7
|
+
SELECT DISTINCT
|
8
|
+
|
9
|
+
Store.name AS account,
|
10
|
+
Calendar.title AS calendar,
|
11
|
+
*
|
12
|
+
|
13
|
+
FROM #{self.name.split('::').last}
|
14
|
+
|
15
|
+
JOIN Store ON store_id = Store.rowid
|
16
|
+
|
17
|
+
WHERE Store.disabled IS NOT 1
|
18
|
+
AND Store.display_order IS NOT -1
|
19
|
+
AND (Calendar.display_order IS NOT -1 OR external_rep IS NOT NULL)
|
20
|
+
AND Calendar.flags IS NOT 519
|
21
|
+
SQL
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|