mhc 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/bin/mhc +66 -3
  3. data/emacs/Cask +1 -1
  4. data/emacs/mhc-date.el +1 -1
  5. data/emacs/mhc-day.el +1 -1
  6. data/emacs/mhc-db.el +57 -38
  7. data/emacs/mhc-draft.el +36 -22
  8. data/emacs/mhc-face.el +1 -1
  9. data/emacs/mhc-header.el +20 -1
  10. data/emacs/mhc-minibuf.el +12 -7
  11. data/emacs/mhc-parse.el +1 -1
  12. data/emacs/mhc-process.el +26 -9
  13. data/emacs/mhc-ps.el +1 -1
  14. data/emacs/mhc-schedule.el +5 -2
  15. data/emacs/mhc-summary.el +31 -12
  16. data/emacs/mhc-vars.el +15 -2
  17. data/emacs/mhc.el +50 -24
  18. data/lib/mhc.rb +3 -1
  19. data/lib/mhc/builder.rb +5 -1
  20. data/lib/mhc/calendar.rb +5 -1
  21. data/lib/mhc/command/cache.rb +5 -4
  22. data/lib/mhc/converter.rb +3 -2
  23. data/lib/mhc/datastore.rb +52 -13
  24. data/lib/mhc/date_enumerator.rb +2 -2
  25. data/lib/mhc/event.rb +42 -21
  26. data/lib/mhc/formatter.rb +17 -312
  27. data/lib/mhc/formatter/base.rb +125 -0
  28. data/lib/mhc/formatter/emacs.rb +47 -0
  29. data/lib/mhc/formatter/howm.rb +35 -0
  30. data/lib/mhc/formatter/icalendar.rb +17 -0
  31. data/lib/mhc/formatter/json.rb +27 -0
  32. data/lib/mhc/formatter/mail.rb +20 -0
  33. data/lib/mhc/formatter/org_table.rb +24 -0
  34. data/lib/mhc/formatter/symbolic_expression.rb +42 -0
  35. data/lib/mhc/formatter/text.rb +29 -0
  36. data/lib/mhc/occurrence.rb +27 -5
  37. data/lib/mhc/occurrence_enumerator.rb +1 -1
  38. data/lib/mhc/property_value.rb +6 -0
  39. data/lib/mhc/property_value/date.rb +23 -14
  40. data/lib/mhc/property_value/date_time.rb +19 -0
  41. data/lib/mhc/property_value/integer.rb +5 -1
  42. data/lib/mhc/property_value/list.rb +7 -6
  43. data/lib/mhc/property_value/period.rb +3 -1
  44. data/lib/mhc/property_value/range.rb +1 -1
  45. data/lib/mhc/property_value/time.rb +8 -1
  46. data/lib/mhc/version.rb +1 -1
  47. data/spec/mhc_spec.rb +83 -0
  48. metadata +13 -3
