curl-multi 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.
data/Manifest.txt ADDED
@@ -0,0 +1,25 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/curl-multi.rb
9
+ lib/curl-multi/version.rb
10
+ lib/uri-extensions.rb
11
+ log/debug.log
12
+ script/destroy
13
+ script/generate
14
+ script/txt2html
15
+ setup.rb
16
+ tasks/deployment.rake
17
+ tasks/environment.rake
18
+ tasks/website.rake
19
+ test/test_curl-multi.rb
20
+ test/test_helper.rb
21
+ website/index.html
22
+ website/index.txt
23
+ website/javascripts/rounded_corners_lite.inc.js
24
+ website/stylesheets/screen.css
25
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,7 @@
1
+ curl-multi - Ruby bindings for the libcurl multi interface
2
+ Copyright (C) 2007 Philotic, Inc.
3
+
4
+ See http://curl-multi.rubyforge.net/ for instructions.
5
+
6
+ If you enjoy curl-multi, check out http://xph.us/software/ for other software
7
+ you might find useful.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'curl-multi/version'
2
+
3
+ AUTHOR = ['Kristjan Petursson', 'Keith Rarick']
4
+ EMAIL = "kr@essembly.com"
5
+ DESCRIPTION = 'Ruby bindings for the libcurl multi interface'
6
+ GEM_NAME = 'curl-multi' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'curl-multi' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Curl::Multi::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'curl-multi documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ p.extra_deps = [['RubyInline', '>= 3.6.2'], ['curb', '>= 0.1.2']]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'curl-multi'
@@ -0,0 +1,23 @@
1
+ # curl-multi - Ruby bindings for the libcurl multi interface
2
+ # Copyright (C) 2007 Philotic, Inc.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ module Curl #:nodoc:
18
+ class Multi #:nodoc:
19
+ module VERSION #:nodoc:
20
+ STRING = '0.1'
21
+ end
22
+ end
23
+ end
data/lib/curl-multi.rb ADDED
@@ -0,0 +1,372 @@
1
+ # curl-multi - Ruby bindings for the libcurl multi interface
2
+ # Copyright (C) 2007 Philotic, Inc.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ $:.unshift File.dirname(__FILE__)
18
+
19
+ require 'rubygems'
20
+ require 'inline'
21
+ require 'uri-extensions'
22
+ require 'curb'
23
+
24
+ module Curl
25
+ class Error < RuntimeError
26
+ def initialize(code)
27
+ super("Curl error #{code}")
28
+ end
29
+ end
30
+
31
+ class HTTPError < RuntimeError
32
+ def initialize(status)
33
+ super("Curl HTTP error #{status}")
34
+ end
35
+ end
36
+
37
+ class MultiError < RuntimeError
38
+ attr_reader :errors
39
+ def initialize(errors)
40
+ @errors = errors
41
+ super("Multiple errors: #{errors}")
42
+ end
43
+ end
44
+
45
+ # An instance of this class collects the response for each curl easy handle.
46
+ class Req
47
+ CURLE_OK = 0
48
+
49
+ attr_reader :url, :body
50
+
51
+ def done(code, status)
52
+ @done = true
53
+ if code != CURLE_OK
54
+ @error = Error.new(code)
55
+ elsif status != 200
56
+ @error = HTTPError.new(status)
57
+ end
58
+ end
59
+
60
+ def done?() @done end
61
+
62
+ def failed?() !!@error end
63
+
64
+ def do_success() @success.call(body) end
65
+
66
+ def do_failure() @failure.call(@error) end
67
+
68
+ def inspect() "{Curl::Req #{url}}" end
69
+ alias_method :to_s, :inspect
70
+
71
+ def initialize(url, success, failure)
72
+ @url = url
73
+ @success = success
74
+ @failure = failure
75
+ @body = ''
76
+ @done = false
77
+ @error = nil
78
+ end
79
+
80
+ def add_chunk(chunk)
81
+ @body = body + chunk
82
+ end
83
+ end
84
+
85
+ class Multi
86
+ def size() @handles.size end
87
+
88
+ def inspect() '{Curl::Multi' + @handles.map{|h| ' '+h.url}.join + '}' end
89
+ alias_method :to_s, :inspect
90
+
91
+ def get(url, success, failure=lambda{})
92
+ add(url, nil, success, failure)
93
+ end
94
+
95
+ def post(url, params, success, failure=lambda{})
96
+ add(url, URI.escape_params(params), success, failure)
97
+ end
98
+
99
+ def select(rfds, wfds)
100
+ loop do
101
+ ready_rfds, ready_wfds = c_select(rfds.map{|s|s.fileno},
102
+ wfds.map{|s|s.fileno})
103
+ work() # Curl may or may not have work to do, but we can't tell.
104
+ return ready_rfds, ready_wfds if ready_rfds.any? or ready_wfds.any?
105
+ return [], [] if rfds == [] and wfds == []
106
+ end
107
+ end
108
+
109
+ # Do as much work as possible without blocking on the network. That is,
110
+ # read as much data as is ready and write as much data as we have buffer
111
+ # space for. If any complete responses arrive, call their handlers.
112
+ def work
113
+ perform()
114
+
115
+ done, @handles = @handles.partition{|h| h.done?}
116
+ failed, done = done.partition{|h| h.failed?}
117
+
118
+ errors = []
119
+ errors += post_process(done) {|x| x.do_success}
120
+ errors += post_process(failed) {|x| x.do_failure}
121
+ raise errors[0] if errors.size == 1
122
+ raise MultiError.new(errors) if errors.size > 1
123
+
124
+ :ok
125
+ end
126
+
127
+ def cleanup
128
+ @handles = []
129
+ :ok
130
+ end
131
+
132
+ def initialize
133
+ @handles = []
134
+ end
135
+
136
+ # Add a URL to the queue of items to fetch
137
+ def add(url, body, success, failure=lambda{})
138
+ while (h = add_to_curl(Req.new(url, success, failure), url, body)) == nil
139
+ select([], [])
140
+ end
141
+ @handles << h
142
+ work()
143
+ :ok
144
+ end
145
+
146
+ def post_process(handles)
147
+ errors = []
148
+ handles.each do |h|
149
+ begin
150
+ yield(h) # This ought not to raise anything.
151
+ rescue => ex
152
+ errors << ex
153
+ end
154
+ end
155
+ return errors
156
+ end
157
+
158
+ inline do |builder|
159
+ builder.include('<errno.h>')
160
+ if File.exists?('/usr/include/curl/curl.h')
161
+ builder.include('"/usr/include/curl/curl.h"')
162
+ else
163
+ builder.include('"/usr/local/include/curl/curl.h"')
164
+ end
165
+
166
+ builder.prefix <<-end
167
+ #define GET_MULTI_HANDLE(name) \
168
+ CURLM *(name);\
169
+ Data_Get_Struct(self, CURLM, (name));\
170
+ if (!(name)) rb_raise(rb_eTypeError,\
171
+ "Expected initialized curl multi handle")
172
+
173
+ #define CHECK(e) do {\
174
+ if (!(e)) rb_raise(rb_eTypeError, "curl error");\
175
+ } while (0)
176
+
177
+ #define CHECKN(e) CHECK(!(e))
178
+
179
+ ID id_initialize, id_done, id_add_chunk, id_size;
180
+ end
181
+
182
+ builder.c_raw_singleton <<-end
183
+ void c_curl_multi_cleanup(void *x)
184
+ {
185
+ curl_multi_cleanup(x);
186
+ }
187
+ end
188
+
189
+ # Creates a new CurlMulti handle
190
+ builder.c_singleton <<-end
191
+ VALUE new() {
192
+ CURLcode r;
193
+ VALUE inst;
194
+
195
+ /* can't think of a better place to put this */
196
+ id_initialize = rb_intern("initialize");
197
+ id_done = rb_intern("done");
198
+ id_add_chunk = rb_intern("add_chunk");
199
+ id_size = rb_intern("size");
200
+
201
+ /* must be called at least once before any other curl functions */
202
+ r = curl_global_init(CURL_GLOBAL_ALL);
203
+ CHECKN(r);
204
+
205
+ inst = Data_Wrap_Struct(self, 0, c_curl_multi_cleanup, curl_multi_init());
206
+ rb_funcall(inst, id_initialize, 0);
207
+ return inst;
208
+ }
209
+ end
210
+
211
+ # We tell CurlEasy handles to write to this function in the constructor.
212
+ # The self argument is a parameter we set up with CURLOPT_WRITEDATA that
213
+ # lets us pass whatever data we'd like into the write fn.
214
+ builder.c_raw_singleton <<-end
215
+ uint c_add_chunk(char *chunk, uint size, uint nmemb, VALUE rb_req) {
216
+ uint bytes = size * nmemb; /* Number of bytes of data */
217
+ if (bytes == 0) return 0;
218
+
219
+ /* This is (rubyInstance, methodId, numArgs, arg1, arg2...) */
220
+ rb_funcall(rb_req, id_add_chunk, 1, rb_str_new(chunk, bytes));
221
+ return bytes;
222
+ }
223
+ end
224
+
225
+ # Adds an easy handle to the multi handle's list
226
+ builder.c <<-end
227
+ VALUE add_to_curl(VALUE rb_req, VALUE url, VALUE body) {
228
+ CURLMcode r;
229
+ GET_MULTI_HANDLE(multi_handle);
230
+
231
+ /* We start getting errors if we have too many open connections at
232
+ once, so make a hard limit. */
233
+ if (FIX2INT(rb_funcall(self, id_size, 0)) > 500) return Qnil;
234
+
235
+ CURL *easy_handle = curl_easy_init();
236
+ char *c_url = StringValuePtr(url);
237
+
238
+ /* Pass it the URL */
239
+ curl_easy_setopt(easy_handle, CURLOPT_URL, c_url);
240
+
241
+ /* GET or POST? */
242
+ if (body != Qnil) {
243
+ char *c_body = StringValuePtr(body);
244
+ uint body_sz = RSTRING(body)->len;
245
+ curl_easy_setopt(easy_handle, CURLOPT_POST, 1);
246
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDS, c_body);
247
+ curl_easy_setopt(easy_handle, CURLOPT_POSTFIELDSIZE, body_sz);
248
+ }
249
+
250
+ /* Tell curl to use our callbacks */
251
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, c_add_chunk);
252
+
253
+ /* Make curl give us a ruby pointer in the callbacks */
254
+ curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, rb_req);
255
+ curl_easy_setopt(easy_handle, CURLOPT_PRIVATE, rb_req);
256
+
257
+ /* Add it to the multi handle */
258
+ r = curl_multi_add_handle(multi_handle, easy_handle);
259
+ CHECKN(r);
260
+
261
+ return rb_req;
262
+ }
263
+ end
264
+
265
+ # Wait until one of the given fds or one of curl's fds is ready.
266
+ # Return two arrays of fds that are ready.
267
+ builder.c <<-end
268
+ void c_select(VALUE rfda, VALUE wfda) {
269
+ int i, r, n = -1;
270
+ long timeout;
271
+ fd_set rfds, wfds, efds;
272
+ CURLMcode cr;
273
+ VALUE ready_rfda, ready_wfda;
274
+ struct timeval tv = {0, 0};
275
+ GET_MULTI_HANDLE(multi_handle);
276
+
277
+ FD_ZERO(&rfds);
278
+ FD_ZERO(&wfds);
279
+ FD_ZERO(&efds);
280
+
281
+ /* Put curl's fds into the sets. */
282
+ cr = curl_multi_fdset(multi_handle, &rfds, &wfds, &efds, &n);
283
+ CHECKN(cr);
284
+
285
+ /* Put the given fds into the sets. */
286
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
287
+ int fd = FIX2INT(RARRAY(rfda)->ptr[i]);
288
+ FD_SET(fd, &rfds);
289
+ n = n > fd ? n : fd;
290
+ }
291
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
292
+ int fd = FIX2INT(RARRAY(wfda)->ptr[i]);
293
+ FD_SET(fd, &wfds);
294
+ n = n > fd ? n : fd;
295
+ }
296
+
297
+ cr = curl_multi_timeout(multi_handle, &timeout);
298
+ CHECKN(cr);
299
+
300
+ tv.tv_sec = timeout / 1000;
301
+ tv.tv_usec = (timeout * 1000) % 1000000;
302
+
303
+ /* Wait */
304
+ r = select(n + 1, &rfds, &wfds, &efds, (timeout < 0) ? NULL : &tv);
305
+ if (r < 0) rb_raise(rb_eRuntimeError, "select(): %s", sys_errlist[errno]);
306
+
307
+ ready_rfda = rb_ary_new();
308
+ ready_wfda = rb_ary_new();
309
+
310
+ /* Collect the fds that are ready */
311
+ for (i = 0; i < RARRAY(rfda)->len; i++) {
312
+ VALUE fd = FIX2INT(RARRAY(rfda)->ptr[i]);
313
+ if (FD_ISSET(fd, &rfds)) rb_ary_push(ready_rfda, INT2FIX(fd));
314
+ }
315
+ for (i = 0; i < RARRAY(wfda)->len; i++) {
316
+ VALUE fd = FIX2INT(RARRAY(wfda)->ptr[i]);
317
+ if (FD_ISSET(fd, &wfds)) rb_ary_push(ready_wfda, INT2FIX(fd));
318
+ }
319
+
320
+ return rb_ary_new3(2, ready_rfda, ready_wfda);
321
+ }
322
+ end
323
+
324
+ # Basically just a wrapper for curl_multi_perform().
325
+ builder.c <<-end
326
+ void perform() {
327
+ CURLMsg *msg;
328
+ CURLcode er;
329
+ CURLMcode r;
330
+ int status;
331
+ int running;
332
+ GET_MULTI_HANDLE(multi_handle);
333
+
334
+ /* do some work */
335
+ do {
336
+ r = curl_multi_perform(multi_handle, &running);
337
+ } while (r == CURLM_CALL_MULTI_PERFORM);
338
+ CHECK(r == CURLM_OK);
339
+
340
+ /* check which ones are done and mark them as done */
341
+ while ((msg = curl_multi_info_read(multi_handle, &r))) {
342
+ VALUE rb_req;
343
+ CURL *easy_handle;
344
+
345
+ if (msg->msg != CURLMSG_DONE) continue;
346
+
347
+ /* Save out the easy handle, because the msg struct becomes invalid
348
+ * whene we call curl_easy_cleanup, curl_multi_remove_handle, or
349
+ * curl_easy_cleanup. */
350
+ easy_handle = msg->easy_handle;
351
+
352
+ r = curl_easy_getinfo(easy_handle, CURLINFO_PRIVATE, &rb_req);
353
+ CHECKN(r);
354
+
355
+ er = curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE,
356
+ &status);
357
+ CHECKN(er);
358
+
359
+ rb_funcall(rb_req, id_done, 2, INT2FIX(msg->data.result),
360
+ INT2FIX(status));
361
+
362
+ r = curl_multi_remove_handle(DATA_PTR(self), easy_handle);
363
+ CHECKN(r);
364
+
365
+ /* Free the handle */
366
+ curl_easy_cleanup(easy_handle);
367
+ }
368
+ }
369
+ end
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,34 @@
1
+ # curl-multi - Ruby bindings for the libcurl multi interface
2
+ # Copyright (C) 2007 Philotic, Inc.
3
+
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ require 'uri'
18
+
19
+ module URI
20
+ # Fully escape the web parameters
21
+ def self.fully_escape(uri)
22
+ self.escape(uri, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
23
+ end
24
+
25
+ def self.escaped_params_array(params)
26
+ params.map{|k,v| fully_escape(k.to_s) + '=' + fully_escape(v.to_s)}
27
+ end
28
+
29
+ # This returns a URL-encoded string suitable for use as a POST body or for
30
+ # pasting into a URL.
31
+ def self.escape_params(params)
32
+ escaped_params_array(params).join('&')
33
+ end
34
+ end
data/log/debug.log ADDED
File without changes
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/script/txt2html ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'newgem'
6
+ rescue LoadError
7
+ puts "\n\nGenerating the website requires the newgem RubyGem"
8
+ puts "Install: gem install newgem\n\n"
9
+ exit(1)
10
+ end
11
+ require 'redcloth'
12
+ require 'syntax/convertors/html'
13
+ require 'erb'
14
+ require File.dirname(__FILE__) + '/../lib/curl-multi/version.rb'
15
+
16
+ version = Curl::Multi::VERSION::STRING
17
+ download = 'http://rubyforge.org/projects/curl-multi'
18
+
19
+ class Fixnum
20
+ def ordinal
21
+ # teens
22
+ return 'th' if (10..19).include?(self % 100)
23
+ # others
24
+ case self % 10
25
+ when 1: return 'st'
26
+ when 2: return 'nd'
27
+ when 3: return 'rd'
28
+ else return 'th'
29
+ end
30
+ end
31
+ end
32
+
33
+ class Time
34
+ def pretty
35
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
36
+ end
37
+ end
38
+
39
+ def convert_syntax(syntax, source)
40
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
41
+ end
42
+
43
+ if ARGV.length >= 1
44
+ src, template = ARGV
45
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
46
+
47
+ else
48
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
49
+ exit!
50
+ end
51
+
52
+ template = ERB.new(File.open(template).read)
53
+
54
+ title = nil
55
+ body = nil
56
+ File.open(src) do |fsrc|
57
+ title_text = fsrc.readline
58
+ body_text = fsrc.read
59
+ syntax_items = []
60
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
61
+ ident = syntax_items.length
62
+ element, syntax, source = $1, $2, $3
63
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
64
+ "syntax-temp-#{ident}"
65
+ }
66
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
67
+ body = RedCloth.new(body_text).to_html
68
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
69
+ end
70
+ stat = File.stat(src)
71
+ created = stat.ctime
72
+ modified = stat.mtime
73
+
74
+ $stdout << template.result(binding)