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.
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
@@ -15,12 +15,18 @@ module Mhc
15
15
  return @value.to_s
16
16
  end
17
17
 
18
+ def empty?
19
+ return true if @value.nil? || (@value.respond_to?(:empty?) && @value.empty?)
20
+ return false
21
+ end
22
+
18
23
  alias_method :to_s, :to_mhc_string
19
24
  end
20
25
 
21
26
  dir = File.dirname(__FILE__) + "/property_value"
22
27
 
23
28
  autoload :Date, "#{dir}/date.rb"
29
+ autoload :DateTime, "#{dir}/date_time.rb"
24
30
  autoload :Integer, "#{dir}/integer.rb"
25
31
  autoload :List, "#{dir}/list.rb"
26
32
  autoload :Period, "#{dir}/period.rb"
@@ -8,12 +8,25 @@ module Mhc
8
8
 
9
9
  DAYS_OF_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
10
10
 
11
- def self.parse(string)
12
- if /^(\d{4})(\d{2})(\d{2})$/ =~ string
13
- # don't use super(string) because it's slow.
14
- new($1.to_i, $2.to_i, $3.to_i)
15
- else
16
- return nil # raise ParseError
11
+ def self.parse(string, default = nil)
12
+ begin
13
+ # YYYYMMDD/HH:MM => DateTime
14
+ if /^(\d{4})(\d{2})(\d{2})\/(\d{2}):(\d{2})$/ =~ string
15
+ DateTime.new($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, 0)
16
+
17
+ # YYYYMMDD => Date
18
+ elsif /^(\d{4})(\d{2})(\d{2})$/ =~ string
19
+ Date.new($1.to_i, $2.to_i, $3.to_i)
20
+
21
+ # YYYYMMDD/hh:mm-HH:MM => DateTime taking YYYYMMDD, HH:MM
22
+ elsif /^(\d+):(\d+)$/ =~ string && default
23
+ DateTime.new(default.year, default.month, default.day, $1.to_i, $2.to_i, 0)
24
+
25
+ else
26
+ fail ParseError
27
+ end
28
+ rescue
29
+ raise ParseError, "invalid date string \"#{string}\""
17
30
  end
18
31
  end
19
32
 
@@ -38,7 +51,7 @@ module Mhc
38
51
  return self.today.first_day_of_month.next_month
39
52
 
40
53
  else
41
- raise ParseError, "invalid date string '#{date_string}'"
54
+ raise ParseError, "invalid date string \"#{date_string}\""
42
55
  end
43
56
  end
44
57
 
@@ -65,7 +78,7 @@ module Mhc
65
78
  date = parse_relative($1)
66
79
  return date..date
67
80
  else
68
- raise ParseError, "invalid date range string '#{range_string}'"
81
+ raise ParseError, "invalid date range string \"#{range_string}\""
69
82
  end
70
83
  end
71
84
 
@@ -80,12 +93,8 @@ module Mhc
80
93
  end
81
94
  end
82
95
 
83
- def parse(string)
84
- if /^\d{8}$/ =~ string
85
- self.class.parse(string)
86
- else
87
- return nil # raise ParseError
88
- end
96
+ def parse(string, default = nil)
97
+ self.class.parse(string, default)
89
98
  end
90
99
 
91
100
  def add_time(time = nil)
@@ -0,0 +1,19 @@
1
+ require "date"
2
+
3
+ module Mhc
4
+ module PropertyValue
5
+ class DateTime < ::DateTime
6
+
7
+ def to_mhc_string
8
+ return strftime("%Y%m%d/%H:%M")
9
+ end
10
+
11
+ def absolute_from_epoch
12
+ return (self - Date.new(1970, 1, 1)).to_i
13
+ end
14
+
15
+ alias_method :to_s, :to_mhc_string
16
+
17
+ end # class DateTime
18
+ end # module PropertyValue
19
+ end # module Mhc
@@ -2,7 +2,11 @@ module Mhc
2
2
  module PropertyValue
