synfeld 0.0.4
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/.gitignore +8 -0
- data/History.txt +15 -0
- data/README.rdoc +206 -0
- data/README.txt +206 -0
- data/Rakefile +74 -0
- data/TODO +1 -0
- data/example/public/erb_files/erb_test.erb +16 -0
- data/example/public/haml_files/haml_test.haml +15 -0
- data/example/public/haml_files/home.haml +19 -0
- data/example/public/html_files/html_test.html +14 -0
- data/example/public/images/beef_interstellar_thm.jpg +0 -0
- data/example/public/images/rails.png +0 -0
- data/example/try_me.rb +65 -0
- data/example/try_me.ru +6 -0
- data/lib/synfeld/base.rb +281 -0
- data/lib/synfeld.rb +17 -0
- data/lib/synfeld_info.rb +51 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/synfeld_spec.rb +7 -0
- data/synfeld.gemspec +50 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/test_synfeld.rb +0 -0
- data/work/rackmount-test.ru +59 -0
- metadata +134 -0
data/lib/synfeld/base.rb
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
module Synfeld # :nodoc:
|
2
|
+
|
3
|
+
#
|
4
|
+
# See the synopsis section of README.rdoc for usage.
|
5
|
+
#
|
6
|
+
# See the README.rdoc for an overview of an Synfeld::App, and see the Rack::Mount project for
|
7
|
+
# more information on Rack::Mount style routing.
|
8
|
+
#
|
9
|
+
# Variables of note:
|
10
|
+
#
|
11
|
+
# @response
|
12
|
+
# a hash with keys :body, :headers, :status_code, the 3 items all rack handlers are expected to set.
|
13
|
+
# Body is a string, status code is an http status code integer, and headers is a hash that
|
14
|
+
# should conform to rack's contract.
|
15
|
+
#
|
16
|
+
# @env
|
17
|
+
# The rack env passed into this apps #call method
|
18
|
+
#
|
19
|
+
# @params
|
20
|
+
# The params as determined by the matching Rack::Mount route.
|
21
|
+
#
|
22
|
+
# @root_dir
|
23
|
+
# This dir is prepended to relative paths to locate files.
|
24
|
+
#
|
25
|
+
# @logger
|
26
|
+
# Either you pass in the @logger that synfeld uses, or it sets one up on STDOUT.
|
27
|
+
#
|
28
|
+
class App
|
29
|
+
attr_accessor :response, :params, :env, :root_dir, :logger
|
30
|
+
|
31
|
+
# Options:
|
32
|
+
# :logger => where to log to.
|
33
|
+
# Note this is not the same thing as the rack access log (although you
|
34
|
+
# can pass that logger in if you want). Default: Logger.new(STDOUT)
|
35
|
+
def initialize(opts = {})
|
36
|
+
|
37
|
+
@logger = opts[:logger]
|
38
|
+
if self.logger.nil?
|
39
|
+
@logger = Logger.new(STDOUT)
|
40
|
+
puts "WARNING: Synfeld not configured with a logger, using STDOUT. Won't have much to say if running as a daemon."
|
41
|
+
end
|
42
|
+
|
43
|
+
@root_dir = opts[:root_dir]
|
44
|
+
if self.root_dir.nil?
|
45
|
+
raise "You have to pass in the location of the 'root_dir', where all the files in your synfeld app are located"
|
46
|
+
end
|
47
|
+
|
48
|
+
Kernel.at_exit {self.whine("Alright, I'm outta here.")}
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# RACK PLUMBING
|
53
|
+
#
|
54
|
+
|
55
|
+
# Return self as a rackup-able rack application.
|
56
|
+
def as_rack_app
|
57
|
+
#routes = Rack::Mount::RouteSet.new_without_optimizations do |set|
|
58
|
+
routes = Rack::Mount::RouteSet.new do |set|
|
59
|
+
@set = set
|
60
|
+
self.add_routes
|
61
|
+
add_route %r{^.*$}, :action => "render_static"
|
62
|
+
end
|
63
|
+
return routes
|
64
|
+
end
|
65
|
+
|
66
|
+
# The rack #call method
|
67
|
+
def call(env)
|
68
|
+
dup._call(env) # be thread-safe
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# ROUTING
|
73
|
+
#
|
74
|
+
|
75
|
+
@@__regex_colon = (RUBY_VERSION =~ /^1.8/)? ':' : '' # :nodoc:
|
76
|
+
|
77
|
+
# See the README for a full explanation of how to use this method.
|
78
|
+
def add_route(string_or_regex, opts = {})
|
79
|
+
raise "You have to provide an :action method to call" unless opts[:action]
|
80
|
+
method = (opts.delete(:method) || 'GET').to_s.upcase
|
81
|
+
# Adapt string_or_regex into a rack-mount regex route. If it is a string, convert it to a
|
82
|
+
# rack-mount compatable regex. In paths that look like /some/:var/in/path, convert the ':var'
|
83
|
+
# bits to rack-mount variables.
|
84
|
+
if string_or_regex.is_a?(String)
|
85
|
+
regex_string = "^" + string_or_regex.gsub(/:(([^\/]+))/){|s| "(?#{@@__regex_colon}<#{$1}>.*)" } + "$"
|
86
|
+
regex = %r{#{regex_string}}
|
87
|
+
#puts regex_string # dbg
|
88
|
+
else
|
89
|
+
regex = string_or_regex
|
90
|
+
end
|
91
|
+
|
92
|
+
# Add the route to rack-mount
|
93
|
+
@set.add_route(self,
|
94
|
+
{:path_info => regex, :request_method => method.upcase},
|
95
|
+
opts)
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# ACCESSORS & SUGAR
|
100
|
+
#
|
101
|
+
|
102
|
+
# The name of the action method bound to the route that mathed the incoming request.
|
103
|
+
def action
|
104
|
+
self.params[:action]
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
def _call(env) # :nodoc:
|
110
|
+
begin
|
111
|
+
start_time = Time.now.to_f
|
112
|
+
@env = env
|
113
|
+
@params = env[ Rack::Mount::Const::RACK_ROUTING_ARGS ]
|
114
|
+
@response = {
|
115
|
+
:status_code => 200,
|
116
|
+
:headers => {'Content-Type' => 'text/html'},
|
117
|
+
:body => nil
|
118
|
+
}
|
119
|
+
|
120
|
+
action = self.action
|
121
|
+
if self.respond_to?(action)
|
122
|
+
result = self.send(self.action)
|
123
|
+
else
|
124
|
+
result = self.no_action
|
125
|
+
end
|
126
|
+
|
127
|
+
if result.is_a?(String)
|
128
|
+
response[:body] = result
|
129
|
+
else
|
130
|
+
raise "You have to set the response body" if response[:body].nil?
|
131
|
+
end
|
132
|
+
|
133
|
+
response[:headers]["Content-Length"] = response[:body].size.to_s
|
134
|
+
|
135
|
+
logger.debug("It took #{Time.now.to_f - start_time} sec for #{self.class} to handle request.")
|
136
|
+
[response[:status_code], response[:headers], Array(response[:body])]
|
137
|
+
rescue Exception => e
|
138
|
+
# It seems like we should get this next line for free from the CommonLogger, so I guess
|
139
|
+
# I'm doing something wrong, missing some piece of rack middleware or something. Until I
|
140
|
+
# figure it out, I'm explicitly logging the exception manually.
|
141
|
+
self.whine "#{e.class}, #{e}\n\t#{e.backtrace.join("\n\t")} "
|
142
|
+
raise e
|
143
|
+
end
|
144
|
+
end
|
145
|
+
# :startdoc:
|
146
|
+
|
147
|
+
|
148
|
+
#
|
149
|
+
# EXCEPTIONS
|
150
|
+
#
|
151
|
+
|
152
|
+
# send an error message to the log prepended by "Synfeld: "
|
153
|
+
def whine msg
|
154
|
+
logger.error("Synfeld laments: " + msg)
|
155
|
+
return msg
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
# Overrideable method that handles a missing action that was defined by a route
|
160
|
+
def no_action
|
161
|
+
self.response[:body] = "Action '#{self.action}' not found in '#{self.class}'"
|
162
|
+
self.response[:status_code] = 500
|
163
|
+
end
|
164
|
+
|
165
|
+
# Overrideable method that handles 404
|
166
|
+
def no_route
|
167
|
+
self.response[:body] = "route not found for: '#{self.env['REQUEST_URI']}'"
|
168
|
+
self.response[:status_code] = 404
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# RENDERING
|
173
|
+
#
|
174
|
+
|
175
|
+
# Render an html file. 'fn' is a full path, or a path relative to @root_dir.
|
176
|
+
def render_html(fn)
|
177
|
+
F.read(full_path(fn))
|
178
|
+
end
|
179
|
+
|
180
|
+
# Serve up a blob of json (just sets Content-Type to 'text/javascript' and
|
181
|
+
# sets the body to the json passed in to this method).
|
182
|
+
def render_json(json)
|
183
|
+
self.response[:headers]['Content-Type'] = 'text/javascript'
|
184
|
+
self.response[:body] = json
|
185
|
+
end
|
186
|
+
|
187
|
+
# Render a haml file. 'fn' is a full path, or a path relative to @root_dir.
|
188
|
+
# 'locals' is a hash definining variables to be passed to the template.
|
189
|
+
def render_haml(fn, locals = {})
|
190
|
+
|
191
|
+
if not defined? Haml
|
192
|
+
begin
|
193
|
+
require 'haml'
|
194
|
+
rescue LoadError => x
|
195
|
+
return self.whine("Haml is not installed, required in order to render '#{fn}'")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
Haml::Engine.new(F.read(full_path(fn)) ).render(Object.new, locals)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Render an erb file. 'fn' is a full path, or a path relative to @root_dir.
|
203
|
+
# 'locals' is a hash definining variables to be passed to the template.
|
204
|
+
def render_erb(fn, locals = {})
|
205
|
+
|
206
|
+
if not defined? Erb
|
207
|
+
begin
|
208
|
+
require 'erb'
|
209
|
+
rescue LoadError => x
|
210
|
+
return self.whine("Erb is not installed, required in order to render '#{fn}'")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
template = ERB.new F.read(full_path(fn))
|
215
|
+
|
216
|
+
bind = binding
|
217
|
+
locals.each do |n,v|
|
218
|
+
raise "Locals must be symbols. Not a symbol: #{n.inspect}" unless n.is_a?(Symbol)
|
219
|
+
eval("#{n} = locals[:#{n}]", bind)
|
220
|
+
end
|
221
|
+
template.result(bind)
|
222
|
+
end
|
223
|
+
|
224
|
+
def render_static
|
225
|
+
fn = F.expand_path(F.join(root_dir, self.env['REQUEST_URI']))
|
226
|
+
#puts fn # dbg
|
227
|
+
if F.exist?(fn) and not F.directory?(fn)
|
228
|
+
self.content_type!(fn.split('.').last)
|
229
|
+
F.read(fn)
|
230
|
+
else
|
231
|
+
return self.no_route
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# UTIL
|
237
|
+
#
|
238
|
+
|
239
|
+
# Given a file extention, determine the 'Content-Type' and then set the
|
240
|
+
# @response[:headers]['Content-Type']. Unrecognized extentions are
|
241
|
+
# set to content type of 'text/plain'.
|
242
|
+
def content_type!(ext)
|
243
|
+
case ext.downcase
|
244
|
+
when 'haml'; t = 'text/html'
|
245
|
+
when 'erb'; t = 'text/html'
|
246
|
+
# I believe all the rest are determined accurately by the Rack::Mime.mime_type call in the else clause below.
|
247
|
+
# when 'html'; t = 'text/html'
|
248
|
+
# when 'js'; t = 'text/javascript'
|
249
|
+
# when 'css'; t = 'text/css'
|
250
|
+
# when 'png'; t = 'image/png'
|
251
|
+
# when 'gif'; t = 'image/gif'
|
252
|
+
# when 'jpg'; t = 'image/jpeg'
|
253
|
+
# when 'jpeg'; t = 'image/jpeg'
|
254
|
+
else t = Rack::Mime.mime_type('.' + ext, 'text/plain')
|
255
|
+
end
|
256
|
+
#puts("----#{ext}:" + t.inspect) # dbg
|
257
|
+
(self.response[:headers]['Content-Type'] = t) if t
|
258
|
+
end
|
259
|
+
|
260
|
+
# Return fn if a file by that name exists. If not, concatenate the @root_dir with the fn, and
|
261
|
+
# return that if it exists. Raise if the actual file cannot be determined.
|
262
|
+
#
|
263
|
+
# NOTE: no effort is made to protect access to files outside of your application's root
|
264
|
+
# dir. If you permit filepaths as request parameters, then it is up to you to make sure
|
265
|
+
# that they do not point to some sensitive part of your file-system.
|
266
|
+
def full_path(fn)
|
267
|
+
if F.exist?(fn)
|
268
|
+
return fn
|
269
|
+
elsif F.exist?(full_fn = F.join(self.root_dir, fn))
|
270
|
+
return full_fn
|
271
|
+
else
|
272
|
+
raise "Could not find file '#{fn}' (full path '#{full_fn}')"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end # class App
|
277
|
+
|
278
|
+
end # mod Synfeld
|
279
|
+
|
280
|
+
|
281
|
+
|
data/lib/synfeld.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
F = ::File
|
2
|
+
|
3
|
+
# base ruby requires
|
4
|
+
require 'rubygems'
|
5
|
+
require 'logger'
|
6
|
+
|
7
|
+
# gems dependencies
|
8
|
+
require 'rubygems'
|
9
|
+
|
10
|
+
require 'rack'
|
11
|
+
require 'rack/mount'
|
12
|
+
require 'rack/mime'
|
13
|
+
|
14
|
+
# my files (require_all_libs_relative_to is a bones util method in synfeld_info.rb)
|
15
|
+
require F.join(File.dirname(__FILE__), 'synfeld_info')
|
16
|
+
Synfeld.require_all_libs_relative_to(__FILE__)
|
17
|
+
|
data/lib/synfeld_info.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#
|
2
|
+
# See README.txt for usage.
|
3
|
+
#
|
4
|
+
module Synfeld
|
5
|
+
|
6
|
+
# :stopdoc:
|
7
|
+
|
8
|
+
VERSION = '0.0.4'
|
9
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
10
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
11
|
+
|
12
|
+
# Returns the version string for the library.
|
13
|
+
#
|
14
|
+
def self.version
|
15
|
+
VERSION
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the library path for the module. If any arguments are given,
|
19
|
+
# they will be joined to the end of the libray path using
|
20
|
+
# <tt>File.join</tt>.
|
21
|
+
#
|
22
|
+
def self.libpath( *args )
|
23
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the lpath for the module. If any arguments are given,
|
27
|
+
# they will be joined to the end of the path using
|
28
|
+
# <tt>File.join</tt>.
|
29
|
+
#
|
30
|
+
def self.path( *args )
|
31
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Utility method used to require all files ending in .rb that lie in the
|
35
|
+
# directory below this file that has the same name as the filename passed
|
36
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
37
|
+
# the _filename_ does not have to be equivalent to the directory.
|
38
|
+
#
|
39
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
40
|
+
dir ||= ::File.basename(fname, '.*')
|
41
|
+
search_me = ::File.expand_path(
|
42
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
43
|
+
|
44
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
45
|
+
end
|
46
|
+
|
47
|
+
# :startdoc:
|
48
|
+
|
49
|
+
end # module Synfeld
|
50
|
+
|
51
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(
|
3
|
+
File.join(File.dirname(__FILE__), %w[.. lib synfeld]))
|
4
|
+
|
5
|
+
Spec::Runner.configure do |config|
|
6
|
+
# == Mock Framework
|
7
|
+
#
|
8
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
9
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
10
|
+
#
|
11
|
+
# config.mock_with :mocha
|
12
|
+
# config.mock_with :flexmock
|
13
|
+
# config.mock_with :rr
|
14
|
+
end
|
15
|
+
|
16
|
+
# EOF
|
data/synfeld.gemspec
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{synfeld}
|
5
|
+
s.version = "0.0.4"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Steven Swerling"]
|
9
|
+
s.date = %q{2009-09-25}
|
10
|
+
s.description = %q{Synfeld is a web application framework that does practically nothing.
|
11
|
+
|
12
|
+
Synfeld is little more than a small wrapper for Rack::Mount (see http://github.com/josh/rack-mount). If you want a web framework that is mostly just going to serve up json blobs, and occasionally serve up some simple content (eg. help files) and media, Synfeld makes that easy.
|
13
|
+
|
14
|
+
The sample app below shows pretty much everything there is to know about synfeld, in particular:
|
15
|
+
|
16
|
+
* How to define routes.
|
17
|
+
* Simple rendering of erb, haml, html, json, and static files.
|
18
|
+
* In the case of erb and haml, passing variables into the template is demonstrated.
|
19
|
+
* A dymamic action where the status code, headers, and body are created 'manually.'
|
20
|
+
* The erb demo link also demos the rendering of a partial (not visible in the code below, you have to look at the template file examples/public/erb_files/erb_test.erb).}
|
21
|
+
s.email = %q{sswerling@yahoo.com}
|
22
|
+
s.extra_rdoc_files = ["History.txt", "README.rdoc", "README.txt"]
|
23
|
+
s.files = [".gitignore", "History.txt", "README.rdoc", "README.txt", "Rakefile", "TODO", "TODO-rack-mount", "example/public/erb_files/erb_test.erb", "example/public/haml_files/haml_test.haml", "example/public/haml_files/home.haml", "example/public/html_files/html_test.html", "example/public/images/beef_interstellar_thm.jpg", "example/public/images/rails.png", "example/try_me.rb", "example/try_me.ru", "lib/synfeld.rb", "lib/synfeld/base.rb", "lib/synfeld_info.rb", "rackmount-test.ru", "spec/spec_helper.rb", "spec/synfeld_spec.rb", "synfeld.gemspec", "test/test_synfeld.rb"]
|
24
|
+
s.homepage = %q{http://tab-a.slot-z.net}
|
25
|
+
s.rdoc_options = ["--inline-source", "--main", "README.txt"]
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.rubyforge_project = %q{synfeld}
|
28
|
+
s.rubygems_version = %q{1.3.5}
|
29
|
+
s.summary = %q{Synfeld is a web application framework that does practically nothing}
|
30
|
+
s.test_files = ["test/test_synfeld.rb"]
|
31
|
+
|
32
|
+
if s.respond_to? :specification_version then
|
33
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
34
|
+
s.specification_version = 3
|
35
|
+
|
36
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
37
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
38
|
+
s.add_runtime_dependency(%q<rack-router>, [">= 0"])
|
39
|
+
s.add_development_dependency(%q<bones>, [">= 2.5.1"])
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
42
|
+
s.add_dependency(%q<rack-router>, [">= 0"])
|
43
|
+
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
44
|
+
end
|
45
|
+
else
|
46
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
47
|
+
s.add_dependency(%q<rack-router>, [">= 0"])
|
48
|
+
s.add_dependency(%q<bones>, [">= 2.5.1"])
|
49
|
+
end
|
50
|
+
end
|
data/tasks/ann.rake
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones/smtp_tls'
|
4
|
+
rescue LoadError
|
5
|
+
require 'net/smtp'
|
6
|
+
end
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
namespace :ann do
|
10
|
+
|
11
|
+
# A prerequisites task that all other tasks depend upon
|
12
|
+
task :prereqs
|
13
|
+
|
14
|
+
file PROJ.ann.file do
|
15
|
+
ann = PROJ.ann
|
16
|
+
puts "Generating #{ann.file}"
|
17
|
+
File.open(ann.file,'w') do |fd|
|
18
|
+
fd.puts("#{PROJ.name} version #{PROJ.version}")
|
19
|
+
fd.puts(" by #{Array(PROJ.authors).first}") if PROJ.authors
|
20
|
+
fd.puts(" #{PROJ.url}") if PROJ.url.valid?
|
21
|
+
fd.puts(" (the \"#{PROJ.release_name}\" release)") if PROJ.release_name
|
22
|
+
fd.puts
|
23
|
+
fd.puts("== DESCRIPTION")
|
24
|
+
fd.puts
|
25
|
+
fd.puts(PROJ.description)
|
26
|
+
fd.puts
|
27
|
+
fd.puts(PROJ.changes.sub(%r/^.*$/, '== CHANGES'))
|
28
|
+
fd.puts
|
29
|
+
ann.paragraphs.each do |p|
|
30
|
+
fd.puts "== #{p.upcase}"
|
31
|
+
fd.puts
|
32
|
+
fd.puts paragraphs_of(PROJ.readme_file, p).join("\n\n")
|
33
|
+
fd.puts
|
34
|
+
end
|
35
|
+
fd.puts ann.text if ann.text
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "Create an announcement file"
|
40
|
+
task :announcement => ['ann:prereqs', PROJ.ann.file]
|
41
|
+
|
42
|
+
desc "Send an email announcement"
|
43
|
+
task :email => ['ann:prereqs', PROJ.ann.file] do
|
44
|
+
ann = PROJ.ann
|
45
|
+
from = ann.email[:from] || Array(PROJ.authors).first || PROJ.email
|
46
|
+
to = Array(ann.email[:to])
|
47
|
+
|
48
|
+
### build a mail header for RFC 822
|
49
|
+
rfc822msg = "From: #{from}\n"
|
50
|
+
rfc822msg << "To: #{to.join(',')}\n"
|
51
|
+
rfc822msg << "Subject: [ANN] #{PROJ.name} #{PROJ.version}"
|
52
|
+
rfc822msg << " (#{PROJ.release_name})" if PROJ.release_name
|
53
|
+
rfc822msg << "\n"
|
54
|
+
rfc822msg << "Date: #{Time.new.rfc822}\n"
|
55
|
+
rfc822msg << "Message-Id: "
|
56
|
+
rfc822msg << "<#{"%.8f" % Time.now.to_f}@#{ann.email[:domain]}>\n\n"
|
57
|
+
rfc822msg << File.read(ann.file)
|
58
|
+
|
59
|
+
params = [:server, :port, :domain, :acct, :passwd, :authtype].map do |key|
|
60
|
+
ann.email[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
params[3] = PROJ.email if params[3].nil?
|
64
|
+
|
65
|
+
if params[4].nil?
|
66
|
+
STDOUT.write "Please enter your e-mail password (#{params[3]}): "
|
67
|
+
params[4] = STDIN.gets.chomp
|
68
|
+
end
|
69
|
+
|
70
|
+
### send email
|
71
|
+
Net::SMTP.start(*params) {|smtp| smtp.sendmail(rfc822msg, from, to)}
|
72
|
+
end
|
73
|
+
end # namespace :ann
|
74
|
+
|
75
|
+
desc 'Alias to ann:announcement'
|
76
|
+
task :ann => 'ann:announcement'
|
77
|
+
|
78
|
+
CLOBBER << PROJ.ann.file
|
79
|
+
|
80
|
+
# EOF
|
data/tasks/bones.rake
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
if HAVE_BONES
|
3
|
+
|
4
|
+
namespace :bones do
|
5
|
+
|
6
|
+
desc 'Show the PROJ open struct'
|
7
|
+
task :debug do |t|
|
8
|
+
atr = if t.application.top_level_tasks.length == 2
|
9
|
+
t.application.top_level_tasks.pop
|
10
|
+
end
|
11
|
+
|
12
|
+
if atr then Bones::Debug.show_attr(PROJ, atr)
|
13
|
+
else Bones::Debug.show PROJ end
|
14
|
+
end
|
15
|
+
|
16
|
+
end # namespace :bones
|
17
|
+
|
18
|
+
end # HAVE_BONES
|
19
|
+
|
20
|
+
# EOF
|