pdf_templator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|