htmldoc-rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +95 -0
- data/init.rb +1 -0
- data/lib/htmldoc-rails.rb +1 -0
- data/lib/htmldoc_rails.rb +20 -0
- data/lib/htmldoc_rails/controller.rb +97 -0
- data/lib/htmldoc_rails/htmldoc_ext.rb +24 -0
- data/lib/htmldoc_rails/version.rb +3 -0
- data/test/all.rb +32 -0
- data/test/helper.rb +136 -0
- data/test/rails_template_process.txt +21 -0
- data/test/test_render_pdf.rb +156 -0
- data/test/test_render_pdf_to_file.rb +78 -0
- data/test/views/render_pdf/_foo.html.erb +1 -0
- data/test/views/render_pdf/doc.html.erb +1 -0
- data/test/views/render_pdf/doc.rpdf +1 -0
- data/test/views/render_pdf/doc_with_partial.html.erb +3 -0
- data/test/views/render_pdf/no_arguments.html.erb +1 -0
- data/test/views/render_pdf_to_file/_foo.html.erb +1 -0
- data/test/views/render_pdf_to_file/doc.html.erb +1 -0
- data/test/views/render_pdf_to_file/doc_with_partial.html.erb +3 -0
- data/test/views/render_pdf_to_file/no_arguments.html.erb +1 -0
- metadata +153 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Elliot Winkler
|
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,69 @@
|
|
1
|
+
# htmldoc-rails
|
2
|
+
|
3
|
+
## Summary
|
4
|
+
|
5
|
+
Generate PDFs from your Rails views using [HTMLDoc](http://www.htmldoc.org).
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Let's say we've got this setup:
|
10
|
+
|
11
|
+
# controller
|
12
|
+
class FooController < ApplicationController
|
13
|
+
def bar
|
14
|
+
# render bar.html.erb
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# view - bar.html.erb
|
19
|
+
<p>Some content goes here</p>
|
20
|
+
|
21
|
+
Now let's say we want to give the user a regular HTML view when they go to `/foo/bar`, but a PDF file when they go to `/foo/bar.pdf`. We can use `respond_to` to differentiate between the two course of actions, but as for generating the PDF file itself, `htmldoc-rails` provides a special method called `render_pdf`. This method is responsible for piping a view through HTMLDoc and telling the action to return a PDF file. It also accepts options such as which view you want the PDF to be generated from, whether or not a download box should appear, and so on (you can find the possible options in the [documentation](http://mcmire.github.com/htmldoc-rails/HtmldocRails/Controller.html#render_pdf-instance_method)). Since we want to use the HTML view to generate the PDF, all we have to say is this:
|
22
|
+
|
23
|
+
# controller
|
24
|
+
class FooController < ApplicationController
|
25
|
+
def bar
|
26
|
+
respond_to do |wants|
|
27
|
+
wants.html
|
28
|
+
wants.pdf { render_pdf }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
## Prerequisites
|
34
|
+
|
35
|
+
* HTMLDoc 1.9.x
|
36
|
+
* htmldoc gem
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
First, you'll need to download, compile, and install the htmldoc executable. If you're on Mac or Linux, this is really easy. Just head on over to [the HTMLDoc website](http://www.htmldoc.org/software.php). You'll see a link to download 1.8, but don't even bother with that, since 1.8.27 was released way back in 2006 and doesn't support CSS or tables very well. These days new improvements are being done (however occasionally) on the 1.9.x branch, so you'll want to download the latest developer snapshot instead (it worked fine for us). Once you've done that, you should be able to just
|
41
|
+
|
42
|
+
./configure
|
43
|
+
make
|
44
|
+
sudo make install
|
45
|
+
|
46
|
+
If you happen to be on Windows, well, you aren't so lucky. If you're fine with using 1.8, you can find a pre-compiled copy either from the HTMLDoc website or from Cygwin. If you really want a version of 1.9, you'll have to look around -- possibly someone has been generous enough to put one out there.
|
47
|
+
|
48
|
+
Once you've got the htmldoc executable installed, you'll need the htmldoc Ruby gem, which provides a thin wrapper around the executable. Just
|
49
|
+
|
50
|
+
gem install htmldoc
|
51
|
+
|
52
|
+
Finally, you can install this gem:
|
53
|
+
|
54
|
+
* `gem install htmldoc-rails` (probably as root)
|
55
|
+
* Add `config.gem 'htmldoc-rails'` to your environment.rb file
|
56
|
+
|
57
|
+
## Compatibility
|
58
|
+
|
59
|
+
This gem has been tested successfully on Rails 2.1.2, 2.2.3, and 2.3.5 under Ruby 1.8.6, 1.8.7, and 1.9.1.
|
60
|
+
|
61
|
+
## I found a bug!
|
62
|
+
|
63
|
+
Great! File a [Github issue](http://github.com/mcmire/htmldoc-rails/issues) so that I am aware of the problem and I will try to fix it as soon as I can. If you're able to figure out a solution on your own, feel free to write a patch and send it to me, or fork the [project on Github](http://github.com/mcmire/htmldoc-rails) and send me a pull request. Bonus points if you write tests -- that helps me out a lot.
|
64
|
+
|
65
|
+
If you need to get in contact with me, you can [find me on Twitter](http://twitter.com/mcmire) or [send me an email](mailto:elliot.winkler@gmail.com).
|
66
|
+
|
67
|
+
## Author
|
68
|
+
|
69
|
+
(c) 2008-2010 Elliot Winkler. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + "/lib/htmldoc_rails/version"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.version = HtmldocRails::VERSION
|
10
|
+
gem.name = "htmldoc-rails"
|
11
|
+
gem.summary = %Q{Generate PDFs from your Rails views using HTMLDoc}
|
12
|
+
gem.description = %Q{Generate PDFs from your Rails views using HTMLDoc}
|
13
|
+
gem.email = "elliot.winkler@gmail.com"
|
14
|
+
gem.homepage = "http://github.com/mcmire/htmldoc-rails"
|
15
|
+
gem.authors = ["Elliot Winkler"]
|
16
|
+
gem.add_dependency "htmldoc"
|
17
|
+
unless ENV["AP_VERSION"]
|
18
|
+
gem.add_dependency "actionpack", "< 3.0"
|
19
|
+
end
|
20
|
+
gem.add_development_dependency "mcmire-protest"
|
21
|
+
gem.add_development_dependency "mcmire-matchy"
|
22
|
+
gem.add_development_dependency "mcmire-mocha"
|
23
|
+
gem.add_development_dependency "mocha-protest-integration"
|
24
|
+
gem.add_development_dependency "yard", ">= 0"
|
25
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
26
|
+
end
|
27
|
+
Jeweler::GemcutterTasks.new
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
30
|
+
end
|
31
|
+
|
32
|
+
require 'rake/testtask'
|
33
|
+
Rake::TestTask.new(:test) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/**/test_*.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace :test do
|
40
|
+
def run(*cmd)
|
41
|
+
cmd = cmd.flatten
|
42
|
+
options = Hash === cmd.last ? cmd.pop : {}
|
43
|
+
cmd.unshift("sudo") if options[:sudo]
|
44
|
+
puts cmd.join(" ")
|
45
|
+
system(*cmd)
|
46
|
+
exit if $? != 0
|
47
|
+
end
|
48
|
+
def run_gem(*cmd)
|
49
|
+
run "gem", cmd, :sudo => (ENV["GEM_PATH"] !~ /rvm/)
|
50
|
+
end
|
51
|
+
|
52
|
+
task :all do
|
53
|
+
require File.dirname(__FILE__) + '/test/all'
|
54
|
+
end
|
55
|
+
|
56
|
+
task :install_dependencies do
|
57
|
+
puts
|
58
|
+
puts "Installing dev test gems..."
|
59
|
+
puts
|
60
|
+
run_gem %w(install jeweler htmldoc mcmire-protest mcmire-matchy mcmire-mocha mocha-protest-integration)
|
61
|
+
for version in %w(2.1.2 2.2.3 2.3.5)
|
62
|
+
puts
|
63
|
+
puts "Installing rails v#{version}..."
|
64
|
+
puts
|
65
|
+
run_gem %w(install rails -v), version
|
66
|
+
end
|
67
|
+
puts
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
task :check_platform do
|
72
|
+
if RUBY_PLATFORM !~ /darwin|linux/
|
73
|
+
warn <<-EOT
|
74
|
+
Sorry, you can only run the tests if you're on Mac or Linux. This is because the
|
75
|
+
tests use the `file` command to detect file type, but to my knowledge this
|
76
|
+
command is only available on Mac or Linux.
|
77
|
+
EOT
|
78
|
+
exit 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
task :test => [:check_platform, :check_dependencies, :"check_dependencies:development"]
|
83
|
+
|
84
|
+
task :default => :test
|
85
|
+
|
86
|
+
begin
|
87
|
+
require 'yard'
|
88
|
+
YARD::Rake::YardocTask.new do |t|
|
89
|
+
t.options = ['--no-private']
|
90
|
+
end
|
91
|
+
rescue LoadError
|
92
|
+
task :yardoc do
|
93
|
+
abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
|
94
|
+
end
|
95
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'mcmire/render_htmldoc_pdf'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'htmldoc_rails'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'htmldoc_rails/htmldoc_ext'
|
2
|
+
require 'htmldoc_rails/controller'
|
3
|
+
|
4
|
+
module HtmldocRails
|
5
|
+
class << self
|
6
|
+
# @private
|
7
|
+
def action_view
|
8
|
+
ActionPack::VERSION::MAJOR == 1 ? ActionView::Base : ActionView::Template
|
9
|
+
end
|
10
|
+
def debug=(mode)
|
11
|
+
@debug = mode
|
12
|
+
end
|
13
|
+
def debug?
|
14
|
+
@debug
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
ActionController::Base.send(:include, HtmldocRails::Controller)
|
20
|
+
Mime::Type.register('application/pdf', :pdf)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module HtmldocRails
|
2
|
+
module Controller
|
3
|
+
# Runs the given view through HTMLDoc and sends the generated PDF data back
|
4
|
+
# to the browser.
|
5
|
+
#
|
6
|
+
# @param [Hash] options Various options
|
7
|
+
# @option options [Hash, String] :url (nil) The hash/url that represents which
|
8
|
+
# view you want to render. Passed straight to <tt>render()</tt>.
|
9
|
+
# @option options [Symbol] :as (:inline) The disposition; <tt>:inline</tt>
|
10
|
+
# renders the PDF in the browser, <tt>:attachment</tt> pops up a download
|
11
|
+
# box when the page loads. You can also set the disposition at runtime
|
12
|
+
# by appending <tt>?as=attachment|inline</tt> to the URL.
|
13
|
+
# @option options [String] :filename (nil) The default filename for the file being
|
14
|
+
# downloaded, assuming <tt>:as => :attachment</tt>.
|
15
|
+
# @option options [Hash] :htmldoc ({}) Options that will be passed to HTMLDoc
|
16
|
+
# when the PDF is rendered.
|
17
|
+
#
|
18
|
+
# @example Rendering default view of action as PDF
|
19
|
+
# render_pdf
|
20
|
+
# @example Rendering a specific view as PDF
|
21
|
+
# render_pdf :action => 'bar'
|
22
|
+
# @example Set a top-margin of 50px in the PDF and force a download box when the page loads
|
23
|
+
# render_pdf :action => "bar", :as => :attachment, :htmldoc => { :top => 50 }
|
24
|
+
#
|
25
|
+
def render_pdf(options={})
|
26
|
+
filename = options.delete(:filename)
|
27
|
+
disposition = (options.delete(:as) || params[:as] || :inline).to_sym
|
28
|
+
htmldoc_options = options.delete(:htmldoc) || {}
|
29
|
+
render_options = options.merge(options.delete(:url) || {})
|
30
|
+
if !render_options.include?(:layout)
|
31
|
+
render_options[:layout] = false
|
32
|
+
end
|
33
|
+
|
34
|
+
send_data_options = { :type => content_type }
|
35
|
+
send_data_options[:filename] = filename if filename
|
36
|
+
send_data_options[:disposition] = (disposition == :attachment) ? 'attachment' : 'inline'
|
37
|
+
|
38
|
+
# Make sure that the rendered PDF isn't cached
|
39
|
+
if request.env['HTTP_USER_AGENT'] =~ /msie/i
|
40
|
+
headers['Pragma'] = ''
|
41
|
+
headers['Cache-Control'] = ''
|
42
|
+
else
|
43
|
+
headers['Pragma'] = 'no-cache'
|
44
|
+
headers['Cache-Control'] = 'no-cache, must-revalidate'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Run view through PDF::HTMLDoc::View
|
48
|
+
html_content = render_to_string(render_options)
|
49
|
+
pdf_data = run_through_htmldoc(html_content, htmldoc_options)
|
50
|
+
unless pdf_data.blank?
|
51
|
+
send_data(pdf_data, send_data_options)
|
52
|
+
else
|
53
|
+
render :text => "HTMLDoc had trouble parsing the HTML to create the PDF."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Runs the given view through HTMLDoc and writes the generated PDF data
|
58
|
+
# to the file of your choice.
|
59
|
+
#
|
60
|
+
# @param [String] filename The outfile
|
61
|
+
# @param [Hash, String] render_options The hash/url that represents which
|
62
|
+
# view you want to render. Passed straight to <tt>render()</tt>.
|
63
|
+
# @param [Hash] htmldoc_options Options that will be passed to HTMLDoc
|
64
|
+
# when the PDF is rendered.
|
65
|
+
#
|
66
|
+
# @example Render the 'blah' view to 'foo.pdf'
|
67
|
+
# render_pdf_to_file 'foo.pdf', :action => 'blah'
|
68
|
+
# @example Same thing, but set a top-margin of 50px in the PDF
|
69
|
+
# render_pdf_to_file 'foo.pdf', { :action => 'blah' }, { :top => 50 }
|
70
|
+
#
|
71
|
+
def render_pdf_to_file(filename, render_options={}, htmldoc_options={})
|
72
|
+
if !render_options.include?(:layout)
|
73
|
+
render_options[:layout] = false
|
74
|
+
end
|
75
|
+
headers["Content-Disposition"] = "inline"
|
76
|
+
html_content = render_to_string(render_options)
|
77
|
+
pdf_data = run_through_htmldoc(html_content, htmldoc_options)
|
78
|
+
File.open(filename, 'w') {|f| f.write(pdf_data) }
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def run_through_htmldoc(content, htmldoc_options)
|
83
|
+
PDF::HTMLDoc.create do |pdf|
|
84
|
+
PDF::HTMLDoc::DEFAULT_OPTIONS.merge(htmldoc_options).each do |name, value|
|
85
|
+
pdf.set_option(name, value)
|
86
|
+
end
|
87
|
+
# don't know what this does??
|
88
|
+
pdf.set_option :path, Pathname.new(File.join(RAILS_ROOT, 'public')).realpath.to_s
|
89
|
+
pdf << content
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def content_type
|
94
|
+
Mime::Type.lookup_by_extension('pdf')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Extend 'htmldoc' gem to support the edge releases of the htmldoc executable
|
2
|
+
# and to make usage a bit more intuitive
|
3
|
+
module PDF
|
4
|
+
class HTMLDoc
|
5
|
+
@@basic_options << :top unless @@basic_options.include?(:top)
|
6
|
+
@@basic_options << :bottom unless @@basic_options.include?(:bottom)
|
7
|
+
@@all_options = @@basic_options + @@extra_options
|
8
|
+
|
9
|
+
DEFAULT_OPTIONS = {
|
10
|
+
:bodycolor => 'white',
|
11
|
+
:toc => false,
|
12
|
+
:portrait => true,
|
13
|
+
:continuous => true,
|
14
|
+
:footer => '...',
|
15
|
+
:header => '...',
|
16
|
+
:links => false,
|
17
|
+
:webpage => true,
|
18
|
+
:left => '50',
|
19
|
+
:right => '50',
|
20
|
+
:top => '90',
|
21
|
+
:size => 'Letter'
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
data/test/all.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
proj_dir = File.expand_path(File.dirname(__FILE__) + '/..')
|
4
|
+
lib_dir = "#{proj_dir}/lib"
|
5
|
+
test_dir = "#{proj_dir}/test"
|
6
|
+
|
7
|
+
$:.unshift(lib_dir, test_dir)
|
8
|
+
|
9
|
+
files = Dir["#{test_dir}/test_*.rb"]
|
10
|
+
|
11
|
+
pids = []
|
12
|
+
|
13
|
+
%w(2.1.2 2.2.3 2.3.5).each do |version|
|
14
|
+
(class << $stdout; self; end).class_eval <<-EOT, __FILE__, __LINE__
|
15
|
+
def puts(*str)
|
16
|
+
str = str.flatten
|
17
|
+
str[0] = "#{version})) \#{str[0]}"
|
18
|
+
super(*str)
|
19
|
+
end
|
20
|
+
def print(*str)
|
21
|
+
str = str.flatten
|
22
|
+
str[0] = "#{version})) \#{str[0]}"
|
23
|
+
super(*str)
|
24
|
+
end
|
25
|
+
EOT
|
26
|
+
|
27
|
+
pids << fork do
|
28
|
+
ENV["AP_VERSION"] = version
|
29
|
+
files.each {|file| require file }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
pids.each {|pid| Process.wait(pid) }
|
data/test/helper.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
gem 'mcmire-protest'
|
6
|
+
require 'protest'
|
7
|
+
gem 'mcmire-mocha'
|
8
|
+
require 'mocha'
|
9
|
+
# This must be required before matchy since matchy patches
|
10
|
+
# the current test case's #run method, however mocha-protest-integration
|
11
|
+
# completely overrides it
|
12
|
+
require 'mocha-protest-integration'
|
13
|
+
#gem 'mcmire-matchy'
|
14
|
+
$:.unshift "/Users/elliot/code/github/forks/matchy/lib"
|
15
|
+
require 'matchy'
|
16
|
+
|
17
|
+
# Matchy/Protest integration
|
18
|
+
Matchy.adapter :protest, "Protest" do
|
19
|
+
def assertions_module; Test::Unit::Assertions; end
|
20
|
+
def test_case_class; Protest::TestCase; end
|
21
|
+
def assertion_failed_error; Protest::AssertionFailed; end
|
22
|
+
end
|
23
|
+
Matchy.use(:protest)
|
24
|
+
|
25
|
+
Protest.report_with :documentation
|
26
|
+
Protest::Utils::BacktraceFilter::ESCAPE_PATHS << %r|test/unit| << %r|matchy| << %r|mocha-protest-integration| << %r|actionpack|
|
27
|
+
#Protest::Utils::BacktraceFilter::ESCAPE_PATHS.clear
|
28
|
+
|
29
|
+
#----
|
30
|
+
|
31
|
+
if ENV["AP_VERSION"]
|
32
|
+
gem 'actionpack', "= #{ENV["AP_VERSION"]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'action_controller'
|
36
|
+
|
37
|
+
# Since ActionController::TestCase in 2.3.5 tries to require Mocha
|
38
|
+
# and replaces it with a stub on failure we have to save a reference
|
39
|
+
# to the current class and then replace the stub Mocha with the real Mocha
|
40
|
+
CurrentMocha = Mocha
|
41
|
+
require 'action_controller/test_case'
|
42
|
+
Object.const_set :Mocha, CurrentMocha
|
43
|
+
|
44
|
+
require 'action_controller/integration'
|
45
|
+
require 'action_view'
|
46
|
+
require 'action_pack/version'
|
47
|
+
require 'active_support/version'
|
48
|
+
|
49
|
+
puts
|
50
|
+
puts " -- Using ActionPack v#{ActionPack::VERSION::STRING}, on Ruby #{RUBY_VERSION}."
|
51
|
+
puts
|
52
|
+
|
53
|
+
RAILS_ROOT = File.expand_path(File.dirname(__FILE__))
|
54
|
+
RAILS_ENV = "test"
|
55
|
+
|
56
|
+
#logger = Logger.new(STDOUT)
|
57
|
+
#logger.level = Logger::DEBUG
|
58
|
+
#ActionController::Base.logger = logger
|
59
|
+
|
60
|
+
# Disable sessions so we don't get a 'key is required to write a cookie
|
61
|
+
# containing the session data' error
|
62
|
+
ActionController::Base.session_store = nil
|
63
|
+
|
64
|
+
module Protest
|
65
|
+
module Rails
|
66
|
+
# Copied from ActionController::TestCase
|
67
|
+
class FunctionalTestCase < ::Protest::TestCase
|
68
|
+
include ActionController::TestProcess
|
69
|
+
|
70
|
+
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
|
71
|
+
# (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
|
72
|
+
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
|
73
|
+
# than 0.0.0.0.
|
74
|
+
#
|
75
|
+
# The exception is stored in the exception accessor for further inspection.
|
76
|
+
module RaiseActionExceptions
|
77
|
+
attr_accessor :exception
|
78
|
+
|
79
|
+
def rescue_action(e)
|
80
|
+
self.exception = e
|
81
|
+
|
82
|
+
if request.remote_addr == "0.0.0.0"
|
83
|
+
raise(e)
|
84
|
+
else
|
85
|
+
super(e)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
@@controller_class = nil
|
91
|
+
|
92
|
+
class << self
|
93
|
+
def controller_class=(new_class)
|
94
|
+
prepare_controller_class(new_class)
|
95
|
+
write_inheritable_attribute(:controller_class, new_class)
|
96
|
+
end
|
97
|
+
|
98
|
+
def controller_class
|
99
|
+
if current_controller_class = read_inheritable_attribute(:controller_class)
|
100
|
+
current_controller_class
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def prepare_controller_class(new_class)
|
105
|
+
new_class.send :include, RaiseActionExceptions
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :controller, :request, :response
|
110
|
+
|
111
|
+
setup do
|
112
|
+
@controller = self.class.controller_class.new
|
113
|
+
@controller.request = @request = ActionController::TestRequest.new
|
114
|
+
@response = ActionController::TestResponse.new
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
require 'htmldoc'
|
121
|
+
|
122
|
+
#----
|
123
|
+
|
124
|
+
# This is only here so our template handler won't complain
|
125
|
+
module ApplicationHelper; end
|
126
|
+
|
127
|
+
ActionController::Routing::Routes.draw do |map|
|
128
|
+
map.connect ':controller/:action'
|
129
|
+
end
|
130
|
+
|
131
|
+
require 'htmldoc-rails'
|
132
|
+
|
133
|
+
# This has to be set after we require htmldoc-rails and consequently
|
134
|
+
# after rpdf is added to the template handlers, that way the Template objects
|
135
|
+
# have rpdf stored as the extension
|
136
|
+
ActionController::Base.view_paths = File.expand_path(File.dirname(__FILE__) + '/views')
|
@@ -0,0 +1,21 @@
|
|
1
|
+
AV::Template#initialize("/full/path/to/render_pdf/doc.rpdf")
|
2
|
+
- AV::Template#split("/full/path/to/render_pdf/doc.rpdf")
|
3
|
+
- AV::Template#valid_extension?("rpdf") #=> [base_path = "/full/path/to/render_pdf/", name = "doc", format = nil, extension = "rpdf"]
|
4
|
+
|
5
|
+
AC::Base#render(options = {:action => 'doc.rpdf', :layout => false}, extra_options = {}):
|
6
|
+
- AC::Base#pick_layout(options = {:action => 'doc.rpdf', :layout => false}) #=> nil
|
7
|
+
- AC::Base#default_template_name(action_name = "doc.rpdf") #=> "render_pdf/doc.rpdf"
|
8
|
+
- AC::Base#render_for_file(action_name = "render_pdf/doc.rpdf", status = nil, layout = nil, locals = {})
|
9
|
+
- AV::Base#render(options = {:file => "render_pdf/doc.rpdf", :locals => {}, :layout => nil}, local_assigns = {})
|
10
|
+
- AV::Base#_pick_template(template_path = "render_pdf/doc.rpdf") #=> AV::Template.new("/full/path/to/render_pdf/doc.rpdf")
|
11
|
+
- AV::Template#render_template(view, locals = {})
|
12
|
+
- AV::Template#render(view, locals = {}) # in Renderable
|
13
|
+
- AV::Template#compile(locals = {}) # in Renderable
|
14
|
+
- AV::Template#recompile?(render_symbol) #=> true # in Renderable
|
15
|
+
- AV::PathSet::Path.eager_load_templates? #=> false since config.cache_classes is false
|
16
|
+
- AV::Template#compile!(render_symbol, local_assigns = {}) # in Renderable
|
17
|
+
- AV::Template#compiled_source #=> "the template" # Renderable
|
18
|
+
- AV::Template#handler
|
19
|
+
- AV::Template.handler_class_for_extension(extension = "rpdf") #=> should return PDF::HTMLDoc::View?? # in TemplateHandlers
|
20
|
+
- PDF::HTMLDoc::View#call #=> "PDF::HTMLDoc::View.new(self).render(template, local_assigns)"
|
21
|
+
- PDF::HTMLDoc::View.new(view).render(template, local_assigns = {})
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class RenderPdfController < ActionController::Base
|
4
|
+
def default
|
5
|
+
render_pdf :action => 'doc.html.erb'
|
6
|
+
end
|
7
|
+
def default_with_layout
|
8
|
+
render_pdf :action => 'doc.html.erb', :layout => "layout"
|
9
|
+
end
|
10
|
+
def download
|
11
|
+
render_pdf :url => {:action => 'doc.html.erb'}, :as => :attachment
|
12
|
+
end
|
13
|
+
def download_with_filename
|
14
|
+
render_pdf :url => {:action => 'doc.html.erb'}, :as => :attachment, :filename => "foo.pdf"
|
15
|
+
end
|
16
|
+
def with_just_filename
|
17
|
+
render_pdf :url => {:action => 'doc.html.erb'}, :filename => "foo.pdf"
|
18
|
+
end
|
19
|
+
def inline_with_filename
|
20
|
+
render_pdf :url => {:action => 'doc.html.erb'}, :as => :inline, :filename => "foo.pdf"
|
21
|
+
end
|
22
|
+
def with_htmldoc_options
|
23
|
+
render_pdf :action => 'doc.html.erb', :htmldoc => {:top => 10, :left => 10}
|
24
|
+
end
|
25
|
+
def with_no_url
|
26
|
+
render_pdf
|
27
|
+
end
|
28
|
+
def partial
|
29
|
+
render_pdf :action => 'doc_with_partial.html.erb'
|
30
|
+
end
|
31
|
+
def responder
|
32
|
+
respond_to do |wants|
|
33
|
+
wants.html { render :text => "HTML" }
|
34
|
+
wants.pdf { render_pdf :action => 'doc.html.erb' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
def no_arguments
|
38
|
+
render_pdf
|
39
|
+
end
|
40
|
+
def rpdf
|
41
|
+
render_pdf :action => 'doc.rpdf'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module RenderPdfHelper; end
|
46
|
+
|
47
|
+
Protest::Rails::FunctionalTestCase.describe("render_pdf") do
|
48
|
+
self.controller_class = RenderPdfController
|
49
|
+
|
50
|
+
def visit(url, options={})
|
51
|
+
get(url, options)
|
52
|
+
#puts "Headers:"
|
53
|
+
#pp :headers => response.headers
|
54
|
+
#puts "Response body:"
|
55
|
+
#puts response.body unless response.success?
|
56
|
+
end
|
57
|
+
|
58
|
+
def should_render_a_pdf
|
59
|
+
response.content_type.should == 'application/pdf'
|
60
|
+
tempfile = Tempfile.new("test_htmldoc_rails")
|
61
|
+
tempfile.write(response.body)
|
62
|
+
`file -Ib "#{tempfile.path}"`.chomp.should == "application/pdf"
|
63
|
+
end
|
64
|
+
|
65
|
+
test "converts a view into a PDF with the right content type" do
|
66
|
+
visit :default
|
67
|
+
should_render_a_pdf
|
68
|
+
end
|
69
|
+
test "returns an error if the PDF file couldn't be generated for some reason" do
|
70
|
+
PDF::HTMLDoc.stubs(:create).returns(nil)
|
71
|
+
visit :default
|
72
|
+
response.body.should =~ /HTMLDoc had trouble parsing the HTML to create the PDF/
|
73
|
+
end
|
74
|
+
test ":url option is required" do
|
75
|
+
lambda { visit :with_no_url }.should raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
test "the PDF file is rendered with no layout by default" do
|
79
|
+
controller.stubs(:render_to_string).returns("")
|
80
|
+
visit :default
|
81
|
+
controller.should have_received(:render_to_string).with(has_entry(:layout, false))
|
82
|
+
end
|
83
|
+
test "the PDF file is rendered with a layout if one was specified" do
|
84
|
+
controller.stubs(:render_to_string).returns("")
|
85
|
+
visit :default_with_layout
|
86
|
+
controller.should have_received(:render_to_string).with(has_entry(:layout, "layout"))
|
87
|
+
end
|
88
|
+
|
89
|
+
test "renders the PDF in the browser by default" do
|
90
|
+
visit :default
|
91
|
+
response.headers["Content-Disposition"].should == "inline"
|
92
|
+
end
|
93
|
+
test ":as => :attachment option gives the user a download box" do
|
94
|
+
visit :download
|
95
|
+
response.headers["Content-Disposition"].should == "attachment"
|
96
|
+
end
|
97
|
+
test ":filename option when disposition is attachment pre-sets the filename that the user will download the PDF as" do
|
98
|
+
visit :download_with_filename
|
99
|
+
response.headers["Content-Disposition"].should == 'attachment; filename="foo.pdf"'
|
100
|
+
end
|
101
|
+
test ":filename option without disposition does not auto-set disposition to attachment" do
|
102
|
+
visit :with_just_filename
|
103
|
+
response.headers["Content-Disposition"].should == 'inline; filename="foo.pdf"'
|
104
|
+
end
|
105
|
+
test ":filename option when disposition is inline sets filename" do
|
106
|
+
visit :inline_with_filename
|
107
|
+
response.headers["Content-Disposition"].should == 'inline; filename="foo.pdf"'
|
108
|
+
end
|
109
|
+
test "?as=attachment in the querystring gives the user a download box" do
|
110
|
+
visit :download, :as => "attachment"
|
111
|
+
response.headers["Content-Disposition"].should == "attachment"
|
112
|
+
end
|
113
|
+
test "?as=inline in the querystring renders the PDF in the browser" do
|
114
|
+
visit :default, :as => "inline"
|
115
|
+
#response.success?.should == true
|
116
|
+
response.headers["Content-Disposition"].should == "inline"
|
117
|
+
end
|
118
|
+
|
119
|
+
test "sends headers to ensure that the PDF file isn't cached" do
|
120
|
+
visit :default
|
121
|
+
response.headers["Pragma"].should == "no-cache"
|
122
|
+
response.headers["Cache-Control"].should == 'no-cache, must-revalidate'
|
123
|
+
end
|
124
|
+
test "doesn't ensure PDF file isn't cached on IE" do
|
125
|
+
request.env['HTTP_USER_AGENT'] = "msie"
|
126
|
+
visit :default
|
127
|
+
response.headers["Pragma"].should == ""
|
128
|
+
response.headers["Cache-Control"].should == ""
|
129
|
+
end
|
130
|
+
|
131
|
+
test "forwards :htmldoc options straight to HTMLDoc" do
|
132
|
+
pdf = PDF::HTMLDoc.new
|
133
|
+
pdf.stubs(:set_option)
|
134
|
+
PDF::HTMLDoc.stubs(:create).returns("").yields(pdf)
|
135
|
+
visit :with_htmldoc_options
|
136
|
+
pdf.should have_received(:set_option).with(:left, 10)
|
137
|
+
pdf.should have_received(:set_option).with(:top, 10)
|
138
|
+
end
|
139
|
+
|
140
|
+
test "rendering a partial within a view works" do
|
141
|
+
visit :partial
|
142
|
+
should_render_a_pdf
|
143
|
+
end
|
144
|
+
|
145
|
+
test "rendering a view in a responder works" do
|
146
|
+
request.accept = "application/pdf"
|
147
|
+
visit :responder
|
148
|
+
should_render_a_pdf
|
149
|
+
end
|
150
|
+
|
151
|
+
test "renders a file named after the action if passed no arguments" do
|
152
|
+
visit :no_arguments
|
153
|
+
should_render_a_pdf
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class RenderPdfToFileController < ActionController::Base
|
4
|
+
def default
|
5
|
+
render_pdf_to_file "/tmp/htmldoc-rails.test.pdf", :action => 'doc.html.erb'
|
6
|
+
render :nothing => true
|
7
|
+
end
|
8
|
+
def default_with_layout
|
9
|
+
render_pdf_to_file "/tmp/htmldoc-rails.test.pdf", :action => 'doc.html.erb', :layout => "layout"
|
10
|
+
render :nothing => true
|
11
|
+
end
|
12
|
+
def with_htmldoc_options
|
13
|
+
render_pdf_to_file "/tmp/htmldoc-rails.test.pdf", {:action => 'doc.html.erb'}, :top => 10, :left => 10
|
14
|
+
render :nothing => true
|
15
|
+
end
|
16
|
+
def partial
|
17
|
+
render_pdf_to_file "/tmp/htmldoc-rails.test.pdf", :action => 'doc_with_partial.html.erb'
|
18
|
+
render :nothing => true
|
19
|
+
end
|
20
|
+
def no_arguments
|
21
|
+
render_pdf_to_file "/tmp/htmldoc-rails.test.pdf"
|
22
|
+
render :nothing => true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module RenderPdfToFileHelper; end
|
27
|
+
|
28
|
+
Protest::Rails::FunctionalTestCase.describe("render_pdf_to_file") do
|
29
|
+
self.controller_class = RenderPdfToFileController
|
30
|
+
|
31
|
+
def visit(url, options={})
|
32
|
+
get(url, options)
|
33
|
+
#puts "Headers:"
|
34
|
+
#pp :headers => response.headers
|
35
|
+
#puts "Response body:"
|
36
|
+
#puts response.body unless response.success?
|
37
|
+
end
|
38
|
+
|
39
|
+
def should_render_a_pdf
|
40
|
+
File.exists?("/tmp/htmldoc-rails.test.pdf").should == true
|
41
|
+
`file -Ib "/tmp/htmldoc-rails.test.pdf"`.chomp.should == "application/pdf"
|
42
|
+
end
|
43
|
+
|
44
|
+
test "writes the rendered PDF to the given file" do
|
45
|
+
visit :default
|
46
|
+
should_render_a_pdf
|
47
|
+
end
|
48
|
+
test "sends the PDF straight to the browser" do
|
49
|
+
visit :default
|
50
|
+
response.headers["Content-Disposition"].should == "inline"
|
51
|
+
end
|
52
|
+
test "forwards :htmldoc options straight to HTMLDoc" do
|
53
|
+
pdf = PDF::HTMLDoc.new
|
54
|
+
pdf.stubs(:set_option)
|
55
|
+
PDF::HTMLDoc.stubs(:create).returns("").yields(pdf)
|
56
|
+
visit :with_htmldoc_options
|
57
|
+
pdf.should have_received(:set_option).with(:left, 10)
|
58
|
+
pdf.should have_received(:set_option).with(:top, 10)
|
59
|
+
end
|
60
|
+
test "renders the PDF in no layout by default" do
|
61
|
+
controller.stubs(:render_to_string).returns("")
|
62
|
+
visit :default
|
63
|
+
controller.should have_received(:render_to_string).with(has_entry(:layout, false))
|
64
|
+
end
|
65
|
+
test "renders the PDF in the layout that's specified" do
|
66
|
+
controller.stubs(:render_to_string).returns("")
|
67
|
+
visit :default_with_layout
|
68
|
+
controller.should have_received(:render_to_string).with(has_entry(:layout, "layout"))
|
69
|
+
end
|
70
|
+
test "rendering a partial within a view works" do
|
71
|
+
visit :partial
|
72
|
+
should_render_a_pdf
|
73
|
+
end
|
74
|
+
test "renders a file named after the action if passed no arguments" do
|
75
|
+
visit :no_arguments
|
76
|
+
should_render_a_pdf
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Some more stuff.</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>This is some <%= "text" %> and stuff</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>This is some <%= "text" %> and stuff</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
Alright here we go
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>Some more stuff.</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
<p>This is some <%= "text" %> and stuff</p>
|
@@ -0,0 +1 @@
|
|
1
|
+
Alright here we go
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: htmldoc-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Elliot Winkler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-11 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: htmldoc
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: actionpack
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - <
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "3.0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: mcmire-protest
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: mcmire-matchy
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mcmire-mocha
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: mocha-protest-integration
|
67
|
+
type: :development
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: yard
|
77
|
+
type: :development
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0"
|
84
|
+
version:
|
85
|
+
description: Generate PDFs from your Rails views using HTMLDoc
|
86
|
+
email: elliot.winkler@gmail.com
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files:
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
files:
|
95
|
+
- .gitignore
|
96
|
+
- LICENSE
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- init.rb
|
100
|
+
- lib/htmldoc-rails.rb
|
101
|
+
- lib/htmldoc_rails.rb
|
102
|
+
- lib/htmldoc_rails/controller.rb
|
103
|
+
- lib/htmldoc_rails/htmldoc_ext.rb
|
104
|
+
- lib/htmldoc_rails/version.rb
|
105
|
+
- test/all.rb
|
106
|
+
- test/helper.rb
|
107
|
+
- test/rails_template_process.txt
|
108
|
+
- test/test_render_pdf.rb
|
109
|
+
- test/test_render_pdf_to_file.rb
|
110
|
+
- test/tmp/out.html
|
111
|
+
- test/tmp/out.pdf
|
112
|
+
- test/views/render_pdf/_foo.html.erb
|
113
|
+
- test/views/render_pdf/doc.html.erb
|
114
|
+
- test/views/render_pdf/doc.rpdf
|
115
|
+
- test/views/render_pdf/doc_with_partial.html.erb
|
116
|
+
- test/views/render_pdf/no_arguments.html.erb
|
117
|
+
- test/views/render_pdf_to_file/_foo.html.erb
|
118
|
+
- test/views/render_pdf_to_file/doc.html.erb
|
119
|
+
- test/views/render_pdf_to_file/doc_with_partial.html.erb
|
120
|
+
- test/views/render_pdf_to_file/no_arguments.html.erb
|
121
|
+
has_rdoc: true
|
122
|
+
homepage: http://github.com/mcmire/htmldoc-rails
|
123
|
+
licenses: []
|
124
|
+
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options:
|
127
|
+
- --charset=UTF-8
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: "0"
|
135
|
+
version:
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: "0"
|
141
|
+
version:
|
142
|
+
requirements: []
|
143
|
+
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 1.3.5
|
146
|
+
signing_key:
|
147
|
+
specification_version: 3
|
148
|
+
summary: Generate PDFs from your Rails views using HTMLDoc
|
149
|
+
test_files:
|
150
|
+
- test/all.rb
|
151
|
+
- test/helper.rb
|
152
|
+
- test/test_render_pdf.rb
|
153
|
+
- test/test_render_pdf_to_file.rb
|