rapport 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/.document +5 -0
- data/.tmtags +96 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +20 -0
- data/README.md +93 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/helpers.rb +41 -0
- data/lib/load_rapport.rb +4 -0
- data/lib/rapport/report.rb +177 -0
- data/lib/rapport/report_generator.rb +105 -0
- data/lib/rapport/report_generators/report_generator_csv.rb +58 -0
- data/lib/rapport/report_generators/report_generator_fake.rb +16 -0
- data/lib/rapport.rb +5 -0
- data/lib/test.rb +12 -0
- data/rapport.gemspec +88 -0
- data/test/logs/test.log +6 -0
- data/test/rapport/report_generator_test.rb +157 -0
- data/test/rapport/report_generators/report_generator_csv_test.rb +56 -0
- data/test/rapport/report_test.rb +253 -0
- data/test/test_helper.rb +112 -0
- metadata +244 -0
data/.document
ADDED
data/.tmtags
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
|
2
|
+
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
|
3
|
+
!_TAG_PROGRAM_AUTHOR Darren Hiebert /dhiebert@users.sourceforge.net/
|
4
|
+
!_TAG_PROGRAM_NAME Exuberant Ctags //
|
5
|
+
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
|
6
|
+
!_TAG_PROGRAM_VERSION 5.7 //
|
7
|
+
Bypass test/test_helper.rb /^ class Bypass$/;" class line:22
|
8
|
+
CellFormatter lib/rapport/report_generator.rb /^ class CellFormatter$/;" class line:89
|
9
|
+
ClassMethods lib/rapport/report.rb /^ module ClassMethods$/;" module line:15
|
10
|
+
ClassMethods lib/rapport/report_generator.rb /^ module ClassMethods$/;" module line:20
|
11
|
+
Foo lib/test.rb /^class Foo$/;" class line:1
|
12
|
+
Object test/test_helper.rb /^class Object$/;" class line:21
|
13
|
+
Rapport lib/helpers.rb /^module Rapport$/;" module line:1
|
14
|
+
Rapport lib/rapport/report.rb /^module Rapport$/;" module line:1
|
15
|
+
Rapport lib/rapport/report_generator.rb /^module Rapport$/;" module line:3
|
16
|
+
Rapport lib/rapport/report_generators/report_generator_csv.rb /^module Rapport$/;" module line:3
|
17
|
+
Rapport lib/rapport/report_generators/report_generator_fake.rb /^module Rapport$/;" module line:1
|
18
|
+
Rapport test/test_helper.rb /^module Rapport$/;" module line:47
|
19
|
+
Report lib/rapport/report.rb /^ module Report$/;" module line:2
|
20
|
+
ReportGenerator lib/rapport/report_generator.rb /^ module ReportGenerator$/;" module line:4
|
21
|
+
ReportGeneratorCsv lib/rapport/report_generators/report_generator_csv.rb /^ class ReportGeneratorCsv$/;" class line:4
|
22
|
+
ReportGeneratorFake lib/rapport/report_generators/report_generator_fake.rb /^ class ReportGeneratorFake$/;" class line:2
|
23
|
+
ReportGeneratorTest test/test_helper.rb /^ class ReportGeneratorTest$/;" class line:79
|
24
|
+
ReportTest test/rapport/report_generator_test.rb /^class ReportTest < Test::Unit::TestCase$/;" class line:3
|
25
|
+
ReportTest test/rapport/report_test.rb /^class ReportTest < Test::Unit::TestCase$/;" class line:3
|
26
|
+
Test build/test/helper.rb /^class Test::Unit::TestCase$/;" class line:17
|
27
|
+
Test test/test_helper.rb /^class Test::Unit::TestCase$/;" class line:41
|
28
|
+
TestBuild build/test/test_build.rb /^class TestBuild < Test::Unit::TestCase$/;" class line:3
|
29
|
+
TestReport test/test_helper.rb /^ class TestReport$/;" class line:48
|
30
|
+
_calculators lib/rapport/report.rb /^ def _calculators$/;" method line:120
|
31
|
+
add_calculator lib/rapport/report.rb /^ def add_calculator(symbol, options)$/;" method line:34
|
32
|
+
add_cell_format lib/rapport/report_generator.rb /^ def add_cell_format(type, &block)$/;" method line:99
|
33
|
+
base_calculator lib/rapport/report.rb /^ def base_calculator(key, calculator)$/;" method line:157
|
34
|
+
bypass test/test_helper.rb /^ def bypass$/;" method line:36
|
35
|
+
cell_calculator_for lib/rapport/report.rb /^ def cell_calculator_for(key,row_type)$/;" method line:124
|
36
|
+
cell_format lib/rapport/report_generator.rb /^ def cell_format(type, &block)$/;" method line:48
|
37
|
+
cell_format_for lib/rapport/report.rb /^ def cell_format_for(key)$/;" method line:166
|
38
|
+
cell_formatter lib/rapport/report_generator.rb /^ def cell_formatter$/;" method line:59
|
39
|
+
column lib/rapport/report.rb /^ def column(*args, &block)$/;" method line:16
|
40
|
+
column_headers lib/rapport/report.rb /^ def column_headers$/;" method line:68
|
41
|
+
column_symbols lib/rapport/report.rb /^ def column_symbols$/;" method line:72
|
42
|
+
columns lib/rapport/report.rb /^ def columns$/;" method line:60
|
43
|
+
dup lib/rapport/report_generator.rb /^ def dup$/;" method line:95
|
44
|
+
each_model lib/rapport/report.rb /^ def each_model(&block)$/;" method line:105
|
45
|
+
each_model test/test_helper.rb /^ def each_model$/;" method line:55
|
46
|
+
each_row lib/rapport/report.rb /^ def each_row$/;" method line:53
|
47
|
+
each_row lib/rapport/report_generator.rb /^ def each_row$/;" method line:79
|
48
|
+
format lib/rapport/report_generator.rb /^ def format(type, value)$/;" method line:63
|
49
|
+
format lib/rapport/report_generator.rb /^ def format(type,value)$/;" method line:111
|
50
|
+
format for class test/rapport/report_generator_test.rb /^ share_should("format for class") do$/;" shared_should line:96
|
51
|
+
format for symbol test/rapport/report_generator_test.rb /^ share_should("format for symbol") do$/;" shared_should line:92
|
52
|
+
format_camel_case lib/helpers.rb /^ def format_camel_case(value)$/;" method line:22
|
53
|
+
format_underscore lib/helpers.rb /^ def format_underscore(value)$/;" method line:18
|
54
|
+
formatted_cell_value lib/rapport/report.rb /^ def formatted_cell_value(key, model, row_type)$/;" method line:88
|
55
|
+
formatted_row lib/rapport/report.rb /^ def formatted_row(model, row_type)$/;" method line:111
|
56
|
+
from lib/rapport/report_generator.rb /^ def from(report)$/;" method line:23
|
57
|
+
from lib/rapport/report_generator.rb /^ def self.from(report)$/;" singleton method line:6
|
58
|
+
generate lib/rapport/report.rb /^ def generate$/;" method line:45
|
59
|
+
generate_internal lib/rapport/report_generators/report_generator_fake.rb /^ def generate_internal$/;" method line:11
|
60
|
+
generate_with lib/rapport/report_generator.rb /^ def generate_with(&block)$/;" method line:29
|
61
|
+
hello lib/test.rb /^ def self.hello$/;" singleton method line:8
|
62
|
+
included lib/rapport/report.rb /^ def self.included(base)$/;" singleton method line:4
|
63
|
+
included lib/rapport/report_generator.rb /^ def self.included(base)$/;" singleton method line:11
|
64
|
+
inherited lib/rapport/report.rb /^ def base.inherited(subclass)$/;" singleton method line:8
|
65
|
+
inherited lib/rapport/report_generator.rb /^ def base.inherited(subclass)$/;" singleton method line:14
|
66
|
+
inherited lib/test.rb /^ def self.inherited(subclass)$/;" singleton method line:4
|
67
|
+
initialize lib/rapport/report_generator.rb /^ def initialize(procs = {})$/;" method line:91
|
68
|
+
initialize lib/rapport/report_generators/report_generator_fake.rb /^ def initialize(report)$/;" method line:7
|
69
|
+
initialize test/test_helper.rb /^ def initialize$/;" method line:84
|
70
|
+
initialize test/test_helper.rb /^ def initialize(options = {})$/;" method line:51
|
71
|
+
initialize test/test_helper.rb /^ def initialize(ref)$/;" method line:27
|
72
|
+
logger test/test_helper.rb /^ def logger$/;" method line:88
|
73
|
+
method_missing lib/rapport/report_generator.rb /^ def method_missing(method, *args)$/;" method line:67
|
74
|
+
method_missing test/test_helper.rb /^ def method_missing(sym, *args)$/;" method line:31
|
75
|
+
options lib/rapport/report.rb /^ def options$/;" method line:49
|
76
|
+
options lib/rapport/report_generator.rb /^ def options$/;" method line:55
|
77
|
+
output_filename lib/rapport/report_generator.rb /^ def output_filename$/;" method line:83
|
78
|
+
raw_cell_value lib/rapport/report.rb /^ def raw_cell_value(key, model, row_type)$/;" method line:84
|
79
|
+
report_generator lib/rapport/report.rb /^ def report_generator$/;" method line:41
|
80
|
+
report_name lib/rapport/report_generators/report_generator_csv.rb /^ def report_name$/;" method line:31
|
81
|
+
return a proc test/rapport/report_test.rb /^ share_should("return a proc") do$/;" shared_should line:77
|
82
|
+
return a proc never sending test/rapport/report_test.rb /^ share_should("return a proc never sending") do |symbols|$/;" shared_should line:98
|
83
|
+
return a proc sending test/rapport/report_test.rb /^ share_should("return a proc sending") do |symbols|$/;" shared_should line:89
|
84
|
+
return nil test/test_helper.rb /^ share_should("return nil") do$/;" shared_should line:42
|
85
|
+
return the lambda test/rapport/report_test.rb /^ share_should "return the lambda" do$/;" shared_should line:81
|
86
|
+
return value test/rapport/report_generator_test.rb /^ share_should "return value" do$/;" shared_should line:123
|
87
|
+
safe_proc lib/helpers.rb /^ def safe_proc(proc)$/;" method line:4
|
88
|
+
safe_send lib/helpers.rb /^ def safe_send(key)$/;" method line:14
|
89
|
+
section lib/rapport/report.rb /^ def section(section_data = {}, &block)$/;" method line:99
|
90
|
+
table_name lib/rapport/report.rb /^ def table_name$/;" method line:80
|
91
|
+
through_calculator lib/rapport/report.rb /^ def through_calculator(throughs, base_calculator)$/;" method line:144
|
92
|
+
to_model_class lib/rapport/report.rb /^ def to_model_class$/;" method line:76
|
93
|
+
to_s lib/rapport/report.rb /^ def to_s$/;" method line:93
|
94
|
+
with :through test/rapport/report_test.rb /^ share_setup("with :through") do$/;" shared_should line:73
|
95
|
+
wrap the lambda test/rapport/report_test.rb /^ share_should "wrap the lambda" do$/;" shared_should line:85
|
96
|
+
zip_output_file! lib/rapport/report_generators/report_generator_csv.rb /^ def zip_output_file!$/;" method line:23
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "rake", "0.8.7"
|
10
|
+
gem "shoulda", "~> 2.11.3", :require => nil
|
11
|
+
gem "mocha", "0.9.8", :require => nil
|
12
|
+
gem "shared_should", "0.8.1", :require => nil
|
13
|
+
gem "always_execute", "0.0.2", :require => nil
|
14
|
+
gem "bundler", "~> 1.0.0"
|
15
|
+
gem "jeweler", "~> 1.6.4"
|
16
|
+
gem "rcov", ">= 0"
|
17
|
+
gem "rubyzip", "0.9.5"
|
18
|
+
gem "fastercsv", "1.5.1"
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
always_execute (0.0.2)
|
5
|
+
shoulda
|
6
|
+
fastercsv (1.5.1)
|
7
|
+
git (1.2.5)
|
8
|
+
jeweler (1.6.4)
|
9
|
+
bundler (~> 1.0)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
rake
|
12
|
+
mocha (0.9.8)
|
13
|
+
rake
|
14
|
+
rake (0.8.7)
|
15
|
+
rcov (0.9.11)
|
16
|
+
rubyzip (0.9.5)
|
17
|
+
shared_should (0.8.1)
|
18
|
+
shoulda
|
19
|
+
shoulda (2.11.3)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
always_execute (= 0.0.2)
|
26
|
+
bundler (~> 1.0.0)
|
27
|
+
fastercsv (= 1.5.1)
|
28
|
+
jeweler (~> 1.6.4)
|
29
|
+
mocha (= 0.9.8)
|
30
|
+
rake (= 0.8.7)
|
31
|
+
rcov
|
32
|
+
rubyzip (= 0.9.5)
|
33
|
+
shared_should (= 0.8.1)
|
34
|
+
shoulda (~> 2.11.3)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Andrew Wheeler
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Rapport
|
2
|
+
|
3
|
+
Rapport allows you to create tabular reports with a DSL, which can be outputted via customizable formatters.
|
4
|
+
|
5
|
+
## Quick-Start
|
6
|
+
|
7
|
+
Include the Rapport::Report module in any object to allow it to generate reports in the pre-built formatters (csv, fake) or
|
8
|
+
in a custom formatter. Use an easy DSL to write all your reports once, and all your formats once, and then mix and match as you please.
|
9
|
+
|
10
|
+
### Creating a Report
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'rapport'
|
14
|
+
|
15
|
+
class OrderReport
|
16
|
+
include Rapport::Report
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
self.options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
# Add columns to the report with column(column_header, *args)
|
23
|
+
# Column Header is what appears in the report, and *args specify how to calculate the values using the model
|
24
|
+
#
|
25
|
+
# examples:
|
26
|
+
# column('column header')
|
27
|
+
# column('column header', :column_symbol)
|
28
|
+
# column('column header', :model_type => :column_symbol, ...)
|
29
|
+
#
|
30
|
+
# All equivalent:
|
31
|
+
# column 'Product ID', :map_to => lambda {|model| !model.nil? and model.respond_to?(:product_id) ? model.product_id : nil }
|
32
|
+
# column 'Product ID', :map_to => :product_id
|
33
|
+
# column 'Product ID', :product_id
|
34
|
+
column 'Product ID' # by default converts 'Product ID' to :product_id
|
35
|
+
|
36
|
+
# special calculation depending on type of model
|
37
|
+
column 'Price', :additional_charge => :amount,
|
38
|
+
:line_item => :price, # not required; defaults to :price
|
39
|
+
:special_line_item => lambda {|line_item| line_item.price - line_item.tax },
|
40
|
+
# Display price (in cents) as dollars
|
41
|
+
:format => :cents
|
42
|
+
|
43
|
+
# Navigate an object heirarchy using :through
|
44
|
+
column 'Order ID', :through -> :order, :map_to => :id
|
45
|
+
column 'Customer Name', :through => [:order, :customer], :map_to => lambda {|customer| "#{customer.first_name} #{customer.last_name}" }
|
46
|
+
|
47
|
+
# Implement each_model to specify which models belong to the report.
|
48
|
+
# The method should yield each model, and optionally specify the model type in order to customize how the value is calculated.
|
49
|
+
def each_model
|
50
|
+
AdditionalCharge.in_order_report.each do |additional_charge|
|
51
|
+
yield additional_charge # equivalent to yield additional_charge, :additional_charge
|
52
|
+
end
|
53
|
+
|
54
|
+
LineItem.in_order_report.each do |line_item|
|
55
|
+
if line_item.special?
|
56
|
+
yield line_item, :special_line_item # uses special price calculation above
|
57
|
+
else
|
58
|
+
yield line_item # equivalent to yield line_item, :line_item
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
### Running a Report
|
65
|
+
|
66
|
+
order_report = OrderReport.new(:format => 'csv', :output_dir => 'my_dir')
|
67
|
+
order_report.generate # generates a csv file named 'my_dir/OrderReport_{timestamp}.csv
|
68
|
+
|
69
|
+
order_report = OrderReport.new(:format => 'my_custom')
|
70
|
+
order_report.generate # generates the same report, but in your custom format
|
71
|
+
|
72
|
+
### Creating a Custom Format
|
73
|
+
module Rapport
|
74
|
+
class ReportGeneratorMyCustom
|
75
|
+
include ReportGenerator
|
76
|
+
|
77
|
+
generate_with do |report|
|
78
|
+
File.open(output_filename,'w') do |file|
|
79
|
+
file.puts(report.column_headers.join('|'))
|
80
|
+
report.each_row do |row|
|
81
|
+
file.puts(row.join('|'))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
## Copyright
|
90
|
+
|
91
|
+
Copyright (c) 2011 Andrew Wheeler. See LICENSE.txt for
|
92
|
+
further details.
|
93
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "rapport"
|
18
|
+
gem.homepage = "http://github.com/jawheeler/rapport"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Rapport Reporting}
|
21
|
+
gem.description = %Q{Allows you to create tablular reports and output them to various formats interchangeably.}
|
22
|
+
gem.email = "andrew.wheeler@bookrenter.com"
|
23
|
+
gem.authors = ["Andrew Wheeler"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/test_*.rb'
|
39
|
+
test.verbose = true
|
40
|
+
test.rcov_opts << '--exclude "gems/*"'
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "rapport #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.4
|
data/lib/helpers.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Rapport
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def safe_proc(proc)
|
7
|
+
lambda do |m|
|
8
|
+
begin
|
9
|
+
proc.call(m)
|
10
|
+
rescue NameError => ne
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def safe_send(key)
|
17
|
+
lambda{|m| !m.nil? and m.respond_to?(key) ? m.send(key) : nil }
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_underscore(value)
|
21
|
+
value.to_s.gsub(/\W/,'_').gsub(/(.)([A-Z])/,'\1_\2').gsub(/_+/,'_').downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_camel_case(value)
|
25
|
+
value.to_s.capitalize.gsub(/_(.)/){ $1.upcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
if Module.const_defined?("Rails")
|
30
|
+
@_logger ||= Rails.logger
|
31
|
+
else
|
32
|
+
@_logger ||= Logger.new(STDERR)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def logger=(logger)
|
37
|
+
@_logger = logger
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/lib/load_rapport.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
module Rapport
|
2
|
+
module Report
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
base.instance_variable_set(:@columns, [])
|
7
|
+
base.instance_variable_set(:@cell_calculators, {})
|
8
|
+
def base.inherited(subclass)
|
9
|
+
super(subclass)
|
10
|
+
subclass.instance_variable_set(:@columns, base.instance_variable_get(:@columns).dup )
|
11
|
+
subclass.instance_variable_set(:@cell_calculators, base.instance_variable_get(:@cell_calculators).dup )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
attr_accessor :row_data
|
17
|
+
|
18
|
+
def column(*args, &block)
|
19
|
+
raise ArgumentError.new("wrong number of arguments (#{args.length} for 1,2, or 3)") unless args.length >= 1 and args.length <= 3
|
20
|
+
header = args[0]
|
21
|
+
if args.length == 3
|
22
|
+
symbol = args[1].to_sym; options = args[2].to_hash
|
23
|
+
elsif args.length == 2
|
24
|
+
if args[1].is_a?(Symbol)
|
25
|
+
symbol = args[1]; options = {}
|
26
|
+
else
|
27
|
+
symbol = Rapport.format_underscore(header.downcase).to_sym; options = args[1].to_hash
|
28
|
+
end
|
29
|
+
end
|
30
|
+
add_calculator(symbol, options) unless options.nil?
|
31
|
+
instance_variable_get(:@columns) << [header, symbol]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def add_calculator(symbol, options)
|
37
|
+
instance_variable_get(:@cell_calculators)[symbol] = options
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :section_data, :current_model, :options
|
42
|
+
|
43
|
+
def row_data
|
44
|
+
self.class.row_data
|
45
|
+
end
|
46
|
+
|
47
|
+
def report_generator
|
48
|
+
@_report_generator ||= ReportGenerator.from(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate
|
52
|
+
report_generator.generate
|
53
|
+
end
|
54
|
+
|
55
|
+
def options
|
56
|
+
@options ||= {:format => 'fake'}
|
57
|
+
end
|
58
|
+
|
59
|
+
def each_row
|
60
|
+
each_model do |model, row_type|
|
61
|
+
self.current_model = model # For error reporting
|
62
|
+
yield formatted_row(model, row_type || Rapport.format_underscore(model.class.to_s))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def columns
|
67
|
+
if @_columns.nil?
|
68
|
+
@_columns = []
|
69
|
+
self.class.send(:instance_variable_get, :@columns).each {|col| @_columns << col.dup }
|
70
|
+
end
|
71
|
+
@_columns
|
72
|
+
end
|
73
|
+
|
74
|
+
def column_headers
|
75
|
+
@_column_headers ||= columns.map{|c| c[0]}
|
76
|
+
end
|
77
|
+
|
78
|
+
def column_symbols
|
79
|
+
@_column_symbols ||= columns.map{|c| c[1]}
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_model_class
|
83
|
+
@_to_model_class ||= Struct.new("#{self.class}Model", *column_symbols)
|
84
|
+
end
|
85
|
+
|
86
|
+
def table_name
|
87
|
+
@table_name ||= options[:report_table_name] || "reports_#{Rapport.format_underscore(self.class).sub(/_?report_?/,'')}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def raw_cell_value(key, model, row_type)
|
91
|
+
self.row_data[key] = cell_calculator_for(key,row_type).call(model)
|
92
|
+
end
|
93
|
+
|
94
|
+
def formatted_cell_value(key, model, row_type)
|
95
|
+
row_data[key] = raw_value = raw_cell_value(key, model, row_type)
|
96
|
+
report_generator.format(cell_format_for(key), raw_value)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
[self.class, options[:format]].compact.map{|p| p.to_s }.join(' ')
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def section(section_data = {}, &block)
|
106
|
+
self.section_data = section_data
|
107
|
+
yield
|
108
|
+
self.section_data = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def each_model(&block)
|
112
|
+
raise "#each_model has not been implemented"
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def formatted_row(model, row_type)
|
118
|
+
self.class.row_data = {}
|
119
|
+
out = column_symbols.map do |key|
|
120
|
+
formatted_cell_value(key, model, row_type)
|
121
|
+
end
|
122
|
+
self.class.row_data = {}
|
123
|
+
out
|
124
|
+
end
|
125
|
+
|
126
|
+
def _calculators
|
127
|
+
@_calculators ||= self.class.send(:instance_variable_get, :@cell_calculators).dup
|
128
|
+
end
|
129
|
+
|
130
|
+
def cell_calculator_for(key,row_type)
|
131
|
+
calculator = _calculators[key] ||= {}
|
132
|
+
|
133
|
+
if calculator[row_type].nil?
|
134
|
+
base_calculator = base_calculator(key,calculator)
|
135
|
+
calculator_for_row_type = if calculator[:through]
|
136
|
+
calculator[:through] = [calculator[:through]] unless calculator[:through].is_a?(Enumerable)
|
137
|
+
through_calculator(calculator[:through], base_calculator)
|
138
|
+
else
|
139
|
+
base_calculator
|
140
|
+
end
|
141
|
+
calculator[row_type] = calculator_for_row_type
|
142
|
+
elsif !calculator[row_type].respond_to?(:call)
|
143
|
+
row_type_key = calculator[row_type]
|
144
|
+
calculator[row_type] = Rapport.safe_send(row_type_key)
|
145
|
+
end
|
146
|
+
|
147
|
+
calculator[row_type]
|
148
|
+
end
|
149
|
+
|
150
|
+
def through_calculator(throughs, base_calculator)
|
151
|
+
lambda do |m|
|
152
|
+
path = m
|
153
|
+
throughs.each do |hint|
|
154
|
+
if path.respond_to?(hint)
|
155
|
+
new_path = path.send(hint)
|
156
|
+
path = new_path unless new_path.nil?
|
157
|
+
end
|
158
|
+
end
|
159
|
+
base_calculator.call(path)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def base_calculator(key, calculator)
|
164
|
+
base_key = key
|
165
|
+
if calculator[:map_to]
|
166
|
+
return Rapport.safe_proc(calculator[:map_to]) if calculator[:map_to].respond_to?(:call)
|
167
|
+
base_key = calculator[:map_to].to_sym
|
168
|
+
end
|
169
|
+
Rapport.safe_send(base_key)
|
170
|
+
end
|
171
|
+
|
172
|
+
def cell_format_for(key)
|
173
|
+
_calculators[key][:"format_#{options[:format]}"] || _calculators[key][:format]
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Rapport
|
2
|
+
module ReportGenerator
|
3
|
+
|
4
|
+
def self.from(report)
|
5
|
+
options = {:format => 'fake'}.merge(report.options)
|
6
|
+
Rapport.const_get("ReportGenerator#{Rapport.format_camel_case(options[:format].to_s)}").from(report)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
base.instance_variable_set(:@cell_formatter, CellFormatter.new)
|
12
|
+
def base.inherited(subclass)
|
13
|
+
super(subclass)
|
14
|
+
subclass.instance_variable_set(:@cell_formatter, base.instance_variable_get(:@cell_formatter).dup)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
# Override to customize the report generator based on the charactersistics of the report or its options
|
20
|
+
# Default implementation uses default constructor and assigns to the report attribute.
|
21
|
+
def from(report)
|
22
|
+
out = self.new
|
23
|
+
out.report = report
|
24
|
+
out
|
25
|
+
end
|
26
|
+
|
27
|
+
# generate_with {|report| ... }
|
28
|
+
# Provides a logging/error handling wrapper for the passed block. Automatically cretes report_generator.generate.
|
29
|
+
def generate_with(&block)
|
30
|
+
raise "Only one call to generate_with is permitted (or none if #generate is implemented)" if public_method_defined?(:generate)
|
31
|
+
define_method :generate do
|
32
|
+
raise "No report to generate!" if report.nil?
|
33
|
+
out = nil
|
34
|
+
begin
|
35
|
+
Rapport.logger.info("Generating #{report}...")
|
36
|
+
out = block.call(report)
|
37
|
+
Rapport.logger.info("Generated #{report}.")
|
38
|
+
rescue Exception => e
|
39
|
+
error = report.current_model.nil? ? '' : "While processing:\n\n#{report.current_model.inspect}\n\n"
|
40
|
+
error += "#{report} failed:\n\n#{e.message}\n\n#{e.backtrace.join("\n")}"
|
41
|
+
Rapport.logger.error(error)
|
42
|
+
Rapport.logger.mail(:error,error) if Rapport.logger.respond_to?(:mail)
|
43
|
+
end
|
44
|
+
out
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def cell_format(type, &block)
|
49
|
+
instance_variable_get(:@cell_formatter).add_cell_format(type, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_as(type, value)
|
53
|
+
instance_variable_get(:@cell_formatter).format(type, value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :report
|
58
|
+
|
59
|
+
def cell_formatter
|
60
|
+
@cell_formatter ||= self.class.instance_variable_get(:@cell_formatter).dup
|
61
|
+
end
|
62
|
+
|
63
|
+
def format(type, value)
|
64
|
+
cell_formatter.format(type, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def output_filename
|
68
|
+
@output_filename ||= File.join(@options[:output_dir] || '.', "#{report_name}_#{Time.now.strftime('%Y-%m-%d-%H%M%S')}.csv")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
class CellFormatter
|
74
|
+
|
75
|
+
def initialize(procs = {})
|
76
|
+
@procs = procs
|
77
|
+
end
|
78
|
+
|
79
|
+
def dup
|
80
|
+
CellFormatter.new(@procs.dup)
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_cell_format(type, &block)
|
84
|
+
proc = Proc.new(&block)
|
85
|
+
@procs[type] = proc
|
86
|
+
@procs[Rapport.format_underscore(type.to_s)] = proc if type.is_a?(Class)
|
87
|
+
|
88
|
+
# Support ActiveSupport::TimeWithZone automatically
|
89
|
+
if type == Time
|
90
|
+
add_cell_format(ActiveSupport::TimeWithZone, &block) rescue nil
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
def format(type,value)
|
96
|
+
if type.is_a?(Proc)
|
97
|
+
type.call(value)
|
98
|
+
else
|
99
|
+
proc = @procs[type] || @procs[value.class]
|
100
|
+
proc.nil? ? value : proc.call(value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|