adzap-wicked_pdf 2.0.0.beta1

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.
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