mhc 1.0.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.
Files changed (127) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/COPYRIGHT +28 -0
  6. data/Gemfile +8 -0
  7. data/README.org +209 -0
  8. data/Rakefile +13 -0
  9. data/bin/mhc +312 -0
  10. data/emacs/Cask +25 -0
  11. data/emacs/Makefile +58 -0
  12. data/emacs/mhc-calendar.el +1723 -0
  13. data/emacs/mhc-calfw.el +135 -0
  14. data/emacs/mhc-compat.el +90 -0
  15. data/emacs/mhc-date.el +642 -0
  16. data/emacs/mhc-day.el +149 -0
  17. data/emacs/mhc-db.el +158 -0
  18. data/emacs/mhc-draft.el +211 -0
  19. data/emacs/mhc-e21.el +167 -0
  20. data/emacs/mhc-face.el +236 -0
  21. data/emacs/mhc-file.el +224 -0
  22. data/emacs/mhc-guess.el +648 -0
  23. data/emacs/mhc-header.el +176 -0
  24. data/emacs/mhc-logic.el +563 -0
  25. data/emacs/mhc-message.el +130 -0
  26. data/emacs/mhc-minibuf.el +466 -0
  27. data/emacs/mhc-misc.el +248 -0
  28. data/emacs/mhc-mua.el +260 -0
  29. data/emacs/mhc-parse.el +286 -0
  30. data/emacs/mhc-process.el +35 -0
  31. data/emacs/mhc-ps.el +1174 -0
  32. data/emacs/mhc-record.el +201 -0
  33. data/emacs/mhc-schedule.el +202 -0
  34. data/emacs/mhc-summary.el +763 -0
  35. data/emacs/mhc-sync.el +158 -0
  36. data/emacs/mhc-vars.el +149 -0
  37. data/emacs/mhc.el +1114 -0
  38. data/icons/Anniversary.xbm +6 -0
  39. data/icons/Anniversary.xpm +27 -0
  40. data/icons/Birthday.xbm +6 -0
  41. data/icons/Birthday.xpm +25 -0
  42. data/icons/Business.xbm +6 -0
  43. data/icons/Business.xpm +24 -0
  44. data/icons/CheckBox.xbm +6 -0
  45. data/icons/CheckBox.xpm +24 -0
  46. data/icons/CheckedBox.xbm +6 -0
  47. data/icons/CheckedBox.xpm +25 -0
  48. data/icons/Conflict.xbm +6 -0
  49. data/icons/Conflict.xpm +22 -0
  50. data/icons/Date.xbm +6 -0
  51. data/icons/Date.xpm +29 -0
  52. data/icons/Holiday.xbm +6 -0
  53. data/icons/Holiday.xpm +25 -0
  54. data/icons/Link.xbm +6 -0
  55. data/icons/Link.xpm +25 -0
  56. data/icons/Other.xbm +6 -0
  57. data/icons/Other.xpm +28 -0
  58. data/icons/Party.xbm +6 -0
  59. data/icons/Party.xpm +23 -0
  60. data/icons/Private.xbm +6 -0
  61. data/icons/Private.xpm +26 -0
  62. data/icons/Recurrence.xbm +6 -0
  63. data/icons/Recurrence.xpm +98 -0
  64. data/icons/Vacation.xbm +6 -0
  65. data/icons/Vacation.xpm +26 -0
  66. data/lib/mhc.rb +45 -0
  67. data/lib/mhc/builder.rb +64 -0
  68. data/lib/mhc/caldav.rb +304 -0
  69. data/lib/mhc/calendar.rb +106 -0
  70. data/lib/mhc/command.rb +13 -0
  71. data/lib/mhc/command/cache.rb +14 -0
  72. data/lib/mhc/command/completions.rb +108 -0
  73. data/lib/mhc/command/init.rb +133 -0
  74. data/lib/mhc/command/scan.rb +33 -0
  75. data/lib/mhc/command/sync.rb +22 -0
  76. data/lib/mhc/config.rb +229 -0
  77. data/lib/mhc/converter.rb +330 -0
  78. data/lib/mhc/datastore.rb +164 -0
  79. data/lib/mhc/date_enumerator.rb +274 -0
  80. data/lib/mhc/date_frame.rb +124 -0
  81. data/lib/mhc/date_helper.rb +49 -0
  82. data/lib/mhc/etag.rb +68 -0
  83. data/lib/mhc/event.rb +396 -0
  84. data/lib/mhc/formatter.rb +312 -0
  85. data/lib/mhc/logger.rb +94 -0
  86. data/lib/mhc/modifier.rb +149 -0
  87. data/lib/mhc/occurrence.rb +94 -0
  88. data/lib/mhc/occurrence_enumerator.rb +113 -0
  89. data/lib/mhc/property_value.rb +33 -0
  90. data/lib/mhc/property_value/date.rb +190 -0
  91. data/lib/mhc/property_value/integer.rb +15 -0
  92. data/lib/mhc/property_value/list.rb +41 -0
  93. data/lib/mhc/property_value/period.rb +49 -0
  94. data/lib/mhc/property_value/range.rb +100 -0
  95. data/lib/mhc/property_value/recurrence_condition.rb +272 -0
  96. data/lib/mhc/property_value/text.rb +11 -0
  97. data/lib/mhc/property_value/time.rb +45 -0
  98. data/lib/mhc/query.rb +210 -0
  99. data/lib/mhc/sync.rb +46 -0
  100. data/lib/mhc/sync/driver.rb +108 -0
  101. data/lib/mhc/sync/status.rb +70 -0
  102. data/lib/mhc/sync/status_manager.rb +142 -0
  103. data/lib/mhc/sync/strategy.rb +233 -0
  104. data/lib/mhc/sync/syncinfo.rb +98 -0
  105. data/lib/mhc/templates/config.yml.erb +142 -0
  106. data/lib/mhc/version.rb +4 -0
  107. data/lib/mhc/webdav.rb +319 -0
  108. data/mhc.gemspec +24 -0
  109. data/samples/DOT.mhc-config.yml +116 -0
  110. data/samples/japanese-holidays.mhcc +153 -0
  111. data/samples/mhc-completions.zsh +11 -0
  112. data/spec/mhc_spec.rb +682 -0
  113. data/spec/spec_helper.rb +9 -0
  114. data/xpm/close.xpm +18 -0
  115. data/xpm/delete.xpm +19 -0
  116. data/xpm/exit.xpm +18 -0
  117. data/xpm/month.xpm +18 -0
  118. data/xpm/next.xpm +18 -0
  119. data/xpm/next2.xpm +18 -0
  120. data/xpm/next_year.xpm +18 -0
  121. data/xpm/open.xpm +19 -0
  122. data/xpm/prev.xpm +18 -0
  123. data/xpm/prev2.xpm +18 -0
  124. data/xpm/prev_year.xpm +18 -0
  125. data/xpm/save.xpm +19 -0
  126. data/xpm/today.xpm +18 -0
  127. metadata +214 -0
