mhc 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/mhc +66 -3
- data/emacs/Cask +1 -1
- data/emacs/mhc-date.el +1 -1
- 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-schedule.el +5 -2
- data/emacs/mhc-summary.el +31 -12
- data/emacs/mhc-vars.el +15 -2
- data/emacs/mhc.el +50 -24
- data/lib/mhc.rb +3 -1
- data/lib/mhc/builder.rb +5 -1
- data/lib/mhc/calendar.rb +5 -1
- data/lib/mhc/command/cache.rb +5 -4
- 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/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/spec/mhc_spec.rb +83 -0
- 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
|
data/lib/mhc/occurrence.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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)
|