whistle 0.1

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 (53) hide show
  1. data/History.txt +3 -0
  2. data/README.txt +38 -0
  3. data/bin/whistle +90 -0
  4. data/lib/config.rb +19 -0
  5. data/lib/phash.rb +16 -0
  6. data/lib/relay.rb +24 -0
  7. data/lib/resource.rb +113 -0
  8. data/lib/ssl_patch.rb +15 -0
  9. data/lib/switchbox.rb +54 -0
  10. data/lib/time_ext.rb +30 -0
  11. data/lib/version.rb +3 -0
  12. data/sample/config.yml +12 -0
  13. data/vendor/rscm-0.5.1-patched-stripped/README +218 -0
  14. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm.rb +14 -0
  15. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/abstract_log_parser.rb +35 -0
  16. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/base.rb +289 -0
  17. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/command_line.rb +146 -0
  18. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/difftool.rb +44 -0
  19. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/line_editor.rb +46 -0
  20. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/mockit.rb +157 -0
  21. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/parser.rb +39 -0
  22. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/path_converter.rb +60 -0
  23. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/platform.rb +26 -0
  24. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision.rb +103 -0
  25. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_file.rb +85 -0
  26. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revision_poller.rb +93 -0
  27. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/revisions.rb +79 -0
  28. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/clearcase.rb +182 -0
  29. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs.rb +374 -0
  30. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/cvs_log_parser.rb +154 -0
  31. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs.rb +120 -0
  32. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/darcs_log_parser.rb +65 -0
  33. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone.rb +338 -0
  34. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/monotone_log_parser.rb +109 -0
  35. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/mooky.rb +6 -0
  36. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/perforce.rb +216 -0
  37. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/star_team.rb +104 -0
  38. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion.rb +397 -0
  39. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/scm/subversion_log_parser.rb +165 -0
  40. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/tempdir.rb +17 -0
  41. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/time_ext.rb +11 -0
  42. data/vendor/rscm-0.5.1-patched-stripped/lib/rscm/version.rb +13 -0
  43. data/vendor/ruby-feedparser-0.5-stripped/README +14 -0
  44. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser.rb +28 -0
  45. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/feedparser.rb +300 -0
  46. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/filesizes.rb +12 -0
  47. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html-output.rb +126 -0
  48. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/html2text-parser.rb +409 -0
  49. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/rexml_patch.rb +28 -0
  50. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/sgml-parser.rb +332 -0
  51. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/text-output.rb +83 -0
  52. data/vendor/ruby-feedparser-0.5-stripped/lib/feedparser/textconverters.rb +120 -0
  53. metadata +132 -0