3
3
  class Integer < Base
4
4
  def parse(string)
5
- @value = string.to_i if /^\d+$/ =~ string
5
+ if /^\d+$/ =~ string
6
+ @value = string.to_i
7
+ else
8
+ raise ParseError, "invalid integer format \"#{string}\""
9
+ end
6
10
  return self
7
11
  end
8
12
 
@@ -6,35 +6,36 @@ module Mhc
6
6
  ITEM_SEPARATOR = " "
7
7
 
8
8
  def initialize(item_class)
9
- @list = []
9
+ @value = []
10
10
  @item_class = item_class
11
11
  end
12
12
 
13
13
  def each
14
- @list.each do |value|
14
+ @value.each do |value|
15
15
  yield value
16
16
  end
17
17
  end
18
18
 
19
19
  def include?(o)
20
- @list.include?(o)
20
+ @value.include?(o)
21
21
  end
22
22
 
23
23
  def empty?
24
- @list.empty?
24
+ @value.empty?
25
25
  end
26
26
 
27
27
  def parse(string)
28
28
  string.strip.split(ITEM_SEPARATOR).each do |str|
29
29
  item = @item_class.parse(str)
30
- @list << item if item
30
+ @value << item if item
31
31
  end
32
32
  return self
33
33
  end
34
34
 
35
35
  def to_mhc_string
36
- @list.map{|item| item.to_mhc_string}.join(ITEM_SEPARATOR)
36
+ @value.map{|item| item.to_mhc_string}.join(ITEM_SEPARATOR)
37
37
  end
38
+ alias_method :to_s, :to_mhc_string
38
39
 
39
40
  end # class List
40
41
  end # module PropertyValue
@@ -4,7 +4,7 @@ module Mhc
4
4
 
5
5
  UNIT2MIN = {'minute' => 1, 'hour' => 60, 'day' => 60*24}
6
6
  UNITS = UNIT2MIN.keys
