breezy_pdf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 51ab1fda7ecf5c352d77df5fc07e2ba2e9b4116c
4
+ data.tar.gz: 1e80909046b6fe970e499abc0ac0ed0cbad88e1a
5
+ SHA512:
6
+ metadata.gz: 2861c609191489fb38d112d8f495e37b38ccd0fbc803c2afc1b84a43a60fb0568686535e7e17f6542cb3b75694754b33f639e6801981672dcb88c3aab589fd66
7
+ data.tar.gz: 287fcee5ebd2fdf7ac0f6a87e1717abda8586deeaa24b3082778d0fb6fca8ccf4b803cbd9b200d994431226e392eb330f20399a3259ebd6b3e36303e425b68b4
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,18 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+ Style/StringLiterals:
4
+ EnforcedStyle: double_quotes
5
+ Metrics/LineLength:
6
+ Max: 110
7
+ Metrics/MethodLength:
8
+ Max: 20
9
+ Style/ClassAndModuleChildren:
10
+ Enabled: false
11
+ Style/ClassVars:
12
+ Enabled: false
13
+ Metrics/CyclomaticComplexity:
14
+ Enabled: false
15
+ Metrics/PerceivedComplexity:
16
+ Enabled: false
17
+ Metrics/AbcSize:
18
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.2
5
+ - 2.5.0
6
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in breezy_pdf.gemspec
8
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ breezy_pdf (0.0.1)
5
+ concurrent-ruby
6
+ nokogiri
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.0)
12
+ concurrent-ruby (1.0.5)
13
+ mini_portile2 (2.3.0)
14
+ minitest (5.11.3)
15
+ minitest-stub-const (0.6)
16
+ nokogiri (1.8.2)
17
+ mini_portile2 (~> 2.3.0)
18
+ parallel (1.12.1)
19
+ parser (2.5.0.5)
20
+ ast (~> 2.4.0)
21
+ powerpack (0.1.1)
22
+ rack (2.0.4)
23
+ rack-test (0.8.3)
24
+ rack (>= 1.0, < 3)
25
+ rainbow (2.2.2)
26
+ rake
27
+ rake (10.5.0)
28
+ rubocop (0.49.0)
29
+ parallel (~> 1.10)
30
+ parser (>= 2.3.3.1, < 3.0)
31
+ powerpack (~> 0.1)
32
+ rainbow (>= 1.99.1, < 3.0)
33
+ ruby-progressbar (~> 1.7)
34
+ unicode-display_width (~> 1.0, >= 1.0.1)
35
+ ruby-progressbar (1.9.0)
36
+ unicode-display_width (1.3.0)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ breezy_pdf!
43
+ bundler (~> 1.16)
44
+ minitest (~> 5.0)
45
+ minitest-stub-const
46
+ rack-test
47
+ rake (~> 10.0)
48
+ rubocop (= 0.49)
49
+
50
+ BUNDLED WITH
51
+ 1.16.0
data/LICENSE.txt ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) Daniel Westendorf
2
+
3
+ BreezyPDF is an Open Source project licensed under the terms of
4
+ the LGPLv3 license. Please see <http://www.gnu.org/licenses/lgpl-3.0.html>
5
+ for license text.
data/README.md ADDED
@@ -0,0 +1,254 @@
1
+ # BreezyPDF
2
+
3
+ Make PDF generation a breezy-easy.
4
+
5
+ No binaries to install. No reinventing the wheel to match HTML layouts. Just simple HTML to PDF generation as a Rack Middleware with support for modern CSS and JavaScript. `.pdf` any of your app's URL's for fast, out-of-process, PDF generation of that URL. Support for public and authenticated views, local development and production.
6
+
7
+ [Sign Up](https://BreezyPDF.com/) for an account to get started.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'breezy_pdf'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install breezy_pdf
24
+
25
+ ## Usage - Ruby on Rails
26
+
27
+ To add the middleware:
28
+
29
+ ```ruby
30
+ # config/application.rb
31
+
32
+ BreezyPDF.secret_api_key = "YOUR_SECRET_API_KEY" # Remove if specified elsewhere
33
+ config.middleware.use BreezyPDF::Middleware
34
+ ```
35
+
36
+ ```ruby
37
+ # view.html.erb
38
+ <%= link_to "Download as PDF", my_resource_path(format: :pdf) %>
39
+ ```
40
+
41
+ Proceed to configuration.
42
+
43
+ ## Usage - Sinatra/Hamani/Rack
44
+
45
+ To add the middleware:
46
+
47
+ ```ruby
48
+ # app.rb
49
+
50
+ BreezyPDF.secret_api_key = "YOUR_SECRET_API_KEY" # Remove if specified elsewhere
51
+ use BreezyPDF::Middleware
52
+ ```
53
+
54
+ ```ruby
55
+ # view.html
56
+ <a href="/this/url.pdf">Download as PDF</a>
57
+ ```
58
+
59
+ Proceed to configuration.
60
+
61
+ ## Configuration
62
+
63
+ BreezyPDF supports extensive configuration for how want PDF's to render:
64
+
65
+ ```ruby
66
+ # config/initializers/breezy_pdf.rb
67
+
68
+ BreezyPDF.setup do |config|
69
+ # Secret API Key
70
+ #
71
+ # Obtain an API key from https://breezypdf.com
72
+ # Store your API key in a secure location with the rest of your app secrets
73
+ config.secret_api_key = "YOUR_SECRET_API_KEY"
74
+
75
+
76
+ # Middleware Path Matchers
77
+ #
78
+ # An array for Regular Expressions which identify which URL's should be
79
+ # intercepted by the Middleware. Defaults to [/\.pdf$/], which will match
80
+ # all requests ending with a .pdf extension.
81
+ # config.middleware_path_matchers = [/\.pdf$/]
82
+
83
+
84
+ # Treat URL's as Private
85
+ #
86
+ # This indicates if the URL requested is protected or unaccessible for the
87
+ # public Internet. Examples of this would be when hosted on localhost (development)
88
+ # URL's which are protected by authentication, or URL's that depend on session/cookie
89
+ # data to display correctly. Default is true.
90
+ #
91
+ # config.treat_urls_as_private = true
92
+
93
+
94
+ # Upload Assets
95
+ #
96
+ # Express your desire to upload assets within the requested HTML which are not
97
+ # publicly accessible with a FQDN. This might include images, CSS, and
98
+ # JavaScript when running in development mode, or assets which are referenced
99
+ # with relative URL's. If you're able to turn this off, performance will be increased
100
+ # Default is true.
101
+ #
102
+ # Only applicable when `config.treat_urls_as_private == true`
103
+ #
104
+ # config.upload_assets = true
105
+ #
106
+ # or, if your assets are only publicly available on production
107
+ # config.upload_assets = Rails.env.development?
108
+
109
+
110
+ # Asset Selectors
111
+ #
112
+ # Configure what types of assets should be evaluated to be uploaded. Expects an
113
+ # array of string CSS selectors. Default is `[img script link[rel="stylesheet"]]`.
114
+ #
115
+ # Only applicable when `config.treat_urls_as_private == true`
116
+ # Only applicable when `config.upload_assets == true`
117
+ #
118
+ # config.asset_selectors = %w(img script link[rel="stylesheet"])
119
+
120
+
121
+ # Asset path matchers
122
+ #
123
+ # Determine which attribute path's to replace with an uploaded version of the asset.
124
+ # Expects a hash of attr: Regexp values. Default is `{ href: /^\/\w+/, src: /^\/\w+/}`
125
+ # which matches all relative paths.
126
+ #
127
+ # Only applicable when `config.treat_urls_as_private == true`
128
+ # Only applicable when `config.upload_assets == true`
129
+ #
130
+ # config.asset_path_matchers = {
131
+ # href: %r{^\/\w+},
132
+ # src: %r{^\/\w+}
133
+ # }
134
+
135
+
136
+ # Extract Metadata
137
+ #
138
+ # BreezyPDF supports specifying how a page should be rendered through meta tags within
139
+ # the HTML to be rendered. Contact support@breezypdf.com for more details. Default is
140
+ # true.
141
+ #
142
+ # Only applicable when `treat_urls_as_private == true`
143
+ # config.extract_metadata = true
144
+
145
+
146
+ # Threads
147
+ #
148
+ # Specify the maximum number of Threads to use when uploading assets. This speeds up the
149
+ # uploading of assets by doing them concurrently. Default is 1.
150
+ #
151
+ # Only applicable when `config.treat_urls_as_private == true`
152
+ # Only applicable when `config.upload_assets == true`
153
+ #
154
+ # config.threads = 1
155
+
156
+
157
+ # Filter Elements
158
+ #
159
+ # Remove certain elements from the HTML which shouldn't be included in the PDF, such as
160
+ # a navigation or footer elements. Default is false.
161
+ #
162
+ # Only applicable when `config.treat_urls_as_private == true`
163
+ #
164
+ # config.fitler_elements = false
165
+
166
+
167
+ # Filter Elements Selectors
168
+ #
169
+ # CSS selectors to configure which element you'd like to remove. Expects an array of of
170
+ # CSS selectors. Default is `[.breezy-pdf-remove]`.
171
+ #
172
+ # Only applicable when `config.treat_urls_as_private == true`
173
+ #
174
+ # config.filtered_element_selectors = %w[.breezy-pdf-remove]
175
+
176
+ # Logger
177
+ #
178
+ # Configure the logger, if you're into that sort of thing.
179
+ #
180
+ # config.logger = Logger.new(STDOUT).tap { |logger| logger.level = Logger::FATAL }
181
+ end
182
+ ```
183
+
184
+ ## Where's my loading animation?
185
+
186
+ By default, the requested PDF will eventually returned by the request after a series of redirects. The PDF will be sent with a Content-Disposition of `attachment`, so the browser will attempt to download it instead of showing it inline. All the while, and after the PDF has been downloaded, the current page's HTML will continue to be displayed.
187
+
188
+ If you want to show a loading animation on the current page, you can simply load the PDF with an AJAX request, eventually redirecting to the final URL.
189
+
190
+ Here is a contrived example:
191
+
192
+ ```html
193
+ <a href="/this/path.pdf" class="breezy-pdf-download">Download as PDF</a>
194
+
195
+ <script type="text/javascript">
196
+ var downloadLinkEls = document.querySelectorAll('.breezy-pdf-download');
197
+ var loadingEl = document.createElement('div');
198
+ loadingEl.innerHTML = "<h1>Loading!</h1><p>Loading icon here, maybe?</p><p id='breezy-progress'></p>";
199
+ loadingEl.style = "position: absolute; width: 100%; height: 100%; text-align: center; background: #fff; top: 0; left: 0; z-index: 10000; display: none;"
200
+
201
+ document.body.appendChild(loadingEl);
202
+ var progressEl = document.getElementById('breezy-progress');
203
+
204
+ for (var i = downloadLinkEls.length - 1; i >= 0; i--) {
205
+ var linkEl = downloadLinkEls[i];
206
+ var pdfUrl = linkEl.getAttribute('href');
207
+
208
+ // Listen for a click on the link, then start handling the change of state
209
+ linkEl.addEventListener('click', function(ev) {
210
+ loadingEl.style.display = "block"; // Display ad-hoc loading element
211
+ ev.preventDefault();
212
+
213
+ var i = 1;
214
+ var interval = setInterval(function() {
215
+ progressEl.innerText = i / 10.0 + ' waiting seconds so far...';
216
+ i++;
217
+ }, 100);
218
+
219
+ var ajaxRequest = new XMLHttpRequest();
220
+
221
+ ajaxRequest.addEventListener('load', function(ev) {
222
+ clearInterval(interval);
223
+ console.log("Done waiting. We'd close modals or remove loading animations here before setting the location.")
224
+ loadingEl.style.display = "none";
225
+
226
+ // Redirect the eventual URL of the PDF
227
+ // If the browser downloads the file, the current page's HTML will still be shown
228
+ window.location = ev.currentTarget.responseURL;
229
+ })
230
+
231
+ ajaxRequest.open('GET', pdfUrl);
232
+ ajaxRequest.send();
233
+ })
234
+ }
235
+ </script>
236
+ ```
237
+
238
+ ## Development
239
+
240
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
241
+
242
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
243
+
244
+ ## Contributing
245
+
246
+ Bug reports and pull requests are welcome on GitHub at https://github.com/danielwestendorf/breezy_pdf.
247
+
248
+ ## License
249
+
250
+ Copyright (c) Daniel Westendorf
251
+
252
+ BreezyPDF is an Open Source project licensed under the terms of
253
+ the LGPLv3 license. Please see <http://www.gnu.org/licenses/lgpl-3.0.html>
254
+ for license text.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "breezy_pdf"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require "breezy_pdf/version"
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "breezy_pdf"
10
+ spec.version = BreezyPDF::VERSION
11
+ spec.authors = ["Daniel Westendorf"]
12
+ spec.email = ["daniel@prowestech.com"]
13
+
14
+ spec.summary = "Ruby client for BreezyPDF.com"
15
+ spec.description = "Client and Rack Middlware which submits URL's and HTML fragments to " \
16
+ "be rendered as a PDF"
17
+ spec.homepage = "https://www.breezypdf.com"
18
+ spec.license = "LGPL-3.0"
19
+
20
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
21
+ f.match(%r{^(test|spec|features)/})
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency "nokogiri"
28
+ spec.add_dependency "concurrent-ruby"
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.16"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "minitest", "~> 5.0"
33
+ spec.add_development_dependency "minitest-stub-const"
34
+ spec.add_development_dependency "rack-test"
35
+ spec.add_development_dependency "rubocop", "0.49"
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF
4
+ # HTTP Client for BreezyPDF API
5
+ class Client
6
+ def post(path, body)
7
+ uri = URI.parse(BreezyPDF.base_url + path)
8
+ http = Net::HTTP.new(uri.host, uri.port).tap { |h| h.use_ssl = true }
9
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
10
+
11
+ request.body = body.to_json
12
+
13
+ Response.new http.request(request)
14
+ end
15
+
16
+ def put(path, body)
17
+ uri = URI.parse(BreezyPDF.base_url + path)
18
+ http = Net::HTTP.new(uri.host, uri.port).tap { |h| h.use_ssl = true }
19
+ request = Net::HTTP::Put.new(uri.request_uri, headers)
20
+
21
+ request.body = body.to_json
22
+
23
+ Response.new http.request(request)
24
+ end
25
+
26
+ private
27
+
28
+ def headers
29
+ {
30
+ "Content-Type": "application/json",
31
+ "Authorization": "Bearer #{BreezyPDF.secret_api_key}"
32
+ }
33
+ end
34
+
35
+ def success?(code)
36
+ code >= 200 && code < 300
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF
4
+ # https://github.com/rails/rails/blob/master/activesupport/lib/active_support/gzip.rb
5
+ module Gzip
6
+ # :nodoc
7
+ class Stream < StringIO
8
+ def initialize(*)
9
+ super
10
+ set_encoding "BINARY"
11
+ end
12
+
13
+ def close
14
+ rewind
15
+ end
16
+ end
17
+
18
+ # Compresses a string using gzip.
19
+ def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY)
20
+ output = Stream.new
21
+ gz = Zlib::GzipWriter.new(output, level, strategy)
22
+ gz.write(source)
23
+ gz.close
24
+ output.string
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF::HTML
4
+ # Replace assets with uploaded URL's
5
+ class Publicize
6
+ def initialize(base_url, html_fragment)
7
+ @base_url = base_url
8
+ @html_fragment = html_fragment
9
+ @log_queue = []
10
+ end
11
+
12
+ def public_fragment
13
+ @public_fragment ||= parsed_document.tap do
14
+ publicize!
15
+ end.to_html
16
+ end
17
+
18
+ private
19
+
20
+ def publicize!
21
+ BreezyPDF.asset_selectors.each do |selector|
22
+ parsed_document.css(selector).each do |asset_element|
23
+ replace_asset_elements_matched_paths(asset_element)
24
+ end
25
+ end
26
+
27
+ @log_queue.each { |msg| BreezyPDF.logger.info(msg) }
28
+
29
+ thread_pool.shutdown
30
+ thread_pool.wait_for_termination
31
+ end
32
+
33
+ def parsed_document
34
+ @parsed_document ||= Nokogiri::HTML(@html_fragment)
35
+ end
36
+
37
+ def replace_asset_elements_matched_paths(asset_element)
38
+ BreezyPDF.asset_path_matchers.each do |attr, matcher|
39
+ attr_value = asset_element[attr.to_s]
40
+
41
+ next unless attr_value && attr_value.match?(matcher)
42
+
43
+ @log_queue << %([BreezyPDF] Replacing element #{asset_element.name}[#{attr}="#{asset_element[attr]}"])
44
+ replace_asset_element_attr(asset_element, attr.to_s)
45
+ end
46
+ end
47
+
48
+ def replace_asset_element_attr(asset_element, attr)
49
+ thread_pool.post do
50
+ asset = BreezyPDF::PrivateAssets::Asset.new(@base_url, asset_element[attr])
51
+
52
+ asset_element[attr] = BreezyPDF::Uploads::Base.new(
53
+ asset.filename, asset.content_type, asset.file_path
54
+ ).public_url
55
+ end
56
+ end
57
+
58
+ def thread_pool
59
+ @thread_pool ||= Concurrent::FixedThreadPool.new(BreezyPDF.threads.to_i)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF::HTML
4
+ # Replace assets with uploaded URL's
5
+ class Strip
6
+ def initialize(html_fragment)
7
+ @html_fragment = html_fragment
8
+ end
9
+
10
+ def stripped_fragment
11
+ @stripped_fragment ||= parsed_document.tap do
12
+ strip!
13
+ end.to_html
14
+ end
15
+
16
+ private
17
+
18
+ def strip!
19
+ BreezyPDF.filter_elements_selectors.each do |selector|
20
+ BreezyPDF.logger.info("[BreezyPDF] Stripping out elements matching selector `#{selector}`")
21
+ parsed_document.css(selector).each(&:remove)
22
+ end
23
+ end
24
+
25
+ def parsed_document
26
+ @parsed_document ||= Nokogiri::HTML(@html_fragment)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF
4
+ # :nodoc
5
+ module HTML
6
+ autoload :Publicize, "breezy_pdf/html/publicize"
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BreezyPDF::Intercept
4
+ # :nodoc
5
+ class Base
6
+ attr_reader :app, :env
7
+
8
+ def initialize(app, env)
9
+ @app = app
10
+ @env = env
11
+ end
12
+ end
13
+ end