@@ -0,0 +1,28 @@
1
+ require 'feedparser/textconverters'
2
+
3
+ # Patch for REXML
4
+ # Very ugly patch to make REXML error-proof.
5
+ # The problem is REXML uses IConv, which isn't error-proof at all.
6
+ # With those changes, it uses unpack/pack with some error handling
7
+ module REXML
8
+ module Encoding
9
+ def decode(str)
10
+ return str.toUTF8(@encoding)
11
+ end
12
+
13
+ def encode(str)
14
+ return str
15
+ end
16
+
17
+ def encoding=(enc)
18
+ return if defined? @encoding and enc == @encoding
19
+ @encoding = enc || 'utf-8'
20
+ end
21
+ end
22
+
23
+ class Element
24
+ def children
25
+ @children
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,332 @@
1
+ # A parser for SGML, using the derived class as static DTD.
2
+ # from http://raa.ruby-lang.org/project/html-parser
3
+ module FeedParser
4
+ class SGMLParser
5
+ # Regular expressions used for parsing:
6
+ Interesting = /[&<]/
7
+ Incomplete = Regexp.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|' +
8
+ '<([a-zA-Z][^<>]*|/([a-zA-Z][^<>]*)?|' +
9
+ '![^<>]*)?')
10
+
11
+ Entityref = /&([a-zA-Z][-.a-zA-Z0-9]*);/
12
+ Charref = /&#([0-9]+);/
13
+
14
+ Starttagopen = /<[>a-zA-Z]/
15
+ Endtagopen = /<\/[<>a-zA-Z]/
16
+ Endbracket = /[<>]/
17
+ Special = /<![^<>]*>/
18
+ Commentopen = /<!--/
19
+ Commentclose = /--[ \t\n]*>/
20
+ Tagfind = /[a-zA-Z][a-zA-Z0-9.-]*/
21
+ Attrfind = Regexp.compile('[\s,]*([a-zA-Z_][a-zA-Z_0-9.-]*)' +
22
+ '(\s*=\s*' +
23
+ "('[^']*'" +
24
+ '|"[^"]*"' +
25
+ '|[-~a-zA-Z0-9,./:+*%?!()_#=]*))?')
26
+
27
+ Entitydefs =
28
+ {'lt'=>'<', 'gt'=>'>', 'amp'=>'&', 'quot'=>'"', 'apos'=>'\''}
29
+
30
+ def initialize(verbose=false)
31
+ @verbose = verbose
32
+ reset
33
+ end
34
+
35
+ def reset
36
+ @rawdata = ''
37
+ @stack = []
38
+ @lasttag = '???'
39
+ @nomoretags = false
40
+ @literal = false
41
+ end
42
+
43
+ def has_context(gi)
44
+ @stack.include? gi
45
+ end
46
+
47
+ def setnomoretags
48
+ @nomoretags = true
49
+ @literal = true
50
+ end
51
+
52
+ def setliteral(*args)
53
+ @literal = true
54
+ end
55
+
56
+ def feed(data)
57
+ @rawdata << data
58
+ goahead(false)
59
+ end
60
+
61
+ def close
62
+ goahead(true)
63
+ end
64
+
65
+ def goahead(_end)
66
+ rawdata = @rawdata
67
+ i = 0
68
+ n = rawdata.length
69
+ while i < n
70
+ if @nomoretags
71
+ handle_data(rawdata[i..(n-1)])
72
+ i = n
73
+ break
74
+ end
75
+ j = rawdata.index(Interesting, i)
76
+ j = n unless j
77
+ if i < j
78
+ handle_data(rawdata[i..(j-1)])
79
+ end
80
+ i = j
81
+ break if (i == n)
82
+ if rawdata[i] == ?< #
83
+ if rawdata.index(Starttagopen, i) == i
84
+ if @literal
85
+ handle_data(rawdata[i, 1])
86
+ i += 1
87
+ next
88
+ end
89
+ k = parse_starttag(i)
90
+ break unless k
91
+ i = k
92
+ next
93
+ end
94
+ if rawdata.index(Endtagopen, i) == i
95
+ k = parse_endtag(i)
96
+ break unless k
97
+ i = k
98
+ @literal = false
99
+ next
100
+ end
101
+ if rawdata.index(Commentopen, i) == i
102
+ if @literal
103
+ handle_data(rawdata[i,1])
104
+ i += 1
105
+ next
106
+ end
107
+ k = parse_comment(i)
108
+ break unless k
109
+ i += k
110
+ next
111
+ end
112
+ if rawdata.index(Special, i) == i
113
+ if @literal
114
+ handle_data(rawdata[i, 1])
115
+ i += 1
116
+ next
117
+ end
118
+ k = parse_special(i)
119
+ break unless k
120
+ i += k
121
+ next
122
+ end
123
+ elsif rawdata[i] == ?& #
124
+ if rawdata.index(Charref, i) == i
125
+ i += $&.length
126
+ handle_charref($1)
127
+ i -= 1 unless rawdata[i-1] == ?;
128
+ next
129
+ end
130
+ if rawdata.index(Entityref, i) == i
131
+ i += $&.length
132
+ handle_entityref($1)
133
+ i -= 1 unless rawdata[i-1] == ?;
134
+ next
135
+ end
136
+ else
137
+ raise RuntimeError, 'neither < nor & ??'
138
+ end
139
+ # We get here only if incomplete matches but
140
+ # nothing else
141
+ match = rawdata.index(Incomplete, i)
142
+ unless match == i
143
+ handle_data(rawdata[i, 1])
144
+ i += 1
145
+ next
146
+ end
147
+ j = match + $&.length
148
+ break if j == n # Really incomplete
149
+ handle_data(rawdata[i..(j-1)])
150
+ i = j
151
+ end
152
+ # end while
153
+ if _end and i < n
154
+ handle_data(@rawdata[i..(n-1)])
155
+ i = n
156
+ end
157
+ @rawdata = rawdata[i..-1]
158
+ end
159
+
160
+ def parse_comment(i)
161
+ rawdata = @rawdata
162
+ if rawdata[i, 4] != '<!--'
163
+ raise RuntimeError, 'unexpected call to handle_comment'
164
+ end
165
+ match = rawdata.index(Commentclose, i)
166
+ return nil unless match
167
+ matched_length = $&.length
168
+ j = match
169
+ handle_comment(rawdata[i+4..(j-1)])
170
+ j = match + matched_length
171
+ return j-i
172
+ end
173
+
174
+ def parse_starttag(i)
175
+ rawdata = @rawdata
176
+ j = rawdata.index(Endbracket, i + 1)
177
+ return nil unless j
178
+ attrs = []
179
+ if rawdata[i+1] == ?> #
180
+ # SGML shorthand: <> == <last open tag seen>
181
+ k = j
182
+ tag = @lasttag
183
+ else
184
+ match = rawdata.index(Tagfind, i + 1)
185
+ unless match
186
+ raise RuntimeError, 'unexpected call to parse_starttag'
187
+ end
188
+ k = i + 1 + ($&.length)
189
+ tag = $&.downcase
190
+ @lasttag = tag
191
+ end
192
+ while k < j
193
+ break unless rawdata.index(Attrfind, k)
194
+ matched_length = $&.length
195
+ attrname, rest, attrvalue = $1, $2, $3
196
+ if not rest
197
+ attrvalue = '' # was: = attrname
198
+ elsif (attrvalue[0] == ?' && attrvalue[-1] == ?') or
199
+ (attrvalue[0] == ?" && attrvalue[-1,1] == ?")
200
+ attrvalue = attrvalue[1..-2]
201
+ end
202
+ attrs << [attrname.downcase, attrvalue]
203
+ k += matched_length
204
+ end
205
+ if rawdata[j] == ?> #
206
+ j += 1
207
+ end
208
+ finish_starttag(tag, attrs)
209
+ return j
210
+ end
211
+
212
+ def parse_endtag(i)
213
+ rawdata = @rawdata
214
+ j = rawdata.index(Endbracket, i + 1)
215
+ return nil unless j
216
+ tag = (rawdata[i+2..j-1].strip).downcase
217
+ if rawdata[j] == ?> #
218
+ j += 1
219
+ end
220
+ finish_endtag(tag)
221
+ return j
222
+ end
223
+
224
+ def finish_starttag(tag, attrs)
225
+ method = 'start_' + tag
226
+ if self.respond_to?(method)
227
+ @stack << tag
228
+ handle_starttag(tag, method, attrs)
229
+ return 1
230
+ else
231
+ method = 'do_' + tag
232
+ if self.respond_to?(method)
233
+ handle_starttag(tag, method, attrs)
234
+ return 0
235
+ else
236
+ unknown_starttag(tag, attrs)
237
+ return -1
238
+ end
239
+ end
240
+ end
241
+
242
+ def finish_endtag(tag)
243
+ if tag == ''
244
+ found = @stack.length - 1
245
+ if found < 0
246
+ unknown_endtag(tag)
247
+ return
248
+ end
249
+ else
250
+ unless @stack.include? tag
251
+ method = 'end_' + tag
252
+ unless self.respond_to?(method)
253
+ unknown_endtag(tag)
254
+ end
255
+ return
256
+ end
257
+ found = @stack.index(tag) #or @stack.length
258
+ end
259
+ while @stack.length > found
260
+ tag = @stack[-1]
261
+ method = 'end_' + tag
262
+ if respond_to?(method)
263
+ handle_endtag(tag, method)
264
+ else
265
+ unknown_endtag(tag)
266
+ end
267
+ @stack.pop
268
+ end
269
+ end
270
+
271
+ def parse_special(i)
272
+ rawdata = @rawdata
273
+ match = rawdata.index(Endbracket, i+1)
274
+ return nil unless match
275
+ matched_length = $&.length
276
+ handle_special(rawdata[i+1..(match-1)])
277
+ return match - i + matched_length
278
+ end
279
+
280
+ def handle_starttag(tag, method, attrs)
281
+ self.send(method, attrs)
282
+ end
283
+
284
+ def handle_endtag(tag, method)
285
+ self.send(method)
286
+ end
287
+
288
+ def report_unbalanced(tag)
289
+ if @verbose
290
+ print '*** Unbalanced </' + tag + '>', "\n"
291
+ print '*** Stack:', self.stack, "\n"
292
+ end
293
+ end
294
+
295
+ def handle_charref(name)
296
+ n = name.to_i
297
+ if !(0 <= n && n <= 255)
298
+ unknown_charref(name)
299
+ return
300
+ end
301
+ handle_data(n.chr)
302
+ end
303
+
304
+ def handle_entityref(name)
305
+ table = Entitydefs
306
+ if table.include?(name)
307
+ handle_data(table[name])
308
+ else
309
+ unknown_entityref(name)
310
+ return
311
+ end
312
+ end
313
+
314
+ def handle_data(data)
315
+ end
316
+
317
+ def handle_comment(data)
318
+ end
319
+
320
+ def handle_special(data)
321
+ end
322
+
323
+ def unknown_starttag(tag, attrs)
324
+ end
325
+ def unknown_endtag(tag)
326
+ end
327
+ def unknown_charref(ref)
328
+ end
329
+ def unknown_entityref(ref)
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,83 @@
1
+ require 'feedparser'
2
+ require 'feedparser/html2text-parser'
3
+ require 'feedparser/filesizes'
4
+
5
+ class String
6
+ # Convert an HTML text to plain text
7
+ def html2text
8
+ text = self.clone
9
+ # parse HTML
10
+ p = FeedParser::HTML2TextParser::new(true)
11
+ p.feed(text)
12
+ p.close
13
+ text = p.savedata
14
+ # remove leading and trailing whilespace
15
+ text.gsub!(/\A\s*/m, '')
16
+ text.gsub!(/\s*\Z/m, '')
17
+ # remove whitespace around \n
18
+ text.gsub!(/ *\n/m, "\n")
19
+ text.gsub!(/\n */m, "\n")
20
+ # and duplicates \n
21
+ text.gsub!(/\n\n+/m, "\n\n")
22
+ # and remove duplicated whitespace
23
+ text.gsub!(/[ \t]+/, ' ')
24
+ text
25
+ end
26
+ end
27
+
28
+ module FeedParser
29
+ class Feed
30
+ def to_text(localtime = true)
31
+ s = ''
32
+ s += "Type: #{@type}\n"
33
+ s += "Encoding: #{@encoding}\n"
34
+ s += "Title: #{@title}\n"
35
+ s += "Link: #{@link}\n"
36
+ if @description
37
+ s += "Description: #{@description.html2text}\n"
38
+ else
39
+ s += "Description:\n"
40
+ end
41
+ s += "Creator: #{@creator}\n"
42
+ s += "\n"
43
+ @items.each do |i|
44
+ s += '*' * 40 + "\n"
45
+ s += i.to_text(localtime)
46
+ end
47
+ s
48
+ end
49
+ end
50
+
51
+ class FeedItem
52
+ def to_text(localtime = true)
53
+ s = ""
54
+ s += "Feed: "
55
+ s += @feed.title + ' ' if @feed.title
56
+ s += "<#{@feed.link}>" if @feed.link
57
+ s += "\n"
58
+ s += "Item: "
59
+ s += @title + ' ' if @title
60
+ s += "<#{@link}>" if @link
61
+ s += "\n"
62
+ if @date
63
+ if localtime
64
+ s += "\nDate: #{@date.to_s}"
65
+ else
66
+ s += "\nDate: #{@date.getutc.to_s}"
67
+ end
68
+ end
69
+ s += "\nAuthor: #{@creator}" if @creator
70
+ s += "\nSubject: #{@subject}" if @subject
71
+ s += "\nCategory: #{@category}" if @category
72
+ s += "\n\n"
73
+ s += "#{@content.html2text}\n" if @content
74
+ if @enclosures and @enclosures.length > 0
75
+ s += "Files:\n"
76
+ @enclosures.each do |e|
77
+ s += " #{e[0]} (#{e[1].to_i.to_human_readable}, #{e[2]})\n"
78
+ end
79
+ end
80
+ s
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,120 @@
1
+ # for URI::regexp
2
+ require 'uri'
3
+ require 'feedparser/html2text-parser'
4
+
5
+ # This class provides various converters
6
+ class String
7
+ # is this text HTML ? search for tags. used by String#text2html
8
+ def html?
9
+ return (self =~ /<p>/i) || (self =~ /<\/p>/i) || (self =~ /<br>/i) || (self =~ /<br\s*(\/)?\s*>/i) || (self =~ /<\/a>/i) || (self =~ /<img.*>/i)
10
+ end
11
+
12
+ # returns true if the text contains escaped HTML (with HTML entities). used by String#text2html
13
+ def escaped_html?
14
+ return (self =~ /&lt;img src=/i) || (self =~ /&lt;a href=/i) || (self =~ /&lt;br(\/| \/|)&gt;/i) || (self =~ /&lt;p&gt;/i)
15
+ end
16
+
17
+ def escape_html
18
+ r = self.gsub('&', '&amp;')
19
+ r = r.gsub('<', '&lt;')
20
+ r = r.gsub('>', '&gt;')
21
+ r
22
+ end
23
+
24
+ MY_ENTITIES = {}
25
+ FeedParser::HTML2TextParser::entities.each do |k, v|
26
+ MY_ENTITIES["&#{k};"] = [v].pack('U*')
27
+ MY_ENTITIES["&##{v};"] = [v].pack('U*')
28
+ end
29
+
30
+ # un-escape HTML in the text. used by String#text2html
31
+ def unescape_html
32
+ r = self
33
+ MY_ENTITIES.each do |k, v|
34
+ r = r.gsub(k, v)
35
+ end
36
+ r
37
+ end
38
+
39
+ # convert text to HTML
40
+ def text2html(feed)
41
+ text = self.clone
42
+ realhtml = text.html?
43
+ eschtml = text.escaped_html?
44
+ # fix for RSS feeds with both real and escaped html (crazy!):
45
+ # we take the first one
46
+ if (realhtml && eschtml)
47
+ if (realhtml < eschtml)
48
+ eschtml = nil
49
+ else
50
+ realhtml = nil
51
+ end
52
+ end
53
+ if realhtml
54
+ # do nothing
55
+ elsif eschtml
56
+ text = text.unescape_html
57
+ else
58
+ # paragraphs
59
+ text.gsub!(/\A\s*(.*)\Z/m, '<p>\1</p>')
60
+ text.gsub!(/\s*\n(\s*\n)+\s*/, "</p>\n<p>")
61
+ # uris
62
+ text.gsub!(/(#{URI::regexp(['http','ftp','https'])})/,
63
+ '<a href="\1">\1</a>')
64
+ end
65
+ # Handle broken hrefs in <a> and <img>
66
+ if feed and feed.link
67
+ text.gsub!(/(\s(src|href)=['"])([^'"]*)(['"])/) do |m|
68
+ begin
69
+ first, url, last = $1, $3, $4
70
+ if (url =~ /^\s*\w+:\/\//) or (url =~ /^\s*\w+:\w/)
71
+ m
72
+ elsif url =~ /^\//
73
+ (first + feed.link.split(/\//)[0..2].join('/') + url + last)
74
+ else
75
+ t = feed.link.split(/\//)
76
+ if t.length == 3 # http://toto with no trailing /
77
+ (first + feed.link + '/' + url + last)
78
+ else
79
+ if feed.link =~ /\/$/
80
+ (first + feed.link + url + last)
81
+ else
82
+ (first + t[0...-1].join('/') + '/' + url + last)
83
+ end
84
+ end
85
+ end
86
+ rescue
87
+ m
88
+ end
89
+ end
90
+ end
91
+ text
92
+ end
93
+
94
+ # Remove white space around the text
95
+ def rmWhiteSpace!
96
+ return self.gsub!(/\A\s*/m, '').gsub!(/\s*\Z/m,'')
97
+ end
98
+
99
+ # Convert a text in inputenc to a text in UTF8
100
+ # must take care of wrong input locales
101
+ def toUTF8(inputenc)
102
+ if inputenc.downcase != 'utf-8'
103
+ # it is said it is not UTF-8. Ensure it is REALLY not UTF-8
104
+ begin
105
+ if self.unpack('U*').pack('U*') == self
106
+ return self
107
+ end
108
+ rescue
109
+ # do nothing
110
+ end
111
+ begin
112
+ return self.unpack('C*').pack('U*')
113
+ rescue
114
+ return self #failsafe solution. but a dirty one :-)
115
+ end
116
+ else
117
+ return self
118
+ end
119
+ end
120
+ end