Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for configuring a front and back cover page for PDFs #4

Merged
merged 6 commits into from
Sep 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
exclude_patterns:
- "lib/active_support_ext/**/*.rb"
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ RSpec/MessageSpies:

RSpec/NestedGroups:
Max: 4

Style/Documentation:
Exclude:
- "lib/active_support_ext/**/*.rb"
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Changelog

## Unreleased
### Added
- Support for front/back cover pages for middleware (combined with the original request PDF)

## [0.4.4](releases/tag/v0.4.4) - 2018-09-10
### Fixed
- Bug with options containing mixed symbol/string keys (and how they merge with the parsed meta options)

## [0.4.3](releases/tag/v0.4.3) - 2018-09-10
### Added
- Pass through flag to indicate to downstream middleware/app that Grover has interacted with the environment
- Pass through flag to indicate to upstream middleware/app that Grover has interacted with the environment

## [0.4.2](releases/tag/v0.4.2) - 2018-09-09
### Fixed
Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,51 @@ any page on your site by appending .pdf to the URL.
**Non-Rails Rack apps**
```ruby
# in config.ru
require 'pdfkit'
require 'grover'
use Grover::Middleware
```

**Rails apps**
```ruby
# in application.rb
require 'pdfkit'
require 'grover'
config.middleware.use Grover::Middleware
```


## Cover pages
Since the header/footer for Puppeteer is configured globally, displaying of front/back cover
pages (with potentially different headers/footers etc) is not possible.

To get around this, Grover's middleware allows you to specify relative paths for the cover page contents
via `front_cover_path` and `back_cover_path` either via the global configuration, or via meta tags.
These paths (with query parameters) are then requested from the upstream app.

The cover pages are converted to PDF in isolation, and then combined together with the original PDF response,
before being returned back down through the Rack stack.

_N.B_ To simplify things, the same request method and body are used for the cover page requests.

```ruby
# config/initializers/grover.rb
Grover.configure do |config|
config.options = {
front_cover_path: '/some/global/cover/page?foo=bar'
}
end
```

Or via the meta tags in the original response:
```HTML
<html>
<head>
<meta name="grover-back_cover_path" content="/back/cover/page?bar=baz" />
</head>
...
</html>
```


## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/Studiosity/grover.
Expand Down
1 change: 1 addition & 0 deletions grover.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^spec/})
spec.require_paths = ['lib']

spec.add_dependency 'combine_pdf', '~> 1.0'
spec.add_dependency 'nokogiri', '~> 1.0'
spec.add_dependency 'schmooze', '~> 0.2'

Expand Down
58 changes: 58 additions & 0 deletions lib/active_support_ext/object/deep_dup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

# Copied from active support
# @see active_support/core_ext/object/duplicable.rb

require 'active_support_ext/object/duplicable'

class Object
# Returns a deep copy of object if it's duplicable. If it's
# not duplicable, returns +self+.
#
# object = Object.new
# dup = object.deep_dup
# dup.instance_variable_set(:@a, 1)
#
# object.instance_variable_defined?(:@a) # => false
# dup.instance_variable_defined?(:@a) # => true
def deep_dup
duplicable? ? dup : self
end
end

class Array
# Returns a deep copy of array.
#
# array = [1, [2, 3]]
# dup = array.deep_dup
# dup[1][2] = 4
#
# array[1][2] # => nil
# dup[1][2] # => 4
def deep_dup
map(&:deep_dup)
end
end

class Hash
# Returns a deep copy of hash.
#
# hash = { a: { b: 'b' } }
# dup = hash.deep_dup
# dup[:a][:c] = 'c'
#
# hash[:a][:c] # => nil
# dup[:a][:c] # => "c"
def deep_dup
hash = dup
each_pair do |key, value|
if key.frozen? && key.is_a?(::String)
hash[key] = value.deep_dup
else
hash.delete(key)
hash[key.deep_dup] = value.deep_dup
end
end
hash
end
end
152 changes: 152 additions & 0 deletions lib/active_support_ext/object/duplicable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# frozen_string_literal: true

# Copied from active support
# @see active_support/core_ext/object/duplicable.rb

#--
# Most objects are cloneable, but not all. For example you can't dup methods:
#
# method(:puts).dup # => TypeError: allocator undefined for Method
#
# Classes may signal their instances are not duplicable removing +dup+/+clone+
# or raising exceptions from them. So, to dup an arbitrary object you normally
# use an optimistic approach and are ready to catch an exception, say:
#
# arbitrary_object.dup rescue object
#
# Rails dups objects in a few critical spots where they are not that arbitrary.
# That rescue is very expensive (like 40 times slower than a predicate), and it
# is often triggered.
#
# That's why we hardcode the following cases and check duplicable? instead of
# using that rescue idiom.
#++
class Object
# Can you safely dup this object?
#
# False for method objects;
# true otherwise.
def duplicable?
true
end
end

class NilClass
begin
nil.dup
rescue TypeError
# +nil+ is not duplicable:
#
# nil.duplicable? # => false
# nil.dup # => TypeError: can't dup NilClass
def duplicable?
false
end
end
end

class FalseClass
begin
false.dup
rescue TypeError
# +false+ is not duplicable:
#
# false.duplicable? # => false
# false.dup # => TypeError: can't dup FalseClass
def duplicable?
false
end
end
end

class TrueClass
begin
true.dup
rescue TypeError
# +true+ is not duplicable:
#
# true.duplicable? # => false
# true.dup # => TypeError: can't dup TrueClass
def duplicable?
false
end
end
end

class Symbol
begin
:symbol.dup # Ruby 2.4.x.
'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
rescue TypeError
# Symbols are not duplicable:
#
# :my_symbol.duplicable? # => false
# :my_symbol.dup # => TypeError: can't dup Symbol
def duplicable?
false
end
end
end

class Numeric
begin
1.dup
rescue TypeError
# Numbers are not duplicable:
#
# 3.duplicable? # => false
# 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
end
end

require 'bigdecimal'
class BigDecimal
# BigDecimals are duplicable:
#
# BigDecimal("1.2").duplicable? # => true
# BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
end

class Method
# Methods are not duplicable:
#
# method(:puts).duplicable? # => false
# method(:puts).dup # => TypeError: allocator undefined for Method
def duplicable?
false
end
end

class Complex
begin
Complex(1).dup
rescue TypeError
# Complexes are not duplicable:
#
# Complex(1).duplicable? # => false
# Complex(1).dup # => TypeError: can't copy Complex
def duplicable?
false
end
end
end

class Rational
begin
Rational(1).dup
rescue TypeError
# Rationals are not duplicable:
#
# Rational(1).duplicable? # => false
# Rational(1).dup # => TypeError: can't copy Rational
def duplicable?
false
end
end
end
Loading