adzap-wicked_pdf 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +15 -0
  3. data/.gitignore +21 -0
  4. data/.rubocop.yml +22 -0
  5. data/.rubocop_todo.yml +63 -0
  6. data/.travis.yml +30 -0
  7. data/CHANGELOG.md +135 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +446 -0
  11. data/Rakefile +31 -0
  12. data/gemfiles/4.2.gemfile +6 -0
  13. data/gemfiles/5.0.gemfile +6 -0
  14. data/gemfiles/5.1.gemfile +6 -0
  15. data/gemfiles/5.2.gemfile +9 -0
  16. data/gemfiles/rails_edge.gemfile +9 -0
  17. data/generators/wicked_pdf/templates/wicked_pdf.rb +21 -0
  18. data/generators/wicked_pdf/wicked_pdf_generator.rb +7 -0
  19. data/init.rb +2 -0
  20. data/lib/generators/wicked_pdf_generator.rb +6 -0
  21. data/lib/wicked_pdf.rb +29 -0
  22. data/lib/wicked_pdf/asset_helper.rb +141 -0
  23. data/lib/wicked_pdf/binary.rb +56 -0
  24. data/lib/wicked_pdf/command.rb +52 -0
  25. data/lib/wicked_pdf/document.rb +47 -0
  26. data/lib/wicked_pdf/middleware.rb +101 -0
  27. data/lib/wicked_pdf/option_parser.rb +220 -0
  28. data/lib/wicked_pdf/pdf_helper.rb +17 -0
  29. data/lib/wicked_pdf/progress.rb +33 -0
  30. data/lib/wicked_pdf/railtie.rb +19 -0
  31. data/lib/wicked_pdf/renderer.rb +121 -0
  32. data/lib/wicked_pdf/tempfile.rb +13 -0
  33. data/lib/wicked_pdf/version.rb +3 -0
  34. data/test/dummy/app/assets/javascripts/application.js +16 -0
  35. data/test/dummy/app/assets/javascripts/wicked.js +1 -0
  36. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  37. data/test/dummy/app/assets/stylesheets/wicked.css +1 -0
  38. data/test/dummy/config/database.yml +3 -0
  39. data/test/dummy/config/routes.rb +5 -0
  40. data/test/dummy/log/.gitignore +1 -0
  41. data/test/dummy/public/favicon.ico +0 -0
  42. data/test/fixtures/database.yml +4 -0
  43. data/test/fixtures/document_with_long_line.html +16 -0
  44. data/test/fixtures/wicked.css +1 -0
  45. data/test/fixtures/wicked.js +1 -0
  46. data/test/functional/pdf_helper_test.rb +61 -0
  47. data/test/functional/wicked_pdf_asset_helper_test.rb +118 -0
  48. data/test/test_helper.rb +33 -0
  49. data/test/unit/wicked_pdf_binary_test.rb +52 -0
  50. data/test/unit/wicked_pdf_command_test.rb +4 -0
  51. data/test/unit/wicked_pdf_document_test.rb +60 -0
  52. data/test/unit/wicked_pdf_option_parser_test.rb +128 -0
  53. data/test/unit/wicked_pdf_renderer_test.rb +43 -0
  54. data/test/unit/wicked_pdf_test.rb +8 -0
  55. data/test/unit/wkhtmltopdf_location_test.rb +50 -0
  56. data/wicked_pdf.gemspec +38 -0
  57. metadata +249 -0
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require 'rails/version'
5
+ require 'bundler/gem_tasks'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => [:test, :rubocop]
9
+
10
+ desc 'Test the wicked_pdf plugin.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Run RuboCop'
19
+ task :rubocop do
20
+ require 'rubocop/rake_task'
21
+ RuboCop::RakeTask.new
22
+ end
23
+
24
+ desc 'Generate documentation for the wicked_pdf gem.'
25
+ RDoc::Task.new(:rdoc) do |rdoc|
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = 'WickedPdf'
28
+ rdoc.options << '--line-numbers' << '--inline-source'
29
+ rdoc.rdoc_files.include('README.md')
30
+ rdoc.rdoc_files.include('lib/**/*.rb')
31
+ end
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rdoc'
4
+ gem 'rails', '~> 4.2.0'
5
+
6
+ gemspec path: '../'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rdoc'
4
+ gem 'rails', '~> 5.0.0'
5
+
6
+ gemspec path: '../'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rdoc'
4
+ gem 'rails', '~> 5.1.0'
5
+
6
+ gemspec path: '../'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rdoc'
4
+ gem 'rails', '~> 5.2'
5
+ gem 'sqlite3', '~> 1.3.6'
6
+ gem 'bootsnap' # required to run `rake test` in Rails 5.2
7
+ gem 'mocha', '= 1.3' # newer versions blow up
8
+
9
+ gemspec path: '../'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rdoc'
4
+ gem 'rails', git: 'https://github.com/rails/rails.git'
5
+ gem 'sqlite3', '~> 1.3.6'
6
+ gem 'bootsnap' # required to run `rake test` in Rails 6.0
7
+ gem 'mocha', '= 1.3' # newer versions blow up
8
+
9
+ gemspec path: '../'
@@ -0,0 +1,21 @@
1
+ # WickedPDF Global Configuration
2
+ #
3
+ # Use this to set up shared configuration options for your entire application.
4
+ # Any of the configuration options shown here can also be applied to single
5
+ # models by passing arguments to the `render :pdf` call.
6
+ #
7
+ # To learn more, check out the README:
8
+ #
9
+ # https://github.com/adzap/wicked_pdf/blob/master/README.md
10
+
11
+ WickedPdf.config = {
12
+ # Path to the wkhtmltopdf executable: This usually isn't needed if using
13
+ # one of the wkhtmltopdf-binary family of gems.
14
+ # exe_path: '/usr/local/bin/wkhtmltopdf',
15
+ # or
16
+ # exe_path: Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf')
17
+
18
+ # Layout file to be used for all PDFs
19
+ # (but can be overridden in `render :pdf` calls)
20
+ # layout: 'pdf.html',
21
+ }
@@ -0,0 +1,7 @@
1
+ class WickedPdfGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.file 'wicked_pdf.rb', 'config/initializers/wicked_pdf.rb'
5
+ end
6
+ end
7
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'wicked_pdf'
2
+ require 'wicked_pdf_tempfile'
@@ -0,0 +1,6 @@
1
+ class WickedPdfGenerator < Rails::Generators::Base
2
+ source_root(File.expand_path(File.dirname(__FILE__) + '/../../generators/wicked_pdf/templates'))
3
+ def copy_initializer
4
+ copy_file 'wicked_pdf.rb', 'config/initializers/wicked_pdf.rb'
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ # wkhtml2pdf Ruby interface
2
+ # http://wkhtmltopdf.org/
3
+
4
+ require 'logger'
5
+ require 'digest/md5'
6
+ require 'rbconfig'
7
+ require 'open3'
8
+
9
+ require 'active_support/core_ext/object/blank'
10
+
11
+ require 'wicked_pdf/version'
12
+ require 'wicked_pdf/tempfile'
13
+ require 'wicked_pdf/binary'
14
+ require 'wicked_pdf/option_parser'
15
+ require 'wicked_pdf/progress'
16
+ require 'wicked_pdf/command'
17
+ require 'wicked_pdf/document'
18
+ require 'wicked_pdf/railtie' if defined?(Rails.env)
19
+ require 'wicked_pdf/middleware'
20
+
21
+ module WickedPdf
22
+ class << self
23
+ attr_accessor :config
24
+ end
25
+
26
+ def self.new(command = WickedPdf::Command.new)
27
+ WickedPdf::Document.new(command)
28
+ end
29
+ end
@@ -0,0 +1,141 @@
1
+ require 'open-uri'
2
+
3
+ module WickedPdf
4
+ module AssetHelper
5
+ ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/
6
+
7
+ def self.root_path
8
+ String === Rails.root ? Pathname.new(Rails.root) : Rails.root
9
+ end
10
+
11
+ def self.add_extension(filename, extension)
12
+ filename.to_s.split('.').include?(extension) ? filename : "#{filename}.#{extension}"
13
+ end
14
+
15
+ def wicked_pdf_asset_base64(path)
16
+ asset = find_asset(path)
17
+ raise "Could not find asset '#{path}'" if asset.nil?
18
+ base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '')
19
+ "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
20
+ end
21
+
22
+ def wicked_pdf_stylesheet_link_tag(*sources)
23
+ stylesheet_contents = sources.collect do |source|
24
+ source = WickedPdf::AssetHelper.add_extension(source, 'css')
25
+ "<style type='text/css'>#{read_asset(source)}</style>"
26
+ end.join("\n")
27
+
28
+ stylesheet_contents.gsub(ASSET_URL_REGEX) do
29
+ if Regexp.last_match[1].starts_with?('data:')
30
+ "url(#{Regexp.last_match[1]})"
31
+ else
32
+ "url(#{wicked_pdf_asset_path(Regexp.last_match[1])})"
33
+ end
34
+ end.html_safe
35
+ end
36
+
37
+ def wicked_pdf_image_tag(img, options = {})
38
+ image_tag wicked_pdf_asset_path(img), options
39
+ end
40
+
41
+ def wicked_pdf_javascript_src_tag(jsfile, options = {})
42
+ jsfile = WickedPdf::AssetHelper.add_extension(jsfile, 'js')
43
+ javascript_include_tag wicked_pdf_asset_path(jsfile), options
44
+ end
45
+
46
+ def wicked_pdf_javascript_include_tag(*sources)
47
+ sources.collect do |source|
48
+ source = WickedPdf::AssetHelper.add_extension(source, 'js')
49
+ "<script type='text/javascript'>#{read_asset(source)}</script>"
50
+ end.join("\n").html_safe
51
+ end
52
+
53
+ def wicked_pdf_asset_path(asset)
54
+ if (pathname = asset_pathname(asset).to_s) =~ URI_REGEXP
55
+ pathname
56
+ else
57
+ "file:///#{pathname}"
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # borrowed from actionpack/lib/action_view/helpers/asset_url_helper.rb
64
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
65
+
66
+ def asset_pathname(source)
67
+ if precompiled_or_absolute_asset?(source)
68
+ asset = asset_path(source)
69
+ pathname = prepend_protocol(asset)
70
+ if pathname =~ URI_REGEXP
71
+ # asset_path returns an absolute URL using asset_host if asset_host is set
72
+ pathname
73
+ else
74
+ File.join(Rails.public_path, asset.sub(/\A#{Rails.application.config.action_controller.relative_url_root}/, ''))
75
+ end
76
+ else
77
+ asset = find_asset(source)
78
+ if asset
79
+ # older versions need pathname, Sprockets 4 supports only filename
80
+ asset.respond_to?(:filename) ? asset.filename : asset.pathname
81
+ else
82
+ File.join(Rails.public_path, source)
83
+ end
84
+ end
85
+ end
86
+
87
+ def find_asset(path)
88
+ if Rails.application.assets.respond_to?(:find_asset)
89
+ Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s)
90
+ else
91
+ Sprockets::Railtie.build_environment(Rails.application).find_asset(path, :base_path => Rails.application.root.to_s)
92
+ end
93
+ end
94
+
95
+ # will prepend a http or default_protocol to a protocol relative URL
96
+ # or when no protcol is set.
97
+ def prepend_protocol(source)
98
+ protocol = WickedPdf.config[:default_protocol] || 'http'
99
+ if source[0, 2] == '//'
100
+ source = [protocol, ':', source].join
101
+ elsif source[0] != '/' && !source[0, 8].include?('://')
102
+ source = [protocol, '://', source].join
103
+ end
104
+ source
105
+ end
106
+
107
+ def precompiled_or_absolute_asset?(source)
108
+ Rails.configuration.assets.compile == false ||
109
+ source.to_s[0] == '/' ||
110
+ source.to_s.match(/\Ahttps?\:\/\//)
111
+ end
112
+
113
+ def read_asset(source)
114
+ if precompiled_or_absolute_asset?(source)
115
+ pathname = asset_pathname(source)
116
+ if pathname =~ URI_REGEXP
117
+ read_from_uri(pathname)
118
+ elsif File.file?(pathname)
119
+ IO.read(pathname)
120
+ end
121
+ else
122
+ find_asset(source).to_s
123
+ end
124
+ end
125
+
126
+ def read_from_uri(uri)
127
+ encoding = ':UTF-8'
128
+ asset = open(uri, "r#{encoding}", &:read)
129
+ asset = gzip(asset) if WickedPdf.config[:expect_gzipped_remote_assets]
130
+ asset
131
+ end
132
+
133
+ def gzip(asset)
134
+ stringified_asset = StringIO.new(asset)
135
+ gzipper = Zlib::GzipReader.new(stringified_asset)
136
+ gzipper.read
137
+ rescue Zlib::GzipFile::Error
138
+ nil
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,56 @@
1
+ module WickedPdf
2
+ class Binary
3
+ EXE_NAME = 'wkhtmltopdf'.freeze
4
+ DEFAULT_BINARY_VERSION = Gem::Version.new('0.9.9')
5
+
6
+ attr_reader :path, :default_version
7
+
8
+ def initialize(binary_path = nil, default_version = DEFAULT_BINARY_VERSION)
9
+ @path = binary_path || find_binary_path
10
+ @default_version = default_version
11
+
12
+ raise "Location of #{EXE_NAME} unknown" if @path.empty?
13
+ raise "Bad #{EXE_NAME}'s path: #{@path}" unless File.exist?(@path)
14
+ raise "#{EXE_NAME} is not executable" unless File.executable?(@path)
15
+ end
16
+
17
+ def version
18
+ @version ||= retrieve_binary_version
19
+ end
20
+
21
+ def parse_version_string(version_info)
22
+ match_data = /wkhtmltopdf\s*(\d*\.\d*\.\d*\w*)/.match(version_info)
23
+ if match_data && (match_data.length == 2)
24
+ Gem::Version.new(match_data[1])
25
+ else
26
+ default_version
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def retrieve_binary_version
33
+ _stdin, stdout, _stderr = Open3.popen3(@path + ' -V')
34
+ parse_version_string(stdout.gets(nil))
35
+ rescue StandardError
36
+ default_version
37
+ end
38
+
39
+ def find_binary_path
40
+ return WickedPdf.config[:exe_path] if WickedPdf.config[:exe_path]
41
+
42
+ begin
43
+ detected_path = (defined?(Bundler) ? Bundler.which('wkhtmltopdf') : `which wkhtmltopdf`).chomp
44
+ return detected_path if detected_path.present?
45
+ rescue StandardError
46
+ nil
47
+ end
48
+
49
+ possible_locations = (ENV['PATH'].split(':') + %w[/usr/bin /usr/local/bin]).uniq
50
+ possible_locations += %w[~/bin] if ENV.key?('HOME')
51
+
52
+ exe_path ||= possible_locations.map { |l| File.expand_path("#{l}/#{EXE_NAME}") }.find { |location| File.exist?(location) }
53
+ exe_path || ''
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ module WickedPdf
2
+ class Command
3
+ attr_reader :binary, :option_parser
4
+
5
+ def initialize(binary: Binary.new, option_parser: nil)
6
+ @binary = binary
7
+ @option_parser = option_parser || OptionParser.new(@binary.version)
8
+ end
9
+
10
+ def execute(options, *args)
11
+ command = [binary.path]
12
+ command += option_parser.parse(options)
13
+ command += args
14
+
15
+ print_command(command.inspect) if in_development_mode?
16
+
17
+ if track_progress?(options)
18
+ Progress.new(options[:progress]).execute(command)
19
+ else
20
+ begin
21
+ err = Open3.popen3(*command) do |_stdin, _stdout, stderr|
22
+ stderr.read
23
+ end
24
+ rescue StandardError => e
25
+ raise "Failed to execute:\n#{command}\nError: #{e}"
26
+ end
27
+
28
+ raise "Error generating PDF\n Command Error: #{err}" if options[:raise_on_all_errors] && !err.empty?
29
+ err
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def in_development_mode?
36
+ defined?(Rails.env) && Rails.env.development?
37
+ end
38
+
39
+ def print_command(cmd)
40
+ # TODO: if no Rails what then?
41
+ Rails.logger.debug '[wicked_pdf]: ' + cmd
42
+ end
43
+
44
+ def track_progress?(options)
45
+ options[:progress] && !on_windows?
46
+ end
47
+
48
+ def on_windows?
49
+ RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
1
+ module WickedPdf
2
+ class Document
3
+ def initialize(command = Command.new)
4
+ @command = command
5
+ end
6
+
7
+ def pdf_from_html_file(filepath, options = {})
8
+ pdf_from_url("file:///#{filepath}", options)
9
+ end
10
+
11
+ def pdf_from_string(string, options = {})
12
+ options = options.dup
13
+ options.merge!(WickedPdf.config) { |_key, option, _config| option }
14
+ string_file = WickedPdf::Tempfile.new('wicked_pdf.html', options[:temp_path])
15
+ string_file.binmode
16
+ string_file.write(string)
17
+ string_file.close
18
+
19
+ pdf_from_html_file(string_file.path, options)
20
+ ensure
21
+ string_file.close! if string_file
22
+ end
23
+
24
+ def pdf_from_url(url, options = {})
25
+ # merge in global config options
26
+ options.merge!(WickedPdf.config) { |_key, option, _config| option }
27
+ generated_pdf_file = WickedPdf::Tempfile.new('wicked_pdf_generated_file.pdf', options[:temp_path])
28
+
29
+ result = @command.execute(options, url, generated_pdf_file.path.to_s)
30
+
31
+ if options[:return_file]
32
+ return_file = options.delete(:return_file)
33
+ return generated_pdf_file
34
+ end
35
+
36
+ generated_pdf_file.rewind
37
+ generated_pdf_file.binmode
38
+ pdf = generated_pdf_file.read
39
+
40
+ raise "PDF could not be generated!\n Command Error: #{result}" if pdf && pdf.rstrip.empty?
41
+
42
+ pdf
43
+ ensure
44
+ generated_pdf_file.close! if generated_pdf_file && !return_file
45
+ end
46
+ end
47
+ end