curl-multi 0.1

Sign up to get free protection for your applications and to get access to all the features.
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)