invoicegenerator 1.0.0
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/.gitignore +5 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +6 -0
- data/exe/invoicegenerator +5 -0
- data/invoicegenerator.gemspec +30 -0
- data/lib/invoicegenerator.rb +1 -0
- data/lib/invoicegenerator/invoicegenerator.html +114 -0
- data/lib/invoicegenerator/invoicegenerator.rb +157 -0
- data/lib/invoicegenerator/version.rb +3 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2a2ae2f90644130b064a3f74f499282010910997c1b01f91bb54b2262e9f26d0
|
4
|
+
data.tar.gz: 7f182917dad778dce5e3e957d130b6d28d07782bccf37a1d1b511f06909336d9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8360844a20cdbcddd216504f8d724f99d5e3cddcbf131542ad7a534aa89ebeb0fc09f2bea23469b146e8fadb5796f3d7f40065ca20123d0ac5b198a121973ea4
|
7
|
+
data.tar.gz: 66c310273372f90b18123e40d530e470dc6dde89473e0bac870873d798d2a008c59acfcd8edaaaf91b2695a6c01aa1823c1339760f7d72324b636ccb4d5b8784
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) [year] [fullname]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Stupid invoice generator
|
2
|
+
|
3
|
+
Put the data you usualy uses to create your invoices into a YML file, run this
|
4
|
+
gem and done, you have a nice pdf invoice
|
5
|
+
|
6
|
+
To see an example the fast possible way run:
|
7
|
+
|
8
|
+
```
|
9
|
+
$ gem install invoicegenerator
|
10
|
+
$ invoicegenerator --show-yml-example | invoicegenerator --stdin
|
11
|
+
```
|
12
|
+
|
13
|
+
To have a starting point to write your YAML describing your invoice, type:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ invoicegenerator --show-yml-example > invoice.yml
|
17
|
+
```
|
18
|
+
|
19
|
+
Then change the generated invoice.yml
|
20
|
+
|
21
|
+
## How to change the invoice template?
|
22
|
+
|
23
|
+
Use the --template command line switch to specify an HTML template, to have a look in a example, type:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ invoicegenerator --show-template-example
|
27
|
+
```
|
28
|
+
|
29
|
+
## What to write in the YAML file?
|
30
|
+
|
31
|
+
Run `invoicegenerator.rb --show-yml-example` and you will see an example, basically you can use all keys you find as a command line option.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'invoicegenerator'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'invoicegenerator/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'invoicegenerator'
|
7
|
+
spec.version = InvoiceGenerator::VERSION
|
8
|
+
spec.authors = ['Hugo Parente Lima']
|
9
|
+
spec.email = ['hugo.pl@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Invoice generator'
|
12
|
+
spec.description = 'A stupid and simple invoice generator'
|
13
|
+
spec.homepage = 'https://github.com/hugopl/invoicegenerator'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency('money', '~> 6.13')
|
26
|
+
spec.add_dependency('optimist', '~> 3.0')
|
27
|
+
spec.add_dependency('pdfkit', '~> 0.8')
|
28
|
+
spec.add_development_dependency('bundler', '~> 1.17')
|
29
|
+
spec.add_development_dependency('rake', '~> 10.0')
|
30
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'invoicegenerator/invoicegenerator'
|
@@ -0,0 +1,114 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<style>
|
4
|
+
h1 {
|
5
|
+
text-align: center;
|
6
|
+
background-color: black;
|
7
|
+
color: white;
|
8
|
+
border-radius: 10px;
|
9
|
+
}
|
10
|
+
|
11
|
+
td {
|
12
|
+
padding: 5px;
|
13
|
+
vertical-align: top;
|
14
|
+
white-space: pre;
|
15
|
+
}
|
16
|
+
|
17
|
+
table {
|
18
|
+
border-spacing: 0px;
|
19
|
+
border-collapse: separate;
|
20
|
+
margin-bottom: 40px;
|
21
|
+
}
|
22
|
+
|
23
|
+
table td:first-child {
|
24
|
+
font-weight: bold;
|
25
|
+
white-space: nowrap;
|
26
|
+
}
|
27
|
+
|
28
|
+
table.items td:first-child {
|
29
|
+
font-weight: normal;
|
30
|
+
}
|
31
|
+
|
32
|
+
.summary > td {
|
33
|
+
white-space: nowrap;
|
34
|
+
}
|
35
|
+
|
36
|
+
.client {
|
37
|
+
float: left;
|
38
|
+
}
|
39
|
+
|
40
|
+
.summary {
|
41
|
+
float: right;
|
42
|
+
}
|
43
|
+
|
44
|
+
.items {
|
45
|
+
clear: both;
|
46
|
+
width: 100%;
|
47
|
+
}
|
48
|
+
.items th {
|
49
|
+
padding: 4px;
|
50
|
+
background-color: #e9e9e9;
|
51
|
+
text-align: left;
|
52
|
+
}
|
53
|
+
.items th:first-child {
|
54
|
+
border-radius: 5px 0px 0px 5px;
|
55
|
+
}
|
56
|
+
.items th:last-child {
|
57
|
+
border-radius: 0px 5px 5px 0px;
|
58
|
+
}
|
59
|
+
.notes {
|
60
|
+
white-space: pre;
|
61
|
+
}
|
62
|
+
</style>
|
63
|
+
</head>
|
64
|
+
<body>
|
65
|
+
<h1>%header%</h1>
|
66
|
+
|
67
|
+
<table class="from">
|
68
|
+
<tr>
|
69
|
+
<td>From</td>
|
70
|
+
<td>%from%</td>
|
71
|
+
</tr>
|
72
|
+
</table>
|
73
|
+
|
74
|
+
<table class="client">
|
75
|
+
<tr>
|
76
|
+
<td>Client</td>
|
77
|
+
<td>%client%</td>
|
78
|
+
</tr>
|
79
|
+
</table>
|
80
|
+
|
81
|
+
|
82
|
+
<table class="summary">
|
83
|
+
<tr>
|
84
|
+
<td>Invoice Number</td>
|
85
|
+
<td>%number%</td>
|
86
|
+
</tr>
|
87
|
+
<tr>
|
88
|
+
<td>Date</td>
|
89
|
+
<td>%date%</td>
|
90
|
+
</tr>
|
91
|
+
<tr>
|
92
|
+
<td>Due date</td>
|
93
|
+
<td>%due-date%</td>
|
94
|
+
</tr>
|
95
|
+
<tr>
|
96
|
+
<td>Balance Due</td>
|
97
|
+
<td>%balance%</td>
|
98
|
+
</tr>
|
99
|
+
</table>
|
100
|
+
|
101
|
+
<table class="items">
|
102
|
+
<tr>
|
103
|
+
<th>Item</th>
|
104
|
+
<th>Quantity</th>
|
105
|
+
<th>Rate</th>
|
106
|
+
<th>Amount</th>
|
107
|
+
</tr>
|
108
|
+
%items%
|
109
|
+
</table>
|
110
|
+
|
111
|
+
<h2>Notes</h2>
|
112
|
+
<p class="notes">%notes%</p>
|
113
|
+
</body>
|
114
|
+
</html>
|
@@ -0,0 +1,157 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pdfkit'
|
4
|
+
require 'yaml'
|
5
|
+
require 'optimist'
|
6
|
+
require 'date'
|
7
|
+
require 'money'
|
8
|
+
|
9
|
+
I18n.enforce_available_locales = false
|
10
|
+
Money.locale_backend = :currency
|
11
|
+
|
12
|
+
module InvoiceGenerator
|
13
|
+
def show_yml_example_and_exit
|
14
|
+
puts <<eot
|
15
|
+
from: |
|
16
|
+
My multiline name
|
17
|
+
Here's a second line
|
18
|
+
client: |
|
19
|
+
My multiline client
|
20
|
+
Hey ho, second line here
|
21
|
+
number: 2019-123
|
22
|
+
notes: |
|
23
|
+
If all your data are always the same, just the invoice number changes,
|
24
|
+
save the the static data in a yml and pass the invoice number on command line
|
25
|
+
by using (--number).
|
26
|
+
|
27
|
+
Note that the date is always default to today, and the due-date to today + 15
|
28
|
+
items:
|
29
|
+
-
|
30
|
+
- Nice item for %past_month% %year%
|
31
|
+
- 1
|
32
|
+
- 12334
|
33
|
+
-
|
34
|
+
- Other item, for %month%
|
35
|
+
- 0.5
|
36
|
+
- 100000
|
37
|
+
currency: GBP
|
38
|
+
eot
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
|
42
|
+
def show_template_example_and_exit
|
43
|
+
puts File.read(template_path)
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
|
47
|
+
def map_date_fields(opts)
|
48
|
+
[:date, 'due-date'].each do |i|
|
49
|
+
opts[i] = yield(opts[i])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_params
|
54
|
+
Optimist.options do
|
55
|
+
opt :client, 'Contents of client field.', type: :string
|
56
|
+
opt :currency, 'Currency used.', type: :string, default: 'USD'
|
57
|
+
opt :date, 'Invoice date.', type: :date, default: Date.today
|
58
|
+
opt 'due-date', 'Due date.', type: :date, default: (Date.today + 15)
|
59
|
+
opt :from, 'Contents of from field.', type: :string
|
60
|
+
opt :header, 'Contents of the header.', type: :string, default: 'Invoice'
|
61
|
+
opt :notes, 'Contents of notes field.', type: :string
|
62
|
+
opt :number, 'Invoice number.', type: :string
|
63
|
+
opt 'show-yml-example', 'Show an example of a YML file that can be used by this script.'
|
64
|
+
opt 'show-template-example', 'Show an example of a HTML template.'
|
65
|
+
opt :stdin, 'Read YML file from STDIN.'
|
66
|
+
opt :template, 'HTML template to use', type: :string
|
67
|
+
opt :yml, 'YML file with values for parameters not given into command line.', default: 'invoice.yml'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_yml(opts)
|
72
|
+
if opts[:stdin]
|
73
|
+
data = StringIO.new
|
74
|
+
data << STDIN.read until STDIN.eof?
|
75
|
+
yaml = data.string
|
76
|
+
else
|
77
|
+
yaml = File.read(opts[:yml])
|
78
|
+
end
|
79
|
+
YAML.safe_load(yaml).inject(opts) do |memo, item|
|
80
|
+
memo[item[0].to_sym] = item[1]
|
81
|
+
memo
|
82
|
+
end
|
83
|
+
fail('Items not in the right format, something is missing.') unless opts[:items].is_a?(Array)
|
84
|
+
|
85
|
+
opts
|
86
|
+
rescue Errno::ENOENT
|
87
|
+
raise "YML file #{opts[:yml]} not found or can't be read."
|
88
|
+
end
|
89
|
+
|
90
|
+
def fix_date_options(opts)
|
91
|
+
map_date_fields(opts) do |value|
|
92
|
+
value.is_a?(Date) ? value : Date.parse(value)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_extra_options(opts)
|
97
|
+
today = Date.today
|
98
|
+
opts[:month] = today.strftime('%B')
|
99
|
+
opts[:past_month] = (today << 1).strftime('%B')
|
100
|
+
opts[:year] = today.strftime('%Y')
|
101
|
+
|
102
|
+
raw_balance = opts[:items].inject(0) do |balance, item|
|
103
|
+
balance + (item[2] * item[1])
|
104
|
+
end
|
105
|
+
opts[:balance] = Money.new(raw_balance, opts[:currency]).format
|
106
|
+
end
|
107
|
+
|
108
|
+
def format_items(opts)
|
109
|
+
opts[:items].map! do |i|
|
110
|
+
fail 'Items must have 3 values' if i.size != 3
|
111
|
+
|
112
|
+
i[3] = Money.new(i[2] * i[1], opts[:currency]).format
|
113
|
+
i[2] = Money.new(i[2], opts[:currency]).format
|
114
|
+
"<tr><td>#{i.join('</td><td>')}</td></tr>"
|
115
|
+
end
|
116
|
+
opts[:items] = opts[:items].join
|
117
|
+
end
|
118
|
+
|
119
|
+
def template_path
|
120
|
+
File.join(File.dirname(__FILE__), 'invoicegenerator.html')
|
121
|
+
end
|
122
|
+
|
123
|
+
def generate_html(opts)
|
124
|
+
map_date_fields(opts) do |value|
|
125
|
+
value.strftime('%B %-d, %Y')
|
126
|
+
end
|
127
|
+
|
128
|
+
html = File.read(opts[:template] || template_path)
|
129
|
+
opts.each do |opt, value|
|
130
|
+
html.gsub!("%#{opt}%", value.to_s)
|
131
|
+
end
|
132
|
+
html
|
133
|
+
end
|
134
|
+
|
135
|
+
def main
|
136
|
+
opts = read_params
|
137
|
+
show_yml_example_and_exit if opts[:'show-yml-example_given']
|
138
|
+
show_template_example_and_exit if opts[:'show-template-example_given']
|
139
|
+
|
140
|
+
read_yml(opts)
|
141
|
+
fix_date_options(opts)
|
142
|
+
add_extra_options(opts)
|
143
|
+
format_items(opts)
|
144
|
+
html = generate_html(opts)
|
145
|
+
|
146
|
+
kit = PDFKit.new(html.encode('iso-8859-1'))
|
147
|
+
name = "invoice-#{opts[:number]}.pdf"
|
148
|
+
puts "Generating #{name}..."
|
149
|
+
kit.to_file(name)
|
150
|
+
rescue Errno::ENOENT => e
|
151
|
+
abort e.message
|
152
|
+
rescue RuntimeError => e
|
153
|
+
abort e.message
|
154
|
+
end
|
155
|
+
|
156
|
+
extend self
|
157
|
+
end
|
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: invoicegenerator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Hugo Parente Lima
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: money
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6.13'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: optimist
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pdfkit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.17'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.17'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: A stupid and simple invoice generator
|
84
|
+
email:
|
85
|
+
- hugo.pl@gmail.com
|
86
|
+
executables:
|
87
|
+
- invoicegenerator
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- exe/invoicegenerator
|
99
|
+
- invoicegenerator.gemspec
|
100
|
+
- lib/invoicegenerator.rb
|
101
|
+
- lib/invoicegenerator/invoicegenerator.html
|
102
|
+
- lib/invoicegenerator/invoicegenerator.rb
|
103
|
+
- lib/invoicegenerator/version.rb
|
104
|
+
homepage: https://github.com/hugopl/invoicegenerator
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.7.8
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Invoice generator
|
128
|
+
test_files: []
|