text-invoice 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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in text-invoice.gemspec
4
+ gemspec
@@ -0,0 +1,66 @@
1
+ # Text Invoice
2
+
3
+ Some tools and helpers for creating, summarising and transforming invoices stored as [YAML][yaml] documents.
4
+
5
+ The tools are designed to be used in a UNIX-like environment.
6
+
7
+ ## Typical uses
8
+
9
+ Create a new invoice
10
+
11
+ text-invoice new > some-job.yaml
12
+
13
+ Update totals, transform to HTML and write to disk
14
+
15
+ cat some-job.yaml | text-invoice update | text-invoice html > some-job.html
16
+
17
+ Get a summary of all invoices
18
+
19
+ find -name "*.yaml" | xargs text-invoice summary
20
+
21
+ Get a summarised list of all invoices
22
+
23
+ find -name "*.yaml" | xargs text-invoice list
24
+
25
+ Get a summarised list of all invoices which contain a word
26
+
27
+ grep -r -l "some pattern" * | xargs text-invoice list
28
+
29
+ You get the idea.
30
+
31
+ ## Custom templates
32
+
33
+ It's just YAML so it's easy template. I use [Mustache templates][mustache] for the default HTML invoice, and added an option to support custom Mustache templates
34
+
35
+ cat some-job.yml | text-invoice template my-template.mustache > some-job.something
36
+
37
+ Of course if you have the Mustache gem installed (which is a dependency of this gem!) you can just use that!
38
+
39
+ cat some-job.yml | mustache - template.mustache > some-job.something
40
+
41
+ ## PDF
42
+
43
+ I transform my invoices in PDF using [wkhtmltopdf][wkhtmltopdf]. There is a [wkpdf gem][wkpdf] but it's only for Max OS X, so that wasn't getting included.
44
+
45
+ cat some-job.yaml | text-invoice update | text-invoice html > /tmp/invoice.html && wkhtmltopdf /tmp/invoice.html some-job.pdf
46
+
47
+ Use whatever makes you happy.
48
+
49
+ ## This seems stupid
50
+
51
+ I like having invoices in text so I can update them in another Vim buffer as I'm doing work. If I ever think I'm doing to much typing I can always wrap the commands in shell scripts or Vim mappings.
52
+
53
+ I prefer this to context switching to another application/website or trying to work out what I did from git logs.
54
+
55
+ ## Acknowledgements
56
+
57
+ The template I'm using is a modified version of the pretty-neat-but-not-what-I-wanted [Editable, Printable Invoice by Chris Coyer and others][editable-invoice].
58
+
59
+ This is the first gem I've written, and [this guide][gem-dev] was helpful.
60
+
61
+ [editable-invoice]: http://css-tricks.com/editable-invoice-v2/
62
+ [wkhtmltopdf]: http://code.google.com/p/wkhtmltopdf/
63
+ [mustache]: http://mustache.github.com/
64
+ [gem-dev]: https://github.com/radar/guides/blob/master/gem-development.md
65
+ [yaml]: http://yaml.org/
66
+ [wkpdf]: http://rubygems.org/gems/wkpdf
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'text-invoice/cli'
3
+ cli = TextInvoice::CLI.new
4
+ puts cli.run()
5
+
@@ -0,0 +1,11 @@
1
+ require "text-invoice/version"
2
+ require "text-invoice/totals"
3
+ require "text-invoice/summary"
4
+ require "text-invoice/cli"
5
+ require "text-invoice/tasks"
6
+ require "text-invoice/invoice"
7
+ require "text-invoice/template"
8
+ require 'yaml'
9
+
10
+ module TextInvoice
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class CLI
5
+ attr_accessor :tasks, :argv, :stdin
6
+
7
+ def initialize()
8
+ @tasks = TextInvoice::Tasks.new
9
+ @argv = ARGV
10
+ @stdin = $stdin
11
+ end
12
+
13
+ def run()
14
+ mode = @argv.shift
15
+ if mode == "update"
16
+ @tasks.totals(@stdin.read)
17
+ elsif mode == "summary"
18
+ @tasks.summary(@argv)
19
+ elsif mode == "list"
20
+ @tasks.list(@argv)
21
+ elsif mode == "new"
22
+ @tasks.new_invoice()
23
+ elsif mode == "html"
24
+ @tasks.html(@stdin.read)
25
+ elsif mode == "template"
26
+ template = @argv.shift
27
+ if template
28
+ @tasks.template(@stdin.read, template)
29
+ else
30
+ @tasks.usage()
31
+ end
32
+ else
33
+ @tasks.usage()
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class Invoice
5
+ def self.blank()
6
+ open(root + "invoice.yaml").read
7
+ end
8
+
9
+ def self.root
10
+ File.dirname(__FILE__) + "/"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ ---
2
+ invoice:
3
+ date:
4
+ from:
5
+ name:
6
+ abn:
7
+ address1:
8
+ address2:
9
+ phone:
10
+ email:
11
+ to:
12
+ attn:
13
+ for:
14
+ name:
15
+ address1:
16
+ address2:
17
+ address3:
18
+ phone:
19
+ items:
20
+ - description: item
21
+ quantity: 0
22
+ unit: 0
23
+ paid: 0
24
+ terms:
25
+ ---
@@ -0,0 +1,34 @@
1
+ module TextInvoice
2
+ class Summary
3
+ def summary(invoices)
4
+ paid = 0
5
+ due = 0
6
+ total = 0
7
+ count = 0
8
+ invoices.each do |invoice|
9
+ data = load(invoice)
10
+ total += data["total"]
11
+ paid += data["paid"]
12
+ due += data["due"]
13
+ count += 1
14
+ end
15
+ headings = ["Count", "Total", "Paid", "Due"].join("\t")
16
+ results = [count, total, paid, due].join("\t")
17
+ [headings, results].join("\n")
18
+ end
19
+
20
+ def list(invoices)
21
+ response = [["Invoice","Date", "Total", "Paid", "Due"].join("\t")]
22
+ invoices.each do |invoice|
23
+ data = load(invoice)
24
+ response << ([data["invoice"], data["date"], data["total"], data["paid"], data["due"]].join("\t"))
25
+ end
26
+ response.join("\n")
27
+ end
28
+
29
+ def load(invoice)
30
+ calculator = TextInvoice::Totals.new
31
+ YAML.load(calculator.process(open(invoice)))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class Tasks
5
+ attr_accessor :totals_calculator, :summary_calculator, :invoice, :template
6
+
7
+ def initialize()
8
+ @totals_calculator = TextInvoice::Totals.new
9
+ @summary_calculator = TextInvoice::Summary.new
10
+ @invoice = TextInvoice::Invoice
11
+ @template = TextInvoice::Template.new
12
+ end
13
+
14
+ def totals(input)
15
+ @totals_calculator.process(input)
16
+ end
17
+
18
+ def summary(input)
19
+ @summary_calculator.summary(input)
20
+ end
21
+
22
+ def list(input)
23
+ @summary_calculator.list(input)
24
+ end
25
+
26
+ def new_invoice()
27
+ @invoice.blank()
28
+ end
29
+
30
+ def html(invoice)
31
+ @template.html(invoice)
32
+ end
33
+
34
+ def template(invoice, template)
35
+ @template.custom(invoice, template)
36
+ end
37
+
38
+ def usage()
39
+ text = """
40
+ Usage: text-invoice [new|totals|summary|list|html|template] [additional arguments..]
41
+
42
+ new Writes an empty invoice to STDOUT
43
+
44
+ update Calculates subtotals, totals and due amounts
45
+ Reads an invoice from STDIN and writes an updated invoice to STDOUT
46
+
47
+ summary Returns a summary of invoice files.
48
+ Summarises invoice files from additional arguments
49
+
50
+ list Returns a summarised list of invoices
51
+ Lists invoice files from additional arguments
52
+
53
+ html Transforms an invoice to HTML using a built-in template
54
+ Reads invoice from STDIN
55
+
56
+ template Transforms an invoice to HTML using a custom Mustache template
57
+ Reads invoice from STDIN. File path of a Mustache template must
58
+ be provided as an additional argument.
59
+ """
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ require 'text-invoice'
2
+ require 'mustache'
3
+
4
+ module TextInvoice
5
+ class Template
6
+ def html(invoice)
7
+ parsed = YAML.load(invoice)
8
+ Mustache.render(open(root + "templates/invoice.html").read(), parsed)
9
+ end
10
+
11
+ def custom(invoice, template)
12
+ parsed = YAML.load(invoice)
13
+ Mustache.render(open(template).read(), parsed)
14
+ end
15
+
16
+ def root
17
+ File.dirname(__FILE__) + "/../../"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module TextInvoice
2
+ class Totals
3
+ def process(invoice)
4
+ # parse
5
+ parsed = YAML.load(invoice)
6
+
7
+ # total accumulator
8
+ total = 0
9
+
10
+ # process line items
11
+ if not parsed["items"] == nil
12
+ for item in parsed["items"]
13
+ quantity = item["quantity"]
14
+ unit = item["unit"]
15
+ subtotal = quantity * unit
16
+ item["subtotal"] = subtotal
17
+ total += subtotal
18
+ end
19
+ end
20
+
21
+ # update totals
22
+ parsed["total"] = total
23
+
24
+ if parsed["paid"]
25
+ parsed["due"] = total - parsed["paid"]
26
+ else
27
+ parsed["due"] = total
28
+ end
29
+
30
+ parsed.to_yaml
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module TextInvoice
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,150 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::CLI do
4
+ it "should return usage details with no parameters" do
5
+ tasks = double("tasks")
6
+ tasks.should_receive(:usage).and_return("usage details")
7
+
8
+ cli = TextInvoice::CLI.new
9
+ cli.tasks = tasks
10
+ cli.run().should eql("usage details")
11
+ end
12
+
13
+ it "should return usage details with unknow parameters" do
14
+ argv = double("argv")
15
+ argv.should_receive(:shift).and_return("something we don't do")
16
+
17
+ tasks = double("tasks")
18
+ tasks.should_receive(:usage).and_return("usage details")
19
+
20
+ cli = TextInvoice::CLI.new
21
+ cli.argv = argv
22
+ cli.tasks = tasks
23
+ cli.run().should eql("usage details")
24
+ end
25
+
26
+ it "should return summary task with argv parameter" do
27
+ argv = double("argv")
28
+ argv.should_receive(:shift).and_return("summary")
29
+
30
+ tasks = double("tasks")
31
+ tasks.should_receive(:summary).with(argv).and_return("summary results")
32
+
33
+ cli = TextInvoice::CLI.new
34
+ cli.tasks = tasks
35
+ cli.argv = argv
36
+
37
+ cli.run().should eql("summary results")
38
+ end
39
+
40
+ it "should return summary task with argv parameters" do
41
+ argv = double("argv")
42
+ argv.should_receive(:shift).and_return("summary")
43
+
44
+ tasks = double("tasks")
45
+ tasks.should_receive(:summary).with(argv).and_return("summary results")
46
+
47
+ cli = TextInvoice::CLI.new
48
+ cli.tasks = tasks
49
+ cli.argv = argv
50
+
51
+ cli.run().should eql("summary results")
52
+ end
53
+
54
+ it "should return list task with argv parameter" do
55
+ argv = double("argv")
56
+ argv.should_receive(:shift).and_return("list")
57
+
58
+ tasks = double("tasks")
59
+ tasks.should_receive(:list).with(argv).and_return("list results")
60
+
61
+ cli = TextInvoice::CLI.new
62
+ cli.tasks = tasks
63
+ cli.argv = argv
64
+
65
+ cli.run().should eql("list results")
66
+ end
67
+
68
+ it "should return totals task with stdin parameter" do
69
+ argv = double("argv")
70
+ argv.should_receive(:shift).and_return("update")
71
+
72
+ stdin = double("stdin")
73
+ stdin.should_receive(:read).and_return("stdin")
74
+
75
+ tasks = double("tasks")
76
+ tasks.should_receive(:totals).with("stdin").and_return("totals results")
77
+
78
+ cli = TextInvoice::CLI.new
79
+ cli.tasks = tasks
80
+ cli.stdin = stdin
81
+ cli.argv = argv
82
+
83
+ cli.run().should eql("totals results")
84
+ end
85
+
86
+ it "should return new invoice" do
87
+ argv = double("argv")
88
+ argv.should_receive(:shift).and_return("new")
89
+
90
+ tasks = double("tasks")
91
+ tasks.should_receive(:new_invoice).with(no_args()).and_return("new invoice yaml")
92
+
93
+ cli = TextInvoice::CLI.new
94
+ cli.tasks = tasks
95
+ cli.argv = argv
96
+
97
+ cli.run().should eql("new invoice yaml")
98
+ end
99
+
100
+ it "should html template invoices" do
101
+ argv = double("argv")
102
+ argv.should_receive(:shift).and_return("html")
103
+
104
+ stdin = double("stdin")
105
+ stdin.should_receive(:read).and_return("stdin")
106
+
107
+ tasks = double("tasks")
108
+ tasks.should_receive(:html).with("stdin").and_return("invoice html")
109
+
110
+ cli = TextInvoice::CLI.new
111
+ cli.tasks = tasks
112
+ cli.argv = argv
113
+ cli.stdin = stdin
114
+
115
+ cli.run().should eql("invoice html")
116
+ end
117
+
118
+ it "should custom template invoices" do
119
+ argv = double("argv")
120
+ argv.should_receive(:shift).and_return("template", "template_path")
121
+
122
+ stdin = double("stdin")
123
+ stdin.should_receive(:read).and_return("stdin")
124
+
125
+ tasks = double("tasks")
126
+ tasks.should_receive(:template).with("stdin","template_path").and_return("invoice html")
127
+
128
+ cli = TextInvoice::CLI.new
129
+ cli.tasks = tasks
130
+ cli.argv = argv
131
+ cli.stdin = stdin
132
+
133
+ cli.run().should eql("invoice html")
134
+ end
135
+
136
+ it "should return usage if custom template path not provided" do
137
+ argv = double("argv")
138
+ argv.should_receive(:shift).and_return("template", nil)
139
+
140
+ tasks = double("tasks")
141
+ tasks.should_receive(:usage)
142
+
143
+ cli = TextInvoice::CLI.new
144
+ cli.tasks = tasks
145
+ cli.argv = argv
146
+
147
+ cli.run()
148
+ end
149
+
150
+ end
@@ -0,0 +1,8 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Invoice do
4
+ it "should return invoice yaml" do
5
+ TextInvoice::Invoice.blank().should include("invoice")
6
+ end
7
+ end
8
+
@@ -0,0 +1,39 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Summary do
4
+ it "should open a yaml file and update totals" do
5
+ summary = TextInvoice::Summary.new
6
+ calculate = TextInvoice::Totals.new
7
+
8
+ invoice = summary.load("lib/text-invoice/invoice.yaml").to_yaml
9
+
10
+ blank = calculate.process(TextInvoice::Invoice.blank)
11
+ invoice.should eql(blank)
12
+ end
13
+
14
+ it "should return invoice summary" do
15
+ summary = TextInvoice::Summary.new
16
+ invoice1 = { 'due' => 5, 'total' => 10, 'paid' => 5 }
17
+ invoice2 = { 'due' => 1, 'total' => 10, 'paid' => 9 }
18
+ summary.stub(:load).and_return(invoice1, invoice2)
19
+
20
+ response = summary.summary(['file1','file2'])
21
+
22
+ headings = response.split("\n")[0].should eql(['Count','Total','Paid','Due'].join("\t"))
23
+ results = response.split("\n")[1].should eql(['2','20','14','6'].join("\t"))
24
+ end
25
+
26
+ it "should return invoice list" do
27
+ summary = TextInvoice::Summary.new
28
+ invoice1 = { 'date' => '1/3/2012', 'invoice' => "1", 'due' => 5, 'total' => 10, 'paid' => 5 }
29
+ invoice2 = { 'date' => '2/3/2012', 'invoice' => "2", 'due' => 1, 'total' => 10, 'paid' => 9 }
30
+ summary.stub(:load).and_return(invoice1, invoice2)
31
+
32
+ response = summary.list(['file1','file2'])
33
+
34
+ response.split("\n")[0].should eql(['Invoice','Date','Total','Paid','Due'].join("\t"))
35
+ response.split("\n")[1].should eql(['1','1/3/2012','10','5','5'].join("\t"))
36
+ response.split("\n")[2].should eql(['2','2/3/2012','10','9','1'].join("\t"))
37
+ end
38
+ end
39
+
@@ -0,0 +1,69 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Tasks do
4
+ it "should return usage details" do
5
+ tasks = TextInvoice::Tasks.new
6
+ tasks.usage().should include("Usage")
7
+ end
8
+
9
+ it "should return summary" do
10
+ summary_calculator = double("summary")
11
+ summary_calculator.should_receive(:summary).with([]).and_return("summary results")
12
+
13
+ tasks = TextInvoice::Tasks.new
14
+ tasks.summary_calculator = summary_calculator
15
+
16
+ tasks.summary([]).should include("summary results")
17
+ end
18
+
19
+ it "should return list" do
20
+ summary_calculator = double("summary")
21
+ summary_calculator.should_receive(:list).with([]).and_return("list results")
22
+
23
+ tasks = TextInvoice::Tasks.new
24
+ tasks.summary_calculator = summary_calculator
25
+
26
+ tasks.list([]).should include("list results")
27
+ end
28
+
29
+ it "should return totals" do
30
+ totals_calculator = double("summary")
31
+ totals_calculator.should_receive(:process).with([]).and_return("totals results")
32
+
33
+ tasks = TextInvoice::Tasks.new
34
+ tasks.totals_calculator = totals_calculator
35
+
36
+ tasks.totals([]).should include("totals results")
37
+ end
38
+
39
+ it "should return blank invoice yaml" do
40
+ invoice = double("invoice")
41
+ invoice.should_receive(:blank).and_return("new invoice yaml")
42
+
43
+ tasks = TextInvoice::Tasks.new
44
+ tasks.invoice = invoice
45
+
46
+ tasks.new_invoice().should eql("new invoice yaml")
47
+ end
48
+
49
+ it "should return html" do
50
+ template = double("template")
51
+ template.should_receive(:html).with({}).and_return("invoice html")
52
+
53
+ tasks = TextInvoice::Tasks.new
54
+ tasks.template = template
55
+
56
+ tasks.html({}).should eql("invoice html")
57
+ end
58
+
59
+ it "should return template" do
60
+ template = double("template")
61
+ template.should_receive(:custom).with({},"").and_return("invoice html")
62
+
63
+ tasks = TextInvoice::Tasks.new
64
+ tasks.template = template
65
+
66
+ tasks.template({},"").should eql("invoice html")
67
+ end
68
+
69
+ end
@@ -0,0 +1,29 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Template do
4
+ it "should return invoice html the using default template" do
5
+ template = TextInvoice::Template.new
6
+ invoice = YAML.load(TextInvoice::Invoice.blank)
7
+ invoice["invoice"] = "123"
8
+
9
+ html = template.html(invoice.to_yaml)
10
+
11
+ # check for something from the template
12
+ html.should include("html")
13
+ # check for something from the data
14
+ html.should include("123")
15
+ end
16
+
17
+ it "should return invoice html the using a custom template" do
18
+ template = TextInvoice::Template.new
19
+ invoice = YAML.load(TextInvoice::Invoice.blank)
20
+ invoice["invoice"] = "123"
21
+
22
+ html = template.custom(invoice.to_yaml, "templates/invoice.html")
23
+
24
+ # check for something from the template
25
+ html.should include("html")
26
+ # check for something from the data
27
+ html.should include("123")
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Totals do
4
+ it "should add totals for empty invoice" do
5
+ # empty invoice
6
+ invoice = {}.to_yaml
7
+ calculator = TextInvoice::Totals.new
8
+
9
+ # process
10
+ result = YAML.load(calculator.process(invoice))
11
+
12
+ # validate
13
+ result["total"].should eql(0)
14
+ result["due"].should eql(0)
15
+ end
16
+
17
+ it "should update invoice totals and subtotals" do
18
+
19
+ # setup invoice
20
+ invoice = { 'items' =>
21
+ [ { 'quantity' => 1, 'unit' => 4 },
22
+ { 'quantity' => 2, 'unit' => 3 } ] }.to_yaml
23
+ calculator = TextInvoice::Totals.new
24
+
25
+ # process
26
+ result = YAML.load(calculator.process(invoice))
27
+
28
+ # validate
29
+ result["items"][0]["subtotal"].should eql(4)
30
+ result["items"][1]["subtotal"].should eql(6)
31
+ result["total"].should eql(10)
32
+ result["due"].should eql(10)
33
+ end
34
+
35
+ it "should update 'due' amount to be 'total' minus 'paid'" do
36
+
37
+ # setup invoice
38
+ invoice = { 'items' => [ { 'quantity' => 1, 'unit' => 10 } ], 'paid' => 5 }.to_yaml
39
+ calculator = TextInvoice::Totals.new
40
+
41
+ # process
42
+ result = YAML.load(calculator.process(invoice))
43
+
44
+ # validate
45
+ result["total"].should eql(10)
46
+ result["paid"].should eql(5)
47
+ result["due"].should eql(5)
48
+ end
49
+ end
@@ -0,0 +1,261 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
5
+
6
+ <title>Invoice</title>
7
+
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ body {
15
+ font: 14px/1.4 Georgia, serif;
16
+ }
17
+
18
+ #page-wrap {
19
+ width: 800px;
20
+ margin: 0 auto;
21
+ }
22
+
23
+ table {
24
+ border-collapse: collapse;
25
+ }
26
+
27
+ table td, table th {
28
+ border: 1px solid black;
29
+ padding: 5px;
30
+ }
31
+
32
+ #header {
33
+ height: 15px;
34
+ width: 100%;
35
+ margin: 20px 0;
36
+ background: #222;
37
+ text-align: center;
38
+ color: white;
39
+ font: bold 15px Helvetica, Sans-Serif;
40
+ text-decoration: uppercase;
41
+ padding: 8px 0px;
42
+ }
43
+
44
+ #identity {
45
+ width: 300px;
46
+ float: left
47
+ }
48
+
49
+ #customer {
50
+ width: 300px;
51
+ float: right
52
+ }
53
+
54
+ #customer table {
55
+ width: 100%
56
+ }
57
+
58
+ #customer table th {
59
+ text-align: left;
60
+ background: #eee;
61
+ }
62
+
63
+ #address {
64
+ width: 250px;
65
+ height: 150px;
66
+ float: left;
67
+ }
68
+
69
+ #meta {
70
+ margin-top: 30px;
71
+ margin-bottom: 30px;
72
+ width: 300px;
73
+ float: right;
74
+ }
75
+
76
+ #meta td {
77
+ text-align: right;
78
+ }
79
+
80
+ #meta td.meta-head {
81
+ text-align: left;
82
+ background: #eee;
83
+ }
84
+
85
+ #meta td textarea {
86
+ width: 100%;
87
+ height: 20px;
88
+ text-align: right;
89
+ }
90
+
91
+ #items {
92
+ clear: both;
93
+ width: 100%;
94
+ margin: 30px 0 0 0;
95
+ border: 1px none black;
96
+ }
97
+
98
+ #items th {
99
+ background: #eee;
100
+ }
101
+
102
+ #items tr.item-row td {
103
+ vertical-align: top;
104
+ border: none;
105
+ }
106
+
107
+ #items tr.item-row {
108
+ border-left: 1px solid black;
109
+ border-right: 1px solid black;
110
+ }
111
+
112
+ #items tr.totals {
113
+ border-right: 1px solid black;
114
+ border-top: 1px solid black;
115
+ }
116
+
117
+ #items tr.total {
118
+ border-right: 1px solid black;
119
+ }
120
+
121
+ #items tr.due {
122
+ background: #eee;
123
+ }
124
+
125
+ #items td.description {
126
+ width: 300px;
127
+ }
128
+
129
+ #items td.blank {
130
+ border: none;
131
+ }
132
+
133
+ #items td.money {
134
+ border-right: 0;
135
+ text-align: left;
136
+ padding-left: 50px;
137
+ }
138
+
139
+ #terms {
140
+ text-align: center;
141
+ margin: 20px 0 0 0;
142
+ }
143
+
144
+ #terms h5 {
145
+ text-transform: uppercase;
146
+ font: 13px Helvetica, Sans-Serif;
147
+ border-bottom: 1px solid black;
148
+ padding: 0 0 8px 0;
149
+ margin: 0 0 8px 0;
150
+ }
151
+
152
+ #terms textarea {
153
+ width: 100%;
154
+ text-align: center;
155
+ }
156
+ </style>
157
+ </head>
158
+
159
+ <body>
160
+
161
+ <div id="page-wrap">
162
+
163
+ <div id="header">INVOICE</div>
164
+
165
+ <div id="identity">
166
+ <div>{{from.name}}</div>
167
+ <div>{{from.abn}}</div>
168
+ <br/>
169
+ <div>{{from.address1}}</div>
170
+ <div>{{from.address2}}</div>
171
+ <div>{{from.phone}}</div>
172
+ <div>{{from.email}}</div>
173
+ </div>
174
+
175
+
176
+ <div id="customer">
177
+ <table>
178
+ <tr>
179
+ <th>
180
+ To
181
+ </th>
182
+ </tr>
183
+ <tr>
184
+ <td>
185
+ <div>{{to.attn}}</div>
186
+ <div>{{to.name}}</div>
187
+ <div>{{to.address1}}</div>
188
+ <div>{{to.address2}}</div>
189
+ <div>{{to.address3}}</div>
190
+ <div>{{to.phone}}</div>
191
+ </td>
192
+ </tr>
193
+ </table>
194
+ </div>
195
+ <div style="clear:both"></div>
196
+
197
+ <div id="details">
198
+ <table id="meta">
199
+ <tr>
200
+ <td class="meta-head">Invoice #</td>
201
+ <td>{{invoice}}</td>
202
+ </tr>
203
+ <tr>
204
+
205
+ <td class="meta-head">Date</td>
206
+ <td>{{date}}</td>
207
+ </tr>
208
+ <tr>
209
+ <td class="meta-head">Amount Due</td>
210
+ <td><div class="due">${{due}}</div></td>
211
+ </tr>
212
+
213
+ </table>
214
+
215
+ </div>
216
+
217
+
218
+ <table id="items">
219
+
220
+ <tr>
221
+ <th>Description</th>
222
+ <th>Unit Cost</th>
223
+ <th>Quantity</th>
224
+ <th>Price</th>
225
+ </tr>
226
+
227
+ {{#items}}
228
+ <tr class="item-row">
229
+ <td class="description">{{description}}</td>
230
+ <td class="money">${{unit}}</td>
231
+ <td class="money">{{quantity}}</td>
232
+ <td class="money">${{subtotal}}</td>
233
+ </tr>
234
+ {{/items}}
235
+
236
+ <tr class="totals">
237
+ <td colspan="2" class="blank"> </td>
238
+ <td colspan="1" class="total-line">Total</td>
239
+ <td class="money"><div id="total">${{total}}</div></td>
240
+ </tr>
241
+ <tr class="total">
242
+ <td colspan="2" class="blank"> </td>
243
+ <td colspan="1" class="total-line">Amount Paid</td>
244
+
245
+ <td class="money">${{paid}}</td>
246
+ </tr>
247
+ <tr class="total">
248
+ <td colspan="2" class="blank"> </td>
249
+ <td colspan="1" class="total-line balance">Balance Due</td>
250
+ <td class="money"><div class="due">{{due}}</div></td>
251
+ </tr>
252
+
253
+ </table>
254
+
255
+ <div id="terms">
256
+ <h5>Terms</h5>
257
+ {{terms}}
258
+ </div>
259
+ </div>
260
+ </body>
261
+ </html>
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "text-invoice/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "text-invoice"
7
+ s.version = TextInvoice::VERSION
8
+ s.authors = ["tarn"]
9
+ s.email = ["tarn.barford@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = "invoicing tools"
12
+ s.description = ""
13
+
14
+ s.rubyforge_project = "text-invoice"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_dependency "mustache"
23
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: text-invoice
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - tarn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-25 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70476390 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70476390
25
+ - !ruby/object:Gem::Dependency
26
+ name: mustache
27
+ requirement: &70475980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70475980
36
+ description: ''
37
+ email:
38
+ - tarn.barford@gmail.com
39
+ executables:
40
+ - text-invoice
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - README.md
47
+ - Rakefile
48
+ - bin/text-invoice
49
+ - lib/text-invoice.rb
50
+ - lib/text-invoice/cli.rb
51
+ - lib/text-invoice/invoice.rb
52
+ - lib/text-invoice/invoice.yaml
53
+ - lib/text-invoice/summary.rb
54
+ - lib/text-invoice/tasks.rb
55
+ - lib/text-invoice/template.rb
56
+ - lib/text-invoice/totals.rb
57
+ - lib/text-invoice/version.rb
58
+ - spec/cli_spec.rb
59
+ - spec/invoice_spec.rb
60
+ - spec/summary_spec.rb
61
+ - spec/tasks_spec.rb
62
+ - spec/template_spec.rb
63
+ - spec/totals_spec.rb
64
+ - templates/invoice.html
65
+ - text-invoice.gemspec
66
+ homepage: ''
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project: text-invoice
86
+ rubygems_version: 1.8.10
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: invoicing tools
90
+ test_files:
91
+ - spec/cli_spec.rb
92
+ - spec/invoice_spec.rb
93
+ - spec/summary_spec.rb
94
+ - spec/tasks_spec.rb
95
+ - spec/template_spec.rb
96
+ - spec/totals_spec.rb