ircbot 0.1.5 → 0.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 (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