Shazburg-webby 0.9.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.
- data/History.txt +176 -0
- data/Manifest.txt +171 -0
- data/README.txt +92 -0
- data/Rakefile +54 -0
- data/bin/webby +8 -0
- data/bin/webby-gen +8 -0
- data/examples/blog/Sitefile +7 -0
- data/examples/blog/tasks/blog.rake +72 -0
- data/examples/blog/templates/atom_feed.erb +40 -0
- data/examples/blog/templates/blog/month.erb +22 -0
- data/examples/blog/templates/blog/post.erb +16 -0
- data/examples/blog/templates/blog/year.erb +22 -0
- data/examples/presentation/Sitefile +10 -0
- data/examples/presentation/content/css/uv/twilight.css +137 -0
- data/examples/presentation/content/presentation/_sample_code.txt +10 -0
- data/examples/presentation/content/presentation/index.txt +63 -0
- data/examples/presentation/content/presentation/s5/blank.gif +0 -0
- data/examples/presentation/content/presentation/s5/bodybg.gif +0 -0
- data/examples/presentation/content/presentation/s5/framing.css +23 -0
- data/examples/presentation/content/presentation/s5/iepngfix.htc +42 -0
- data/examples/presentation/content/presentation/s5/opera.css +7 -0
- data/examples/presentation/content/presentation/s5/outline.css +15 -0
- data/examples/presentation/content/presentation/s5/pretty.css +86 -0
- data/examples/presentation/content/presentation/s5/print.css +1 -0
- data/examples/presentation/content/presentation/s5/s5-core.css +9 -0
- data/examples/presentation/content/presentation/s5/slides.css +3 -0
- data/examples/presentation/content/presentation/s5/slides.js +553 -0
- data/examples/presentation/layouts/presentation.txt +43 -0
- data/examples/presentation/templates/_code_partial.erb +13 -0
- data/examples/presentation/templates/presentation.erb +40 -0
- data/examples/tumblog/Sitefile +9 -0
- data/examples/tumblog/content/css/tumblog.css +308 -0
- data/examples/tumblog/content/images/tumblog/permalink.gif +0 -0
- data/examples/tumblog/content/images/tumblog/rss.gif +0 -0
- data/examples/tumblog/content/tumblog/200806/the-noble-chicken/index.txt +12 -0
- data/examples/tumblog/content/tumblog/200807/historical-perspectives-on-the-classic-chicken-joke/index.txt +12 -0
- data/examples/tumblog/content/tumblog/200807/mad-city-chickens/index.txt +10 -0
- data/examples/tumblog/content/tumblog/200807/the-wisdom-of-the-dutch/index.txt +11 -0
- data/examples/tumblog/content/tumblog/200807/up-a-tree/index.txt +13 -0
- data/examples/tumblog/content/tumblog/index.txt +37 -0
- data/examples/tumblog/content/tumblog/rss.txt +37 -0
- data/examples/tumblog/layouts/tumblog/default.txt +44 -0
- data/examples/tumblog/layouts/tumblog/post.txt +15 -0
- data/examples/tumblog/lib/tumblog_helper.rb +32 -0
- data/examples/tumblog/tasks/tumblog.rake +30 -0
- data/examples/tumblog/templates/atom_feed.erb +40 -0
- data/examples/tumblog/templates/tumblog/conversation.erb +12 -0
- data/examples/tumblog/templates/tumblog/link.erb +10 -0
- data/examples/tumblog/templates/tumblog/photo.erb +13 -0
- data/examples/tumblog/templates/tumblog/post.erb +12 -0
- data/examples/tumblog/templates/tumblog/quote.erb +11 -0
- data/examples/webby/Sitefile +19 -0
- data/examples/webby/content/css/background.gif +0 -0
- data/examples/webby/content/css/blueprint/print.css +76 -0
- data/examples/webby/content/css/blueprint/screen.css +696 -0
- data/examples/webby/content/css/coderay.css +96 -0
- data/examples/webby/content/css/site.css +184 -0
- data/examples/webby/content/css/uv/twilight.css +137 -0
- data/examples/webby/content/index.txt +37 -0
- data/examples/webby/content/manual/index.txt +430 -0
- data/examples/webby/content/reference/index.txt +202 -0
- data/examples/webby/content/release-notes/rel-0-9-0/index.txt +73 -0
- data/examples/webby/content/robots.txt +6 -0
- data/examples/webby/content/script/jquery.corner.js +152 -0
- data/examples/webby/content/script/jquery.js +31 -0
- data/examples/webby/content/sitemap.txt +31 -0
- data/examples/webby/content/tips_and_tricks/index.txt +96 -0
- data/examples/webby/content/tutorial/index.txt +131 -0
- data/examples/webby/layouts/default.txt +55 -0
- data/examples/webby/templates/page.erb +10 -0
- data/examples/website/Sitefile +7 -0
- data/examples/website/content/css/blueprint/License.txt +21 -0
- data/examples/website/content/css/blueprint/Readme.txt +100 -0
- data/examples/website/content/css/blueprint/compressed/print.css +76 -0
- data/examples/website/content/css/blueprint/compressed/screen.css +696 -0
- data/examples/website/content/css/blueprint/lib/forms.css +45 -0
- data/examples/website/content/css/blueprint/lib/grid.css +193 -0
- data/examples/website/content/css/blueprint/lib/grid.png +0 -0
- data/examples/website/content/css/blueprint/lib/ie.css +30 -0
- data/examples/website/content/css/blueprint/lib/reset.css +39 -0
- data/examples/website/content/css/blueprint/lib/typography.css +116 -0
- data/examples/website/content/css/blueprint/plugins/buttons/Readme +31 -0
- data/examples/website/content/css/blueprint/plugins/buttons/buttons.css +97 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/cross.png +0 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/key.png +0 -0
- data/examples/website/content/css/blueprint/plugins/buttons/icons/tick.png +0 -0
- data/examples/website/content/css/blueprint/plugins/css-classes/Readme +14 -0
- data/examples/website/content/css/blueprint/plugins/css-classes/css-classes.css +24 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/Readme +22 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type-compressed.css +5 -0
- data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type.css +74 -0
- data/examples/website/content/css/blueprint/print.css +68 -0
- data/examples/website/content/css/blueprint/screen.css +22 -0
- data/examples/website/content/css/coderay.css +111 -0
- data/examples/website/content/css/site.css +67 -0
- data/examples/website/content/index.txt +19 -0
- data/examples/website/layouts/default.txt +58 -0
- data/examples/website/lib/breadcrumbs.rb +28 -0
- data/examples/website/templates/_partial.erb +10 -0
- data/examples/website/templates/page.erb +18 -0
- data/examples/website/templates/presentation.erb +40 -0
- data/lib/webby.rb +227 -0
- data/lib/webby/apps.rb +12 -0
- data/lib/webby/apps/generator.rb +283 -0
- data/lib/webby/apps/main.rb +221 -0
- data/lib/webby/auto_builder.rb +83 -0
- data/lib/webby/builder.rb +183 -0
- data/lib/webby/core_ext/enumerable.rb +11 -0
- data/lib/webby/core_ext/hash.rb +28 -0
- data/lib/webby/core_ext/kernel.rb +21 -0
- data/lib/webby/core_ext/string.rb +163 -0
- data/lib/webby/core_ext/time.rb +9 -0
- data/lib/webby/filters.rb +91 -0
- data/lib/webby/filters/basepath.rb +97 -0
- data/lib/webby/filters/erb.rb +9 -0
- data/lib/webby/filters/haml.rb +18 -0
- data/lib/webby/filters/markdown.rb +16 -0
- data/lib/webby/filters/outline.rb +308 -0
- data/lib/webby/filters/sass.rb +17 -0
- data/lib/webby/filters/slides.rb +56 -0
- data/lib/webby/filters/textile.rb +16 -0
- data/lib/webby/filters/tidy.rb +76 -0
- data/lib/webby/helpers.rb +30 -0
- data/lib/webby/helpers/capture_helper.rb +141 -0
- data/lib/webby/helpers/coderay_helper.rb +69 -0
- data/lib/webby/helpers/graphviz_helper.rb +136 -0
- data/lib/webby/helpers/tag_helper.rb +65 -0
- data/lib/webby/helpers/tex_img_helper.rb +133 -0
- data/lib/webby/helpers/ultraviolet_helper.rb +63 -0
- data/lib/webby/helpers/url_helper.rb +235 -0
- data/lib/webby/link_validator.rb +152 -0
- data/lib/webby/renderer.rb +379 -0
- data/lib/webby/resources.rb +96 -0
- data/lib/webby/resources/db.rb +251 -0
- data/lib/webby/resources/file.rb +221 -0
- data/lib/webby/resources/layout.rb +63 -0
- data/lib/webby/resources/page.rb +118 -0
- data/lib/webby/resources/partial.rb +79 -0
- data/lib/webby/resources/resource.rb +160 -0
- data/lib/webby/resources/static.rb +52 -0
- data/lib/webby/stelan/mktemp.rb +135 -0
- data/lib/webby/stelan/paginator.rb +150 -0
- data/lib/webby/stelan/spawner.rb +339 -0
- data/lib/webby/tasks/build.rake +27 -0
- data/lib/webby/tasks/create.rake +22 -0
- data/lib/webby/tasks/deploy.rake +22 -0
- data/lib/webby/tasks/growl.rake +15 -0
- data/lib/webby/tasks/heel.rake +28 -0
- data/lib/webby/tasks/validate.rake +19 -0
- data/spec/core_ext/hash_spec.rb +47 -0
- data/spec/core_ext/string_spec.rb +110 -0
- data/spec/core_ext/time_spec.rb +19 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/webby/apps/generator_spec.rb +111 -0
- data/spec/webby/apps/main_spec.rb +75 -0
- data/spec/webby/helpers/capture_helper_spec.rb +56 -0
- data/spec/webby/resources/file_spec.rb +104 -0
- data/spec/webby/resources_spec.rb +17 -0
- data/tasks/ann.rake +81 -0
- data/tasks/bones.rake +21 -0
- data/tasks/gem.rake +126 -0
- data/tasks/git.rake +41 -0
- data/tasks/manifest.rake +49 -0
- data/tasks/notes.rake +28 -0
- data/tasks/post_load.rake +39 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +57 -0
- data/tasks/setup.rb +268 -0
- data/tasks/spec.rake +55 -0
- data/tasks/website.rake +38 -0
- metadata +287 -0
@@ -0,0 +1,135 @@
|
|
1
|
+
# :stopdoc:
|
2
|
+
# Skeleton module for the 'mktemp' routine.
|
3
|
+
#
|
4
|
+
# Ideally, one would do this in their code to import the "mktemp" call
|
5
|
+
# directly into their current namespace:
|
6
|
+
#
|
7
|
+
# require 'mktemp'
|
8
|
+
# include MkTemp
|
9
|
+
# # do something with mktemp()
|
10
|
+
#
|
11
|
+
#
|
12
|
+
# It is recommended that you look at the documentation for the mktemp()
|
13
|
+
# call directly for specific usage.
|
14
|
+
#
|
15
|
+
#--
|
16
|
+
#
|
17
|
+
# The compilation of software known as mktemp.rb is distributed under the
|
18
|
+
# following terms:
|
19
|
+
# Copyright (C) 2005-2006 Erik Hollensbe. All rights reserved.
|
20
|
+
#
|
21
|
+
# Redistribution and use in source form, with or without
|
22
|
+
# modification, are permitted provided that the following conditions
|
23
|
+
# are met:
|
24
|
+
# 1. Redistributions of source code must retain the above copyright
|
25
|
+
# notice, this list of conditions and the following disclaimer.
|
26
|
+
#
|
27
|
+
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
28
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
29
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
30
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
31
|
+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
32
|
+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
33
|
+
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
34
|
+
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
35
|
+
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
36
|
+
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
37
|
+
# SUCH DAMAGE.
|
38
|
+
#
|
39
|
+
#++
|
40
|
+
|
41
|
+
module Webby
|
42
|
+
module MkTemp
|
43
|
+
VALID_TMPNAM_CHARS = (?a..?z).to_a + (?A..?Z).to_a
|
44
|
+
|
45
|
+
#
|
46
|
+
# This routine just generates a temporary file similar to the
|
47
|
+
# routines from 'mktemp'. A trailing series of 'X' characters will
|
48
|
+
# be transformed into a randomly-generated set of alphanumeric
|
49
|
+
# characters.
|
50
|
+
#
|
51
|
+
# This routine performs no file testing, at all. It is not suitable
|
52
|
+
# for anything beyond that.
|
53
|
+
#
|
54
|
+
|
55
|
+
def tmpnam(filename)
|
56
|
+
m = filename.match(/(X*)$/)
|
57
|
+
|
58
|
+
retnam = filename.dup
|
59
|
+
|
60
|
+
if m[1]
|
61
|
+
mask = ""
|
62
|
+
m[1].length.times { mask += VALID_TMPNAM_CHARS[rand(52)].chr }
|
63
|
+
retnam.sub!(/(X*)$/, mask)
|
64
|
+
end
|
65
|
+
|
66
|
+
return retnam
|
67
|
+
end
|
68
|
+
|
69
|
+
module_function :tmpnam
|
70
|
+
|
71
|
+
#
|
72
|
+
# This routine works similarly to mkstemp(3) in that it gets a new
|
73
|
+
# file, and returns a file handle for that file. The mask parameter
|
74
|
+
# determines whether or not to process the filename as a mask by
|
75
|
+
# calling the tmpnam() routine in this module. This routine will
|
76
|
+
# continue until it finds a valid filename, which may not do what
|
77
|
+
# you expect.
|
78
|
+
#
|
79
|
+
# While all attempts have been made to keep this as secure as
|
80
|
+
# possible, due to a few problems with Ruby's file handling code, we
|
81
|
+
# are required to allow a few concessions. If a 0-length file is
|
82
|
+
# created before we attempt to create ours, we have no choice but to
|
83
|
+
# accept it. Do not rely on this code for any expected level of
|
84
|
+
# security, even though we have taken all the measures we can to
|
85
|
+
# handle that situation.
|
86
|
+
#
|
87
|
+
|
88
|
+
def mktemp(filename, mask=true)
|
89
|
+
fh = nil
|
90
|
+
|
91
|
+
begin
|
92
|
+
loop do
|
93
|
+
fn = mask ? tmpnam(filename) : filename
|
94
|
+
|
95
|
+
if File.exist? fn
|
96
|
+
fail "Unable to create a temporary filename" unless mask
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
fh = File.new(fn, "a", 0600)
|
101
|
+
fh.seek(0, IO::SEEK_END)
|
102
|
+
break if fh.pos == 0
|
103
|
+
|
104
|
+
fail "Unable to create a temporary filename" unless mask
|
105
|
+
fh.close
|
106
|
+
end
|
107
|
+
rescue Exception => e
|
108
|
+
# in the case that we hit a locked file...
|
109
|
+
fh.close if fh
|
110
|
+
raise e unless mask
|
111
|
+
end
|
112
|
+
|
113
|
+
return fh
|
114
|
+
end
|
115
|
+
|
116
|
+
module_function :mktemp
|
117
|
+
|
118
|
+
#
|
119
|
+
# Create a directory. If mask is true (default), it will use the
|
120
|
+
# random name generation rules from the tmpnam() call in this
|
121
|
+
# module.
|
122
|
+
#
|
123
|
+
|
124
|
+
def mktempdir(filename, mask=true)
|
125
|
+
fn = mask ? tmpnam(filename) : filename
|
126
|
+
Dir.mkdir(fn)
|
127
|
+
return fn
|
128
|
+
end
|
129
|
+
|
130
|
+
module_function :mktempdir
|
131
|
+
end # module MkTemp
|
132
|
+
end # module Webby
|
133
|
+
|
134
|
+
# :startdoc:
|
135
|
+
# EOF
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# This code was originally written by Bruce Williams, and it is available
|
2
|
+
# as the Paginator gem. I've added a few helper methods and modifications so
|
3
|
+
# it plays a little more nicely with Webby. Specifically, a Webby::Resource
|
4
|
+
# can be given to the Page and used to generate links to the previous and
|
5
|
+
# next pages.
|
6
|
+
#
|
7
|
+
# Many thanks to Bruce Williams for letting me use his work. Drop him a note
|
8
|
+
# of praise scribbled on the back of a $100 bill. He'd appreciate it.
|
9
|
+
|
10
|
+
require 'forwardable'
|
11
|
+
|
12
|
+
module Webby
|
13
|
+
class Paginator
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
class ArgumentError < ::ArgumentError; end
|
18
|
+
class MissingCountError < ArgumentError; end
|
19
|
+
class MissingSelectError < ArgumentError; end
|
20
|
+
|
21
|
+
attr_reader :per_page, :count, :resource
|
22
|
+
|
23
|
+
# Instantiate a new Paginator object
|
24
|
+
#
|
25
|
+
# Provide:
|
26
|
+
# * A total count of the number of objects to paginate
|
27
|
+
# * The number of objects in each page
|
28
|
+
# * A block that returns the array of items
|
29
|
+
# * The block is passed the item offset
|
30
|
+
# (and the number of items to show per page, for
|
31
|
+
# convenience, if the arity is 2)
|
32
|
+
def initialize(count, per_page, resource, &select)
|
33
|
+
@count, @per_page, @resource = count, per_page, resource
|
34
|
+
unless select
|
35
|
+
raise MissingSelectError, "Must provide block to select data for each page"
|
36
|
+
end
|
37
|
+
@select = select
|
38
|
+
end
|
39
|
+
|
40
|
+
# Total number of pages
|
41
|
+
def number_of_pages
|
42
|
+
(@count / @per_page).to_i + (@count % @per_page > 0 ? 1 : 0)
|
43
|
+
end
|
44
|
+
|
45
|
+
# First page object
|
46
|
+
def first
|
47
|
+
page 1
|
48
|
+
end
|
49
|
+
|
50
|
+
# Last page object
|
51
|
+
def last
|
52
|
+
page number_of_pages
|
53
|
+
end
|
54
|
+
|
55
|
+
def each
|
56
|
+
1.upto(number_of_pages) do |number|
|
57
|
+
yield page(number)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Retrieve page object by number
|
62
|
+
def page(number)
|
63
|
+
number = (n = number.to_i) > 0 ? n : 1
|
64
|
+
Page.new(self, number, lambda {
|
65
|
+
offset = (number - 1) * @per_page
|
66
|
+
args = [offset]
|
67
|
+
args << @per_page if @select.arity == 2
|
68
|
+
@select.call(*args)
|
69
|
+
})
|
70
|
+
end
|
71
|
+
|
72
|
+
# Page object
|
73
|
+
#
|
74
|
+
# Retrieves items for a page and provides metadata about the position
|
75
|
+
# of the page in the paginator
|
76
|
+
class Page
|
77
|
+
|
78
|
+
include Enumerable
|
79
|
+
|
80
|
+
attr_reader :number, :pager, :url
|
81
|
+
|
82
|
+
def initialize(pager, number, select) #:nodoc:
|
83
|
+
@pager, @number = pager, number
|
84
|
+
@offset = (number - 1) * pager.per_page
|
85
|
+
@select = select
|
86
|
+
|
87
|
+
@pager.resource.number = (number == 1 ? nil : number)
|
88
|
+
@url = @pager.resource.url
|
89
|
+
end
|
90
|
+
|
91
|
+
# Retrieve the items for this page
|
92
|
+
# * Caches
|
93
|
+
def items
|
94
|
+
@items ||= @select.call
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checks to see if there's a page before this one
|
98
|
+
def prev?
|
99
|
+
@number > 1
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get previous page (if possible)
|
103
|
+
def prev
|
104
|
+
@pager.page(@number - 1) if prev?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks to see if there's a page after this one
|
108
|
+
def next?
|
109
|
+
@number < @pager.number_of_pages
|
110
|
+
end
|
111
|
+
|
112
|
+
# Get next page (if possible)
|
113
|
+
def next
|
114
|
+
@pager.page(@number + 1) if next?
|
115
|
+
end
|
116
|
+
|
117
|
+
# The "item number" of the first item on this page
|
118
|
+
def first_item_number
|
119
|
+
1 + @offset
|
120
|
+
end
|
121
|
+
|
122
|
+
# The "item number" of the last item on this page
|
123
|
+
def last_item_number
|
124
|
+
if next?
|
125
|
+
@offset + @pager.per_page
|
126
|
+
else
|
127
|
+
@pager.count
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def ==(other) #:nodoc:
|
132
|
+
@pager == other.pager && self.number == other.number
|
133
|
+
end
|
134
|
+
|
135
|
+
def each(&block)
|
136
|
+
items.each(&block)
|
137
|
+
end
|
138
|
+
|
139
|
+
def method_missing(meth, *args, &block) #:nodoc:
|
140
|
+
if @pager.respond_to?(meth)
|
141
|
+
@pager.__send__(meth, *args, &block)
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end # class Paginator
|
150
|
+
end # module Webby
|
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'thread'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
# :stopdoc:
|
6
|
+
|
7
|
+
# == Synopsis
|
8
|
+
#
|
9
|
+
# A class for spawning child processes and ensuring those children continue
|
10
|
+
# running.
|
11
|
+
#
|
12
|
+
# == Details
|
13
|
+
#
|
14
|
+
# When a spawner is created it is given the command to run in a child
|
15
|
+
# process. This child process has +stdin+, +stdout+, and +stderr+ redirected
|
16
|
+
# to +/dev/null+ (this works even on Windows). When the child dies for any
|
17
|
+
# reason, the spawner will restart a new child process in the exact same
|
18
|
+
# manner as the original.
|
19
|
+
#
|
20
|
+
class Spawner
|
21
|
+
|
22
|
+
@dev_null = test(?e, "/dev/null") ? "/dev/null" : "NUL:"
|
23
|
+
|
24
|
+
c = ::Config::CONFIG
|
25
|
+
ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
|
26
|
+
@ruby = if system('%s -e exit' % ruby) then ruby
|
27
|
+
elsif system('ruby -e exit') then 'ruby'
|
28
|
+
else warn 'no ruby in PATH/CONFIG'
|
29
|
+
end
|
30
|
+
|
31
|
+
class << self
|
32
|
+
attr_reader :ruby
|
33
|
+
attr_reader :dev_null
|
34
|
+
|
35
|
+
def finalizer( cids )
|
36
|
+
pid = $$
|
37
|
+
lambda do
|
38
|
+
break unless pid == $$
|
39
|
+
cids.kill 'TERM', :all
|
40
|
+
end # lambda
|
41
|
+
end # finalizer
|
42
|
+
end
|
43
|
+
|
44
|
+
# call-seq:
|
45
|
+
# Spawner.new( command, *args, opts = {} )
|
46
|
+
#
|
47
|
+
# Creates a new spawner that will execute the given external _command_ in
|
48
|
+
# a sub-process. The calling semantics of <code>Kernel::exec</code> are
|
49
|
+
# used to execute the _command_. Any number of optional _args_ can be
|
50
|
+
# passed to the _command_.
|
51
|
+
#
|
52
|
+
# Available options:
|
53
|
+
#
|
54
|
+
# :spawn => the number of child processes to spawn
|
55
|
+
# :pause => wait time (in seconds) before respawning after termination
|
56
|
+
# :ruby => the Ruby interpreter to use when spawning children
|
57
|
+
# :env => a hash for the child process environment
|
58
|
+
# :stdin => stdin child processes will read from
|
59
|
+
# :stdout => stdout child processes will write to
|
60
|
+
# :stderr => stderr child processes will write to
|
61
|
+
#
|
62
|
+
# The <code>:env</code> option is used to add environemnt variables to
|
63
|
+
# child processes when they are spawned.
|
64
|
+
#
|
65
|
+
# *Note:* all spawned child processes will use the same stdin, stdout, and
|
66
|
+
# stderr if they are given in the options. Otherwise they all default to
|
67
|
+
# <code>/dev/null</code> on *NIX and <code>NUL:</code> on Windows.
|
68
|
+
#
|
69
|
+
def initialize( *args )
|
70
|
+
config = {
|
71
|
+
:ruby => self.class.ruby,
|
72
|
+
:spawn => 1,
|
73
|
+
:pause => 0,
|
74
|
+
:stdin => self.class.dev_null,
|
75
|
+
:stdout => self.class.dev_null,
|
76
|
+
:stderr => self.class.dev_null
|
77
|
+
}
|
78
|
+
config.merge! args.pop if Hash === args.last
|
79
|
+
config[:argv] = args
|
80
|
+
|
81
|
+
raise ArgumentError, 'wrong number of arguments' if args.empty?
|
82
|
+
|
83
|
+
@stop = true
|
84
|
+
@cids = []
|
85
|
+
@group = ThreadGroup.new
|
86
|
+
|
87
|
+
@spawn = config.delete(:spawn)
|
88
|
+
@pause = config.delete(:pause)
|
89
|
+
@ruby = config.delete(:ruby)
|
90
|
+
|
91
|
+
@tmp = child_program(config)
|
92
|
+
|
93
|
+
class << @cids
|
94
|
+
# call-seq:
|
95
|
+
# sync {block}
|
96
|
+
#
|
97
|
+
# Executes the given block in a synchronized fashion -- i.e. only a
|
98
|
+
# single thread can execute at a time. Uses Mutex under the hood.
|
99
|
+
#
|
100
|
+
def sync(&b)
|
101
|
+
@mutex ||= Mutex.new
|
102
|
+
@mutex.synchronize(&b)
|
103
|
+
end
|
104
|
+
|
105
|
+
# call-seq:
|
106
|
+
# kill( signal, num ) => number killed
|
107
|
+
# kill( signal, :all ) => number killed
|
108
|
+
#
|
109
|
+
# Send the _signal_ to a given _num_ of child processes or all child
|
110
|
+
# processes if <code>:all</code> is given instead of a number. Returns
|
111
|
+
# the number of child processes killed.
|
112
|
+
#
|
113
|
+
def kill( signal, arg )
|
114
|
+
return if empty?
|
115
|
+
|
116
|
+
ary = sync do
|
117
|
+
case arg
|
118
|
+
when :all; self.dup
|
119
|
+
when Integer; self.slice(0,arg)
|
120
|
+
else raise ArgumentError end
|
121
|
+
end
|
122
|
+
|
123
|
+
ary.each do |cid|
|
124
|
+
begin
|
125
|
+
Process.kill(signal, cid)
|
126
|
+
rescue SystemCallError
|
127
|
+
sync {delete cid}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
ary.length
|
131
|
+
end # def kill
|
132
|
+
end # class << @cids
|
133
|
+
|
134
|
+
end # def initialize
|
135
|
+
|
136
|
+
attr_reader :spawn
|
137
|
+
attr_accessor :pause
|
138
|
+
|
139
|
+
# call-seq:
|
140
|
+
# spawner.spawn = num
|
141
|
+
#
|
142
|
+
# Set the number of child processes to spawn. If the new spawn number is
|
143
|
+
# less than the current number, then spawner threads will die
|
144
|
+
#
|
145
|
+
def spawn=( num )
|
146
|
+
num = num.abs
|
147
|
+
diff, @spawn = num - @spawn, num
|
148
|
+
return unless running?
|
149
|
+
|
150
|
+
if diff > 0
|
151
|
+
diff.times {_spawn}
|
152
|
+
elsif diff < 0
|
153
|
+
@cids.kill 'TERM', diff.abs
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# call-seq:
|
158
|
+
# start => self
|
159
|
+
#
|
160
|
+
# Spawn the sub-processes.
|
161
|
+
#
|
162
|
+
def start
|
163
|
+
return self if running?
|
164
|
+
@stop = false
|
165
|
+
|
166
|
+
@cleanup = Spawner.finalizer(@cids)
|
167
|
+
ObjectSpace.define_finalizer(self, @cleanup)
|
168
|
+
|
169
|
+
@spawn.times {_spawn}
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# call-seq:
|
174
|
+
# stop( timeout = 5 ) => self
|
175
|
+
#
|
176
|
+
# Stop any spawned sub-processes.
|
177
|
+
#
|
178
|
+
def stop( timeout = 5 )
|
179
|
+
return self unless running?
|
180
|
+
@stop = true
|
181
|
+
|
182
|
+
@cleanup.call
|
183
|
+
ObjectSpace.undefine_finalizer(self)
|
184
|
+
|
185
|
+
# the cleanup call sends SIGTERM to all the child processes
|
186
|
+
# however, some might still be hanging around, so we are going to wait
|
187
|
+
# for a timeout interval and then send a SIGKILL to any remaining child
|
188
|
+
# processes
|
189
|
+
nap_time = 0.05 * timeout # sleep for 5% of the timeout interval
|
190
|
+
timeout = Time.now + timeout
|
191
|
+
|
192
|
+
until @cids.empty?
|
193
|
+
sleep nap_time
|
194
|
+
unless Time.now < timeout
|
195
|
+
@cids.kill 'KILL', :all
|
196
|
+
@cids.clear
|
197
|
+
@group.list.each {|t| t.kill}
|
198
|
+
break
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
self
|
203
|
+
end
|
204
|
+
|
205
|
+
# call-seq:
|
206
|
+
# restart( timeout = 5 )
|
207
|
+
#
|
208
|
+
def restart( timeout = 5 )
|
209
|
+
stop( timeout )
|
210
|
+
start
|
211
|
+
end
|
212
|
+
|
213
|
+
# call-seq:
|
214
|
+
# running?
|
215
|
+
#
|
216
|
+
# Returns +true+ if the spawner is currently running; returns +false+
|
217
|
+
# otherwise.
|
218
|
+
#
|
219
|
+
def running?
|
220
|
+
!@stop
|
221
|
+
end
|
222
|
+
|
223
|
+
# call-seq:
|
224
|
+
# join( timeout = nil ) => spawner or nil
|
225
|
+
#
|
226
|
+
# The calling thread will suspend execution until all child processes have
|
227
|
+
# been stopped. Does not return until all spawner threads have exited (the
|
228
|
+
# child processes have been stopped) or until _timeout seconds have
|
229
|
+
# passed. If the timeout expires +nil+ will be returned; otherwise the
|
230
|
+
# spawner is returned.
|
231
|
+
#
|
232
|
+
def join( limit = nil )
|
233
|
+
loop do
|
234
|
+
t = @group.list.first
|
235
|
+
break if t.nil?
|
236
|
+
return nil unless t.join(limit)
|
237
|
+
end
|
238
|
+
self
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
# call-seq:
|
245
|
+
# _spawn => thread
|
246
|
+
#
|
247
|
+
# Creates a thread that will spawn the sub-process via
|
248
|
+
# <code>IO::popen</code>. If the sub-process terminates, it will be
|
249
|
+
# respawned until the +stop+ message is sent to this spawner.
|
250
|
+
#
|
251
|
+
# If an Exception is encountered during the spawning process, a message
|
252
|
+
# will be printed to stderr and the thread will exit.
|
253
|
+
#
|
254
|
+
def _spawn
|
255
|
+
t = Thread.new do
|
256
|
+
catch(:die) do
|
257
|
+
loop do
|
258
|
+
begin
|
259
|
+
io = IO.popen("#{@ruby} #{@tmp.path}", 'r')
|
260
|
+
cid = io.gets.to_i
|
261
|
+
|
262
|
+
@cids.sync {@cids << cid} if cid > 0
|
263
|
+
Process.wait cid
|
264
|
+
rescue Exception => e
|
265
|
+
STDERR.puts e.inspect
|
266
|
+
STDERR.puts e.backtrace.join("\n")
|
267
|
+
throw :die
|
268
|
+
ensure
|
269
|
+
io.close rescue nil
|
270
|
+
@cids.sync {
|
271
|
+
@cids.delete cid
|
272
|
+
throw :die unless @cids.length < @spawn
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
throw :die if @stop
|
277
|
+
sleep @pause
|
278
|
+
|
279
|
+
end # loop
|
280
|
+
end # catch(:die)
|
281
|
+
end # Thread.new
|
282
|
+
|
283
|
+
@group.add t
|
284
|
+
t
|
285
|
+
end
|
286
|
+
|
287
|
+
# call-seq:
|
288
|
+
# child_program( config ) => tempfile
|
289
|
+
#
|
290
|
+
# Creates a child Ruby program based on the given _config_ hash. The
|
291
|
+
# following hash keys are used:
|
292
|
+
#
|
293
|
+
# :argv => command and arguments passed to <code>Kernel::exec</code>
|
294
|
+
# :env => environment variables for the child process
|
295
|
+
# :cwd => the current working directory to use for the child process
|
296
|
+
# :stdin => stdin the child process will read from
|
297
|
+
# :stdout => stdout the child process will write to
|
298
|
+
# :stderr => stderr the child process will write to
|
299
|
+
#
|
300
|
+
def child_program( config )
|
301
|
+
config = Marshal.dump(config)
|
302
|
+
|
303
|
+
tmp = Tempfile.new(self.class.name.downcase)
|
304
|
+
tmp.write <<-PROG
|
305
|
+
begin
|
306
|
+
config = Marshal.load(#{config.inspect})
|
307
|
+
|
308
|
+
argv = config[:argv]
|
309
|
+
env = config[:env]
|
310
|
+
cwd = config[:cwd]
|
311
|
+
stdin = config[:stdin]
|
312
|
+
stdout = config[:stdout]
|
313
|
+
stderr = config[:stderr]
|
314
|
+
|
315
|
+
Dir.chdir cwd if cwd
|
316
|
+
env.each {|k,v| ENV[k.to_s] = v.to_s} if env
|
317
|
+
rescue Exception => e
|
318
|
+
STDERR.warn e
|
319
|
+
abort
|
320
|
+
end
|
321
|
+
|
322
|
+
STDOUT.puts Process.pid
|
323
|
+
STDOUT.flush
|
324
|
+
|
325
|
+
STDIN.reopen stdin
|
326
|
+
STDOUT.reopen stdout
|
327
|
+
STDERR.reopen stderr
|
328
|
+
|
329
|
+
exec *argv
|
330
|
+
PROG
|
331
|
+
|
332
|
+
tmp.close
|
333
|
+
tmp
|
334
|
+
end
|
335
|
+
end # class Spawner
|
336
|
+
|
337
|
+
# :startdoc:
|
338
|
+
|
339
|
+
# EOF
|