@@ -0,0 +1,125 @@
1
+ module Mhc
2
+ class Formatter
3
+ # prepare
4
+ # format_header
5
+ # format_day_header
6
+ # format_item_header
7
+ # format_item
8
+ # format_item_hooter
9
+ # format_day_hooter
10
+ # format_footer
11
+ # teardown
12
+ class Base
13
+ def initialize(date_range:, options:nil)
14
+ @date_range = date_range
15
+ @options = options
16
+ @occurrences, @events, @items = [], [], {}
17
+ @event_hash = {}
18
+ end
19
+
20
+ def <<(occurrence)
21
+ event = occurrence.event
22
+ @occurrences << occurrence
23
+ @events << event unless @event_hash[event]
24
+ @event_hash[event] = true
25
+
26
+ @items[occurrence.date] ||= []
27
+ @items[occurrence.date] << occurrence
28
+ end
29
+
30
+ def to_s
31
+ context = {:items => @items}.merge(@options)
32
+ prepare(context)
33
+ string = format_header(context) + format_body(context) + format_footer(context)
34
+ teardown(context)
35
+ return string
36
+ end
37
+
38
+ ################################################################
39
+ private
40
+
41
+ def prepare(context); end
42
+ def teardown(context); end
43
+
44
+ def pad_empty_dates
45
+ @date_range.each do |date|
46
+ @items[date] ||= []
47
+ end
48
+ end
49
+
50
+ def expand_multiple_days_occurrences
51
+ @occurrences.each do |oc|
52
+ next if oc.oneday?
53
+ ((oc.first + 1) .. oc.last).each do |date|
54
+ @items[date] ||= []
55
+ @items[date] << oc
56
+ end
57
+ end
58
+ end
59
+
60
+ def format_header(context); ""; end
61
+ def format_footer(context); ""; end
62
+ def format_day_header(context, date, is_holiday); ""; end
63
+ def format_day_footer(context, date); ""; end
64
+
65
+ def format_body(context)
66
+ context[:number] = 0
67
+ @items.keys.sort.map{|date| format_day(context, date, @items[date]) }.join
68
+ end
69
+
70
+ def format_day(context, date, items)
71
+ string = format_day_header(context, date, items.any?{|e| e.holiday?})
72
+
73
+ items = sort_items_in_day(items)
74
+ items.each_with_index do |occurrence, count|
75
+ context[:number] += 1
76
+ context[:number_in_day] = count + 1
77
+ string += format_item(context, date, occurrence)
78
+ end
79
+
80
+ return string + format_day_footer(context, date)
81
+ end
82
+
83
+ def format_item(context, date, item)
84
+ raise "Implement in subclasses."
85
+ end
86
+
87
+ def format_item_header(context, date, item)
88
+ raise "Implement in subclasses."
89
+ end
90
+
91
+ ################################################################
92
+ ## helpers
93
+ def append(item, separator = " ")
94
+ return "" if item.to_s.empty?
95
+ return separator + item.to_s
96
+ end
97
+
98
+ def prepend(item, separator = " ")
99
+ return "" if item.to_s.empty?
100
+ return item.to_s + separator
101
+ end
102
+
103
+ def enclose(item, bracket = "[]")
104
+ return "" if item.to_s.empty?
105
+ return bracket[0] + item.to_s + bracket[1]
106
+ end
107
+
108
+ # sort occurrences in a day
109
+ # make sure all-day occurrences are prior to others
110
+ def sort_items_in_day(items)
111
+ items.sort do |a,b|
112
+ sign_a = a.allday? ? 0 : 1
113
+ sign_b = b.allday? ? 0 : 1
114
+
115
+ if sign_a != sign_b
116
+ sign_a - sign_b
117
+ else
118
+ a <=> b
119
+ end
120
+ end
121
+ end
122
+
123
+ end # class Base
124
+ end # module Formatter
125
+ end # module Mhc
@@ -0,0 +1,47 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Emacs < SymbolicExpression
4
+ private
5
+
6
+ def prepare(context)
7
+ expand_multiple_days_occurrences
8
+ end
9
+
10
+ def format_header(context); "("; end
11
+ def format_footer(context); ")\n"; end
12
+
13
+ def format_day_header(context, date, is_holiday)
14
+ # (DAYS_FROM_EPOC . [year month day wday holiday-p (
15
+ format("(%d . [%d %d %d %d #{is_holiday ? 't' : 'nil'} (",
16
+ date.absolute_from_epoch, date.year, date.month, date.day, date.wday)
17
+ end
18
+
19
+ def format_item(context, date, item)
20
+ subject = item.subject.to_s
21
+ subject = "(no subject)" if subject == ""
22
+
23
+ # [ RECORD CONDITION SUBJECT LOCATION (TIMEB . TIMEE) ALARM
24
+ # CATEGORIES PRIORITY REGION RECURRENCE-TAG]
25
+ format("[(%s . [%s nil nil]) nil %s %s (%s . %s) %s (%s) nil nil %s]",
26
+ elisp_string(item.path.to_s),
27
+ elisp_string(item.uid.to_s),
28
+ elisp_string(subject),
29
+ elisp_string(item.location),
30
+ (item.time_range.first ? (item.time_range.first.to_i / 60) : "nil"),
31
+ (item.time_range.last ? (item.time_range.last.to_i / 60) : "nil"),
32
+ elisp_string(item.alarm.to_s),
33
+ item.categories.map{|c| elisp_string(c.to_s.downcase)}.join(" "),
34
+ elisp_string(item.recurrence_tag))
35
+ end
36
+
37
+ def format_day_footer(context, date)
38
+ ")]) "
39
+ end
40
+
41
+ def elisp_string(string)
42
+ '"' + string.to_s.toutf8.gsub(/[\"\\]/, '\\\\\&').gsub("\n", "\\n") + '"'
43
+ end
44
+
45
+ end # class Emacs
46
+ end # class Formatter
47
+ end # module Mhc
@@ -0,0 +1,35 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Howm < Base
4
+ private
5
+
6
+ def format_header(context)
7
+ format("= mhc %s--%s\n", *context[:items].keys.minmax)
8
+ end
9
+
10
+ def format_item(context, date, item)
11
+ string = format("[%04d-%02d-%02d %5s]%1s %s\n",
12
+ date.year, date.month, date.mday,
13
+ item.time_range.first.to_s,
14
+ mark_todo(item.categories.to_mhc_string),
15
+ item.subject)
16
+ if item.description.to_s != ""
17
+ string += item.description.to_s.gsub(/^/, " ") + "\n"
18
+ end
19
+ return string
20
+ end
21
+
22
+ def mark_todo(category)
23
+ case category
24
+ when /done/i
25
+ "."
26
+ when /todo/i
27
+ "+"
28
+ else
29
+ "@"
30
+ end
31
+ end
32
+
33
+ end # class Howm
34
+ end # class Formatter
35
+ end # module Mhc
@@ -0,0 +1,17 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Icalendar < Base
4
+ private
5
+
6
+ def format_body(context)
7
+ ical = RiCal.Calendar
8
+ ical.prodid = Mhc::PRODID
9
+ @events.each do |event|
10
+ ical.events << event.to_icalendar
11
+ end
12
+ return ical.to_s
13
+ end
14
+
15
+ end # class Icalendar
16
+ end # class Formatter
17
+ end # module Mhc
@@ -0,0 +1,27 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Json < Base
4
+ require "json"
5
+
6
+ def format_body(context)
7
+ events = []
8
+ @occurrences.each do |oc|
9
+ class_name = []
10
+ class_name += oc.categories.map{|c| "mhc-category-#{c.to_s.downcase}"}
11
+ class_name << (oc.allday? ? "mhc-allday" : "mhc-time-range")
12
+
13
+ events << {
14
+ id: oc.record_id,
15
+ allDay: oc.allday?,
16
+ title: oc.subject,
17
+ start: oc.dtstart.iso8601,
18
+ end: oc.dtend.iso8601,
19
+ className: class_name
20
+ }
21
+ end
22
+ return events.to_json
23
+ end
24
+
25
+ end # class Json
26
+ end # class Formatter
27
+ end # module Mhc
@@ -0,0 +1,20 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Mail < Text
4
+ private
5
+
6
+ def format_header(context)
7
+ mail_address = context[:mailto].to_s
8
+ subject = format("MHC schedule report (%s--%s)", *context[:items].keys.minmax)
9
+ header = "To: #{mail_address}\n"
10
+ header += "From: #{append(mail_address, "secretary-of-")}\n"
11
+ header += "Subject: #{subject}\n"
12
+ header += "Content-Type: Text/Plain; charset=utf-8\n"
13
+ header += "Content-Transfer-Encoding: 8bit\n"
14
+ header += "\n"
15
+ header += format("* mhc %s--%s\n", *context[:items].keys.minmax)
16
+ end
17
+
18
+ end # class Mail
19
+ end # class Formatter
20
+ end # module Mhc
@@ -0,0 +1,24 @@
1
+ module Mhc
2
+ class Formatter
3
+ class OrgTable < Base
4
+ private
5
+
6
+ def format_header(context)
7
+ format("* mhc %s--%s\n", *context[:items].keys.minmax)
8
+ end
9
+
10
+ def format_item(context, date, item)
11
+ # | No | Mission | Recurrence | Subject | Path | Date |
12
+ format(" | %4d | %s | %s | %s | %s | [%04d-%02d-%02d%s] |\n",
13
+ context[:number],
14
+ item.mission_tag.to_s.toutf8,
15
+ item.recurrence_tag.to_s.toutf8,
16
+ item.subject.to_s.toutf8,
17
+ item.path.to_s,
18
+ date.year, date.month, date.mday,
19
+ append(item.time_range.to_s))
20
+ end
21
+
22
+ end # class OrgTable
23
+ end # class Formatter
24
+ end # module Mhc
@@ -0,0 +1,42 @@
1
+ module Mhc
2
+ class Formatter
3
+ class SymbolicExpression < Base
4
+ private
5
+
6
+ def format_header(context); "("; end
7
+ def format_footer(context); "(periods #{@periods}))\n"; end
8
+
9
+ def format_day_header(context, date, is_holiday)
10
+ date.strftime("((%2m %2d %Y) . (")
11
+ end
12
+
13
+ def format_item(context, date, item)
14
+ unless item.oneday?
15
+ format_multiple_days_item(context, date, item)
16
+ return ""
17
+ end
18
+ format_item_line(item)
19
+ end
20
+
21
+ def format_multiple_days_item(context, date, item)
22
+ @periods ||= ""
23
+ @periods += item.first.strftime("((%2m %2d %Y) ") +
24
+ item.last.strftime(" (%2m %2d %Y) ") +
25
+ format_item_line(item) + ') '
26
+ end
27
+
28
+ def format_day_footer(context, date); ")) "; end
29
+
30
+ def format_item_line(item)
31
+ '"' +
32
+ format("%s%s%s",
33
+ prepend(item.time_range.first.to_s).toutf8,
34
+ item.subject.to_s.toutf8,
35
+ append(enclose(item.location)).toutf8).gsub(/[\"\\]/, '\\\\\&') +
36
+ '" '
37
+
38
+ end
39
+
40
+ end # class SymbolicExpression
41
+ end # class Formatter
42
+ end # module Mhc
@@ -0,0 +1,29 @@
1
+ module Mhc
2
+ class Formatter
3
+ class Text < Base
4
+ def prepare(context)
5
+ expand_multiple_days_occurrences
6
+ end
7
+
8
+ def format_item(context, date, item)
9
+ subject = item.subject.to_s
10
+ subject = "(no subject)" if subject == ""
11
+ format("%s%-11s %s%s\n",
12
+ format_item_header(context, date, item),
13
+ item.time_range.to_mhc_string.toutf8,
14
+ subject.toutf8,
15
+ append(enclose(item.location)).toutf8
16
+ )
17
+ end
18
+
19
+ def format_item_header(context, date, item)
20
+ if context[:number_in_day] == 1
21
+ date.strftime("%Y/%m/%d %a ")
22
+ else
23
+ " " * 15
24
+ end
25
+ end
26
+
27
+ end # class Text
28
+ end # module Formatter
29
+ end # module Mhc
@@ -16,7 +16,6 @@ module Mhc
16
16
  :record_id,
17
17
  :uid,
18
18
  :subject,
19
- :time_range,
20
19
  :recurrence_tag,
21
20
  :mission_tag,
22
21
  :allday?,
@@ -37,14 +36,32 @@ module Mhc
37
36
  end
38
37
 
39
38
  def date
40
- @start_date
39
+ if @start_date.respond_to?(:hour)
40
+ Mhc::PropertyValue::Date.new(@start_date.year, @start_date.month, @start_date.day)
41
+ else
42
+ @start_date
43
+ end
44
+ end
45
+
46
+ # FIXME: TimeRange class should be implemented
47
+ def time_range
48
+ range = Mhc::PropertyValue::Range.new(Mhc::PropertyValue::Time)
49
+ if dtstart.respond_to?(:hour)
50
+ range.parse("#{dtstart.hour}:#{dtstart.min}-#{dtend.hour}:#{dtend.min}")
51
+ else
52
+ return range # allday
53
+ end
41
54
  end
42
55
 
43
56
  def dtstart
44
57
  if allday?
45
58
  @start_date
46
59
  else
47
- time_range.first.to_datetime(@start_date)
60
+ if @start_date.respond_to?(:hour)
61
+ @start_date
62
+ else
63
+ @event.time_range.first.to_datetime(@start_date)
64
+ end
48
65
  end
49
66
  end
50
67
 
@@ -52,7 +69,11 @@ module Mhc
52
69
  if allday?
53
70
  @end_date + 1
54
71
  else
55
- time_range.last.to_datetime(@end_date)
72
+ if @end_date.respond_to?(:hour)
73
+ @end_date
74
+ else
75
+ @event.time_range.last.to_datetime(@end_date)
76
+ end
56
77
  end
57
78
  end
58
79
 
@@ -77,7 +98,8 @@ module Mhc
77
98
  return "#{dtstart.to_mhc_string}" if oneday?
78
99
  return "#{@start_date.to_mhc_string}-#{@end_date.to_mhc_string}"
79
100
  else
80
- return dtstart.strftime("%Y%m%d %H:%m-") + ((@start_date == @end_date) ? dtend.strftime("%H:%m") : dtend.strftime("%Y%m%dT%H:%m"))
101
+ time = dtstart.strftime("%Y%m%d %H:%m-") + ((@start_date.to_date == @end_date.to_date) ? dtend.strftime("%H:%m") : dtend.strftime("%Y%m%dT%H:%m"))
102
+ return time + " " + subject.to_mhc_string
81
103
  end
82
104
  end
83
105
 
@@ -71,7 +71,7 @@ module Mhc
71
71
  first_date = date_or_range
72
72
  last_date = date_or_range
73
73
  end
74
- if last_date < @date_range.first or @date_range.last < first_date
74
+ if last_date.to_date < @date_range.first or @date_range.last < first_date.to_date
75
75
  next
76
76
  end
77
77
  next if @exceptions.include?(first_date)