ircbot 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +71 -0
  4. data/README +72 -3
  5. data/bin/ircbot +3 -0
  6. data/config/samples/postgres.yml +19 -0
  7. data/config/{sama-zu.yml → samples/sama-zu.yml} +1 -1
  8. data/config/{yml.erb → samples/yml.erb} +0 -0
  9. data/ircbot.gemspec +13 -0
  10. data/lib/ircbot.rb +3 -1
  11. data/lib/ircbot/client.rb +6 -0
  12. data/lib/ircbot/client/config.rb +9 -0
  13. data/lib/ircbot/client/plugins.rb +14 -1
  14. data/lib/ircbot/core_ext/message.rb +4 -1
  15. data/lib/ircbot/plugin.rb +17 -0
  16. data/lib/ircbot/plugins.rb +68 -13
  17. data/lib/ircbot/utils/html_parser.rb +26 -0
  18. data/lib/ircbot/utils/watcher.rb +36 -0
  19. data/lib/ircbot/version.rb +1 -1
  20. data/old/plugins/summary.cpi +267 -0
  21. data/plugins/plugins.rb +1 -1
  22. data/plugins/reminder.rb +79 -175
  23. data/plugins/summary/ch2.rb +272 -0
  24. data/plugins/summary/engines.rb +30 -0
  25. data/plugins/summary/engines/base.rb +105 -0
  26. data/plugins/summary/engines/ch2.rb +14 -0
  27. data/plugins/summary/engines/https.rb +6 -0
  28. data/plugins/summary/engines/none.rb +10 -0
  29. data/plugins/summary/engines/twitter.rb +16 -0
  30. data/plugins/summary/spec/ch2_spec.rb +64 -0
  31. data/plugins/summary/spec/spec_helper.rb +19 -0
  32. data/plugins/summary/spec/summarizers_none_spec.rb +15 -0
  33. data/plugins/summary/spec/summarizers_spec.rb +23 -0
  34. data/plugins/summary/summary.rb +58 -0
  35. data/plugins/watchdog/db.rb +80 -0
  36. data/plugins/watchdog/exceptions.rb +4 -0
  37. data/plugins/watchdog/updater.rb +21 -0
  38. data/plugins/watchdog/watchdog.rb +82 -0
  39. data/spec/plugin_spec.rb +11 -0
  40. data/spec/plugins_spec.rb +35 -1
  41. data/spec/utils/html_parser_spec.rb +30 -0
  42. data/spec/utils/spec_helper.rb +1 -0
  43. metadata +190 -13
@@ -0,0 +1,26 @@
1
+ require 'cgi'
2
+
3
+ module Ircbot
4
+ module Utils
5
+ module HtmlParser
6
+ def get_title(html)
7
+ title = $1.strip if %r{<title>(.*?)</title>}mi =~ html
8
+ title ? trim_tags(title) : ""
9
+ end
10
+
11
+ def trim_tags(html)
12
+ html.gsub!(%r{<head.*?>.*?</head>}mi, '')
13
+ html.gsub!(%r{<script.*?>.*?</script>}mi, '')
14
+ html.gsub!(%r{<style.*?>.*?</style>}mi, '')
15
+ html.gsub!(%r{<noscript.*?>.*?</noscript>}mi, '')
16
+ html.gsub!(%r{</?.*?>}, '')
17
+ html.gsub!(%r{<\!--.*?-->}mi, '')
18
+ html.gsub!(%r{<\!\w.*?>}mi, '')
19
+ html.gsub!(/\s+/m, ' ')
20
+ html.strip!
21
+ html = CGI.unescapeHTML(html)
22
+ return html
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,36 @@
1
+ module Ircbot
2
+ module Utils
3
+ class Watcher
4
+ dsl_accessor :interval, 60, :instance=>true
5
+ dsl_accessor :callback, proc{|e| puts e}, :instance=>true
6
+
7
+ def initialize(options = {})
8
+ interval options[:interval] || self.class.interval
9
+ callback options[:callback] || self.class.callback
10
+ end
11
+
12
+ def srcs
13
+ return []
14
+ end
15
+
16
+ def process(src)
17
+ return true
18
+ end
19
+
20
+ def run
21
+ loop do
22
+ srcs.each do |src|
23
+ if process(src)
24
+ callback.call(src)
25
+ end
26
+ end
27
+ sleep interval
28
+ end
29
+ end
30
+
31
+ def start
32
+ Thread.new{ run }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,4 +1,4 @@
1
1
  module Ircbot
