batch-kit 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +165 -0
- data/lib/batch-kit.rb +9 -0
- data/lib/batch-kit/arguments.rb +57 -0
- data/lib/batch-kit/config.rb +517 -0
- data/lib/batch-kit/configurable.rb +68 -0
- data/lib/batch-kit/core_ext/enumerable.rb +97 -0
- data/lib/batch-kit/core_ext/file.rb +69 -0
- data/lib/batch-kit/core_ext/file_utils.rb +103 -0
- data/lib/batch-kit/core_ext/hash.rb +17 -0
- data/lib/batch-kit/core_ext/numeric.rb +17 -0
- data/lib/batch-kit/core_ext/string.rb +88 -0
- data/lib/batch-kit/database.rb +133 -0
- data/lib/batch-kit/database/java_util_log_handler.rb +65 -0
- data/lib/batch-kit/database/log4r_outputter.rb +57 -0
- data/lib/batch-kit/database/models.rb +548 -0
- data/lib/batch-kit/database/schema.rb +229 -0
- data/lib/batch-kit/encryption.rb +7 -0
- data/lib/batch-kit/encryption/java_encryption.rb +178 -0
- data/lib/batch-kit/encryption/ruby_encryption.rb +175 -0
- data/lib/batch-kit/events.rb +157 -0
- data/lib/batch-kit/framework/acts_as_job.rb +197 -0
- data/lib/batch-kit/framework/acts_as_sequence.rb +123 -0
- data/lib/batch-kit/framework/definable.rb +169 -0
- data/lib/batch-kit/framework/job.rb +121 -0
- data/lib/batch-kit/framework/job_definition.rb +105 -0
- data/lib/batch-kit/framework/job_run.rb +145 -0
- data/lib/batch-kit/framework/runnable.rb +235 -0
- data/lib/batch-kit/framework/sequence.rb +87 -0
- data/lib/batch-kit/framework/sequence_definition.rb +38 -0
- data/lib/batch-kit/framework/sequence_run.rb +48 -0
- data/lib/batch-kit/framework/task_definition.rb +89 -0
- data/lib/batch-kit/framework/task_run.rb +53 -0
- data/lib/batch-kit/helpers/date_time.rb +54 -0
- data/lib/batch-kit/helpers/email.rb +198 -0
- data/lib/batch-kit/helpers/html.rb +175 -0
- data/lib/batch-kit/helpers/process.rb +101 -0
- data/lib/batch-kit/helpers/zip.rb +30 -0
- data/lib/batch-kit/job.rb +11 -0
- data/lib/batch-kit/lockable.rb +138 -0
- data/lib/batch-kit/loggable.rb +78 -0
- data/lib/batch-kit/logging.rb +169 -0
- data/lib/batch-kit/logging/java_util_logger.rb +87 -0
- data/lib/batch-kit/logging/log4r_logger.rb +71 -0
- data/lib/batch-kit/logging/null_logger.rb +35 -0
- data/lib/batch-kit/logging/stdout_logger.rb +96 -0
- data/lib/batch-kit/resources.rb +191 -0
- data/lib/batch-kit/sequence.rb +7 -0
- metadata +122 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
class BatchKit
|
2
|
+
|
3
|
+
module Task
|
4
|
+
|
5
|
+
# Captures details of an execution of a task.
|
6
|
+
class Run < Runnable
|
7
|
+
|
8
|
+
# @return [Job::Run] The job run that this task is running under.
|
9
|
+
attr_reader :job_run
|
10
|
+
# @return [Fixnum] An integer identifier that uniquely identifies
|
11
|
+
# this task run.
|
12
|
+
attr_accessor :task_run_id
|
13
|
+
|
14
|
+
# Make Task::Defintion properties accessible off this Task::Run.
|
15
|
+
add_delegated_properties(*Task::Definition.properties)
|
16
|
+
|
17
|
+
|
18
|
+
# Create a new task run.
|
19
|
+
#
|
20
|
+
# @param task_def [Task::Definition] The Task::Definition to which this
|
21
|
+
# run relates.
|
22
|
+
# @param job_object [Object] The job object instance from which the
|
23
|
+
# task is being executed.
|
24
|
+
# @param job_run [Job::Run] The job run to which this task run belongs.
|
25
|
+
# @param run_args [Array<Object>] An array of the argument values
|
26
|
+
# passed to the task method.
|
27
|
+
def initialize(task_def, job_object, job_run, *run_args)
|
28
|
+
raise ArgumentError, "task_def not a Task::Definition" unless task_def.is_a?(Task::Definition)
|
29
|
+
raise ArgumentError, "job_run not a Job::Run" unless job_run.is_a?(Job::Run)
|
30
|
+
@job_run = job_run
|
31
|
+
@job_run << self
|
32
|
+
super(task_def, job_object, run_args)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# @return [Boolean] True if this task run should be persisted in any
|
37
|
+
# persistence layer.
|
38
|
+
def persist?
|
39
|
+
!definition.job.do_not_track
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# @return [String] A short representation of this Task::Run.
|
44
|
+
def to_s
|
45
|
+
"<BatchKit::Task::Run label='#{label}'>"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
class BatchKit
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
# Methods for displaying times/durations
|
7
|
+
module DateTime
|
8
|
+
|
9
|
+
# Converts the elapsed time in seconds to a string showing days, hours,
|
10
|
+
# minutes and seconds.
|
11
|
+
def display_duration(elapsed)
|
12
|
+
return nil unless elapsed
|
13
|
+
elapsed = elapsed.round
|
14
|
+
display = ''
|
15
|
+
[['days', 86400], ['h', 3600], ['m', 60], ['s', 1]].each do |int, seg|
|
16
|
+
if elapsed >= seg
|
17
|
+
count, elapsed = elapsed.divmod(seg)
|
18
|
+
display << "#{count}#{int.length > 1 && count == 1 ? int[0..-2] : int} "
|
19
|
+
elsif display.length > 0
|
20
|
+
display << "0#{int}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
display = "0s" if display == ''
|
24
|
+
display.strip
|
25
|
+
end
|
26
|
+
module_function :display_duration
|
27
|
+
|
28
|
+
|
29
|
+
# Displays a date/time in abbreviated format, suppressing elements of
|
30
|
+
# the format string for more recent dates/times.
|
31
|
+
#
|
32
|
+
# @param ts [Time, Date, DateTime] The date/time object to be displayed
|
33
|
+
# @return [String] A formatted representation of the date/time.
|
34
|
+
def display_timestamp(ts)
|
35
|
+
return unless ts
|
36
|
+
ts_date = ts.to_date
|
37
|
+
today = Date.today
|
38
|
+
if today - ts_date < 7
|
39
|
+
# Date is within the last week
|
40
|
+
ts.strftime('%a %H:%M:%S')
|
41
|
+
elsif today.year != ts.year
|
42
|
+
# Data is from a different year
|
43
|
+
ts.strftime('%a %b %d %Y %H:%M:%S')
|
44
|
+
else
|
45
|
+
ts.strftime('%a %b %d %H:%M:%S')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
module_function :display_timestamp
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require_relative '../core_ext/file_utils'
|
3
|
+
require_relative 'html'
|
4
|
+
require_relative 'date_time'
|
5
|
+
|
6
|
+
|
7
|
+
class BatchKit
|
8
|
+
|
9
|
+
module Helpers
|
10
|
+
|
11
|
+
# Defines a number of methods to help with generating email messages in
|
12
|
+
# both plain-text and HTML formats.
|
13
|
+
module Email
|
14
|
+
|
15
|
+
include Html
|
16
|
+
|
17
|
+
# Creates a new Mail message object that can be used to create and
|
18
|
+
# send an email.
|
19
|
+
#
|
20
|
+
# @param cfg [BatchKit::Config] A config object containing details of
|
21
|
+
# an SMTP gateway for delivering the message. Defaults to the
|
22
|
+
# config object defined by including BatchKit#Configurable.
|
23
|
+
def create_email(cfg = nil)
|
24
|
+
cfg = config if cfg.nil? && self.respond_to?(:config)
|
25
|
+
Mail.defaults do
|
26
|
+
delivery_method :smtp, cfg.smtp
|
27
|
+
end
|
28
|
+
|
29
|
+
Mail.new(to: mail_list(cfg[:to]),
|
30
|
+
cc: mail_list(cfg[:cc]),
|
31
|
+
from: cfg[:email_from] || "#{self.job.job_class.name}@#{self.job.computer}",
|
32
|
+
reply_to: mail_list(cfg[:reply_to]))
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
# Mail likes its recipient lists as a comma-separated list in a
|
37
|
+
# String. To make this easier to use, this helper method converts
|
38
|
+
# an array of values into sucn a string.
|
39
|
+
def mail_list(recips)
|
40
|
+
recips.is_a?(Array) ? recips.join(', ') : recips
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Creates an HTML formatted email, with a default set of styles.
|
45
|
+
#
|
46
|
+
# @param cfg [BatchKit::Config] A config object containing details of
|
47
|
+
# an SMTP gateway for delivering the message. Defaults to the
|
48
|
+
# config object defined by including BatchKit#Configurable.
|
49
|
+
# @param body_text [String] An optional string containing text to
|
50
|
+
# add to the email body.
|
51
|
+
# @yield [Array<String>] an Array of strings to which body content
|
52
|
+
# can be added.
|
53
|
+
def create_html_email(cfg = config, body_text = nil, &blk)
|
54
|
+
if cfg.is_a?(String) || cfg.is_a?(Array)
|
55
|
+
body_text = cfg
|
56
|
+
cfg = nil
|
57
|
+
end
|
58
|
+
msg = create_email(cfg)
|
59
|
+
body = create_html_document(body_text, &blk)
|
60
|
+
msg.html_part = Mail::Part.new do |part|
|
61
|
+
part.content_type('text/html; charset=UTF-8')
|
62
|
+
part.body(body.join("\n"))
|
63
|
+
end
|
64
|
+
msg
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Adds details of tasks run and their duration to an email.
|
69
|
+
#
|
70
|
+
# @param body [Array<String>] An array containing the lines of the
|
71
|
+
# message body. Job execution details will be added as an HTML
|
72
|
+
# table to this.
|
73
|
+
def add_job_details_to_email(body)
|
74
|
+
has_instances = self.job_run.task_runs.find{ |tr| tr.instance }
|
75
|
+
body << "<br>"
|
76
|
+
body << "<div class='separator'></div>"
|
77
|
+
body << "<p>"
|
78
|
+
body << "<p>Job execution details:</p>"
|
79
|
+
create_html_table(body, self.job_run.task_runs,
|
80
|
+
{name: :name, label: 'Task'},
|
81
|
+
{name: :instance, show: has_instances},
|
82
|
+
{name: :start_time, label: 'Start Time'},
|
83
|
+
{name: :end_time, label: 'End Time'},
|
84
|
+
{label: 'Duration', class: 'right',
|
85
|
+
value: lambda{ |tr| DateTime.display_duration(tr.elapsed) }})
|
86
|
+
body.slice!(-2..-1)
|
87
|
+
body << "<tr><th>#{self.job.name}</th>"
|
88
|
+
body << "<th>#{self.job_run.instance}</th>" if has_instances
|
89
|
+
body << "<th class='right'>#{self.job_run.start_time.strftime("%H:%M:%S")}</th>"
|
90
|
+
body << "<th class='right'></th>"
|
91
|
+
body << "<th class='right'>#{DateTime.display_duration(self.job_run.elapsed)}</th></tr>"
|
92
|
+
body << "</tbody>"
|
93
|
+
body << "</table>"
|
94
|
+
body << "<br>"
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Creates an email message containing details of the exception that
|
99
|
+
# caused this job to fail.
|
100
|
+
def create_failure_email(cfg = config)
|
101
|
+
msg = create_email(cfg)
|
102
|
+
to = cfg[:failure_email_to]
|
103
|
+
to = to.join(', ') if to.is_a?(Array)
|
104
|
+
cc = cfg[:failure_email_cc]
|
105
|
+
cc = cc.join(', ') if cc.is_a?(Array)
|
106
|
+
msg.to = to
|
107
|
+
msg.cc = cc
|
108
|
+
msg.subject = "#{self.job.name} job on #{self.job.computer} Failed!"
|
109
|
+
|
110
|
+
# Add details of the failed job and task runs
|
111
|
+
body = []
|
112
|
+
self.job.runs.each do |jr|
|
113
|
+
ex = nil
|
114
|
+
jr.task_runs.select{ |tr| tr.exception != nil }.each do |tr|
|
115
|
+
ex = tr.exception
|
116
|
+
body << "Job '#{jr.label}' has failed in task '#{tr.label}'."
|
117
|
+
body << "\n#{ex.class.name}: #{ex.message}"
|
118
|
+
body << "\nBacktrace:"
|
119
|
+
body += ex.backtrace
|
120
|
+
body << "\n"
|
121
|
+
end
|
122
|
+
if ex != jr.exception
|
123
|
+
body << "Job '#{jr.label}' has failed."
|
124
|
+
body << "\n#{jr.exception.class.name}: #{jr.exception.message}"
|
125
|
+
body << "\nBacktrace:"
|
126
|
+
body += jr.exception.backtrace
|
127
|
+
body << "\n"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add job log file as attachment (if it exists)
|
132
|
+
if self.respond_to?(:log) && self.log.log_file
|
133
|
+
body << "See the attached log for details.\n"
|
134
|
+
msg.add_file(self.log.log_file)
|
135
|
+
end
|
136
|
+
msg.body = body.join("\n")
|
137
|
+
msg
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# Sends a failure email message in response to a job failure.
|
142
|
+
#
|
143
|
+
# @param recipients [String|Array] The recipient(s) to receive the
|
144
|
+
# email. If no recipients are specified, the con
|
145
|
+
def send_failure_email(cfg = config, recipients = nil)
|
146
|
+
unless cfg.is_a?(Hash)
|
147
|
+
recipients = cfg
|
148
|
+
cfg = config
|
149
|
+
end
|
150
|
+
msg = create_failure_email(cfg)
|
151
|
+
if recipients
|
152
|
+
# Override default recipients
|
153
|
+
msg.to = recipients
|
154
|
+
msg.cc = nil
|
155
|
+
end
|
156
|
+
msg.deliver!
|
157
|
+
log.detail "Failure email sent to #{recipient_count(msg)} recipients"
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Given a message, returns the number of recipients currently set.
|
162
|
+
#
|
163
|
+
# @param msg [Mail] A Mail message object.
|
164
|
+
def recipient_count(msg)
|
165
|
+
count = 0
|
166
|
+
count += msg.to.size if msg.to
|
167
|
+
count += msg.cc.size if msg.cc
|
168
|
+
count
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# Saves the content of a message to a file.
|
173
|
+
#
|
174
|
+
# @param msg [Mail] The message whose content is to be saved.
|
175
|
+
# @param path [String] The path to the file to be created.
|
176
|
+
# @param options [Hash] An options hash; see FileUtils.archive for
|
177
|
+
# details of supported option settings.
|
178
|
+
def save_msg_to_file(msg, path, options = {})
|
179
|
+
FileUtils.archive(path, options)
|
180
|
+
file = File.open(path, "w")
|
181
|
+
in_html = false
|
182
|
+
msg.html_part.to_s.each_line do |line|
|
183
|
+
line.chomp!
|
184
|
+
in_html ||= (line =~ /^<html/i)
|
185
|
+
if in_html
|
186
|
+
file.puts line
|
187
|
+
file.puts "<title>#{msg.subject}</title>" if line =~ /<head>/
|
188
|
+
end
|
189
|
+
end
|
190
|
+
file.close
|
191
|
+
log.detail "Saved email body to #{path}"
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative '../core_ext/numeric'
|
2
|
+
require_relative '../core_ext/string'
|
3
|
+
|
4
|
+
|
5
|
+
class BatchKit
|
6
|
+
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
# Defines a number of methods to help with generating simple HTML documents
|
10
|
+
module Html
|
11
|
+
|
12
|
+
# Creates a new HTML document with a pre-defined set of styles
|
13
|
+
def create_html_document(body_text = nil, opts = {})
|
14
|
+
if body_text.is_a?(Hash)
|
15
|
+
opts = body_text
|
16
|
+
body_text = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
hdr = <<-EOS.gsub(/\s{20}/, '')
|
20
|
+
<html>
|
21
|
+
#{create_head_tag(opts)}
|
22
|
+
<body>
|
23
|
+
EOS
|
24
|
+
body = [hdr]
|
25
|
+
body << body_text if body_text
|
26
|
+
yield body if block_given?
|
27
|
+
body << <<-EOS.gsub(/\s{20}/, '')
|
28
|
+
</body>
|
29
|
+
</html>
|
30
|
+
EOS
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def create_head_tag(opts = {})
|
35
|
+
head_tag = <<-EOS.gsub(/^\w+\n$/, '')
|
36
|
+
<head>
|
37
|
+
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=us-ascii">
|
38
|
+
#{opts[:title] ? "<title>#{opts[:title]}</title>" : ''}
|
39
|
+
#{create_style_tag(opts)}
|
40
|
+
</head>
|
41
|
+
EOS
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def create_style_tag(opts = {})
|
46
|
+
font = opts.fetch(:font, 'Calibri')
|
47
|
+
style_tag = <<-EOS
|
48
|
+
<style>
|
49
|
+
@font-face {font-family: #{font};}
|
50
|
+
|
51
|
+
h1 {font-family: #{font}; font-size: 16pt;}
|
52
|
+
h2 {font-family: #{font}; font-size: 14pt; margin: 1em 0em .2em;}
|
53
|
+
h3 {font-family: #{font}; font-size: 12pt; margin: 1em 0em .2em;}
|
54
|
+
body {font-family: #{font}; font-size: 11pt;}
|
55
|
+
p {margin: .2em 0em;}
|
56
|
+
table {font-family: #{font}; font-size: 10pt;
|
57
|
+
line-height: 12pt; border-collapse: collapse;}
|
58
|
+
th {background-color: #00205B; color: white;
|
59
|
+
font-size: 11pt; font-weight: bold; text-align: left;
|
60
|
+
border: 1px solid #DDDDFF; padding: 1px 5px;}
|
61
|
+
td {border: 1px solid #DDDDFF; padding: 1px 5px;}
|
62
|
+
|
63
|
+
.summary {font-size: 13pt;}
|
64
|
+
.red {background-color: white; color: #FF0000;}
|
65
|
+
.amber {background-color: white; color: #FFA500;}
|
66
|
+
.green {background-color: white; color: #33A000;}
|
67
|
+
.blue {background-color: white; color: #0000A0;}
|
68
|
+
.bold {font-weight: bold;}
|
69
|
+
.center {text-align: center;}
|
70
|
+
.right {text-align: right;}
|
71
|
+
.separator {width: 200px; border-bottom: 1px gray solid;}
|
72
|
+
</style>
|
73
|
+
</head>
|
74
|
+
<body>
|
75
|
+
EOS
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Creates an HTML table from +data+.
|
80
|
+
#
|
81
|
+
# @param body [Array] The HTML body to which this table will be
|
82
|
+
# appended.
|
83
|
+
# @param data [Array|Hash] The data to be added to the table.
|
84
|
+
# @param cols [Array<Symbol|String|Hash>] An array of symbols,
|
85
|
+
# strings, or Hashes. String and symbols output as-is, while the
|
86
|
+
# hash can contain various options that control the display of
|
87
|
+
# the column and the content below it, as follows:
|
88
|
+
# - :name is the name of the property. It will be used to access
|
89
|
+
# data values if +data+ is a Hash, and will be used as the
|
90
|
+
# column header unless a :label property is passed.
|
91
|
+
# - :label is the label to use as the column header.
|
92
|
+
# - :class specifies a CSS class name to assign to each cell in
|
93
|
+
# the column.
|
94
|
+
# - :show is a boolean that determines whether the column is
|
95
|
+
# output or suppressed.
|
96
|
+
# - :prefix is any text that should appear before the content.
|
97
|
+
# - :suffix is any text that should appear after the content.
|
98
|
+
def create_html_table(body, data, *cols)
|
99
|
+
cols.map!{ |col| col.is_a?(Symbol) || col.is_a?(String) ? {name: col.intern} : col }
|
100
|
+
body << "<table>"
|
101
|
+
body << "<thead><tr>"
|
102
|
+
add_table_cells(body,
|
103
|
+
cols.map{ |col| (col[:label] || col[:name]).to_s.titleize },
|
104
|
+
cols.map{ |col| {show: col.fetch(:show, true)} },
|
105
|
+
:th)
|
106
|
+
body << "</tr></thead>"
|
107
|
+
body << "<tbody>"
|
108
|
+
data.each do |row|
|
109
|
+
body << "<tr>"
|
110
|
+
add_table_cells(body, row, cols)
|
111
|
+
body << "</tr>"
|
112
|
+
end
|
113
|
+
body << "</tbody>"
|
114
|
+
body << "</table>"
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
# Adds a row of cells to a table.
|
119
|
+
#
|
120
|
+
# @param body [Array<String>] An array of lines containing the body
|
121
|
+
# of the HTML message to which this table row should be added.
|
122
|
+
# @param row [Array, Hash, Object] An Array, Hash, or Object from
|
123
|
+
# which a row of data shall be retrieved to populate the table.
|
124
|
+
# @param cols [Array<Hash>] An Array of Hashes, each containing
|
125
|
+
# details for a single column. Each Hash can contain the following
|
126
|
+
# options:
|
127
|
+
# - :class: The CSS class with which to style the column cells.
|
128
|
+
# - :show: A boolean value indicating whether the column should
|
129
|
+
# be displayed or skipped.
|
130
|
+
# - :prefix: Text to appear before the content of the cell.
|
131
|
+
# - :suffix: Text to appear after the content of the cell.
|
132
|
+
# - :value: A setting controlling how values are retrieved from
|
133
|
+
# +row+. By default, this is by index, but this setting can
|
134
|
+
# override that, and either supply a name or method to call
|
135
|
+
# on +row+, or a Proc object to invoke on row.
|
136
|
+
# @param cell_type [Symbol] Either :td (the default) or :th.
|
137
|
+
def add_table_cells(body, row, cols, cell_type = :td)
|
138
|
+
cols.each_with_index do |col, i|
|
139
|
+
cls = col[:class]
|
140
|
+
show = col.fetch(:show, true)
|
141
|
+
prefix = col.fetch(:prefix, '')
|
142
|
+
suffix = col.fetch(:suffix, '')
|
143
|
+
next if !show
|
144
|
+
val = case
|
145
|
+
when col[:value]
|
146
|
+
col[:value].call(row)
|
147
|
+
when row.is_a?(Array)
|
148
|
+
row[i]
|
149
|
+
when row.is_a?(Hash)
|
150
|
+
row[col[:name]]
|
151
|
+
when row.respond_to?(col[:name])
|
152
|
+
row.send(col[:name])
|
153
|
+
when row.respond_to?(:[])
|
154
|
+
row[col[:name]]
|
155
|
+
else
|
156
|
+
row
|
157
|
+
end
|
158
|
+
case val
|
159
|
+
when Numeric
|
160
|
+
val = val.with_commas
|
161
|
+
cls = 'right' unless cls
|
162
|
+
when Date, Time, DateTime
|
163
|
+
val = val.strftime('%H:%M:%S')
|
164
|
+
cls = 'right' unless cls
|
165
|
+
end
|
166
|
+
td = %Q{<#{cell_type}#{cls ? " class='#{cls}'" : ''}>#{prefix}#{val}#{suffix}</#{cell_type}>}
|
167
|
+
body << td
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|