mhc 1.1.1 → 1.2.0
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/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)
|