2
- VERSION = '0.1.5'
2
+ VERSION = '0.2.0'
3
3
  HOMEPAGE = "http://github.com/maiha/ircbot"
4
4
  end
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/ruby -Ke
2
+ load 'html.rb'
3
+
4
+ require 'ggcl/net/http'
5
+ require 'cgi'
6
+
7
+ class SummaryAgent
8
+ WAIT_SEC = 3
9
+ OUT_SIZE = 90 # ɽ��ʸ����
10
+ CONTENT_LENGTH_LIMIT = 3 * 1024 * 1024 # within 3Mbytes
11
+
12
+ class IRCFloodError < StandardError; end
13
+ class AlreadyOpenedError < StandardError; end
14
+ class NonTextError < StandardError; end
15
+ class SizeExceededError < StandardError; end
16
+ class MissingUrlError < StandardError; end
17
+ class NoSummaryError < StandardError; end
18
+
19
+ PARENTHESIS = Hash[*%w|
20
+ ( ) [ ] { } �� �� �� ��
21
+ |]
22
+
23
+ class <<self
24
+ def adjust_parenthesis (url, prefix)
25
+ # xxx(http://.../) �ʾ�硢�����γ�̤�Ĵ�����롣
26
+ keys = PARENTHESIS .keys
27
+ vals = PARENTHESIS .values
28
+ regexp1 = '(' + keys .collect{|i| Regexp.escape(i)} .join('|') + ')'
29
+ regexp2 = '(' + vals .collect{|i| Regexp.escape(i)} .join('|') + ')'
30
+ if /(#{regexp1}+)$/ === prefix .to_s
31
+ url .gsub!(/#{regexp2}+$/, '')
32
+ end
33
+ url
34
+ end
35
+
36
+ PATTERN_EUC = '[\xa1-\xfe][\xa1-\xfe]'
37
+ REGEXP_EUC = Regexp .new(PATTERN_EUC, 'n')
38
+ def adjust_2bytes_code (url)
39
+ if REGEXP_EUC === url
40
+ url = $`
41
+ end
42
+ return url
43
+ end
44
+
45
+ def adjust_url(url, prefix)
46
+ url = adjust_2bytes_code(url)
47
+ adjust_parenthesis(url, prefix)
48
+ end
49
+
50
+ def extract_url (string)
51
+ case string .to_s
52
+ when /http:\/\/([^:\/]+)(:(\d+))?(\/[^#\s��]*)(#(\S+))?/oi
53
+ return adjust_url($&, $`)
54
+ when /https:\/\/([^:\/]+)(:(\d+))?(\/[^#\s��]*)(#(\S+))?/oi
55
+ return adjust_url($&, $`)
56
+ else
57
+ raise MissingUrlError
58
+ end
59
+ end
60
+ end
61
+
62
+ def initialize (*)
63
+ @url_history = []
64
+ @previous_times = {} # �ܵ�ǽ�κǽ����ѳ��ϻ���(�ƥ桼����)
65
+ @options = {} # �Ƽ索�ץ����
66
+
67
+ title(true)
68
+ end
69
+
70
+ def title (setter = nil)
71
+ if setter
72
+ return @options[:title] = setter
73
+ else
74
+ return @options[:title]
75
+ end
76
+ end
77
+
78
+ def out_size
79
+ @out_size || OUT_SIZE
80
+ end
81
+
82
+ def wait_sec
83
+ @wait_sec || WAIT_SEC
84
+ end
85
+
86
+ def do_help (msg)
87
+ nick = msg[:client].config[:nick]
88
+ <<HELP
89
+ [Web�ڡ���������](#{wait_sec}���Ե���#{out_size}ʸ��ɽ��)
90
+ ���ޥ��: #{nick}.(summary|����).{wait=,size=,title=(on/off)}
91
+ ������ˡ: URL �� #{nick}.(summary|����)
92
+ HELP
93
+ end
94
+
95
+ def check_type (url)
96
+ response = GGCL::Net::HTTP .new(url) .head
97
+ response .content_type
98
+ end
99
+
100
+ def text? (url)
101
+ /text/i === check_type(url)
102
+ end
103
+
104
+ def response (url)
105
+ GGCL::Net::HTTP .new(url) .head
106
+ end
107
+
108
+ def check_header (url)
109
+ res = response(url)
110
+
111
+ # Content-Type ��Ĵ�٤�
112
+ /text/i === res .content_type or
113
+ raise NonTextError
114
+
115
+ # Content-Length ��Ĵ�٤�
116
+ size = res['content-length'] .to_i
117
+ size <= CONTENT_LENGTH_LIMIT or
118
+ raise SizeExceededError .new("#{size}bytes for #{CONTENT_LENGTH_LIMIT}")
119
+ end
120
+
121
+ def maybe_flood? (from)
122
+ (Time .now .to_i - @previous_times[from] .to_i) < wait_sec
123
+ end
124
+
125
+ def check_flood (from)
126
+ maybe_flood? and raise IRCFloodError
127
+ end
128
+
129
+ def summary (url, size = nil)
130
+ html = HTML.get(url)
131
+
132
+ if html .frame?
133
+ frame = html .frames .sort .first
134
+ if frame .src
135
+ url = HTML::compose_path(url, frame .src)
136
+ return summary(url, size)
137
+ end
138
+ end
139
+
140
+ value = html .summary(size || out_size)
141
+
142
+ if title
143
+ # lead = html .title_or_nil
144
+ lead = ''
145
+ if /<TITLE(.*?)>(.*?)<\/TITLE>/im =~ html.html then
146
+ lead = $2
147
+ end
148
+ if lead
149
+ lead .gsub!(/\s+/, '')
150
+ lead = "[#{lead}]"
151
+ end
152
+ return "#{lead}#{value}"
153
+ else
154
+ return value
155
+ end
156
+ end
157
+
158
+ def already_opened? (url)
159
+ bool = @url_history .include? url
160
+ @url_history << url
161
+ return bool
162
+ end
163
+
164
+ def check_duplicate_url (url)
165
+ already_opened?(url) and
166
+ raise AlreadyOpenedError
167
+ end
168
+
169
+ def do_summary (msg, wait = nil)
170
+ begin
171
+ from = msg[:from]
172
+ return nil if from =~ /sama-zu/
173
+ maybe_flood?(from) if wait
174
+ url = self.class.extract_url(msg[:str])
175
+ return nil if url =~ /onamae/
176
+ return nil if url =~ /dzz.jp\/up\/download/
177
+ check_duplicate_url(url)
178
+ check_header(url)
179
+
180
+ @previous_times[from] = Time .now
181
+ buffer = summary(url) or raise NoSummaryError
182
+ buffer = CGI::unescapeHTML(buffer)
183
+ return ">> #{buffer}"
184
+
185
+ rescue IRCFloodError
186
+ rescue AlreadyOpenedError
187
+ rescue NonTextError
188
+ rescue SizeExceededError ; return "error: size exceeded. #{$!}"
189
+ rescue MissingUrlError
190
+ rescue NoSummaryError
191
+ rescue Exception ; return "error: #{$!}"
192
+ end
193
+
194
+ return nil
195
+ end
196
+
197
+ def do_command (msg)
198
+ case msg[:command] .to_s
199
+ when 'summary', '����'
200
+ return do_summary(msg)
201
+ when /^(summary|����).title=/io
202
+ case $' .to_s
203
+ when /off/i, /0/, ''
204
+ title(nil)
205
+ return "���Ф���OFF�ˤ����ˤ㡼��"
206
+ else
207
+ title(true)
208
+ return "���Ф���ON�ˤ����ˤ㡼��"
209
+ end
210
+
211
+ when /^(summary|����).size=/io
212
+ range = (5..1000)
213
+ if range === (size = $'.to_i)
214
+ @out_size = size
215
+ return "ɽ��ʸ������#{out_size}bytes���ѹ������ˤ㡼��"
216
+ else
217
+ return "error: ɽ��������(#{range})��ȿ�Ǥ���"
218
+ end
219
+
220
+ when /^(summary|����).wait=/io
221
+ if (sec = $'.to_i) >= WAIT_SEC
222
+ @wait_sec = sec
223
+ return "�Ե����֤�#{wait_sec}�ä��ѹ������ˤ㡼��"
224
+ else
225
+ return "error: �Ե����֤β��¤�#{WAIT_SEC}�ä����ꤵ��Ƥ��ޤ���"
226
+ end
227
+ end
228
+ return nil
229
+ end
230
+
231
+ def do_reply (msg)
232
+ return do_summary(msg, :wait)
233
+ end
234
+ end
235
+
236
+ if $0 == __FILE__
237
+
238
+ # require 'nkf'
239
+ # str = NKF::nkf('-e', ARGF.read)
240
+ # str = 'aho'
241
+ # html = HTML .new(str)
242
+ # p html .frame?
243
+ # frames = html .frames
244
+ # best = frames .sort .first
245
+ # p best
246
+ # exit
247
+
248
+ agent = SummaryAgent .new
249
+ string = 'http://www.asahi.com/'
250
+ sum = SummaryAgent.new.summary(string,100)
251
+ puts sum
252
+
253
+
254
+ # string = '���㥢������ http://www.zdnet.co.jp/news/bursts/0111/09/zaku.html �������������������ˤ㡼����������'
255
+ # p SummaryAgent::extract_url(string)
256
+
257
+ # string = 'http://kazu.nori.org/'
258
+ # string = "http://pucca.astron.s.u-tokyo.ac.jp/image/ogiyahagi1.mpg"
259
+ # string = 'http://kazu.nori.org/1.png'
260
+ # agent .check_type(string)
261
+
262
+ # string = 'http://www.asahi.com/'
263
+
264
+ # p agent .do_reply({:from, :AnnaChan, :str, string})
265
+ end
266
+
267
+ SummaryAgent .new
@@ -16,7 +16,7 @@ commands: load, start, stop, delete
16
16
  def reply(text)
17
17
  if me?
18
18
  case command
19
- when "load", "register"
19
+ when "load"
20
20
  plugins.load arg
21
21
  done "Loaded #{arg}"
22
22
 
@@ -1,15 +1,8 @@
1
1
  #!/usr/bin/env ruby -Ku
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
- ######################################################################
5
- # [Install]
6
- #
7
- # gem install chawan night-time dm-core dm-migrations dm-timestamps do_sqlite3 data_objects dm-sqlite-adapter -V
8
- #
9
-
10
4
  require 'rubygems'
11
5
  require 'ircbot'
12
- require 'chawan'
13
6
  require 'night-time'
14
7
 
15
8
  require 'dm-core'
@@ -17,28 +10,24 @@ require 'dm-migrations'
17
10
  require 'dm-timestamps'
18
11
 
19
12
  module Reminder
20
- def self.connect(path = nil)
21
- @connecteds ||= {}
22
- @connecteds[path] ||=
23
- (
24
- path ||= Pathname(Dir.getwd) + "db" + "reminder.db"
25
- path = Pathname(path).expand_path
26
- path.parent.mkpath
27
- DataMapper.setup(:default, "sqlite3://#{path}")
28
- Reminder::Event.auto_upgrade!
29
- )
13
+ REPOSITORY_NAME = :reminder
14
+
15
+ def self.connect(uri)
16
+ DataMapper.setup(REPOSITORY_NAME, uri)
17
+ Reminder::Event.auto_upgrade!
30
18
  end
31
19
 
32
20
  ######################################################################
33
21
  ### Exceptions
34
22
 
35
23
  class EventNotFound < RuntimeError; end
36
- class EventNotSaved < RuntimeError
24
+ class EventFound < RuntimeError
37
25
  attr_accessor :event
38
26
  def initialize(event)
39
27
  @event = event
40
28
  end
41
29
  end
30
+ class EventNotSaved < EventFound ; end
42
31
  class EventHasDone < EventNotSaved; end
43
32
  class StartNotFound < EventNotSaved; end
44
33
 
@@ -46,14 +35,18 @@ module Reminder
46
35
  ### Event
47
36
 
48
37
  class Event
38
+ def self.default_repository_name; REPOSITORY_NAME; end
39
+ def self.default_storage_name ; "event"; end
40
+
49
41
  include DataMapper::Resource
50
42
 
51
43
  property :id , Serial
52
44
  property :st , DateTime # 開始日時
53
45
  property :en , DateTime # 終了日時
54
- property :title , String # 件名
55
- property :desc , String # 詳細
56
- property :where , String # 場所
46
+ property :title , String, :length=>255 # 件名
47
+ property :desc , Text # 詳細
48
+ property :where , String, :length=>255 # 場所
49
+ property :source , String, :length=>255 # 情報ソース
57
50
  property :allday , Boolean , :default=>false # 終日フラグ
58
51
  property :alerted , Boolean , :default=>false # お知らせ済
59
52
  property :alert_at , DateTime # お知らせ日時
@@ -62,12 +55,12 @@ module Reminder
62
55
  ### Class methods
63
56
 
64
57
  class << self
65
- def default_storage_name
66
- "event"
58
+ def alerts
59
+ all(:alerted=>false, :alert_at.lt=>Time.now, :order=>[:alert_at])
67
60
  end
68
61
 
69
- def reminders
70
- all(:alerted=>false, :alert_at.lt=>Time.now, :order=>[:alert_at])
62
+ def future
63
+ all(:alerted=>false, :alert_at.gt=>Time.now, :order=>[:alert_at])
71
64
  end
72
65
  end
73
66
 
@@ -84,33 +77,35 @@ module Reminder
84
77
  end
85
78
  end
86
79
 
87
- module TimeParser
88
- def parse(text)
80
+ def self.parse!(text)
81
+ case text
82
+ when /^\s*>>/
83
+ # 引用は無視
84
+ raise EventNotFound
85
+
86
+ else
87
+ # 先頭40bytesに時刻ぽい文字列があれば登録とみなす
88
+ jst = NightTime::Jst.new(text[0,40])
89
+ array = jst.parse
90
+
91
+ # 日付なし
92
+ array[1] && array[2] or raise EventNotFound
93
+
89
94
  event = Event.new
90
95
  event.desc = text
91
96
  event.title = text.sub(%r{^[\s\d:-]+}, '')
92
- event.allday = false
93
-
94
- t = Date._parse(text)
95
- # => {:zone=>"-14:55", :year=>2010, :hour=>13, :min=>30, :mday=>4, :offset=>-53700, :mon=>1}
96
-
97
- if t[:year] && t[:mon] && t[:mday] && t[:hour]
98
- event.st = Time.mktime(t[:year], t[:mon], t[:mday], t[:hour], t[:min], t[:sec])
99
- if t[:zone].to_s =~ /^-?(\d+):(\d+)(:(\d+))?$/
100
- event.en = Time.mktime(t[:year], t[:mon], t[:mday], $1, $2, $4)
101
- end
102
- else
103
- event.allday = true
104
- event.st = Time.mktime(t[:year], t[:mon], t[:mday]) rescue nil
105
- end
97
+ event.allday = array[3].nil?
98
+ event.st = jst.time
106
99
 
107
100
  return event
108
101
  end
102
+ end
109
103
 
110
- def register(text)
111
- event = parse(text)
104
+ module Registable
105
+ def register(event)
112
106
  event.st or raise StartNotFound, event
113
107
  if event.st.to_time > Time.now
108
+ event.source = "irc/reminder"
114
109
  event.alert_at = Time.at(event.st.to_time.to_i - 30*60)
115
110
  event.save or raise EventNotSaved, event
116
111
  return event
@@ -120,160 +115,69 @@ module Reminder
120
115
  end
121
116
  end
122
117
 
123
- extend TimeParser
118
+ extend Registable
124
119
  end
125
120
 
126
121
 
127
122
  class ReminderPlugin < Ircbot::Plugin
128
- class EventWatcher
129
- attr_accessor :interval
130
- attr_accessor :callback
123
+ class EventWatcher < Ircbot::Utils::Watcher
124
+ def srcs
125
+ Reminder::Event.alerts
126
+ end
127
+ end
128
+
129
+ def help
130
+ ["list -> show future reminders",
131
+ "YYYY-mm-dd or YYYY-mm-dd HH:MM text -> register the text"].join("\n")
132
+ end
133
+
134
+ def setup
135
+ return if @watcher
136
+ bot = self.bot
131
137
 
132
- def initialize(options = {})
133
- @interval = options[:interval] || 60
134
- @callback = options[:callback] || proc{|e| puts e}
138
+ uri = self[:db]
139
+ unless uri
140
+ path = Ircbot.root + "db" + "#{config.nick}-reminder.db"
141
+ uri = "sqlite3://#{path}"
142
+ path.parent.mkpath
135
143
  end
136
144
 
137
- def start
138
- loop do
139
- if callback
140
- events = Reminder::Event.reminders
141
- #debug "#{self.class} found #{events.size} events"
142
- events.each do |event|
143
- callback.call(event)
144
- event.done!
145
- end
146
- end
147
- sleep interval
148
- end
145
+ Reminder.connect(uri)
146
+ callback = proc{|event| bot.broadcast event.to_s; event.done!}
147
+ reminder = EventWatcher.new(:interval=>60, :callback=>callback)
148
+ @watcher = Thread.new { reminder.start }
149
+ end
150
+
151
+ def list
152
+ events = Reminder::Event.future
153
+ if events.size == 0
154
+ return "no reminders"
155
+ else
156
+ lead = "#{events.size} reminder(s)"
157
+ body = events.map(&:to_s)[0,5]
158
+ return ([lead] + body).join("\n")
149
159
  end
150
160
  end
151
161
 
152
162
  def reply(text)
153
- start_reminder
154
-
155
163
  # strip noise
156
164
  text = text.sub(/^<.*?>/,'').strip
157
-
158
- case text
159
- when %r{^\d{4}.?\d{1,2}.?\d{1,2}}
160
- event = Reminder.register(text)
161
- text = "Remind you again at %s" % event.alert_at.strftime("%Y-%m-%d %H:%M")
162
- return text
163
- end
164
- return nil
165
+
166
+ event = Reminder.parse!(text)
167
+ Reminder.register(event)
168
+ text = "Remind you again at %s" % event.alert_at.strftime("%Y-%m-%d %H:%M")
169
+ return text
165
170
 
166
171
  rescue Reminder::EventNotFound
167
172
  return nil
168
173
 
169
174
  rescue Reminder::StartNotFound => e
170
- return "Reminder cannot detect start: #{e.event.st}"
175
+ # return "Reminder cannot detect start: #{e.event.st}"
176
+ return nil
171
177
 
172
178
  rescue Reminder::EventHasDone => e
173
179
  puts "Reminder ignores past event: #{e.event.st}"
174
180
  return nil
175
181
  end
176
-
177
- private
178
- def start_reminder(&callback)
179
- bot = self.bot
180
- callback ||= proc{|event| bot.broadcast event.to_s}
181
- @event_watcher_thread ||=
182
- (connect
183
- reminder = EventWatcher.new(:interval=>60, :callback=>callback)
184
- Thread.new { reminder.start })
185
- end
186
-
187
- def reminder_db_path
188
- Ircbot.root + "db" + "reminder-#{config.nick}.db"
189
- end
190
-
191
- def connect
192
- @connect ||= Reminder.connect(reminder_db_path)
193
- end
194
- end
195
-
196
-
197
- ######################################################################
198
- ### Spec in file:
199
- ### ruby plugins/reminder.rb
200
-
201
- if $0 == __FILE__
202
-
203
- def spec(src, buffer, &block)
204
- buffer = "require '#{Pathname(src).expand_path}'\n" + buffer
205
- tmp = Tempfile.new("dynamic-spec")
206
- tmp.print(buffer)
207
- tmp.close
208
- block.call(tmp)
209
- ensure
210
- tmp.close(true)
211
- end
212
-
213
- spec($0, DATA.read{}) do |tmp|
214
- system("rspec -cfs #{tmp.path}")
215
- end
216
182
  end
217
183
 
218
- __END__
219
-
220
- require 'rspec'
221
- require 'ostruct'
222
-
223
- module RSpec
224
- module Core
225
- module SharedExampleGroup
226
- def parse(text, &block)
227
- describe "(#{text})" do
228
- subject {
229
- event = Reminder.parse(text)
230
- hash = {
231
- :st => (event.st.strftime("%Y-%m-%d %H:%M:%S") rescue nil),
232
- :en => (event.en.strftime("%Y-%m-%d %H:%M:%S") rescue nil),
233
- :title => event.title.to_s,
234
- :desc => event.title.to_s,
235
- :allday => event.allday,
236
- }
237
- OpenStruct.new(hash)
238
- }
239
- instance_eval(&block)
240
- end
241
- end
242
- end
243
- end
244
- end
245
-
246
- describe "Reminder#parse" do
247
-
248
- parse '' do
249
- its(:st) { should == nil }
250
- end
251
-
252
- parse '2010-01-04 CX' do
253
- its(:st) { should == "2010-01-04 00:00:00" }
254
- its(:en) { should == nil }
255
- its(:title) { should == "CX" }
256
- its(:allday) { should == true }
257
- end
258
-
259
- parse '2010-01-04 13:30 CX' do
260
- its(:st) { should == "2010-01-04 13:30:00" }
261
- its(:en) { should == nil }
262
- its(:title) { should == "CX" }
263
- its(:allday) { should == false }
264
- end
265
-
266
- parse '2010-01-04 13:30-14:55 CX' do
267
- its(:st) { should == "2010-01-04 13:30:00" }
268
- its(:en) { should == "2010-01-04 14:55:00" }
269
- its(:title) { should == "CX" }
270
- its(:allday) { should == false }
271
- end
272
-
273
- parse '2010-01-18 27:15-27:45 TX' do
274
- its(:st) { should == "2010-01-19 03:15:00" }
275
- its(:en) { should == "2010-01-19 03:45:00" }
276
- its(:title) { should == "TX" }
277
- its(:allday) { should == false }
278
- end
279
- end