7
- REGEXP = /(\d+)\s*(#{UNITS.join("|")})s?/
7
+ REGEXP = /(\d+)\s*(#{UNITS.join("|")})s?$/
8
8
 
9
9
  def self.parse(string)
10
10
  return new.parse(string)
@@ -13,6 +13,8 @@ module Mhc
13
13
  def parse(string)
14
14
  if REGEXP =~ string
15
15
  @minutes = (UNIT2MIN[$2] * $1.to_i)
16
+ else
17
+ raise ParseError, "invalid period string \"#{string}\""
16
18
  end
17
19
  return self
18
20
  end
@@ -25,7 +25,7 @@ module Mhc
25
25
  last = first if last.nil? # single "A" means "A-A"
26
26
 
27
27
  @first = @item_class.parse(first) unless first.to_s == ""
28
- @last = @item_class.parse(last) unless last.to_s == ""
28
+ @last = @item_class.parse(last, @first) unless last.to_s == ""
29
29
  return self.class.new(@item_class, @prefix, @first, @last)
30
30
  end
31
31
 
@@ -7,13 +7,20 @@ module Mhc
7
7
  class Time < Base
8
8
  include Comparable
9
9
 
10
- def parse(string)
10
+ def parse(string, default = nil)
11
+ # default is dummy for matching interface
11
12
  if /^(\d+):(\d+)$/ =~ string
12
13
  @sec = ($1.to_i) * 3600 + ($2.to_i) * 60
14
+ else
15
+ raise ParseError, "invalid time format \"#{string}\""
13
16
  end
14
17
  return self
15
18
  end
16
19
 
20
+ def self.parse(string, default = nil)
21
+ new.parse(string, default)
22
+ end
23
+
17
24
  def days; (@sec ) / 86400 ;end
18
25
  def hour; (@sec % 86400) / 3600 ;end
19
26
  def minute; (@sec % 3600) / 60 ;end
@@ -1,4 +1,4 @@
1
1
  module Mhc
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  PRODID = "-//Quickhack.net//MHC #{Mhc::VERSION}//EN"
4
4
  end
@@ -125,6 +125,20 @@ describe Mhc::Event do
125
125
  end
126
126
 
127
127
 
128
+ it "should emit an error on parsing invalid time format." do
129
+ str = <<-EOF.strip_heredoc
130
+ X-SC-Subject: Invalid Date format
131
+ X-SC-Location: Room1
132
+ X-SC-Day: 19960930
133
+ X-SC-Duration: 19960930-
134
+ X-SC-Time: 19:30-2030
135
+ X-SC-Record-Id: 54A339AD-F7FD-4E56-9B70-2D09F840E94D
136
+ X-SC-Sequence: 0
137
+ EOF
138
+ errors = Mhc::Event.validate(str)
139
+ expect(errors.length).to eq 1
140
+ expect(errors.first.first.message).to match /invalid time format/
141
+ end
128
142
 
129
143
  it "should occur weekly on Monday and Thursday from 2014-04-01 to 2014-04-30 with exception of 2014-04-10 (Thu)" do
130
144
  ev = Mhc::Event.parse <<-EOF.strip_heredoc
@@ -204,6 +218,53 @@ describe Mhc::Event do
204
218
  ["20140203 10:00-12:00 TEST", "20140509 10:00-12:00 TEST", "20140831 10:00-12:00 TEST"]
205
219
  end
206
220
 
221
+ it "should produce a list of occurrences, and one occurrence has different Time" do
222
+ ev = Mhc::Event.parse <<-EOF.strip_heredoc
223
+ X-SC-Subject: X-Project
224
+ X-SC-Location: 101
225
+ X-SC-Day: 20160601 20160602/16:00-18:00 20160616 20160617 20160623 20160624
226
+ X-SC-Time: 09:00-11:00
227
+ X-SC-Category: Conference
228
+ X-SC-Record-Id: FEDA4C97-21C2-46AA-A395-075856FBD5C3
229
+ EOF
230
+ expect(ev.occurrences.take(30).map{|o| "#{o.dtstart.strftime("%Y%m%d/%H:%M")}-#{o.dtend.strftime("%Y%m%d/%H:%M")} #{o.subject}"}).to eq \
231
+ ["20160601/09:00-20160601/11:00 X-Project",
232
+ "20160602/16:00-20160602/18:00 X-Project",
233
+ "20160616/09:00-20160616/11:00 X-Project",
234
+ "20160617/09:00-20160617/11:00 X-Project",
235
+ "20160623/09:00-20160623/11:00 X-Project",
236
+ "20160624/09:00-20160624/11:00 X-Project"]
237
+ end
238
+
239
+ it "should produce a list of occurrences, and one occurrence has different Time crossing days" do
240
+ ev = Mhc::Event.parse <<-EOF.strip_heredoc
241
+ X-SC-Subject: TEST
242
+ X-SC-Time: 10:00-12:00
243
+ X-SC-Day: 20150203/10:30-20150205/12:30 20150509 20150831
244
+ X-SC-Record-Id: FEDA4C97-21C2-46AA-A395-075856FBD5C3
245
+ EOF
246
+
247
+ expect(ev.occurrences.take(30).map{|o| "#{o.dtstart.strftime("%Y%m%d/%H:%M")}-#{o.dtend.strftime("%Y%m%d/%H:%M")} #{o.subject}"}).to eq \
248
+ ["20150203/10:30-20150205/12:30 TEST",
249
+ "20150509/10:00-20150509/12:00 TEST",
250
+ "20150831/10:00-20150831/12:00 TEST"]
251
+
252
+ expect(ev.to_ics).to eq <<-'EOF'.strip_heredoc
253
+ BEGIN:VEVENT
254
+ RDATE;VALUE=PERIOD:20150509T100000Z/20150509T120000Z,20150831T100000Z/20150831T120000Z
255
+ CREATED;VALUE=DATE-TIME:20140101T000000Z
256
+ DTEND;VALUE=DATE-TIME:20150205T123000Z
257
+ DTSTART;VALUE=DATE-TIME:20150203T103000Z
258
+ DTSTAMP;VALUE=DATE-TIME:20140101T000000Z
259
+ LAST-MODIFIED;VALUE=DATE-TIME:20140101T000000Z
260
+ UID:FEDA4C97-21C2-46AA-A395-075856FBD5C3
261
+ DESCRIPTION:
262
+ SUMMARY:TEST
263
+ SEQUENCE:0
264
+ END:VEVENT
265
+ EOF
266
+ end
267
+
207
268
  it "should return true when #allday? is called if X-SC-Time: is blank" do
208
269
  ev = Mhc::Event.parse <<-EOF.strip_heredoc
209
270
  X-SC-Subject: TEST
@@ -397,6 +458,28 @@ describe Mhc::Event do
397
458
  EOF
398
459
  end
399
460
 
461
+ it "should return icalendar VEVENT over 24h event" do
462
+ ev = Mhc::Event.parse <<-EOF.strip_heredoc
463
+ X-SC-Subject: CS1
464
+ X-SC-Time: 12:00-13:00
465
+ X-SC-Day: 20140508/12:00-20140509/10:10
466
+ X-SC-Record-Id: 69CFD0DF-4058-425B-8C2B-40D81E6A2392
467
+ EOF
468
+ expect(ev.to_ics).to eq <<-'EOF'.strip_heredoc
469
+ BEGIN:VEVENT
470
+ CREATED;VALUE=DATE-TIME:20140101T000000Z
471
+ DTEND;VALUE=DATE-TIME:20140509T101000Z
472
+ DTSTART;VALUE=DATE-TIME:20140508T120000Z
473
+ DTSTAMP;VALUE=DATE-TIME:20140101T000000Z
474
+ LAST-MODIFIED;VALUE=DATE-TIME:20140101T000000Z
475
+ UID:69CFD0DF-4058-425B-8C2B-40D81E6A2392
476
+ DESCRIPTION:
477
+ SUMMARY:CS1
478
+ SEQUENCE:0
479
+ END:VEVENT
480
+ EOF
481
+ end
482
+
400
483
  it "should return icalendar VEVENT two day's allday event" do
401
484
  ev = Mhc::Event.parse <<-EOF.strip_heredoc
402
485
  X-SC-Subject: CS1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mhc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoshinari Nomura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-26 00:00:00.000000000 Z
11
+ date: 2018-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -198,12 +198,22 @@ files:
198
198
  - lib/mhc/etag.rb
199
199
  - lib/mhc/event.rb
200
200
  - lib/mhc/formatter.rb
201
+ - lib/mhc/formatter/base.rb
202
+ - lib/mhc/formatter/emacs.rb
203
+ - lib/mhc/formatter/howm.rb
204
+ - lib/mhc/formatter/icalendar.rb
205
+ - lib/mhc/formatter/json.rb
206
+ - lib/mhc/formatter/mail.rb
207
+ - lib/mhc/formatter/org_table.rb
208
+ - lib/mhc/formatter/symbolic_expression.rb
209
+ - lib/mhc/formatter/text.rb
201
210
  - lib/mhc/logger.rb
202
211
  - lib/mhc/modifier.rb
203
212
  - lib/mhc/occurrence.rb
204
213
  - lib/mhc/occurrence_enumerator.rb
205
214
  - lib/mhc/property_value.rb
206
215
  - lib/mhc/property_value/date.rb
216
+ - lib/mhc/property_value/date_time.rb
207
217
  - lib/mhc/property_value/integer.rb
208
218
  - lib/mhc/property_value/list.rb
209
219
  - lib/mhc/property_value/period.rb
@@ -260,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
260
270
  version: '0'
261
271
  requirements: []
262
272
  rubyforge_project:
263
- rubygems_version: 2.4.5
273
+ rubygems_version: 2.7.6
264
274
  signing_key:
265
275
  specification_version: 4
266
276
  summary: Message Harmonized Calendaring