pdf_templator 0.0.1
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.
- checksums.yaml +7 -0
- data/.editorconfig +24 -0
- data/.gitignore +51 -0
- data/.rspec +3 -0
- data/.rubocop.yml +46 -0
- data/.travis.yml +16 -0
- data/Gemfile +14 -0
- data/Guardfile +30 -0
- data/README.md +96 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bump +70 -0
- data/exe/pdftemplator +75 -0
- data/lib/pdf_templator.rb +29 -0
- data/lib/pdf_templator/formatter.rb +24 -0
- data/lib/pdf_templator/reader.rb +147 -0
- data/lib/pdf_templator/type.rb +20 -0
- data/lib/pdf_templator/types.rb +53 -0
- data/lib/pdf_templator/version.rb +5 -0
- data/pdf_templator.gemspec +39 -0
- data/pdf_templator.sublime-project +13 -0
- metadata +247 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ebb84707bebab41a336aff995fcf7d77de34b3f
|
4
|
+
data.tar.gz: 6dc527e163b6cf19e751816c885a9c82e56b60fa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 48081d9ad88ccd712c987223a9dffa3247cea37bc045feedeba05cf691e24bc44f8e655f736cf46168a029775d28bcff925ba0c8e9f73dff092c3927666b78aa
|
7
|
+
data.tar.gz: 01e208604186f275e272bf89ce5bf6c686fedd40bbe8ef0af3a2c8044c1a7a8293a3aec41c6c08bb8174e853f792739904edcd7f756396f52c0ae66daa128757
|
data/.editorconfig
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# EditorConfig helps developers define and maintain consistent
|
2
|
+
# coding styles between different editors and IDEs
|
3
|
+
# editorconfig.org
|
4
|
+
|
5
|
+
root = true
|
6
|
+
|
7
|
+
[*]
|
8
|
+
|
9
|
+
# Change these settings to your own preference
|
10
|
+
indent_style = space
|
11
|
+
indent_size = 2
|
12
|
+
|
13
|
+
# We recommend you to keep these unchanged
|
14
|
+
end_of_line = lf
|
15
|
+
charset = utf-8
|
16
|
+
trim_trailing_whitespace = true
|
17
|
+
insert_final_newline = true
|
18
|
+
|
19
|
+
[*.md]
|
20
|
+
trim_trailing_whitespace = false
|
21
|
+
|
22
|
+
[nginx.conf.erb]
|
23
|
+
indent_style = tabs
|
24
|
+
indent_size = 4
|
data/.gitignore
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
## Specific to RubyMotion:
|
17
|
+
.dat*
|
18
|
+
.repl_history
|
19
|
+
build/
|
20
|
+
*.bridgesupport
|
21
|
+
build-iPhoneOS/
|
22
|
+
build-iPhoneSimulator/
|
23
|
+
|
24
|
+
## Specific to RubyMotion (use of CocoaPods):
|
25
|
+
#
|
26
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
27
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
28
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
29
|
+
#
|
30
|
+
# vendor/Pods/
|
31
|
+
|
32
|
+
## Documentation cache and generated files:
|
33
|
+
/.yardoc/
|
34
|
+
/_yardoc/
|
35
|
+
/doc/
|
36
|
+
/rdoc/
|
37
|
+
|
38
|
+
## Environment normalization:
|
39
|
+
/.bundle/
|
40
|
+
/vendor/bundle
|
41
|
+
/lib/bundler/man/
|
42
|
+
|
43
|
+
# for a library or gem, you might want to ignore these files since the code is
|
44
|
+
# intended to run in multiple environments; otherwise, check them in:
|
45
|
+
Gemfile.lock
|
46
|
+
.ruby-version
|
47
|
+
.ruby-gemset
|
48
|
+
|
49
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
50
|
+
.rvmrc
|
51
|
+
*.sublime-workspace
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.3.5
|
5
|
+
|
6
|
+
Style/Documentation:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Style/HashSyntax:
|
10
|
+
EnforcedStyle: ruby19
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- Guardfile
|
15
|
+
- 'spec/**/*'
|
16
|
+
- '*.gemspec'
|
17
|
+
|
18
|
+
Style/TrailingCommaInArguments:
|
19
|
+
EnforcedStyleForMultiline: consistent_comma
|
20
|
+
|
21
|
+
Style/TrailingCommaInArrayLiteral:
|
22
|
+
EnforcedStyleForMultiline: consistent_comma
|
23
|
+
|
24
|
+
Style/TrailingCommaInHashLiteral:
|
25
|
+
EnforcedStyleForMultiline: consistent_comma
|
26
|
+
|
27
|
+
Layout/AccessModifierIndentation:
|
28
|
+
EnforcedStyle: outdent
|
29
|
+
|
30
|
+
Style/SignalException:
|
31
|
+
EnforcedStyle: semantic
|
32
|
+
|
33
|
+
Metrics/LineLength:
|
34
|
+
Max: 120
|
35
|
+
|
36
|
+
Metrics/ClassLength:
|
37
|
+
Max: 150
|
38
|
+
|
39
|
+
Metrics/ModuleLength:
|
40
|
+
Max: 150
|
41
|
+
|
42
|
+
Style/Documentation:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
Style/HashSyntax:
|
46
|
+
EnforcedStyle: ruby19
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
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 pdf_templator.gemspec
|
8
|
+
gemspec
|
9
|
+
|
10
|
+
gem 'guard'
|
11
|
+
gem 'guard-rspec', require: false
|
12
|
+
gem 'guard-rubocop', require: false
|
13
|
+
gem 'rubocop'
|
14
|
+
gem 'rubocop-rspec'
|
data/Guardfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
notification :terminal_notifier
|
4
|
+
|
5
|
+
rspec_options = {
|
6
|
+
all_after_pass: true,
|
7
|
+
cmd: 'rspec spec',
|
8
|
+
failed_mode: :focus,
|
9
|
+
}
|
10
|
+
|
11
|
+
clearing :on
|
12
|
+
|
13
|
+
guard :rspec, rspec_options do
|
14
|
+
require 'ostruct'
|
15
|
+
|
16
|
+
# Generic Ruby apps
|
17
|
+
rspec = OpenStruct.new
|
18
|
+
rspec.spec = ->(m) { "spec/#{m}_spec.rb" }
|
19
|
+
rspec.spec_dir = 'spec'
|
20
|
+
rspec.spec_helper = 'spec/spec_helper.rb'
|
21
|
+
|
22
|
+
watch(%r{^spec/.+_spec\.rb$})
|
23
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
24
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
25
|
+
end
|
26
|
+
|
27
|
+
guard :rubocop, all_on_start: true do
|
28
|
+
watch(/.+\.rb$/)
|
29
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
30
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# PDF Templator
|
2
|
+
|
3
|
+
[![Gem Version][rubygems-image]][rubygems-url]
|
4
|
+
[![Build Status][travis-image]][travis-url]
|
5
|
+
[![Coverage Status][coverage-image]][coverage-url]
|
6
|
+
|
7
|
+
Create PDFs from HTML templates in a breeze.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Create a single HTML with `<field>` tags and PDF Templator will fill them for you.
|
12
|
+
- Field types include `date`, `number`, `date`, `money`, `accounting`.
|
13
|
+
- Create your own field type.
|
14
|
+
- Usage as a CLI in case you're not working on Ruby.
|
15
|
+
- It uses [wicked_pdf](https://github.com/mileszs/wicked_pdf) internally so you can use all its features too.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'pdf_templator'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install pdf_templator
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'pdf_templator'
|
37
|
+
|
38
|
+
html = File.read('path/to/my/template.html')
|
39
|
+
# OR
|
40
|
+
html = '<html>' \
|
41
|
+
'My name is <field name="name">' \
|
42
|
+
'and today is <field name="date" type="date" data-format="%b %d, %Y">' \
|
43
|
+
'</html>'
|
44
|
+
args = {
|
45
|
+
name: 'My Name',
|
46
|
+
date: '06/18/2018',
|
47
|
+
}
|
48
|
+
|
49
|
+
reader = PdfTemplator::Reader.new(content: html, footer: footer)
|
50
|
+
pdf = reader.write(args)
|
51
|
+
File.open('path/to/file.pdf', 'wb') { |f| f.write(pdf) }
|
52
|
+
```
|
53
|
+
|
54
|
+
### Custom types
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
module PdfTemplator
|
58
|
+
class ListType < Type
|
59
|
+
def call
|
60
|
+
list = content.split(',')
|
61
|
+
html_items = list.map do |item|
|
62
|
+
"<li>#{item}</li>"
|
63
|
+
end
|
64
|
+
"<ul>#{html_items.join}</ul>"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Then you could have an html like
|
70
|
+
|
71
|
+
<field name="my_list" type="list"></field>
|
72
|
+
|
73
|
+
# And pass the args like
|
74
|
+
|
75
|
+
args = {
|
76
|
+
my_list: 'car,helicopter,motorcycle'
|
77
|
+
}
|
78
|
+
|
79
|
+
```
|
80
|
+
|
81
|
+
## Development
|
82
|
+
|
83
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `rake console` for an interactive prompt that will allow you to experiment.
|
84
|
+
|
85
|
+
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).
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/pdf_templator.
|
90
|
+
|
91
|
+
[rubygems-image]: https://badge.fury.io/rb/pdf_templator.svg
|
92
|
+
[rubygems-url]: https://badge.fury.io/rb/pdf_templator
|
93
|
+
[travis-image]: https://travis-ci.org/Mifiel/pdf-templator.svg?branch=master
|
94
|
+
[travis-url]: https://travis-ci.org/Mifiel/pdf-templator
|
95
|
+
[coverage-image]: https://coveralls.io/repos/github/Mifiel/pdf-templator/badge.svg?branch=master
|
96
|
+
[coverage-url]: https://coveralls.io/github/Mifiel/pdf-templator?branch=master
|
data/Rakefile
ADDED
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 'pdf_templator'
|
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
data/bump
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
import re, glob, sys, os
|
3
|
+
|
4
|
+
__USAGE__ = \
|
5
|
+
"""BUMP is a semantic versioning bump script which accepts the following
|
6
|
+
mutually exclusive arguments:
|
7
|
+
-m - a "major" version bump equal to +1.0.0
|
8
|
+
-n - a "minor" version bump equal to +0.1.0
|
9
|
+
-p - a "patch" version bump equal to +0.0.1
|
10
|
+
-h - a "hot fix" version bump equal to +0.0.1
|
11
|
+
|
12
|
+
All of these options allow for the -r flag, which indicates that the state
|
13
|
+
is a RELEASE not a SNAPSHOT. If -r is not specified, then -SNAPSHOT is
|
14
|
+
appended to the updated version string."""
|
15
|
+
|
16
|
+
__INITIAL__ = ['0', '0', '1']
|
17
|
+
|
18
|
+
|
19
|
+
if __name__ == "__main__":
|
20
|
+
v = []
|
21
|
+
try:
|
22
|
+
version_file = glob.glob("lib/*/version.rb")[0]
|
23
|
+
raw_v = re.search(r'VERSION = \'(.*)\'', open(version_file).read(), re.M|re.I|re.S).group(1)
|
24
|
+
v = re.split(re.compile("\.|-"), raw_v)
|
25
|
+
v = v[0:3]
|
26
|
+
map(int, v)
|
27
|
+
|
28
|
+
except ValueError:
|
29
|
+
print("failed to parse the existing VERSION file, assuming v 0.0.1")
|
30
|
+
v = ['0', '0', '1']
|
31
|
+
|
32
|
+
except FileNotFoundError:
|
33
|
+
print("failed to find a VERSION file, assuming v 0.0.0")
|
34
|
+
v = ['0', '0', '0']
|
35
|
+
|
36
|
+
op = ''
|
37
|
+
try:
|
38
|
+
op = sys.argv[1]
|
39
|
+
except:
|
40
|
+
print(__USAGE__)
|
41
|
+
sys.exit(-1)
|
42
|
+
|
43
|
+
if(op == '-m'):
|
44
|
+
v = [str(int(v[0])+1), '0', '0']
|
45
|
+
|
46
|
+
elif(op == '-n'):
|
47
|
+
v = [v[0], str(int(v[1])+1), '0']
|
48
|
+
|
49
|
+
elif(op == '-p' or op == '-h'):
|
50
|
+
v = [v[0], v[1], str(int(v[2])+1)]
|
51
|
+
|
52
|
+
else:
|
53
|
+
print(__USAGE__)
|
54
|
+
sys.exit(-1)
|
55
|
+
|
56
|
+
v = '.'.join(v)
|
57
|
+
|
58
|
+
if(op == '-h'):
|
59
|
+
os.system("git checkout -b hotfix/v%s master" % v)
|
60
|
+
|
61
|
+
else:
|
62
|
+
os.system("git checkout -b release/v%s develop" % v)
|
63
|
+
|
64
|
+
os.system("bump set %s" % v)
|
65
|
+
|
66
|
+
v += "\n"
|
67
|
+
|
68
|
+
print(v)
|
69
|
+
|
70
|
+
sys.exit(0)
|
data/exe/pdftemplator
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'pdf_templator'
|
5
|
+
require 'slop'
|
6
|
+
require 'rake'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module Slop
|
10
|
+
NonExistentFile = Class.new Error
|
11
|
+
|
12
|
+
class FileOption < StringOption
|
13
|
+
attr_reader :content
|
14
|
+
|
15
|
+
def call(value)
|
16
|
+
fail NonExistentFile, "File '#{value}' does not exist" unless File.exist?(value)
|
17
|
+
self.content = File.read(value)
|
18
|
+
self.value = value
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_writer :content
|
24
|
+
end
|
25
|
+
|
26
|
+
class JsonOption < FileOption
|
27
|
+
def call(value)
|
28
|
+
super
|
29
|
+
self.content = JSON.parse(content, symbolize_names: true)
|
30
|
+
self.value = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
opts = Slop::Options.new
|
36
|
+
opts.banner = 'Usage: pdftemplator [options]'
|
37
|
+
opts.file '--template', 'The path of the HTML/ERB template', required: true
|
38
|
+
opts.json '--in', 'The json with the variables to inject into the template', required: true
|
39
|
+
opts.string '--out', 'The path of the output PDF to be saved'
|
40
|
+
opts.on '-v', '--version', 'print the version' do
|
41
|
+
puts PdfTemplator::VERSION
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
opts.on '--help', 'prints this help' do
|
45
|
+
puts opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
parser = Slop::Parser.new(opts)
|
51
|
+
result = parser.parse(ARGV)
|
52
|
+
|
53
|
+
vars = result.option(:in)
|
54
|
+
template = result.option(:template)
|
55
|
+
|
56
|
+
fields = vars.content[:fields]
|
57
|
+
footer = vars.content[:footer]
|
58
|
+
content = template.content
|
59
|
+
|
60
|
+
result[:out] ||= template.value.pathmap('%X.pdf')
|
61
|
+
|
62
|
+
reader = PdfTemplator::Reader.new(content: content, footer: footer)
|
63
|
+
|
64
|
+
pdf = reader.write(fields)
|
65
|
+
File.open(result[:out], 'wb') { |f| f.write(pdf) }
|
66
|
+
puts "PDF Generated in '#{result[:out]}'"
|
67
|
+
rescue PdfTemplator::Reader::FieldsMismatchError => e
|
68
|
+
puts "Error: #{e.message}"
|
69
|
+
exit 1
|
70
|
+
rescue Slop::MissingRequiredOption, Slop::NonExistentFile, Slop::UnknownOption => e
|
71
|
+
puts "Error: #{e.message}"
|
72
|
+
puts ''
|
73
|
+
puts opts
|
74
|
+
exit 1
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pdf_templator/version'
|
4
|
+
|
5
|
+
module PdfTemplator
|
6
|
+
autoload :Reader, 'pdf_templator/reader'
|
7
|
+
autoload :Formatter, 'pdf_templator/formatter'
|
8
|
+
autoload :Type, 'pdf_templator/type'
|
9
|
+
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# Slop.string_to_type("string") #=> "StringType"
|
13
|
+
# Slop.string_to_type("some_thing") #=> "SomeThingType"
|
14
|
+
#
|
15
|
+
# Returns a camel-cased class looking string with Type suffix.
|
16
|
+
def self.string_to_type(string)
|
17
|
+
string.to_s.gsub(/(?:^|_)([a-z])/) { Regexp.last_match(1).capitalize } + 'Type'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Example:
|
21
|
+
#
|
22
|
+
# Slop.string_to_type_class("string") #=> Slop::StringType
|
23
|
+
# Slop.string_to_type_class("foo") #=> uninitialized constant FooType
|
24
|
+
#
|
25
|
+
# Returns the full qualified type class. Uses `#string_to_type`.
|
26
|
+
def self.string_to_type_class(string)
|
27
|
+
const_get(string_to_type(string))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'types'
|
4
|
+
|
5
|
+
module PdfTemplator
|
6
|
+
class Formatter
|
7
|
+
def initialize(content)
|
8
|
+
@content = content
|
9
|
+
end
|
10
|
+
|
11
|
+
# Format content
|
12
|
+
# @param type [string] money | number | date | string
|
13
|
+
# @param args [hash] :locale, :format
|
14
|
+
#
|
15
|
+
# @return [string]
|
16
|
+
def apply(type, args = {})
|
17
|
+
# Symbolize keys
|
18
|
+
args = args.map { |k, v| [k.to_sym, v] }.to_h
|
19
|
+
I18n.locale = args[:locale] || :en
|
20
|
+
klass = PdfTemplator.string_to_type_class(type)
|
21
|
+
klass.new(@content, args).call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'wicked_pdf'
|
5
|
+
|
6
|
+
module PdfTemplator
|
7
|
+
class Reader
|
8
|
+
FieldsMismatchError = Class.new ArgumentError
|
9
|
+
|
10
|
+
# Create a new Template Reader
|
11
|
+
# @param content [String] Content of the template
|
12
|
+
# @param header=nil [Hash] {
|
13
|
+
# center: 'TEXT',
|
14
|
+
# font_name: 'NAME',
|
15
|
+
# font_size: SIZE,
|
16
|
+
# left: 'TEXT',
|
17
|
+
# right: 'TEXT',
|
18
|
+
# spacing: REAL,
|
19
|
+
# line: true,
|
20
|
+
# content: 'HTML CONTENT ALREADY RENDERED'
|
21
|
+
# }
|
22
|
+
# @param footer=nil [Hash] {
|
23
|
+
# center: 'TEXT',
|
24
|
+
# font_name: 'NAME',
|
25
|
+
# font_size: SIZE,
|
26
|
+
# left: 'TEXT',
|
27
|
+
# right: 'TEXT',
|
28
|
+
# spacing: REAL,
|
29
|
+
# line: true,
|
30
|
+
# content: 'HTML CONTENT ALREADY RENDERED'
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# @see https://github.com/mileszs/wicked_pdf
|
34
|
+
# @return [type] [description]
|
35
|
+
def initialize(content:, header: nil, footer: nil)
|
36
|
+
@content = content
|
37
|
+
@header = header
|
38
|
+
@footer = footer
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get all the <field>s of the template.
|
42
|
+
# @return [Hash] {
|
43
|
+
# type: text|number|money|date,
|
44
|
+
# value: 'The value of the field',
|
45
|
+
# name: 'the_name_of_the_field'
|
46
|
+
# }
|
47
|
+
def fields
|
48
|
+
return @fields if @fields
|
49
|
+
@fields = {}
|
50
|
+
nokogiri_fields.each do |field|
|
51
|
+
name = field.attr('name').downcase.gsub(/ |-/, '_')
|
52
|
+
type = field.attr('type')
|
53
|
+
args = extract_args(field)
|
54
|
+
value = field.text
|
55
|
+
@fields[name.to_sym] = { type: type, value: value, name: name, args: args }
|
56
|
+
end
|
57
|
+
@fields
|
58
|
+
end
|
59
|
+
|
60
|
+
def fields_array
|
61
|
+
@fields_array ||= fields.map { |_, element| element }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Write a PDF with the fields,
|
65
|
+
# The type of the field will be taken from the original field.
|
66
|
+
#
|
67
|
+
# @param fields [Hash] with format
|
68
|
+
# {
|
69
|
+
# fecha: 'The content of the field'
|
70
|
+
# }
|
71
|
+
#
|
72
|
+
# @return [String] String of the PDF
|
73
|
+
def write(fields)
|
74
|
+
fields = clean_fields(fields)
|
75
|
+
|
76
|
+
fail FieldsMismatchError, "Missing fields #{missing_fields(fields)}" unless valid_fields?(fields)
|
77
|
+
build_doc && replace_fields(fields)
|
78
|
+
WickedPdf.new.pdf_from_string(
|
79
|
+
doc.to_html,
|
80
|
+
encoding: 'UTF-8',
|
81
|
+
footer: @footer,
|
82
|
+
dpi: 300,
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def replace_fields(fields)
|
87
|
+
fields.each do |k, v|
|
88
|
+
k = k.downcase.gsub(/ |-/, '_')
|
89
|
+
field_selector = "field[name=#{k}]"
|
90
|
+
doc.css(field_selector).each do |field|
|
91
|
+
new_node = doc.create_element('span')
|
92
|
+
args = extract_args(field)
|
93
|
+
new_node.add_child(format_content(field.attr('type'), args, v))
|
94
|
+
field.replace(new_node)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid_fields?(fields)
|
100
|
+
a = fields_array.map { |field| field[:name] }
|
101
|
+
b = fields if fields.is_a?(Array)
|
102
|
+
b ||= fields.keys
|
103
|
+
a.sort == b.sort
|
104
|
+
end
|
105
|
+
|
106
|
+
def missing_fields(fields)
|
107
|
+
a = fields_array.map { |field| field[:name] }
|
108
|
+
b = fields if fields.is_a?(Array)
|
109
|
+
b ||= fields.keys
|
110
|
+
a - b
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def extract_args(field)
|
116
|
+
data_attrs = field.attributes.select { |atr| atr.start_with?('data-') }
|
117
|
+
data_attrs.values.map { |atr| [atr.name.gsub('data-', ''), atr.value] }.to_h
|
118
|
+
end
|
119
|
+
|
120
|
+
# Cleans fields (lowercase, removes spaces and changes for underscores)
|
121
|
+
def clean_fields(fields)
|
122
|
+
fields.map { |k, v| [k.to_s.downcase.gsub(/ |-/, '_'), v] }.to_h
|
123
|
+
end
|
124
|
+
|
125
|
+
# Build content depending on the type
|
126
|
+
# @param type [String] One of text|number|money|date|accounting
|
127
|
+
# @param args [Hash] Extra variables to pass to the formatter
|
128
|
+
# @param content [String] The content to be formatted
|
129
|
+
#
|
130
|
+
# @return [String] The formatted content
|
131
|
+
def format_content(type, args, content)
|
132
|
+
Formatter.new(content).apply(type, args)
|
133
|
+
end
|
134
|
+
|
135
|
+
def doc
|
136
|
+
@doc ||= build_doc
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_doc
|
140
|
+
@doc = Nokogiri::HTML(@content)
|
141
|
+
end
|
142
|
+
|
143
|
+
def nokogiri_fields
|
144
|
+
doc.css('field')
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PdfTemplator
|
4
|
+
class Type
|
5
|
+
attr_reader :content
|
6
|
+
attr_reader :args
|
7
|
+
|
8
|
+
def initialize(content, args = {})
|
9
|
+
@content = content
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
# This method is called immediately when a type is found.
|
14
|
+
# Override it in sub-classes.
|
15
|
+
def call(_value)
|
16
|
+
fail NotImplementedError,
|
17
|
+
"you must override the `call' method for option #{self.class}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'money'
|
4
|
+
require 'active_support/core_ext/string/conversions'
|
5
|
+
require 'numbers_and_words'
|
6
|
+
|
7
|
+
module PdfTemplator
|
8
|
+
class TextType < Type
|
9
|
+
def call
|
10
|
+
"<strong>#{content}</strong>"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NumericType < Type
|
15
|
+
def initialize(content, args = {})
|
16
|
+
super
|
17
|
+
@content = cents
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cents
|
23
|
+
int, decimals = @content.to_s.split('.')
|
24
|
+
decimals = decimals[0..1]
|
25
|
+
"#{int}#{decimals}".to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class MoneyType < NumericType
|
30
|
+
def call
|
31
|
+
formatted = content.to_s[0..-3].to_i.to_words
|
32
|
+
"#{formatted} #{content.to_s[-2..-1]}/100"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AccountingType < NumericType
|
37
|
+
def call
|
38
|
+
Money.new(content, args[:currency]).format
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class NumberType < NumericType
|
43
|
+
def call
|
44
|
+
Money.new(content).format(symbol: false)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class DateType < TextType
|
49
|
+
def call
|
50
|
+
content.to_time.strftime(args[:format] || '%d/%b/%Y')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'pdf_templator/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'pdf_templator'
|
9
|
+
spec.version = PdfTemplator::VERSION
|
10
|
+
spec.authors = ['Genaro Madrid']
|
11
|
+
spec.email = ['genmadrid@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Write a short summary, because RubyGems requires one.'
|
14
|
+
spec.description = 'Write a longer description or delete this line.'
|
15
|
+
spec.homepage = 'https://github.com/Mifiel/pdf-templator'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_dependency 'activesupport'
|
25
|
+
spec.add_dependency 'money'
|
26
|
+
spec.add_dependency 'nokogiri'
|
27
|
+
spec.add_dependency 'numbers_and_words'
|
28
|
+
spec.add_dependency 'wicked_pdf'
|
29
|
+
# 0.12.4 introduced a bug that rendered super a small font
|
30
|
+
spec.add_dependency 'slop', '~> 4.6'
|
31
|
+
spec.add_dependency 'wkhtmltopdf-binary', '< 0.12.3.1'
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bump', '~> 0.6'
|
34
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
35
|
+
spec.add_development_dependency 'coveralls', '~> 0.7'
|
36
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
37
|
+
spec.add_development_dependency 'rspec', '~> 3.7'
|
38
|
+
spec.add_development_dependency 'simplecov', '~> 0.16'
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pdf_templator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Genaro Madrid
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: money
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: numbers_and_words
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: wicked_pdf
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: slop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.6'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.6'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: wkhtmltopdf-binary
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "<"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.12.3.1
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "<"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.12.3.1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: bump
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.6'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.6'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: bundler
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.16'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.16'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: coveralls
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.7'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.7'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '10.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '10.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rspec
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '3.7'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '3.7'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: simplecov
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0.16'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0.16'
|
195
|
+
description: Write a longer description or delete this line.
|
196
|
+
email:
|
197
|
+
- genmadrid@gmail.com
|
198
|
+
executables:
|
199
|
+
- pdftemplator
|
200
|
+
extensions: []
|
201
|
+
extra_rdoc_files: []
|
202
|
+
files:
|
203
|
+
- ".editorconfig"
|
204
|
+
- ".gitignore"
|
205
|
+
- ".rspec"
|
206
|
+
- ".rubocop.yml"
|
207
|
+
- ".travis.yml"
|
208
|
+
- Gemfile
|
209
|
+
- Guardfile
|
210
|
+
- README.md
|
211
|
+
- Rakefile
|
212
|
+
- bin/console
|
213
|
+
- bin/setup
|
214
|
+
- bump
|
215
|
+
- exe/pdftemplator
|
216
|
+
- lib/pdf_templator.rb
|
217
|
+
- lib/pdf_templator/formatter.rb
|
218
|
+
- lib/pdf_templator/reader.rb
|
219
|
+
- lib/pdf_templator/type.rb
|
220
|
+
- lib/pdf_templator/types.rb
|
221
|
+
- lib/pdf_templator/version.rb
|
222
|
+
- pdf_templator.gemspec
|
223
|
+
- pdf_templator.sublime-project
|
224
|
+
homepage: https://github.com/Mifiel/pdf-templator
|
225
|
+
licenses: []
|
226
|
+
metadata: {}
|
227
|
+
post_install_message:
|
228
|
+
rdoc_options: []
|
229
|
+
require_paths:
|
230
|
+
- lib
|
231
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - ">="
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: '0'
|
236
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
237
|
+
requirements:
|
238
|
+
- - ">="
|
239
|
+
- !ruby/object:Gem::Version
|
240
|
+
version: '0'
|
241
|
+
requirements: []
|
242
|
+
rubyforge_project:
|
243
|
+
rubygems_version: 2.5.2.1
|
244
|
+
signing_key:
|
245
|
+
specification_version: 4
|
246
|
+
summary: Write a short summary, because RubyGems requires one.
|
247
|
+
test_files: []
|