data/lib/mhc/logger.rb ADDED
@@ -0,0 +1,94 @@
1
+ module Mhc
2
+ ################################################################
3
+ # Log maintenance functions.
4
+ #
5
+ # M 2000-04-25 00:06:08 <20.nom@.nomcom> ~nom/Mail/schedule/2000/04/1 Luncheon
6
+ # D 2000-04-25 00:06:08 <20.nom@.nomcom> ~nom/Mail/schedule/2000/04/1 Luncheon
7
+ # S 2000-04-25 00:06:08 user_id
8
+ #
9
+ class Logger
10
+ def initialize(filename)
11
+ @filename = filename
12
+ end
13
+
14
+ def add_entry(entry)
15
+ file = File.open(@filename, "a+")
16
+ file.print "#{entry}\n"
17
+ file.fsync if file.respond_to?("fsync")
18
+ file.close
19
+ end
20
+
21
+ def each_entry
22
+ begin
23
+ file = File.open(@filename)
24
+ while line = file.gets
25
+ yield(LogEntry.new(line.chomp))
26
+ end
27
+ file.close
28
+ rescue
29
+ end
30
+ end
31
+
32
+ def entries()
33
+ arry = []
34
+ each_entry{|e|
35
+ arry << e
36
+ }
37
+ return arry
38
+ end
39
+
40
+ def shrink_entries(user_id)
41
+ hash = {}
42
+ each_entry{|e|
43
+ if e.status == 'S' and e.rec_id == user_id
44
+ hash.clear
45
+ else
46
+ hash[e.rec_id] = e
47
+ end
48
+ }
49
+ return hash.values
50
+ end
51
+ end
52
+
53
+ class LogEntry
54
+ attr :status
55
+ attr :mtime
56
+ attr :rec_id
57
+ attr :path
58
+ attr :subject
59
+
60
+ def initialize(status, mtime = nil, rec_id = nil, path = nil, subject = nil)
61
+ if mtime.nil?
62
+ init_from_string(status)
63
+ else
64
+ @status, @mtime, @rec_id, @path, @subject =
65
+ status, mtime, rec_id, path, subject
66
+ end
67
+ end
68
+
69
+ def to_s
70
+ return [
71
+ @status,
72
+ @mtime.strftime("%Y-%m-%d %H:%M:%S"),
73
+ @rec_id,
74
+ @path,
75
+ @subject
76
+ ].join(' ')
77
+ end
78
+
79
+ ################
80
+ private
81
+ ################
82
+ def init_from_string(line)
83
+ str = line.chomp
84
+ status, yymmdd, hhmmss, rec_id, path, subject = str.split
85
+ yy, mm, dd = yymmdd.split('-')
86
+ h, m, s = hhmmss.split(':')
87
+
88
+ mtime = Time.local(yy.to_i, mm.to_i, dd.to_i,
89
+ h .to_i, m .to_i, s .to_i)
90
+ @status, @mtime, @rec_id, @path, @subject =
91
+ status, mtime, rec_id, path, subject
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,149 @@
1
+ module Mhc
2
+ class Modifier
3
+ class ParseError < StandardError; end
4
+
5
+ attr_accessor :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def to_s
12
+ @name.to_s
13
+ end
14
+
15
+ def decorate(event)
16
+ if deco = Decorator.find_subclass(@name.to_sym)
17
+ deco.new(event)
18
+ else
19
+ raise Mhc::Modifier::ParseError, "Unknown Decorator #{@name}"
20
+ end
21
+ end
22
+
23
+ class Decorator
24
+ require 'forwardable'
25
+ extend Forwardable
26
+
27
+ def_delegators :@event,
28
+ :path,
29
+ :alarm,
30
+ :categories,
31
+ :description,
32
+ :body,
33
+ :location,
34
+ :priority,
35
+ :record_id,
36
+ :occurrences,
37
+ :uid,
38
+ :subject,
39
+ :dates,
40
+ :exceptions,
41
+ :time_range,
42
+ :duration,
43
+ :recurrence_condition,
44
+ :recurrence_tag,
45
+ :mission_tag,
46
+ :sequence,
47
+ # :occurrences,
48
+ :dtstart,
49
+ :dtend,
50
+ :rdates,
51
+ :exdates,
52
+ :etag,
53
+ :recurring?,
54
+ :allday?
55
+
56
+ def self.find_subclass(snake_name)
57
+ @subclasses ||= {}
58
+
59
+ if c = @subclasses[snake_name]
60
+ return c
61
+ end
62
+
63
+ class_name = snake_name.to_s.capitalize.gsub(/_([a-z\d]+)/){ $1.capitalize }.to_sym
64
+ return nil unless const_defined?(class_name)
65
+
66
+ const = const_get(class_name)
67
+ if const.class == Class and const.superclass == self
68
+ return @subclasses[snake_name] = const
69
+ end
70
+ return nil
71
+ end
72
+
73
+ def initialize(event)
74
+ @event = event
75
+ end
76
+
77
+ def to_ics
78
+ Mhc::Converter::Icalendar.new.to_ics(self)
79
+ end
80
+
81
+ def to_icalendar
82
+ Mhc::Converter::Icalendar.new.to_icalendar(self)
83
+ end
84
+
85
+ def to_ics_string
86
+ Mhc::Converter::Icalendar.new.to_ics_string(self)
87
+ end
88
+
89
+ def occurrences(range:nil)
90
+ Mhc::OccurrenceEnumerator.new(self, dates, exceptions, recurrence_condition, duration, range)
91
+ end
92
+
93
+ class HideDetails < Decorator
94
+ def subject
95
+ return Mhc::PropertyValue::Text.new.parse("BUSY")
96
+ end
97
+
98
+ def location
99
+ return nil
100
+ end
101
+
102
+ def description
103
+ return nil
104
+ end
105
+ alias_method :body, :description
106
+
107
+ end # class HideDetails
108
+
109
+
110
+ class HideDescription < Decorator
111
+
112
+ def description
113
+ return nil
114
+ end
115
+
116
+ alias_method :body, :description
117
+
118
+ end # class HideDescription
119
+
120
+
121
+ class HideLocation < Decorator
122
+ def location
123
+ return nil
124
+ end
125
+ end # class HideLocation
126
+
127
+
128
+ class HideTimeRange < Decorator
129
+ def allday?
130
+ return true
131
+ end
132
+
133
+ def time_range
134
+ # create empty time_range
135
+ Mhc::PropertyValue::Range.new(Mhc::PropertyValue::Time)
136
+ end
137
+ end # class HideTimeRange
138
+
139
+ class ReplaceSubjectByCategory < Decorator
140
+ def subject
141
+ categories = @event.categories
142
+ return "BUSY" if categories.empty?
143
+ return categories.first.to_s.capitalize
144
+ end
145
+ end # class ReplaceSubjectByCategory
146
+
147
+ end # class Decorator
148
+ end # class Modifier
149
+ end # module Mhc
@@ -0,0 +1,94 @@
1
+ require 'forwardable'
2
+
3
+ module Mhc
4
+ class Occurrence
5
+ include Comparable
6
+ extend Forwardable
7
+
8
+ def_delegators :@event,
9
+ :path,
10
+ :alarm,
11
+ :categories,
12
+ :description,
13
+ :body,
14
+ :location,
15
+ :priority,
16
+ :record_id,
17
+ :uid,
18
+ :subject,
19
+ :time_range,
20
+ :recurrence_tag,
21
+ :mission_tag,
22
+ :allday?
23
+
24
+ attr_reader :event
25
+
26
+ def initialize(event, date_range)
27
+ @event = event
28
+
29
+ if date_range.respond_to?(:first)
30
+ @start_date = date_range.first
31
+ @end_date = date_range.last
32
+ else
33
+ @start_date = date_range
34
+ @end_date = date_range
35
+ end
36
+ end
37
+
38
+ def date
39
+ @start_date
40
+ end
41
+
42
+ def dtstart
43
+ if allday?
44
+ @start_date
45
+ else
46
+ time_range.first.to_datetime(@start_date)
47
+ end
48
+ end
49
+
50
+ def dtend
51
+ if allday?
52
+ @end_date + 1
53
+ else
54
+ time_range.last.to_datetime(@end_date)
55
+ end
56
+ end
57
+
58
+ def first
59
+ @start_date
60
+ end
61
+
62
+ def last
63
+ @end_date
64
+ end
65
+
66
+ def days
67
+ @end_date - @start_date + 1
68
+ end
69
+
70
+ def oneday?
71
+ @start_date == @end_date
72
+ end
73
+
74
+ def to_mhc_string
75
+ if allday?
76
+ return "#{dtstart.to_mhc_string}" if oneday?
77
+ return "#{@start_date.to_mhc_string}-#{@end_date.to_mhc_string}"
78
+ else
79
+ return dtstart.strftime("%Y%m%d %H:%m-") + ((@start_date == @end_date) ? dtend.strftime("%H:%m") : dtend.strftime("%Y%m%dT%H:%m"))
80
+ end
81
+ end
82
+
83
+ alias_method :to_s, :to_mhc_string
84
+
85
+ def <=>(o)
86
+ if o.respond_to?(:dtstart)
87
+ return self.dtstart <=> o.dtstart
88
+ else
89
+ return self.dtstart <=> o
90
+ end
91
+ end
92
+
93
+ end # class Occurrence
94
+ end # module Mhc
@@ -0,0 +1,113 @@
1
+ module Mhc
2
+ class OccurrenceEnumerator
3
+ include Enumerable
4
+ # ; The FREQ rule part is REQUIRED,
5
+ # ; but MUST NOT occur more than once.
6
+ #
7
+ # FREQ = (DAILY|WEEKLY|MONTHLY|YEARLY)
8
+ #
9
+ # ; The UNTIL or COUNT rule parts are OPTIONAL,
10
+ # ; but they MUST NOT occur in the same 'recur'.
11
+ #
12
+ # UNTIL = (date|date-time)
13
+ # COUNT = \d+
14
+ #
15
+ # ; The other rule parts are OPTIONAL,
16
+ # ; but MUST NOT occur more than once.
17
+ #
18
+ # INTERVAL = \d+ # positive value default is 1
19
+ # BYDAY = ([+-]\d{1,2})?(SU|MO|TU|WE|TH|FR|SA),... # 1 to 53
20
+ # BYMONTHDAY = [+-]\d{1,2},... # 1 to 31
21
+ # BYYEARDAY = [+-]\d{1,3},... # 1 to 366
22
+ # BYWEEKNO = [+-]\d{1,3},... # 1 to 53
23
+ # BYMONTH = \d{1,2},... # 1 to 12
24
+ # BYSETPOS = [+-]\d+,... # 1 to 366
25
+ # WKST = (SU|MO|TU|WE|TH|FR|SA)
26
+ #
27
+ def initialize(event, dates, exceptions, recurrence_condition, duration, date_range = nil)
28
+ @event = event
29
+
30
+ # Since some articles with RECURRENCE_CONDITION and without DURATION
31
+ # makes infinit entries, we have to clip the range by some artificial values
32
+ # It will make 101 enum entries from 1970-1-1 to now+50 years:
33
+ #
34
+ # X-SC-Subject: New Year's Day
35
+ # X-SC-Cond: Jan 1
36
+ #
37
+ date_range = (Date.new(1970, 1, 1) .. Date.new(Date.today.year + 50)) unless date_range
38
+
39
+ # If we have both DURATION and RANGE, we can take narrower term
40
+ # by the combination of the both.
41
+ date_range = duration.narrow(date_range.first, date_range.last)
42
+
43
+ # range.last is effective in narrowing the end_date,
44
+ # however, we can't adopt range.first to the start_date.
45
+ # Original start_date derived from DURATION is required for calculating
46
+ # the start point of recurrence loop in case the
47
+ # loop interval is larger than one.
48
+ #
49
+ # At moment, we will have over-scanning entries even if the range.first
50
+ # is set narrower than duration.first
51
+ #
52
+ # we need some good way to pass the both duration.first and range.first
53
+ # to the down-stream enumerators.
54
+ #
55
+ end_date = date_range.last
56
+ start_date = duration.first || date_range.first
57
+
58
+ @enumerator = Mhc::DateEnumerator.new(start_date: start_date, end_date: date_range.last)
59
+ condition_to_enumerator(@enumerator, recurrence_condition, start_date, date_range.last)
60
+ @enumerator.add_by_range_list(range_list: dates)
61
+ @exceptions = exceptions.map{|range| range.to_a }.flatten
62
+ @date_range = date_range
63
+ end
64
+
65
+ def each
66
+ @enumerator.each do |date_or_range|
67
+ if date_or_range.respond_to?(:first)
68
+ first_date = date_or_range.first
69
+ last_date = date_or_range.last
70
+ else
71
+ first_date = date_or_range
72
+ last_date = date_or_range
73
+ end
74
+ if last_date < @date_range.first or @date_range.last < first_date
75
+ next
76
+ end
77
+ next if @exceptions.include?(first_date)
78
+ yield Mhc::Occurrence.new(@event, date_or_range)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def condition_to_enumerator(enumerator, cond, start_date, end_date)
85
+ if cond.yearly?
86
+ cond.cond_mon.each do |mon|
87
+ cond.cond_ord.each do |ord|
88
+ cond.cond_wek.each do |wek|
89
+ enumerator.add_yearly_by_day(month: mon, nth: ord, wday: wek)
90
+ end
91
+ end
92
+ cond.cond_num.each do |num|
93
+ enumerator.add_yearly_by_monthday(month: mon, mday: num)
94
+ end
95
+ end
96
+ elsif cond.monthly?
97
+ cond.cond_ord.each do |ord|
98
+ cond.cond_wek.each do |wek|
99
+ enumerator.add_monthly_by_day(nth: ord, wday: wek)
100
+ end
101
+ end
102
+ cond.cond_num.each do |num|
103
+ enumerator.add_monthly_by_monthday(mday: num)
104
+ end
105
+ elsif cond.weekly?
106
+ cond.cond_wek.each do |wek|
107
+ enumerator.add_weekly(wday: wek)
108
+ end
109
+ end
110
+ return enumerator
111
+ end
112
+ end # class OccurrenceEnumerator
113
+ end # module Mhc