ascii_invoicer 2.5.5
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/bin/ascii +722 -0
- data/latex/ascii-brief.cls +425 -0
- data/latex/ascii-brief.sty +46 -0
- data/lib/ascii_invoicer.rb +17 -0
- data/lib/ascii_invoicer/InvoiceProject.rb +356 -0
- data/lib/ascii_invoicer/ascii_logger.rb +64 -0
- data/lib/ascii_invoicer/filters.rb +129 -0
- data/lib/ascii_invoicer/generators.rb +209 -0
- data/lib/ascii_invoicer/hash_transformer.rb +23 -0
- data/lib/ascii_invoicer/mixins.rb +268 -0
- data/lib/ascii_invoicer/projectFileReader.rb +106 -0
- data/lib/ascii_invoicer/rfc5322_regex.rb +40 -0
- data/lib/ascii_invoicer/settings_manager.rb +62 -0
- data/lib/ascii_invoicer/texwriter.rb +105 -0
- data/lib/ascii_invoicer/tweaks.rb +25 -0
- data/lib/ascii_invoicer/version.rb +3 -0
- data/repl/ascii +2 -0
- data/settings/default-settings.yml +76 -0
- data/settings/settings_template.yml +54 -0
- data/templates/default.yml.erb +74 -0
- data/templates/document.tex.erb +87 -0
- metadata +297 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'paint'
|
2
|
+
|
3
|
+
require File.join __dir__, 'tweaks.rb'
|
4
|
+
|
5
|
+
|
6
|
+
module ProjectFileReader
|
7
|
+
# reads @raw_data
|
8
|
+
# fills @data
|
9
|
+
# uses @defaults
|
10
|
+
# produces @errors
|
11
|
+
# has a @status
|
12
|
+
|
13
|
+
##
|
14
|
+
# little read function
|
15
|
+
# returns data if read already
|
16
|
+
# or does a lookup
|
17
|
+
def read(key, data = @data)
|
18
|
+
return nil unless key.class == Symbol
|
19
|
+
|
20
|
+
# data has already been read
|
21
|
+
@logger.info Paint["KNOWN KEY: #{key}",:cyan] if data[key] if @settings.DEBUG
|
22
|
+
return data[key] if data[key]
|
23
|
+
|
24
|
+
@logger.info Paint["reading :#{key}", :green, :bold] if @settings.DEBUG
|
25
|
+
|
26
|
+
raw = @raw_data[key.to_s]
|
27
|
+
default = @defaults[key.to_s]
|
28
|
+
|
29
|
+
# if key is in raw_data walk it trough applying filters
|
30
|
+
if raw
|
31
|
+
@logger.info " FOUND RAW #{key}" if @settings.DEBUG
|
32
|
+
@logger.info " walking #{key}" if @settings.DEBUG
|
33
|
+
return data[key] = walk(raw, [key])
|
34
|
+
#fail "#{self.class} DOES NOT KNOW WHAT TO DO WITH #{raw.class}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# or the default from the settings
|
38
|
+
unless default.nil?
|
39
|
+
@logger.info " FOUND DEFAULT #{key}" if @settings.DEBUG
|
40
|
+
return data[key] = walk(default, [key])
|
41
|
+
end
|
42
|
+
|
43
|
+
# otherwise fail
|
44
|
+
return data[key] = fail_at(key)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def walk(tree= @raw_data, path = [])
|
49
|
+
catch :filter_error do
|
50
|
+
if tree.class == Hash
|
51
|
+
new_tree = {}
|
52
|
+
tree.each{|k,v|
|
53
|
+
k = k.to_sym if k.class == String
|
54
|
+
k = walk(k, path+[k] )if k.class == Hash
|
55
|
+
new_tree[k] = walk(v, path+[k])
|
56
|
+
}
|
57
|
+
new_tree = apply_filter path, new_tree
|
58
|
+
return new_tree
|
59
|
+
elsif tree.class == Array
|
60
|
+
new_tree = []
|
61
|
+
tree.each_index{|i|
|
62
|
+
v = tree[i]
|
63
|
+
new_tree[i] = walk(v, path+[i])
|
64
|
+
}
|
65
|
+
new_tree = apply_filter path, new_tree
|
66
|
+
return new_tree
|
67
|
+
else
|
68
|
+
tree = apply_filter path, tree
|
69
|
+
return tree
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def apply_generator(path, value)
|
75
|
+
apply_filter path, value, "generate_"
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_filter(path, value, prefix = "filter_")
|
79
|
+
begin
|
80
|
+
path = path.join('_') if path.class == Array
|
81
|
+
path = path.to_s if path.class == Symbol
|
82
|
+
parser = prefix+path
|
83
|
+
begin parser = method(parser)
|
84
|
+
rescue NameError
|
85
|
+
return value
|
86
|
+
else
|
87
|
+
@logger.info Paint[path, :yellow] if @settings.DEBUG
|
88
|
+
return parser.call(value)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
rescue => error
|
92
|
+
fail_at path
|
93
|
+
puts Array.new($@).keep_if{|line| line.include? "filter_"}.map {|line| Paint[line, :red, :bold]}
|
94
|
+
puts Array.new($@).keep_if{|line| line.include? "generate_"}.map {|line| Paint[line, :blue, :bold]}
|
95
|
+
puts Paint[" #{error} (#{@project_path})", :yellow]
|
96
|
+
end
|
97
|
+
|
98
|
+
def fail_at(*criteria)
|
99
|
+
@data[:valid] = false
|
100
|
+
criteria.each {|c|
|
101
|
+
@errors.push c.to_sym unless @errors.include? c
|
102
|
+
}
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# RFC 5322 Email Validation Regex in Ruby
|
2
|
+
|
3
|
+
$RFC5322 = /
|
4
|
+
(?<addr_spec> (?> \g<local_part> @ \g<domain> ) ){0}
|
5
|
+
(?<local_part> (?> \g<dot_atom> | \g<quoted_string> | \g<obs_local_part> ) ){0}
|
6
|
+
(?<domain> (?> \g<dot_atom> | \g<domain_literal> | \g<obs_domain> ) ){0}
|
7
|
+
(?<domain_literal> (?> \g<CFWS>? \[ (?: \g<FWS>? \g<dtext> )* \g<FWS>? \] \g<CFWS>? ) ){0}
|
8
|
+
(?<dtext> (?> [\x21-\x5a] | [\x5e-\x7e] | \g<obs_dtext> ) ){0}
|
9
|
+
(?<quoted_pair> (?> \\ (?: \g<VCHAR> | \g<WSP> ) | \g<obs_qp> ) ){0}
|
10
|
+
(?<dot_atom> (?> \g<CFWS>? \g<dot_atom_text> \g<CFWS>? ) ){0}
|
11
|
+
(?<dot_atom_text> (?> \g<atext> (?: \. \g<atext> )* ) ){0}
|
12
|
+
(?<atext> (?> [a-zA-Z0-9!\#\$%&'*\+\/\=\?\^_`{\|}~\-]+ ) ){0}
|
13
|
+
(?<atom> (?> \g<CFWS>? \g<atext> \g<CFWS>? ) ){0}
|
14
|
+
(?<word> (?> \g<atom> | \g<quoted_string> ) ){0}
|
15
|
+
(?<quoted_string> (?> \g<CFWS>? " (?: \g<FWS>? \g<qcontent> )* \g<FWS>? " \g<CFWS>? ) ){0}
|
16
|
+
(?<qcontent> (?> \g<qtext> | \g<quoted_pair> ) ){0}
|
17
|
+
(?<qtext> (?> \x21 | [\x23-\x5b] | [\x5d-\x7e] | \g<obs_qtext> ) ){0}
|
18
|
+
|
19
|
+
# comments and whitespace
|
20
|
+
(?<FWS> (?> (?: \g<WSP>* \r\n )? \g<WSP>+ | \g<obs_FWS> ) ){0}
|
21
|
+
(?<CFWS> (?> (?: \g<FWS>? \g<comment> )+ \g<FWS>? | \g<FWS> ) ){0}
|
22
|
+
(?<comment> (?> \( (?: \g<FWS>? \g<ccontent> )* \g<FWS>? \) ) ){0}
|
23
|
+
(?<ccontent> (?>\g<ctext> | \g<quoted_pair> | \g<comment> ) ){0}
|
24
|
+
(?<ctext> (?> [\x21-\x27] | [\x2a-\x5b] | [\x5d-\x7e] | \g<obs_ctext> ) ){0}
|
25
|
+
|
26
|
+
# obsolete tokens
|
27
|
+
(?<obs_domain> (?> \g<atom> (?: \. \g<atom> )* ) ){0}
|
28
|
+
(?<obs_local_part> (?> \g<word> (?: \. \g<word> )* ) ){0}
|
29
|
+
(?<obs_dtext> (?> \g<obs_NO_WS_CTL> | \g<quoted_pair> ) ){0}
|
30
|
+
(?<obs_qp> (?> \\ (?: \x00 | \g<obs_NO_WS_CTL> | \n | \r ) ) ){0}
|
31
|
+
(?<obs_FWS> (?> \g<WSP>+ (?: \r\n \g<WSP>+ )* ) ){0}
|
32
|
+
(?<obs_ctext> (?> \g<obs_NO_WS_CTL> ) ){0}
|
33
|
+
(?<obs_qtext> (?> \g<obs_NO_WS_CTL> ) ){0}
|
34
|
+
(?<obs_NO_WS_CTL> (?> [\x01-\x08] | \x0b | \x0c | [\x0e-\x1f] | \x7f ) ){0}
|
35
|
+
|
36
|
+
# character class definitions
|
37
|
+
(?<VCHAR> (?> [\x21-\x7E] ) ){0}
|
38
|
+
(?<WSP> [ \t] ){0}
|
39
|
+
\g<addr_spec>
|
40
|
+
/uix
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require 'hash-graft'
|
4
|
+
require 'hashr'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
# all about settings
|
8
|
+
class SettingsManager
|
9
|
+
attr_reader :settings, :homedir_settings, :default_settings
|
10
|
+
|
11
|
+
#expacting :homedir_path, :default_path, #template_path
|
12
|
+
def initialize hash
|
13
|
+
@init_hash = Hashr.new hash
|
14
|
+
|
15
|
+
raise "SettingsManager: No default file given." unless @init_hash.default_path?
|
16
|
+
raise "SettingsManager: No homedir file given." unless @init_hash.homedir_path?
|
17
|
+
|
18
|
+
@init_hash.default_path = File.expand_path @init_hash.default_path
|
19
|
+
@init_hash.homedir_path = File.expand_path @init_hash.homedir_path
|
20
|
+
@init_hash.template_path = File.expand_path @init_hash.template_path if @init_hash.template_path?
|
21
|
+
|
22
|
+
if File.exists?(@init_hash.default_path)
|
23
|
+
@default_settings = load_file @init_hash.default_path
|
24
|
+
else
|
25
|
+
raise "SettingsManager: Default settings file does not exist (#{@init_hash.default_path})."
|
26
|
+
end
|
27
|
+
|
28
|
+
if not File.exists?(@init_hash.homedir_path)
|
29
|
+
if @init_hash.template_path?
|
30
|
+
raise "SettingsManager: Template file does not exist" unless File.exists?(@init_hash.template_path)
|
31
|
+
puts "#{@init_hash.homedir_path} does not exist, but #{@init_hash.homedir_path} does"
|
32
|
+
puts "-> copying over"
|
33
|
+
FileUtils.cp @init_hash.template_path, @init_hash.homedir_path
|
34
|
+
else
|
35
|
+
# using only default_settings
|
36
|
+
# suggested use: used a default_settings as template, perhaps comment out everything
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@homedir_settings = load_file @init_hash.homedir_path
|
40
|
+
end
|
41
|
+
|
42
|
+
# putting it all together
|
43
|
+
@settings = Hashr.new @default_settings.graft @homedir_settings
|
44
|
+
|
45
|
+
# adding some meta stuff
|
46
|
+
@settings.settings_homedir_path = @init_hash.homedir_path
|
47
|
+
@settings.settings_default_path = @init_hash.default_path
|
48
|
+
@settings.settings_template_path = @init_hash.template_path
|
49
|
+
@settings.default_settings = @default_settings
|
50
|
+
@settings.homedir_settings = @homedir_settings
|
51
|
+
return @settings
|
52
|
+
end
|
53
|
+
|
54
|
+
def load_file path
|
55
|
+
begin
|
56
|
+
YAML::load File.open File.expand_path path
|
57
|
+
rescue SyntaxError => error
|
58
|
+
puts "ERROR parsing #{File.expand_path path}!"
|
59
|
+
puts error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module TexWriter
|
4
|
+
|
5
|
+
##
|
6
|
+
# fills the template with mined DATA
|
7
|
+
def create_tex type, stdout = true
|
8
|
+
document_template = load_template :document
|
9
|
+
document_type = type
|
10
|
+
|
11
|
+
unless document_template
|
12
|
+
@logger.error "Template Error!"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
#check output path first
|
17
|
+
output_path = File.expand_path $SETTINGS.output_path
|
18
|
+
unless File.directory? output_path
|
19
|
+
@logger.error "your output_path is not a directory! (#{output_path})"
|
20
|
+
exit
|
21
|
+
end
|
22
|
+
|
23
|
+
#set the output filename
|
24
|
+
filename = export_filename type, "tex"
|
25
|
+
|
26
|
+
puts "% #{filename}"
|
27
|
+
# fill out ERB (twice), make sure everything's set
|
28
|
+
template = ERB.new(document_template).result(binding)
|
29
|
+
result = ERB.new(template).result(binding)
|
30
|
+
|
31
|
+
output_path = File.join @project_folder, filename
|
32
|
+
|
33
|
+
puts result if stdout
|
34
|
+
write_to_file result, output_path if !stdout
|
35
|
+
render_tex output_path, filename if !stdout
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_to_file file_content, path
|
39
|
+
begin
|
40
|
+
File.open(path, ?w){ |file|
|
41
|
+
file_content.lines.each do |line|
|
42
|
+
file.write line
|
43
|
+
end
|
44
|
+
}
|
45
|
+
@logger.info "file written: #{path}"
|
46
|
+
rescue
|
47
|
+
@logger.error "Unable to write into #{path}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def render_tex path, filename
|
52
|
+
@logger.info "Rendering #{path} with #{$SETTINGS.latex}"
|
53
|
+
silencer = $SETTINGS.verbose ? "" : "> /dev/null"
|
54
|
+
|
55
|
+
## TODO output directory is not generic
|
56
|
+
system "#{$SETTINGS.latex} \"#{path}\" -output-directory . #{silencer}"
|
57
|
+
|
58
|
+
output_path = File.expand_path $SETTINGS.output_path
|
59
|
+
@logger.error "your output_path is not a directory! (#{output_path})" unless File.directory? output_path
|
60
|
+
|
61
|
+
pdf = filename.gsub('.tex','.pdf')
|
62
|
+
log = filename.gsub('.tex','.log')
|
63
|
+
aux = filename.gsub('.tex','.aux')
|
64
|
+
unless $SETTINGS.keep_log
|
65
|
+
FileUtils.rm log if File.exists? log
|
66
|
+
FileUtils.rm aux if File.exists? aux
|
67
|
+
else
|
68
|
+
unless File.expand_path output_path == FileUtils.pwd
|
69
|
+
FileUtils.mv log, output_path if File.exists? log
|
70
|
+
FileUtils.mv aux, output_path if File.exists? aux
|
71
|
+
end
|
72
|
+
end
|
73
|
+
target = File.join output_path, pdf
|
74
|
+
|
75
|
+
puts "moving #{pdf} to #{target}"
|
76
|
+
if not File.exists? pdf
|
77
|
+
@logger.error "#{pdf} does not exist, so it can not be moved to #{output_path}"
|
78
|
+
elsif File.expand_path(output_path)!= FileUtils.pwd
|
79
|
+
FileUtils.mv pdf, output_path, :force => true, :verbose => true
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
puts "Created #{path}"
|
84
|
+
end
|
85
|
+
|
86
|
+
# loads template files named in settings
|
87
|
+
def load_template(type)
|
88
|
+
return false unless $PLUMBER.check_dir :templates
|
89
|
+
#files = Dir.glob File.join @dirs[:templates] , ?*
|
90
|
+
files = Dir.glob File.join($PLUMBER.dirs[:templates], "*{tex.erb,tex}")
|
91
|
+
templates = {}
|
92
|
+
files.each{|file|
|
93
|
+
templates[File.basename(file.split(?.)[0]).to_sym] = file
|
94
|
+
}
|
95
|
+
path = templates[type]
|
96
|
+
if File.exists?(path)
|
97
|
+
return File.open(path).read
|
98
|
+
else
|
99
|
+
@logger.error "Template (#{path}) File not found!"
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$FB = "foobar"
|
2
|
+
class TrueClass
|
3
|
+
def print(symbol=?✓)
|
4
|
+
"\e[32m#{symbol}\e[0m"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class FalseClass
|
9
|
+
def print(symbol=?✗)
|
10
|
+
"\e[31m#{symbol}\e[0m"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class String
|
15
|
+
def words
|
16
|
+
self.split " "
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Date
|
21
|
+
def to_s
|
22
|
+
self.strftime "%d.%m.%Y"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/repl/ascii
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
---
|
2
|
+
manager_name: "The Unnamed Manager"
|
3
|
+
verbose: false
|
4
|
+
editor: "vim -O"
|
5
|
+
colors: false
|
6
|
+
list_sort: index
|
7
|
+
|
8
|
+
path: "~"
|
9
|
+
output_path: "."
|
10
|
+
dirs:
|
11
|
+
storage: caterings
|
12
|
+
working: working
|
13
|
+
archive: archive
|
14
|
+
templates: templates
|
15
|
+
|
16
|
+
## CAREFUL HERE
|
17
|
+
project_file_extension: .yml
|
18
|
+
use_git: true
|
19
|
+
latex: pdflatex
|
20
|
+
log_file: ~/.ascii_log
|
21
|
+
calendar_file: invoicer.ics # will be put in current directory
|
22
|
+
|
23
|
+
defaults:
|
24
|
+
tax: 0.19
|
25
|
+
lang: de
|
26
|
+
canceled: false
|
27
|
+
includes:
|
28
|
+
logopath:
|
29
|
+
name:
|
30
|
+
strasse:
|
31
|
+
universitaet:
|
32
|
+
fakultaet:
|
33
|
+
zusatz:
|
34
|
+
retouradresse:
|
35
|
+
ort:
|
36
|
+
land:
|
37
|
+
telefon:
|
38
|
+
telefax:
|
39
|
+
telex:
|
40
|
+
http:
|
41
|
+
email:
|
42
|
+
bank:
|
43
|
+
blz:
|
44
|
+
iban:
|
45
|
+
bic:
|
46
|
+
konto:
|
47
|
+
steuernummer:
|
48
|
+
|
49
|
+
messages:
|
50
|
+
de:
|
51
|
+
offer:
|
52
|
+
- Angebot
|
53
|
+
- "hiermit möchten wir Ihnen für die gastronomische Betreuung Ihrer Veranstaltung am <%= @data[:event][:prettydate] %> folgendes Angebot unterbreiten:"
|
54
|
+
- ""
|
55
|
+
invoice:
|
56
|
+
- Rechnung
|
57
|
+
- "wir bedanken uns für Ihren Auftrag für das Catering am <%= @data[:event][:prettydate] %> und erlauben uns Ihnen folgende Rechnung zu stellen:"
|
58
|
+
- "Wir bitten um eine Begleichung des Betrags innerhalb von 14 Tagen nach Erhalt der Rechnung."
|
59
|
+
signature: Mit freundlichen Grüßen
|
60
|
+
|
61
|
+
currency_symbol: €
|
62
|
+
|
63
|
+
gender_matches:
|
64
|
+
herr: male
|
65
|
+
frau: female
|
66
|
+
professor: male
|
67
|
+
professorin: female
|
68
|
+
|
69
|
+
lang_addressing:
|
70
|
+
de:
|
71
|
+
male: Sehr geehrter
|
72
|
+
female: Sehr geehrte
|
73
|
+
en:
|
74
|
+
male: Dear
|
75
|
+
female: Dear
|
76
|
+
...
|
@@ -0,0 +1,54 @@
|
|
1
|
+
---
|
2
|
+
#manager_name: "The Unnamed Manager"
|
3
|
+
#verbose: false
|
4
|
+
#editor: "vim -O"
|
5
|
+
#colors: false
|
6
|
+
#list_sort: date # index, name
|
7
|
+
|
8
|
+
#path: "~" #/where/you/want/to/put/the/dirs
|
9
|
+
#output_path: "."
|
10
|
+
#dirs:
|
11
|
+
# storage: caterings
|
12
|
+
# working: working
|
13
|
+
# archive: archive
|
14
|
+
# templates: templates
|
15
|
+
|
16
|
+
#defaults:
|
17
|
+
# tax: 0.19
|
18
|
+
# lang: de
|
19
|
+
# canceled: false
|
20
|
+
# messages:
|
21
|
+
# de:
|
22
|
+
# invoice:
|
23
|
+
# - Rechnung
|
24
|
+
# - "wir bedanken uns für Ihren Auftrag für das Catering am <%= @data[:raw_date] %> und erlauben uns ihnen folgende Rechnung zu stellen:"
|
25
|
+
# - "Wir bitten um eine Begleichung des Betrags innerhalb von 14 Tagen nach Erhalt der Rechnung."
|
26
|
+
# offer:
|
27
|
+
# - Angebot
|
28
|
+
# - "hiermit möchten wir Ihnen für die gastronomische Betreuung Ihrer Veranstaltung am <%= @data[:raw_date] %> folgendes Angebot unterbreiten:"
|
29
|
+
# - ""
|
30
|
+
# signature: Mit freundlichen Grüßen
|
31
|
+
#
|
32
|
+
# includes:
|
33
|
+
# logopath:
|
34
|
+
# name:
|
35
|
+
# strasse:
|
36
|
+
# universitaet:
|
37
|
+
# fakultaet:
|
38
|
+
# zusatz:
|
39
|
+
# retouradresse:
|
40
|
+
# ort:
|
41
|
+
# land:
|
42
|
+
# telefon:
|
43
|
+
# telefax:
|
44
|
+
# telex:
|
45
|
+
# http:
|
46
|
+
# email:
|
47
|
+
# bank:
|
48
|
+
# blz:
|
49
|
+
# iban:
|
50
|
+
# bic:
|
51
|
+
# konto:
|
52
|
+
# steuernummer:
|
53
|
+
#
|
54
|
+
...
|