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