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.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/bin/mhc +82 -3
  4. data/emacs/Cask +1 -1
  5. data/emacs/mhc-calendar.el +7 -5
  6. data/emacs/mhc-date.el +5 -12
  7. data/emacs/mhc-day.el +1 -1
  8. data/emacs/mhc-db.el +57 -38
  9. data/emacs/mhc-draft.el +36 -22
  10. data/emacs/mhc-face.el +1 -1
  11. data/emacs/mhc-header.el +20 -1
  12. data/emacs/mhc-minibuf.el +12 -7
  13. data/emacs/mhc-parse.el +1 -1
  14. data/emacs/mhc-process.el +26 -9
  15. data/emacs/mhc-ps.el +1 -1
  16. data/emacs/mhc-record.el +1 -1
  17. data/emacs/mhc-schedule.el +5 -2
  18. data/emacs/mhc-summary.el +36 -16
  19. data/emacs/mhc-vars.el +15 -2
  20. data/emacs/mhc.el +51 -25
  21. data/lib/mhc.rb +3 -1
  22. data/lib/mhc/builder.rb +5 -1
  23. data/lib/mhc/caldav.rb +1 -1
  24. data/lib/mhc/calendar.rb +5 -1
  25. data/lib/mhc/command/cache.rb +5 -4
  26. data/lib/mhc/command/completions.rb +1 -1
  27. data/lib/mhc/command/init.rb +2 -0
  28. data/lib/mhc/converter.rb +3 -2
  29. data/lib/mhc/datastore.rb +52 -13
  30. data/lib/mhc/date_enumerator.rb +2 -2
  31. data/lib/mhc/event.rb +42 -21
  32. data/lib/mhc/formatter.rb +17 -312
  33. data/lib/mhc/formatter/base.rb +125 -0
  34. data/lib/mhc/formatter/emacs.rb +47 -0
  35. data/lib/mhc/formatter/howm.rb +35 -0
  36. data/lib/mhc/formatter/icalendar.rb +17 -0
  37. data/lib/mhc/formatter/json.rb +27 -0
  38. data/lib/mhc/formatter/mail.rb +20 -0
  39. data/lib/mhc/formatter/org_table.rb +24 -0
  40. data/lib/mhc/formatter/symbolic_expression.rb +42 -0
  41. data/lib/mhc/formatter/text.rb +29 -0
  42. data/lib/mhc/modifier.rb +1 -0
  43. data/lib/mhc/occurrence.rb +27 -5
  44. data/lib/mhc/occurrence_enumerator.rb +1 -1
  45. data/lib/mhc/property_value.rb +6 -0
  46. data/lib/mhc/property_value/date.rb +23 -14
  47. data/lib/mhc/property_value/date_time.rb +19 -0
  48. data/lib/mhc/property_value/integer.rb +5 -1
  49. data/lib/mhc/property_value/list.rb +7 -6
  50. data/lib/mhc/property_value/period.rb +3 -1
  51. data/lib/mhc/property_value/range.rb +1 -1
  52. data/lib/mhc/property_value/time.rb +8 -1
  53. data/lib/mhc/version.rb +1 -1
  54. data/mhc.gemspec +0 -1
  55. data/samples/japanese-holidays.mhcc +36 -9
  56. data/spec/mhc_spec.rb +83 -0
  57. metadata +13 -18
@@ -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(Mhc::DataStore.new(@config.general.repository), calendar.modifiers, &calendar.filter)
30
+ db = Mhc::Calendar.new(self.datastore, calendar.modifiers, &calendar.filter)
27
31
  end
28
32
  return db
29
33
  end
@@ -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.unescape(href)
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
@@ -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)
@@ -2,10 +2,11 @@ module Mhc
2
2
  module Command
3
3
  class Cache
4
4
 
5
- def initialize(calendar)
6
- calendar.events.each do |event|
7
- range = event.range
8
- puts "#{range.min.to_mhc_string},#{range.max.to_mhc_string},#{event.uid},#{event.subject}"
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
 
@@ -95,7 +95,7 @@ module Mhc
95
95
  when "COMMAND"
96
96
  possible_commands
97
97
  when "RANGE"
98
- "(today tomorrow thismonth nextmonth)"
98
+ "(all today tomorrow thismonth nextmonth)"
99
99
  when /^NUM/
100
100
  "_guard '[0-9]#' 'Number'"
101
101
  else
@@ -1,3 +1,5 @@
1
+ require "fileutils"
2
+
1
3
  module Mhc
2
4
  module Command
3
5
  class Init
@@ -44,8 +44,9 @@ module Mhc
44
44
 
45
45
  def to_emacs_string(str)
46
46
  # 1. quote " and \
47
- # 2. surround by "
48
- '"' + str.to_s.toutf8.gsub(/[\"\\]/, '\\\\\&') + '"'
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)
@@ -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(date_range = nil)
15
- if date_range
16
- int_range = date_range.min.absolute_from_epoch .. date_range.max.absolute_from_epoch
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| yielder << ev} if /\.mhcc$/ =~ ent
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 !date_range || cache_entry.involved?(int_range)
31
- yielder << Event.parse_file(File.expand_path(ent))
32
- end
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, event = File.mtime(filename).to_i, Event.parse_file(filename)
154
- @range = event.range.min.absolute_from_epoch ..
155
- event.range.max.absolute_from_epoch
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 involved?(range)
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
@@ -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
@@ -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
- case key
361
- when "day" ; self.dates = val ; self.exceptions = val
362
- when "date" ; self.obsolete_dates = val
363
- when "subject" ; self.subject = val
364
- when "location" ; self.location = val
365
- when "time" ; self.time_range = val
366
- when "duration" ; self.duration = val
367
- when "category" ; self.categories = val
368
- when "mission-tag" ; self.mission_tag = val
369
- when "recurrence-tag" ; self.recurrence_tag = val
370
- when "cond" ; self.recurrence_condition = val
371
- when "alarm" ; self.alarm = val
372
- when "record-id" ; self.record_id = val
373
- when "sequence" ; self.sequence = val
374
- else
375
- # raise NotImplementedError, "X-SC-#{key.capitalize}"
376
- # STDERR.print "Obsolete: X-SC-#{key.capitalize}\n"
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 self
400
+ return errors
380
401
  end
381
402
 
382
403
  ## return: X-SC-* headers as a hash and
@@ -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
- ICalendar.new(date_range: date_range, options:options)
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
- FullCalendar.new(date_range: date_range, options:options)
23
+ Json.new(date_range: date_range, options:options)
25
24
  else
26
- raise FormatterNameError.new("Unknown format: #{formatter} (#{formatter.class})")
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
- class Text < Base
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
- def format_day_header(context, date, is_holiday)
180
- date.strftime("((%2m %2d %Y) . (")
181
- end
182
-
183
- def format_item(context, date, item)
184
- unless item.oneday?
185
- format_multiple_days_item(context, date, item)
186
- return ""
187
- end
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 # module Formatter
41
+ end # class Formatter
337
42
  end # module Mhc