logging-couchdb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/README.rdoc +49 -0
- data/Rakefile +43 -0
- data/lib/logging/couch_db_appender.rb +169 -0
- data/lib/logging/couch_db_layout.rb +140 -0
- data/lib/logging/plugins/couch_db.rb +15 -0
- data/spec/couch_db_spec.rb +26 -0
- data/spec/spec_helper.rb +23 -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
- metadata +118 -0
data/History.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
= Logging CouchDB
|
2
|
+
* by Tim Pease
|
3
|
+
* http://github.com/TwP/logging-couchdb/tree/master
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Provides a CouchDB appender to the Ruby Logging framework. This appender
|
8
|
+
enables log messages to be written to a CouchDB instance.
|
9
|
+
|
10
|
+
== SYNOPSIS:
|
11
|
+
|
12
|
+
Logging.plugin :couch_db
|
13
|
+
|
14
|
+
Logging.appenders.couch_db( "app_id", :uri => "http://localhost:5984", :db_name => "logging" )
|
15
|
+
|
16
|
+
== REQUIREMENTS:
|
17
|
+
|
18
|
+
logging
|
19
|
+
rest-client
|
20
|
+
json (or json_pure)
|
21
|
+
|
22
|
+
== INSTALL:
|
23
|
+
|
24
|
+
gem install logging-couchdb
|
25
|
+
|
26
|
+
== LICENSE:
|
27
|
+
|
28
|
+
(The MIT License)
|
29
|
+
|
30
|
+
Copyright (c) 2009
|
31
|
+
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
33
|
+
a copy of this software and associated documentation files (the
|
34
|
+
'Software'), to deal in the Software without restriction, including
|
35
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
36
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
37
|
+
permit persons to whom the Software is furnished to do so, subject to
|
38
|
+
the following conditions:
|
39
|
+
|
40
|
+
The above copyright notice and this permission notice shall be
|
41
|
+
included in all copies or substantial portions of the Software.
|
42
|
+
|
43
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
44
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
45
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
46
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
47
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
48
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
49
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
begin
|
10
|
+
load 'tasks/setup.rb'
|
11
|
+
rescue LoadError
|
12
|
+
raise RuntimeError, '### please install the "bones" gem ###'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ensure_in_path 'lib'
|
17
|
+
require 'logging'
|
18
|
+
require 'logging/plugins/couch_db'
|
19
|
+
|
20
|
+
task :default => 'spec:specdoc'
|
21
|
+
|
22
|
+
PROJ.name = 'logging-couchdb'
|
23
|
+
PROJ.authors = 'Tim Pease'
|
24
|
+
PROJ.email = 'tim.pease@gmail.com'
|
25
|
+
PROJ.url = 'http://logging.rubyforge.org/logging-couchdb'
|
26
|
+
PROJ.version = '1.0.0'
|
27
|
+
PROJ.rubyforge.name = 'logging'
|
28
|
+
PROJ.exclude << 'logging-couchdb.gemspec'
|
29
|
+
PROJ.readme_file = 'README.rdoc'
|
30
|
+
PROJ.ignore_file = '.gitignore'
|
31
|
+
PROJ.rdoc.remote_dir = 'logging-couchdb'
|
32
|
+
|
33
|
+
PROJ.spec.opts << '--color'
|
34
|
+
|
35
|
+
PROJ.ann.email[:server] = 'smtp.gmail.com'
|
36
|
+
PROJ.ann.email[:port] = 587
|
37
|
+
PROJ.ann.email[:from] = 'Tim Pease'
|
38
|
+
|
39
|
+
depend_on 'logging'
|
40
|
+
depend_on 'rest-client'
|
41
|
+
depend_on 'rspec'
|
42
|
+
|
43
|
+
# EOF
|
@@ -0,0 +1,169 @@
|
|
1
|
+
|
2
|
+
require 'rest_client'
|
3
|
+
begin
|
4
|
+
require 'json/ext'
|
5
|
+
rescue LoadError
|
6
|
+
require 'json'
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
module Logging::Appenders
|
11
|
+
|
12
|
+
# Accessor / Factory for the CouchDB appender.
|
13
|
+
#
|
14
|
+
def couch_db( *args )
|
15
|
+
return ::Logging::Appenders::CouchDB if args.empty?
|
16
|
+
::Logging::Appenders::CouchDB.new(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# This class provides an Appender that sends log messages to a CouchDB
|
20
|
+
# instance.
|
21
|
+
#
|
22
|
+
class CouchDB < ::Logging::Appender
|
23
|
+
include ::Logging::Appenders::Buffering
|
24
|
+
|
25
|
+
# The string that uniquely identifies this application in the CouchDB
|
26
|
+
# messages.
|
27
|
+
#
|
28
|
+
attr_accessor :app_id
|
29
|
+
|
30
|
+
# call-seq:
|
31
|
+
# CouchDB.new( name, :uri => 'http://localhost:5984',
|
32
|
+
# :db_name => 'logging', :app_id => name )
|
33
|
+
#
|
34
|
+
# Creates a new CouchDB appender that will format log events and send
|
35
|
+
# them to the CouchDB specified by the <tt>:uri</tt> and the
|
36
|
+
# <tt>:db_name</tt>. Your applications should specify a unique
|
37
|
+
# <tt>:app_id</tt> so that metrics about your application can be easily
|
38
|
+
# generated. If an app_id is not given, then the appender <tt>name</tt>
|
39
|
+
# is used as the app_id.
|
40
|
+
#
|
41
|
+
# The CouchDB appender uses message buffering. Log events are saved in
|
42
|
+
# bulk to the CouchDB instance. You can specify the buffer size by
|
43
|
+
# setting <tt>:auto_flushing</tt> to the number of messages to buffer.
|
44
|
+
# Setting the auto_flushing to +true+ will cause messages to be
|
45
|
+
# immediately sent to the CouchDB instance.
|
46
|
+
#
|
47
|
+
# Communication with the CouchDB instance is asynchronous; calls to the
|
48
|
+
# appender sould return quickly. However, there will be some delay
|
49
|
+
# before the log events appear in the CouchDB instance. The
|
50
|
+
# communication thread must be woken up and scheduled in order for the
|
51
|
+
# events to be posted.
|
52
|
+
#
|
53
|
+
def initialize( name, opts = {} )
|
54
|
+
opts = opts.merge(:layout => ::Logging::Layouts::CouchDB.new)
|
55
|
+
super(name, opts)
|
56
|
+
|
57
|
+
# initialize the connection to the CouchDB instance
|
58
|
+
uri = opts.getopt(:uri, 'http://localhost:5984')
|
59
|
+
db_name = opts.getopt(:db_name, 'logging')
|
60
|
+
self.app_id = opts.getopt(:app_id, name)
|
61
|
+
|
62
|
+
@db_uri = uri + '/' + db_name + '/_bulk_docs'
|
63
|
+
configure_buffering(opts)
|
64
|
+
start_thread
|
65
|
+
end
|
66
|
+
|
67
|
+
# Close the appender and wait for the internal writer thread to finish.
|
68
|
+
#
|
69
|
+
def close( *args )
|
70
|
+
super
|
71
|
+
Thread.pass until @dispatcher.status == 'sleep' or !@dispatcher.status
|
72
|
+
@dispatcher.wakeup if @dispatcher.status
|
73
|
+
@dispatcher.join(60)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Send all buffered log events to the CouchDB instance. If the messages
|
78
|
+
# cannot be saved the appender will be disabled, and all messages in the
|
79
|
+
# buffer and all subsequent messages will be lost.
|
80
|
+
#
|
81
|
+
def flush
|
82
|
+
return self if buffer.empty?
|
83
|
+
@dispatch = true
|
84
|
+
@dispatcher.wakeup if @dispatcher.status == 'sleep'
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Write the given _event_ to the CouchDB instance. The message is
|
92
|
+
# formatted into a Ruby Hash instance suitable for conversion into a
|
93
|
+
# CouchDB JSON document.
|
94
|
+
#
|
95
|
+
def write( event )
|
96
|
+
h =
|
97
|
+
if event.instance_of?(::Logging::LogEvent)
|
98
|
+
layout.format event
|
99
|
+
else
|
100
|
+
{
|
101
|
+
:timestamp => ::Logging::Layouts::CouchDB.timestamp,
|
102
|
+
:logger => 'Unknown',
|
103
|
+
:level => 0,
|
104
|
+
:message => event.to_s
|
105
|
+
}
|
106
|
+
end
|
107
|
+
h[:app_id] = @app_id
|
108
|
+
|
109
|
+
buffer << h
|
110
|
+
flush if buffer.length >= auto_flushing || immediate?(event)
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
# This method performs the actual HTTP POST to the CouchDB instance. All
|
116
|
+
# messages in the buffer are posted using the CouchDB bulk storage
|
117
|
+
# semantics.
|
118
|
+
#
|
119
|
+
def post_events
|
120
|
+
return if @dispatch_buffer.empty?
|
121
|
+
|
122
|
+
payload = {:docs => @dispatch_buffer}.to_json
|
123
|
+
RestClient.post(@db_uri, payload)
|
124
|
+
#JSON.parse(RestClient.post(@db_uri, payload))
|
125
|
+
self
|
126
|
+
rescue StandardError => err
|
127
|
+
self.level = :off
|
128
|
+
::Logging.log_internal {"appender #{name.inspect} has been disabled"}
|
129
|
+
::Logging.log_internal(-2) {err}
|
130
|
+
raise
|
131
|
+
ensure
|
132
|
+
@dispatch_buffer.clear
|
133
|
+
end
|
134
|
+
|
135
|
+
# Creats the dispatcher thread that reads from the buffer and writes log
|
136
|
+
# events to the CouchDB instance.
|
137
|
+
#
|
138
|
+
def start_thread
|
139
|
+
@dispatch_buffer = []
|
140
|
+
@dispatch = false
|
141
|
+
|
142
|
+
@dispatcher = Thread.new(self) {
|
143
|
+
loop {
|
144
|
+
sleep(60) unless @dispatch or closed?
|
145
|
+
|
146
|
+
if closed?
|
147
|
+
sync {
|
148
|
+
@dispatch = false
|
149
|
+
@dispatch_buffer.concat @buffer
|
150
|
+
@buffer.clear
|
151
|
+
}
|
152
|
+
post_events
|
153
|
+
break
|
154
|
+
|
155
|
+
else
|
156
|
+
sync {
|
157
|
+
@dispatch = false
|
158
|
+
@buffer, @dispatch_buffer = @dispatch_buffer, @buffer
|
159
|
+
}
|
160
|
+
post_events
|
161
|
+
end
|
162
|
+
} # loop
|
163
|
+
} # Thread.new
|
164
|
+
end
|
165
|
+
|
166
|
+
end # class CouchDB
|
167
|
+
end # module Logging::Appenders
|
168
|
+
|
169
|
+
# EOF
|
@@ -0,0 +1,140 @@
|
|
1
|
+
|
2
|
+
module Logging::Layouts
|
3
|
+
|
4
|
+
# Accessor / Factory for the CouchDB layout.
|
5
|
+
#
|
6
|
+
def couch_db( *args )
|
7
|
+
return ::Logging::Layouts::CouchDB if args.empty?
|
8
|
+
::Logging::Layouts::CouchDB.new(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
# This layout will produce log output in JSON format. This is different
|
12
|
+
# than the standard "Parseable" layout in that the JSON is tailored
|
13
|
+
# specifically for the CouchDB appender. Differences are mainly in the
|
14
|
+
# timestamp format and available options. The LogEvent data is only
|
15
|
+
# formatted into JSON and not inspect format or the standard to_s format.
|
16
|
+
#
|
17
|
+
# The information about the log event can be configured when the layout is
|
18
|
+
# created. Any or all of the following labels can be set as the _items_ to
|
19
|
+
# log:
|
20
|
+
#
|
21
|
+
# 'logger' Used to output the name of the logger that generated the
|
22
|
+
# log event.
|
23
|
+
# 'timestamp' Used to output the timestamp of the log event.
|
24
|
+
# 'level' Used to output the level of the log event.
|
25
|
+
# 'message' Used to output the application supplied message
|
26
|
+
# associated with the log event in JSON format.
|
27
|
+
# 'file' Used to output the file name where the logging request
|
28
|
+
# was issued.
|
29
|
+
# 'line' Used to output the line number where the logging request
|
30
|
+
# was issued.
|
31
|
+
# 'method' Used to output the method name where the logging request
|
32
|
+
# was issued.
|
33
|
+
# 'pid' Used to output the process ID of the currently running
|
34
|
+
# program.
|
35
|
+
# 'thread_id' Used to output the object ID of the thread that generated
|
36
|
+
# the log event.
|
37
|
+
# 'thread' Used to output the name of the thread that generated the
|
38
|
+
# log event. Name can be specified using Thread.current[:name]
|
39
|
+
# notation. Output empty string if name not specified. This
|
40
|
+
# option helps to create more human readable output for
|
41
|
+
# multithread application logs.
|
42
|
+
#
|
43
|
+
# These items are supplied to the layout as an array of strings. The items
|
44
|
+
# 'file', 'line', and 'method' will only work if the Logger generating the
|
45
|
+
# events is configured to generate tracing information. If this is not the
|
46
|
+
# case these fields will always be empty.
|
47
|
+
#
|
48
|
+
class CouchDB < ::Logging::Layout
|
49
|
+
|
50
|
+
# :stopdoc:
|
51
|
+
# Arguments to sprintf keyed to directive letters
|
52
|
+
DIRECTIVE_TABLE = {
|
53
|
+
'logger' => 'event.logger',
|
54
|
+
'timestamp' => 'CouchDB.timestamp(event.time)',
|
55
|
+
'level' => 'event.level',
|
56
|
+
'message' => 'format_obj(event.data)',
|
57
|
+
'file' => 'event.file',
|
58
|
+
'line' => 'event.line',
|
59
|
+
'method' => 'event.method',
|
60
|
+
'pid' => 'Process.pid',
|
61
|
+
'thread_id' => 'Thread.current.object_id',
|
62
|
+
'thread' => 'Thread.current[:name]'
|
63
|
+
}
|
64
|
+
TIME_FMT = '%Y-%m-%dT%H:%M:%S.%%06dZ'
|
65
|
+
|
66
|
+
# call-seq:
|
67
|
+
# CouchDB.create_format_method( layout )
|
68
|
+
#
|
69
|
+
# This method will create the +format+ method in the given CouchDB
|
70
|
+
# _layout_ based on the configured items for the layout instance.
|
71
|
+
#
|
72
|
+
def self.create_format_method( layout )
|
73
|
+
code = "undef :format if method_defined? :format\n"
|
74
|
+
code << "def format( event )\n{"
|
75
|
+
|
76
|
+
code << layout.items.map {|name|
|
77
|
+
"#{name.to_sym.inspect} => #{CouchDB::DIRECTIVE_TABLE[name]}"
|
78
|
+
}.join(',')
|
79
|
+
code << "\n}\nend"
|
80
|
+
|
81
|
+
(class << layout; self end).class_eval(code, __FILE__, __LINE__)
|
82
|
+
end
|
83
|
+
# :startdoc:
|
84
|
+
|
85
|
+
# call-seq:
|
86
|
+
# CouchDB.timestamp
|
87
|
+
#
|
88
|
+
# Returns a timestamp that can be sorted in CouchDB.
|
89
|
+
#
|
90
|
+
def self.timestamp( time = nil )
|
91
|
+
utc = (time || Time.now).utc
|
92
|
+
utc.strftime(TIME_FMT) % utc.usec
|
93
|
+
end
|
94
|
+
|
95
|
+
# call-seq:
|
96
|
+
# CouchDB.new( opts )
|
97
|
+
#
|
98
|
+
# Creates a new CouchDB layout using the following options:
|
99
|
+
#
|
100
|
+
# :items => %w[timestamp level logger message]
|
101
|
+
#
|
102
|
+
def initialize( opts = {} )
|
103
|
+
super
|
104
|
+
self.items = opts.getopt(:items, %w[timestamp level logger message])
|
105
|
+
end
|
106
|
+
|
107
|
+
attr_reader :items
|
108
|
+
|
109
|
+
# call-seq:
|
110
|
+
# layout.items = %w[timestamp level logger message]
|
111
|
+
#
|
112
|
+
# Set the log event items that will be formatted by this layout. These
|
113
|
+
# items, and only these items, will appear in the log output.
|
114
|
+
#
|
115
|
+
def items=( ary )
|
116
|
+
@items = Array(ary).map {|name| name.to_s.downcase}
|
117
|
+
valid = DIRECTIVE_TABLE.keys
|
118
|
+
@items.each do |name|
|
119
|
+
raise ArgumentError, "unknown item - #{name.inspect}" unless valid.include? name
|
120
|
+
end
|
121
|
+
self.class.create_format_method(self)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Take the given _value_ and convert it into a format suitable for use
|
125
|
+
# in a JSON object structure.
|
126
|
+
#
|
127
|
+
def format_obj( value )
|
128
|
+
case value
|
129
|
+
when nil, Numeric, String, Array, Hash; value
|
130
|
+
when Exception
|
131
|
+
ary = ["<#{obj.class.name}> #{obj.message}"]
|
132
|
+
ary.concat(obj.backtrace) unless obj.backtrace.nil?
|
133
|
+
ary
|
134
|
+
else super(value) end
|
135
|
+
end
|
136
|
+
|
137
|
+
end # class CouchDB
|
138
|
+
end # module Logging::Layouts
|
139
|
+
|
140
|
+
# EOF
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
root = File.expand_path(
|
3
|
+
File.join(File.dirname(__FILE__), '..'))
|
4
|
+
|
5
|
+
module Logging::Plugins
|
6
|
+
module CouchDB
|
7
|
+
def initialize_couch_db
|
8
|
+
end
|
9
|
+
end # module CouchDB
|
10
|
+
end # module Logging::Plugins
|
11
|
+
|
12
|
+
require File.join(root, 'couch_db_layout')
|
13
|
+
require File.join(root, 'couch_db_appender')
|
14
|
+
|
15
|
+
# EOF
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
describe Logging::Appenders::CouchDB do
|
5
|
+
|
6
|
+
it "converts a string from camel-case to underscore" do
|
7
|
+
LittlePlugger.underscore('FooBarBaz').should == 'foo_bar_baz'
|
8
|
+
LittlePlugger.underscore('CouchDB').should == 'couch_db'
|
9
|
+
LittlePlugger.underscore('FOOBar').should == 'foo_bar'
|
10
|
+
LittlePlugger.underscore('Foo::Bar::BazBuz').should == 'foo/bar/baz_buz'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "generates a default plugin path" do
|
14
|
+
LittlePlugger.default_plugin_path(LittlePlugger).should == 'little_plugger/plugins'
|
15
|
+
LittlePlugger.default_plugin_path(Spec::Runner).should == 'spec/runner/plugins'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "generates a default plugin module" do
|
19
|
+
LittlePlugger.default_plugin_module('little_plugger').should == LittlePlugger
|
20
|
+
lambda {LittlePlugger.default_plugin_module('little_plugger/plugins')}.
|
21
|
+
should raise_error(NameError, 'uninitialized constant LittlePlugger::Plugins')
|
22
|
+
LittlePlugger.default_plugin_module('spec/runner').should == Spec::Runner
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# EOF
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'logging'
|
4
|
+
|
5
|
+
Logging.plugin.clear
|
6
|
+
Logging.plugin << :none
|
7
|
+
Logging.init
|
8
|
+
|
9
|
+
require File.expand_path(
|
10
|
+
File.join(File.dirname(__FILE__), %w[.. lib logging plugins couch_db]))
|
11
|
+
|
12
|
+
Spec::Runner.configure do |config|
|
13
|
+
# == Mock Framework
|
14
|
+
#
|
15
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
16
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
17
|
+
#
|
18
|
+
# config.mock_with :mocha
|
19
|
+
# config.mock_with :flexmock
|
20
|
+
# config.mock_with :rr
|
21
|
+
end
|
22
|
+
|
23
|
+
# EOF
|
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
|