mhc 1.1.1 → 1.2.4
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 +5 -5
- data/.gitignore +1 -1
- data/bin/mhc +82 -3
- data/emacs/Cask +1 -1
- data/emacs/mhc-calendar.el +7 -5
- data/emacs/mhc-date.el +5 -12
- data/emacs/mhc-day.el +1 -1
- data/emacs/mhc-db.el +57 -38
- data/emacs/mhc-draft.el +36 -22
- data/emacs/mhc-face.el +1 -1
- data/emacs/mhc-header.el +20 -1
- data/emacs/mhc-minibuf.el +12 -7
- data/emacs/mhc-parse.el +1 -1
- data/emacs/mhc-process.el +26 -9
- data/emacs/mhc-ps.el +1 -1
- data/emacs/mhc-record.el +1 -1
- data/emacs/mhc-schedule.el +5 -2
- data/emacs/mhc-summary.el +36 -16
- data/emacs/mhc-vars.el +15 -2
- data/emacs/mhc.el +51 -25
- data/lib/mhc.rb +3 -1
- data/lib/mhc/builder.rb +5 -1
- data/lib/mhc/caldav.rb +1 -1
- data/lib/mhc/calendar.rb +5 -1
- data/lib/mhc/command/cache.rb +5 -4
- data/lib/mhc/command/completions.rb +1 -1
- data/lib/mhc/command/init.rb +2 -0
- data/lib/mhc/converter.rb +3 -2
- data/lib/mhc/datastore.rb +52 -13
- data/lib/mhc/date_enumerator.rb +2 -2
- data/lib/mhc/event.rb +42 -21
- data/lib/mhc/formatter.rb +17 -312
- data/lib/mhc/formatter/base.rb +125 -0
- data/lib/mhc/formatter/emacs.rb +47 -0
- data/lib/mhc/formatter/howm.rb +35 -0
- data/lib/mhc/formatter/icalendar.rb +17 -0
- data/lib/mhc/formatter/json.rb +27 -0
- data/lib/mhc/formatter/mail.rb +20 -0
- data/lib/mhc/formatter/org_table.rb +24 -0
- data/lib/mhc/formatter/symbolic_expression.rb +42 -0
- data/lib/mhc/formatter/text.rb +29 -0
- data/lib/mhc/modifier.rb +1 -0
- data/lib/mhc/occurrence.rb +27 -5
- data/lib/mhc/occurrence_enumerator.rb +1 -1
- data/lib/mhc/property_value.rb +6 -0
- data/lib/mhc/property_value/date.rb +23 -14
- data/lib/mhc/property_value/date_time.rb +19 -0
- data/lib/mhc/property_value/integer.rb +5 -1
- data/lib/mhc/property_value/list.rb +7 -6
- data/lib/mhc/property_value/period.rb +3 -1
- data/lib/mhc/property_value/range.rb +1 -1
- data/lib/mhc/property_value/time.rb +8 -1
- data/lib/mhc/version.rb +1 -1
- data/mhc.gemspec +0 -1
- data/samples/japanese-holidays.mhcc +36 -9
- data/spec/mhc_spec.rb +83 -0
- metadata +13 -18
data/lib/mhc/builder.rb
CHANGED
@@ -11,6 +11,10 @@ module Mhc
|
|
11
11
|
@config = Mhc::Config.create_from_file(config) if config.is_a?(String)
|
12
12
|
end
|
13
13
|
|
14
|
+
def datastore
|
15
|
+
Mhc::DataStore.new(@config.general.repository)
|
16
|
+
end
|
17
|
+
|
14
18
|
def calendar(calendar_name)
|
15
19
|
calendar = @config.calendars[calendar_name]
|
16
20
|
raise Mhc::ConfigurationError, "calendar '#{calendar_name}' not found" unless calendar
|
@@ -23,7 +27,7 @@ module Mhc
|
|
23
27
|
when "lastnote"
|
24
28
|
db = Mhc::LastNote::Client.new(calendar.name)
|
25
29
|
when "mhc"
|
26
|
-
db = Mhc::Calendar.new(
|
30
|
+
db = Mhc::Calendar.new(self.datastore, calendar.modifiers, &calendar.filter)
|
27
31
|
end
|
28
32
|
return db
|
29
33
|
end
|
data/lib/mhc/caldav.rb
CHANGED
@@ -26,7 +26,7 @@ module Mhc
|
|
26
26
|
D:propstat/D:prop/caldav:calendar-data
|
27
27
|
).map{|e| xmldoc.elements[e].text rescue nil}
|
28
28
|
|
29
|
-
info.href = URI.
|
29
|
+
info.href = URI.decode_www_form_component(href)
|
30
30
|
info.uid = File.basename(info.href, ".ics")
|
31
31
|
info.status = status
|
32
32
|
info.content_type = content_type
|
data/lib/mhc/calendar.rb
CHANGED
@@ -19,9 +19,13 @@ module Mhc
|
|
19
19
|
occurrences(date_range, &scope_block).map(&:event).uniq
|
20
20
|
end
|
21
21
|
|
22
|
+
def tasks(&scope_block)
|
23
|
+
@datastore.entries(category: "todo")
|
24
|
+
end
|
25
|
+
|
22
26
|
def occurrences(date_range, &scope_block)
|
23
27
|
ocs = []
|
24
|
-
@datastore.entries(date_range).each do |event|
|
28
|
+
@datastore.entries(range: date_range).each do |event|
|
25
29
|
event = decorate_event(event)
|
26
30
|
event.occurrences(range:date_range).each do |oc|
|
27
31
|
ocs << oc if in_scope?(oc, &scope_block)
|
data/lib/mhc/command/cache.rb
CHANGED
@@ -2,10 +2,11 @@ module Mhc
|
|
2
2
|
module Command
|
3
3
|
class Cache
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def initialize(datastore)
|
6
|
+
epoch = Date.new(1970, 1, 1)
|
7
|
+
puts"UID,MTIME,MIN,MAX,CATEGORIES,RECURRENCE,SUBJECT"
|
8
|
+
datastore.each_cache_entry do |uid, ent|
|
9
|
+
puts"#{ent.uid},#{ent.mtime},#{epoch + ent.range.min},#{epoch + ent.range.max},#{(ent.categories||[]).join(' ')},#{ent.recurrence},#{ent.subject}"
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
data/lib/mhc/command/init.rb
CHANGED
data/lib/mhc/converter.rb
CHANGED
@@ -44,8 +44,9 @@ module Mhc
|
|
44
44
|
|
45
45
|
def to_emacs_string(str)
|
46
46
|
# 1. quote " and \
|
47
|
-
# 2.
|
48
|
-
|
47
|
+
# 2. LF => \n
|
48
|
+
# 3. surround by "
|
49
|
+
'"' + str.to_s.toutf8.gsub(/[\"\\]/, '\\\\\&').gsub("\n", "\\n") + '"'
|
49
50
|
end
|
50
51
|
|
51
52
|
def to_emacs_plist(hash)
|
data/lib/mhc/datastore.rb
CHANGED
@@ -11,9 +11,9 @@ module Mhc
|
|
11
11
|
@cache = Cache.new(File.expand_path("status/cache/events.pstore", @basedir))
|
12
12
|
end
|
13
13
|
|
14
|
-
def entries(
|
15
|
-
if
|
16
|
-
int_range =
|
14
|
+
def entries(range: nil, category: nil, recurrence: nil)
|
15
|
+
if range
|
16
|
+
int_range = range.min.absolute_from_epoch .. range.max.absolute_from_epoch
|
17
17
|
end
|
18
18
|
|
19
19
|
Enumerator.new do |yielder|
|
@@ -23,13 +23,18 @@ module Mhc
|
|
23
23
|
|
24
24
|
Dir.chdir(dir) do
|
25
25
|
Dir.foreach(".") do |ent|
|
26
|
-
parse_mhcc(ent).each {|ev|
|
26
|
+
parse_mhcc(ent).each {|ev|
|
27
|
+
next if category && !ev.in_category?(category)
|
28
|
+
next if recurrence && !ev.in_recurrence?(recurrence)
|
29
|
+
yielder << ev
|
30
|
+
} if /\.mhcc$/ =~ ent
|
27
31
|
next unless /\.mhc$/ =~ ent
|
28
32
|
uid = $`
|
29
33
|
cache_entry = @cache.lookup(uid, ent)
|
30
|
-
if !
|
31
|
-
|
32
|
-
|
34
|
+
next if range && !cache_entry.in_range?(int_range)
|
35
|
+
next if category && !cache_entry.in_category?(category)
|
36
|
+
next if recurrence && !cache_entry.in_recurrence?(recurrence)
|
37
|
+
yielder << Event.parse_file(File.expand_path(ent))
|
33
38
|
end
|
34
39
|
end
|
35
40
|
end
|
@@ -78,6 +83,13 @@ module Mhc
|
|
78
83
|
end
|
79
84
|
end
|
80
85
|
|
86
|
+
# dump cache entry for debug usage
|
87
|
+
def each_cache_entry
|
88
|
+
@cache.each do |uid, ent|
|
89
|
+
yield uid, ent
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
81
93
|
################################################################
|
82
94
|
private
|
83
95
|
|
@@ -105,11 +117,21 @@ module Mhc
|
|
105
117
|
class Cache
|
106
118
|
require 'pstore'
|
107
119
|
|
120
|
+
VERSION = "1"
|
121
|
+
|
108
122
|
def initialize(cache_filename)
|
109
123
|
@pstore = PStore.new(cache_filename)
|
110
124
|
load
|
111
125
|
end
|
112
126
|
|
127
|
+
# dump cache entry for debug usage
|
128
|
+
def each
|
129
|
+
load unless @db
|
130
|
+
@db.each do |uid, ent|
|
131
|
+
yield uid, ent
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
113
135
|
def lookup(uid, filename)
|
114
136
|
unless c = get(uid) and File.mtime(filename).to_i <= c.mtime
|
115
137
|
c = CacheEntry.new(filename)
|
@@ -122,6 +144,7 @@ module Mhc
|
|
122
144
|
return self unless @dirty
|
123
145
|
@pstore.transaction do
|
124
146
|
@pstore["root"] = @db
|
147
|
+
@pstore["version"] = VERSION
|
125
148
|
end
|
126
149
|
@dirty = false
|
127
150
|
end
|
@@ -139,7 +162,7 @@ module Mhc
|
|
139
162
|
|
140
163
|
def load
|
141
164
|
@pstore.transaction do
|
142
|
-
@db = @pstore["root"] || {}
|
165
|
+
@db = (@pstore["version"] == VERSION) && @pstore["root"] || {}
|
143
166
|
end
|
144
167
|
@dirty = false
|
145
168
|
end
|
@@ -147,18 +170,34 @@ module Mhc
|
|
147
170
|
end # class Cache
|
148
171
|
|
149
172
|
class CacheEntry
|
150
|
-
attr_reader :mtime, :range
|
173
|
+
attr_reader :mtime, :uid, :subject, :location, :categories, :recurrence, :mission, :range
|
151
174
|
|
152
175
|
def initialize(filename)
|
153
|
-
@mtime
|
154
|
-
|
155
|
-
|
176
|
+
@mtime = File.mtime(filename).to_i
|
177
|
+
|
178
|
+
event = Event.parse_file(filename)
|
179
|
+
@uid = event.uid.to_s
|
180
|
+
@subject = event.subject.to_s
|
181
|
+
@location = event.location.to_s
|
182
|
+
@categories = event.categories.map {|c| c.to_s.downcase}
|
183
|
+
@recurrence = event.recurrence_tag.to_s
|
184
|
+
@mission = event.mission_tag.to_s
|
185
|
+
@range = event.range.min.absolute_from_epoch ..
|
186
|
+
event.range.max.absolute_from_epoch
|
187
|
+
end
|
188
|
+
|
189
|
+
def in_category?(category)
|
190
|
+
@categories.member?(category.downcase)
|
156
191
|
end
|
157
192
|
|
158
|
-
def
|
193
|
+
def in_range?(range)
|
159
194
|
range.min <= @range.max && @range.min <= range.max
|
160
195
|
end
|
161
196
|
|
197
|
+
def in_recurrence?(recurrence)
|
198
|
+
@recurrence && @recurrence.downcase == recurrence.downcase
|
199
|
+
end
|
200
|
+
|
162
201
|
end # class CacheEntry
|
163
202
|
|
164
203
|
end # class DataStore
|
data/lib/mhc/date_enumerator.rb
CHANGED
@@ -257,8 +257,8 @@ module Mhc
|
|
257
257
|
def each
|
258
258
|
head, tail = range
|
259
259
|
@range_list.each do |date_range|
|
260
|
-
break if date_range.first > tail
|
261
|
-
next if date_range.last < head
|
260
|
+
break if date_range.first.to_date > tail
|
261
|
+
next if date_range.last.to_date < head
|
262
262
|
yield date_range
|
263
263
|
end
|
264
264
|
end
|
data/lib/mhc/event.rb
CHANGED
@@ -40,6 +40,10 @@ module Mhc
|
|
40
40
|
return new.parse_file(path, lazy)
|
41
41
|
end
|
42
42
|
|
43
|
+
def self.validate(string)
|
44
|
+
return new.validate(string)
|
45
|
+
end
|
46
|
+
|
43
47
|
def parse_file(path, lazy = true)
|
44
48
|
STDOUT.puts "parsing #{File.expand_path(path)}" if $MHC_DEBUG
|
45
49
|
|
@@ -325,6 +329,17 @@ module Mhc
|
|
325
329
|
Mhc::Converter::Icalendar.new.to_ics_string(self)
|
326
330
|
end
|
327
331
|
|
332
|
+
def validate(string)
|
333
|
+
header, _ = string.scrub.split(/\n\n/, 2)
|
334
|
+
errors = parse_header(header)
|
335
|
+
|
336
|
+
errors << ["no subject"] if subject.empty?
|
337
|
+
errors << ["no record-id"] if record_id.empty?
|
338
|
+
errors << ["no effective date specified"] if dates.empty? && recurrence_condition.empty?
|
339
|
+
|
340
|
+
return errors
|
341
|
+
end
|
342
|
+
|
328
343
|
################################################################
|
329
344
|
private
|
330
345
|
|
@@ -348,35 +363,41 @@ module Mhc
|
|
348
363
|
|
349
364
|
def parse_header(string)
|
350
365
|
hash = {}
|
351
|
-
string.scan(/^x-sc-([^:]++):[ \t]*([^\n]*(?:\n[ \t]+[^\n]*)*)/i) do |key, val|
|
366
|
+
string.scrub.scan(/^x-sc-([^:]++):[ \t]*([^\n]*(?:\n[ \t]+[^\n]*)*)/i) do |key, val|
|
352
367
|
hash[key.downcase] = val.gsub("\n", " ").strip
|
353
368
|
end
|
354
|
-
parse_xsc_header(hash)
|
355
|
-
return self
|
369
|
+
return parse_xsc_header(hash)
|
356
370
|
end
|
357
371
|
|
358
372
|
def parse_xsc_header(hash)
|
373
|
+
errors = []
|
359
374
|
hash.each do |key, val|
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
375
|
+
next if val.empty?
|
376
|
+
begin
|
377
|
+
case key
|
378
|
+
when "day" ; self.dates = val
|
379
|
+
; self.exceptions = val
|
380
|
+
when "date" ; self.obsolete_dates = val
|
381
|
+
when "subject" ; self.subject = val
|
382
|
+
when "location" ; self.location = val
|
383
|
+
when "time" ; self.time_range = val
|
384
|
+
when "duration" ; self.duration = val
|
385
|
+
when "category" ; self.categories = val
|
386
|
+
when "mission-tag" ; self.mission_tag = val
|
387
|
+
when "recurrence-tag" ; self.recurrence_tag = val
|
388
|
+
when "cond" ; self.recurrence_condition = val
|
389
|
+
when "alarm" ; self.alarm = val
|
390
|
+
when "record-id" ; self.record_id = val
|
391
|
+
when "sequence" ; self.sequence = val
|
392
|
+
else
|
393
|
+
raise Mhc::PropertyValue::ParseError,
|
394
|
+
"invalid X-SC-#{key.capitalize} header"
|
395
|
+
end
|
396
|
+
rescue Mhc::PropertyValue::ParseError => e
|
397
|
+
errors << [e, key]
|
377
398
|
end
|
378
399
|
end
|
379
|
-
return
|
400
|
+
return errors
|
380
401
|
end
|
381
402
|
|
382
403
|
## return: X-SC-* headers as a hash and
|
data/lib/mhc/formatter.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
1
|
module Mhc
|
4
|
-
class FormatterNameError < StandardError; end
|
5
|
-
|
6
2
|
class Formatter
|
3
|
+
|
4
|
+
class NameError < StandardError; end
|
5
|
+
|
7
6
|
def self.build(formatter:, date_range:, **options)
|
8
7
|
case formatter.to_sym
|
9
8
|
when :text
|
@@ -15,323 +14,29 @@ module Mhc
|
|
15
14
|
when :emacs
|
16
15
|
Emacs.new(date_range: date_range, options:options)
|
17
16
|
when :icalendar
|
18
|
-
|
17
|
+
Icalendar.new(date_range: date_range, options:options)
|
19
18
|
when :calfw
|
20
19
|
SymbolicExpression.new(date_range: date_range, options:options)
|
21
20
|
when :howm
|
22
21
|
Howm.new(date_range: date_range, options:options)
|
23
22
|
when :json
|
24
|
-
|
23
|
+
Json.new(date_range: date_range, options:options)
|
25
24
|
else
|
26
|
-
raise
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class Base
|
31
|
-
def initialize(date_range:, options:nil)
|
32
|
-
@date_range = date_range
|
33
|
-
@options = options
|
34
|
-
@occurrences, @events, @items = [], [], {}
|
35
|
-
@event_hash = {}
|
36
|
-
end
|
37
|
-
|
38
|
-
def <<(occurrence)
|
39
|
-
event = occurrence.event
|
40
|
-
@occurrences << occurrence
|
41
|
-
@events << event unless @event_hash[event]
|
42
|
-
@event_hash[event] = true
|
43
|
-
|
44
|
-
@items[occurrence.date] ||= []
|
45
|
-
@items[occurrence.date] << occurrence
|
46
|
-
end
|
47
|
-
|
48
|
-
def to_s
|
49
|
-
context = {:items => @items}.merge(@options)
|
50
|
-
prepare(context)
|
51
|
-
string = format_header(context) + format_body(context) + format_footer(context)
|
52
|
-
teardown(context)
|
53
|
-
return string
|
54
|
-
end
|
55
|
-
|
56
|
-
################################################################
|
57
|
-
private
|
58
|
-
|
59
|
-
def prepare(context); end
|
60
|
-
def teardown(context); end
|
61
|
-
|
62
|
-
def pad_empty_dates
|
63
|
-
@date_range.each do |date|
|
64
|
-
@items[date] ||= []
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def expand_multiple_days_occurrences
|
69
|
-
@occurrences.each do |oc|
|
70
|
-
next if oc.oneday?
|
71
|
-
((oc.first + 1) .. oc.last).each do |date|
|
72
|
-
@items[date] ||= []
|
73
|
-
@items[date] << oc
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def format_header(context); ""; end
|
79
|
-
def format_footer(context); ""; end
|
80
|
-
def format_day_header(context, date, is_holiday); ""; end
|
81
|
-
def format_day_footer(context, date); ""; end
|
82
|
-
|
83
|
-
def format_body(context)
|
84
|
-
context[:number] = 0
|
85
|
-
@items.keys.sort.map{|date| format_day(context, date, @items[date]) }.join
|
86
|
-
end
|
87
|
-
|
88
|
-
def format_day(context, date, items)
|
89
|
-
string = format_day_header(context, date, items.any?{|e| e.holiday?})
|
90
|
-
|
91
|
-
items = sort_items_in_day(items)
|
92
|
-
items.each_with_index do |occurrence, count|
|
93
|
-
context[:number] += 1
|
94
|
-
context[:number_in_day] = count + 1
|
95
|
-
string += format_item(context, date, occurrence)
|
96
|
-
end
|
97
|
-
|
98
|
-
return string + format_day_footer(context, date)
|
99
|
-
end
|
100
|
-
|
101
|
-
def format_item(context, date, item)
|
102
|
-
format("%s%-11s %s%s\n",
|
103
|
-
format_item_header(context, date, item),
|
104
|
-
item.time_range.to_mhc_string.toutf8,
|
105
|
-
item.subject.to_s.toutf8,
|
106
|
-
append(enclose(item.location)).toutf8
|
107
|
-
)
|
108
|
-
end
|
109
|
-
|
110
|
-
def format_item_header(context, date, item)
|
111
|
-
if context[:number_in_day] == 1
|
112
|
-
date.strftime("%Y/%m/%d %a ")
|
113
|
-
else
|
114
|
-
" " * 15
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
################################################################
|
119
|
-
## helpers
|
120
|
-
def append(item, separator = " ")
|
121
|
-
return "" if item.to_s.empty?
|
122
|
-
return separator + item.to_s
|
123
|
-
end
|
124
|
-
|
125
|
-
def prepend(item, separator = " ")
|
126
|
-
return "" if item.to_s.empty?
|
127
|
-
return item.to_s + separator
|
128
|
-
end
|
129
|
-
|
130
|
-
def enclose(item, bracket = "[]")
|
131
|
-
return "" if item.to_s.empty?
|
132
|
-
return bracket[0] + item.to_s + bracket[1]
|
133
|
-
end
|
134
|
-
|
135
|
-
# sort occurrences in a day
|
136
|
-
# make sure all-day occurrences are prior to others
|
137
|
-
def sort_items_in_day(items)
|
138
|
-
items.sort do |a,b|
|
139
|
-
sign_a = a.allday? ? 0 : 1
|
140
|
-
sign_b = b.allday? ? 0 : 1
|
141
|
-
|
142
|
-
if sign_a != sign_b
|
143
|
-
sign_a - sign_b
|
144
|
-
else
|
145
|
-
a <=> b
|
146
|
-
end
|
147
|
-
end
|
25
|
+
raise Formatter::NameError.new("Unknown format: #{formatter} (#{formatter.class})")
|
148
26
|
end
|
149
27
|
end
|
150
28
|
|
151
|
-
|
152
|
-
def prepare(context)
|
153
|
-
expand_multiple_days_occurrences
|
154
|
-
end
|
155
|
-
end # class Text
|
156
|
-
|
157
|
-
class Mail < Base
|
158
|
-
private
|
159
|
-
|
160
|
-
def format_header(context)
|
161
|
-
mail_address = context[:mailto].to_s
|
162
|
-
subject = format("MHC schedule report (%s--%s)", *context[:items].keys.minmax)
|
163
|
-
header = "To: #{mail_address}\n"
|
164
|
-
header += "From: #{append(mail_address, "secretary-of-")}\n"
|
165
|
-
header += "Subject: #{subject}\n"
|
166
|
-
header += "Content-Type: Text/Plain; charset=utf-8\n"
|
167
|
-
header += "Content-Transfer-Encoding: 8bit\n"
|
168
|
-
header += "\n"
|
169
|
-
header += format("* mhc %s--%s\n", *context[:items].keys.minmax)
|
170
|
-
end
|
171
|
-
end # class Mail
|
172
|
-
|
173
|
-
class SymbolicExpression < Base
|
174
|
-
private
|
175
|
-
|
176
|
-
def format_header(context); "("; end
|
177
|
-
def format_footer(context); "(periods #{@periods}))\n"; end
|
29
|
+
dir = File.dirname(__FILE__) + "/formatter"
|
178
30
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
format_item_line(item)
|
189
|
-
end
|
190
|
-
|
191
|
-
def format_multiple_days_item(context, date, item)
|
192
|
-
@periods ||= ""
|
193
|
-
@periods += item.first.strftime("((%2m %2d %Y) ") +
|
194
|
-
item.last.strftime(" (%2m %2d %Y) ") +
|
195
|
-
format_item_line(item) + ') '
|
196
|
-
end
|
197
|
-
|
198
|
-
def format_day_footer(context, date); ")) "; end
|
199
|
-
|
200
|
-
def format_item_line(item)
|
201
|
-
'"' +
|
202
|
-
format("%s%s%s",
|
203
|
-
prepend(item.time_range.first.to_s).toutf8,
|
204
|
-
item.subject.to_s.toutf8,
|
205
|
-
append(enclose(item.location)).toutf8).gsub(/[\"\\]/, '\\\\\&') +
|
206
|
-
'" '
|
207
|
-
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
class Emacs < SymbolicExpression
|
212
|
-
private
|
213
|
-
|
214
|
-
def prepare(context)
|
215
|
-
expand_multiple_days_occurrences
|
216
|
-
end
|
217
|
-
|
218
|
-
def format_header(context); "("; end
|
219
|
-
def format_footer(context); ")\n"; end
|
220
|
-
|
221
|
-
def format_day_header(context, date, is_holiday)
|
222
|
-
# (DAYS_FROM_EPOC . [year month day wday holiday-p (
|
223
|
-
format("(%d . [%d %d %d %d #{is_holiday ? 't' : 'nil'} (", date.absolute_from_epoch, date.year, date.month, date.day, date.wday)
|
224
|
-
end
|
225
|
-
|
226
|
-
def format_item(context, date, item)
|
227
|
-
# [ RECORD CONDITION SUBJECT LOCATION (TIMEB . TIMEE) ALARM CATEGORIES PRIORITY REGION RECURRENCE-TAG]
|
228
|
-
format("[(%s . [%s nil nil]) nil %s %s (%s . %s) %s (%s) nil nil %s]",
|
229
|
-
elisp_string(item.path.to_s),
|
230
|
-
elisp_string(item.uid.to_s),
|
231
|
-
elisp_string(item.subject),
|
232
|
-
elisp_string(item.location),
|
233
|
-
(item.time_range.first ? (item.time_range.first.to_i / 60) : "nil"),
|
234
|
-
(item.time_range.last ? (item.time_range.last.to_i / 60) : "nil"),
|
235
|
-
elisp_string(item.alarm.to_s),
|
236
|
-
item.categories.map{|c| elisp_string(c.to_s.downcase)}.join(" "),
|
237
|
-
elisp_string(item.recurrence_tag))
|
238
|
-
end
|
239
|
-
|
240
|
-
def format_day_footer(context, date)
|
241
|
-
")]) "
|
242
|
-
end
|
243
|
-
|
244
|
-
def elisp_string(string)
|
245
|
-
'"' + string.to_s.toutf8.gsub(/[\"\\]/, '\\\\\&') + '"'
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
class ICalendar < Base
|
250
|
-
private
|
251
|
-
|
252
|
-
def format_body(context)
|
253
|
-
ical = RiCal.Calendar
|
254
|
-
ical.prodid = Mhc::PRODID
|
255
|
-
@events.each do |event|
|
256
|
-
ical.events << event.to_icalendar
|
257
|
-
end
|
258
|
-
return ical.to_s
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
class OrgTable < Base
|
263
|
-
private
|
264
|
-
|
265
|
-
def format_header(context)
|
266
|
-
format("* mhc %s--%s\n", *context[:items].keys.minmax)
|
267
|
-
end
|
268
|
-
|
269
|
-
def format_item(context, date, item)
|
270
|
-
# | No | Mission | Recurrence | Subject | Path | Date |
|
271
|
-
format(" | %4d | %s | %s | %s | %s | [%04d-%02d-%02d%s] |\n",
|
272
|
-
context[:number],
|
273
|
-
item.mission_tag.to_s.toutf8,
|
274
|
-
item.recurrence_tag.to_s.toutf8,
|
275
|
-
item.subject.to_s.toutf8,
|
276
|
-
item.path.to_s,
|
277
|
-
date.year, date.month, date.mday,
|
278
|
-
append(item.time_range.to_s))
|
279
|
-
end
|
280
|
-
end # class OrgTable
|
281
|
-
|
282
|
-
class Howm < Base
|
283
|
-
private
|
284
|
-
|
285
|
-
def format_header(context)
|
286
|
-
format("= mhc %s--%s\n", *context[:items].keys.minmax)
|
287
|
-
end
|
288
|
-
|
289
|
-
def format_item(context, date, item)
|
290
|
-
string = format("[%04d-%02d-%02d %5s]%1s %s\n",
|
291
|
-
date.year, date.month, date.mday,
|
292
|
-
item.time_range.first.to_s,
|
293
|
-
mark_todo(item.categories.to_mhc_string),
|
294
|
-
item.subject)
|
295
|
-
if item.description.to_s != ""
|
296
|
-
string += item.description.to_s.gsub(/^/, " ") + "\n"
|
297
|
-
end
|
298
|
-
return string
|
299
|
-
end
|
300
|
-
|
301
|
-
def mark_todo(category)
|
302
|
-
case category
|
303
|
-
when /done/i
|
304
|
-
"."
|
305
|
-
when /todo/i
|
306
|
-
"+"
|
307
|
-
else
|
308
|
-
"@"
|
309
|
-
end
|
310
|
-
end
|
311
|
-
end # class Howm
|
312
|
-
|
313
|
-
class FullCalendar < Base
|
314
|
-
require "json"
|
315
|
-
|
316
|
-
def format_body(context)
|
317
|
-
events = []
|
318
|
-
@occurrences.each do |oc|
|
319
|
-
class_name = []
|
320
|
-
class_name += oc.categories.map{|c| "mhc-category-#{c.to_s.downcase}"}
|
321
|
-
class_name << (oc.allday? ? "mhc-allday" : "mhc-time-range")
|
322
|
-
|
323
|
-
events << {
|
324
|
-
id: oc.record_id,
|
325
|
-
allDay: oc.allday?,
|
326
|
-
title: oc.subject,
|
327
|
-
start: oc.dtstart.iso8601,
|
328
|
-
end: oc.dtend.iso8601,
|
329
|
-
className: class_name
|
330
|
-
}
|
331
|
-
end
|
332
|
-
return events.to_json
|
333
|
-
end
|
334
|
-
end # class FullCalendar
|
31
|
+
autoload :Base, "#{dir}/base.rb"
|
32
|
+
autoload :Emacs, "#{dir}/emacs.rb"
|
33
|
+
autoload :Howm, "#{dir}/howm.rb"
|
34
|
+
autoload :Icalendar, "#{dir}/icalendar.rb"
|
35
|
+
autoload :Json, "#{dir}/json.rb"
|
36
|
+
autoload :Mail, "#{dir}/mail.rb"
|
37
|
+
autoload :OrgTable, "#{dir}/org_table.rb"
|
38
|
+
autoload :SymbolicExpression, "#{dir}/symbolic_expression.rb"
|
39
|
+
autoload :Text, "#{dir}/text.rb"
|
335
40
|
|
336
|
-
end #
|
41
|
+
end # class Formatter
|
337
42
|
end # module Mhc
|