batch-kit 0.3
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.
- 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
|