sie 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/README.md +64 -0
- data/Rakefile +15 -0
- data/lib/sie.rb +6 -0
- data/lib/sie/document.rb +107 -0
- data/lib/sie/document/financial_years.rb +24 -0
- data/lib/sie/document/renderer.rb +48 -0
- data/lib/sie/document/voucher_series.rb +20 -0
- data/lib/sie/parser.rb +40 -0
- data/lib/sie/parser/entry.rb +15 -0
- data/lib/sie/parser/entry_types.rb +41 -0
- data/lib/sie/parser/line_parser.rb +48 -0
- data/lib/sie/parser/sie_file.rb +13 -0
- data/lib/sie/parser/tokenizer.rb +90 -0
- data/lib/sie/parser/tokenizer/character.rb +34 -0
- data/lib/sie/parser/tokenizer/token.rb +29 -0
- data/lib/sie/version.rb +3 -0
- data/script/bootstrap +3 -0
- data/script/ci/support/env +6 -0
- data/script/ci/tests.sh +5 -0
- data/script/refresh +25 -0
- data/script/turbux_rspec +6 -0
- data/sie.gemspec +27 -0
- data/spec/fixtures/sie_file.se +28 -0
- data/spec/integration/parser_spec.rb +24 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unit/document/financial_years_spec.rb +38 -0
- data/spec/unit/document/voucher_series_spec.rb +41 -0
- data/spec/unit/document_spec.rb +181 -0
- data/spec/unit/parser/line_parser_spec.rb +21 -0
- data/spec/unit/parser/tokenizer_spec.rb +42 -0
- data/spec/unit/parser_spec.rb +23 -0
- metadata +151 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm ruby-1.9.3-p194-falcon@sie --create
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Sie
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
gem 'sie'
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
|
11
|
+
$ bundle
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install sie
|
16
|
+
|
17
|
+
## Developing
|
18
|
+
|
19
|
+
First time setup:
|
20
|
+
|
21
|
+
script/bootstrap
|
22
|
+
|
23
|
+
Running tests:
|
24
|
+
|
25
|
+
rake
|
26
|
+
|
27
|
+
Getting the latest code and gems:
|
28
|
+
|
29
|
+
script/refresh
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
|
34
|
+
## Contributing
|
35
|
+
|
36
|
+
1. Fork it
|
37
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
38
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
39
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
40
|
+
5. Create new Pull Request
|
41
|
+
|
42
|
+
## Credits and license
|
43
|
+
|
44
|
+
By [Barsoom](http://barsoom.se) under the MIT license:
|
45
|
+
|
46
|
+
> Copyright (c) 2013 Barsoom AB
|
47
|
+
>
|
48
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
49
|
+
> of this software and associated documentation files (the "Software"), to deal
|
50
|
+
> in the Software without restriction, including without limitation the rights
|
51
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
52
|
+
> copies of the Software, and to permit persons to whom the Software is
|
53
|
+
> furnished to do so, subject to the following conditions:
|
54
|
+
>
|
55
|
+
> The above copyright notice and this permission notice shall be included in
|
56
|
+
> all copies or substantial portions of the Software.
|
57
|
+
>
|
58
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
59
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
60
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
61
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
62
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
63
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
64
|
+
> THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
namespace :spec do
|
4
|
+
task :unit do
|
5
|
+
puts "Running unit tests:"
|
6
|
+
system("rspec spec/unit/**_spec.rb") || exit(1)
|
7
|
+
end
|
8
|
+
|
9
|
+
task :integration do
|
10
|
+
puts "Running integrated tests:"
|
11
|
+
system("rspec spec/integration/**_spec.rb") || exit(1)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => [ :"spec:unit", :"spec:integration" ]
|
data/lib/sie.rb
ADDED
data/lib/sie/document.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require "attr_extras"
|
2
|
+
require "sie/document/financial_years"
|
3
|
+
require "sie/document/voucher_series"
|
4
|
+
require "sie/document/renderer"
|
5
|
+
require "active_support/core_ext/module/delegation"
|
6
|
+
|
7
|
+
class Sie::Document
|
8
|
+
pattr_initialize :data_source
|
9
|
+
|
10
|
+
def render
|
11
|
+
add_header
|
12
|
+
add_financial_years
|
13
|
+
add_accounts
|
14
|
+
add_balances
|
15
|
+
add_vouchers
|
16
|
+
|
17
|
+
renderer.render
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
delegate :program, :program_version, :generated_on, :company_name,
|
23
|
+
:accounts, :balance_account_numbers, :closing_account_numbers,
|
24
|
+
:balance_before, :each_voucher, :from_date, :to_date, :financial_year_start_month,
|
25
|
+
to: :data_source
|
26
|
+
|
27
|
+
def add_header
|
28
|
+
add_line("FLAGGA", 0)
|
29
|
+
add_line("PROGRAM", program, program_version)
|
30
|
+
add_line("FORMAT", "PC8")
|
31
|
+
add_line("GEN", generated_on)
|
32
|
+
add_line("SIETYP", 4)
|
33
|
+
add_line("FNAMN", company_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_financial_years
|
37
|
+
financial_years.each_with_index do |date_range, index|
|
38
|
+
add_line("RAR", -index, date_range.begin, date_range.end)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_accounts
|
43
|
+
accounts.each do |account|
|
44
|
+
number = account.fetch(:number)
|
45
|
+
description = account.fetch(:description)
|
46
|
+
|
47
|
+
add_line("KONTO", number, description)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_balances
|
52
|
+
financial_years.each_with_index do |date_range, index|
|
53
|
+
add_balance_rows("IB", -index, balance_account_numbers, date_range.begin)
|
54
|
+
add_balance_rows("UB", -index, balance_account_numbers, date_range.end)
|
55
|
+
add_balance_rows("RES", -index, closing_account_numbers, date_range.end)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_balance_rows(label, year_index, account_numbers, date, &block)
|
60
|
+
account_numbers.each do |account_number|
|
61
|
+
balance = balance_before(account_number, date)
|
62
|
+
add_line(label, year_index, account_number, balance)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_vouchers
|
67
|
+
each_voucher do |voucher|
|
68
|
+
add_voucher(voucher)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_voucher(opts)
|
73
|
+
creditor = opts.fetch(:creditor)
|
74
|
+
type = opts.fetch(:type)
|
75
|
+
number = opts.fetch(:number)
|
76
|
+
booked_on = opts.fetch(:booked_on)
|
77
|
+
description = opts.fetch(:description)
|
78
|
+
voucher_lines = opts.fetch(:voucher_lines)
|
79
|
+
|
80
|
+
add_line("VER", voucher_series(creditor, type), number, booked_on, description)
|
81
|
+
|
82
|
+
add_array do
|
83
|
+
voucher_lines.each do |line|
|
84
|
+
account_number = line.fetch(:account_number)
|
85
|
+
amount = line.fetch(:amount)
|
86
|
+
booked_on = line.fetch(:booked_on)
|
87
|
+
description = line.fetch(:description)
|
88
|
+
|
89
|
+
add_line("TRANS", account_number, Renderer::EMPTY_ARRAY, amount, booked_on, description)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
delegate :add_line, :add_array, to: :renderer
|
95
|
+
|
96
|
+
def renderer
|
97
|
+
@renderer ||= Renderer.new
|
98
|
+
end
|
99
|
+
|
100
|
+
def financial_years
|
101
|
+
FinancialYears.between(financial_year_start_month, from_date, to_date).reverse
|
102
|
+
end
|
103
|
+
|
104
|
+
def voucher_series(creditor, type)
|
105
|
+
VoucherSeries.for(creditor, type)
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Sie::Document
|
2
|
+
class FinancialYears
|
3
|
+
def self.between(start_month, from_date, to_date)
|
4
|
+
result = []
|
5
|
+
|
6
|
+
from_date.upto(to_date) do |date|
|
7
|
+
next if result.any? { |r| r.cover?(date) }
|
8
|
+
|
9
|
+
start_of_year = Date.new(date.year, start_month, 1)
|
10
|
+
start_of_year = start_of_year <= date ? start_of_year : (start_of_year << 12)
|
11
|
+
|
12
|
+
a_year_later = start_of_year >> 11
|
13
|
+
end_of_year = Date.new(a_year_later.year, a_year_later.month, -1)
|
14
|
+
|
15
|
+
first_date = [start_of_year, from_date].max
|
16
|
+
last_date = [end_of_year, to_date].min
|
17
|
+
|
18
|
+
result << (first_date..last_date)
|
19
|
+
end
|
20
|
+
|
21
|
+
result
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Sie::Document::Renderer
|
2
|
+
EMPTY_ARRAY = :empty_array
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@io = StringIO.new
|
6
|
+
@io.set_encoding(Encoding::CP437)
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_line(label, *values)
|
10
|
+
append ["##{ label }", *format_values(values)].join(" ")
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_array
|
14
|
+
append "{"
|
15
|
+
yield
|
16
|
+
append "}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
io.rewind
|
21
|
+
io.read
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_private :io
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def append(text)
|
29
|
+
io.puts(text)
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_values(values)
|
33
|
+
values.map { |value| format_value(value) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def format_value(value)
|
37
|
+
case value
|
38
|
+
when Date
|
39
|
+
value.strftime("%Y%m%d")
|
40
|
+
when EMPTY_ARRAY
|
41
|
+
"{}"
|
42
|
+
when Numeric
|
43
|
+
value.to_s
|
44
|
+
else
|
45
|
+
'"' + value.to_s.gsub('"', '\"') + '"'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Sie::Document
|
2
|
+
class VoucherSeries
|
3
|
+
DEBTOR_INVOICE = "KF"
|
4
|
+
DEBTOR_PAYMENT = "KI"
|
5
|
+
SUPPLIER_INVOICE = "LF"
|
6
|
+
SUPPLIER_PAYMENT = "KB"
|
7
|
+
OTHER = "LV"
|
8
|
+
|
9
|
+
def self.for(creditor, type)
|
10
|
+
case type
|
11
|
+
when :invoice
|
12
|
+
creditor ? SUPPLIER_INVOICE : DEBTOR_INVOICE
|
13
|
+
when :payment
|
14
|
+
creditor ? SUPPLIER_PAYMENT : DEBTOR_PAYMENT
|
15
|
+
else
|
16
|
+
OTHER
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/sie/parser.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "attr_extras"
|
2
|
+
require "sie/parser/line_parser"
|
3
|
+
|
4
|
+
module Sie
|
5
|
+
class Parser
|
6
|
+
# TODO: Could this format knowledge be centrailized somewhere, some
|
7
|
+
# of this is duplicated in Character.
|
8
|
+
BEGINNING_OF_ARRAY = "{"
|
9
|
+
END_OF_ARRAY = "}"
|
10
|
+
ENTRY = /^#/
|
11
|
+
|
12
|
+
def parse(io)
|
13
|
+
stack = []
|
14
|
+
sie_file = SieFile.new
|
15
|
+
current = sie_file
|
16
|
+
|
17
|
+
io.each_line do |line|
|
18
|
+
line = line.chomp
|
19
|
+
|
20
|
+
case line
|
21
|
+
when BEGINNING_OF_ARRAY
|
22
|
+
stack << current
|
23
|
+
current = current.entries.last
|
24
|
+
when END_OF_ARRAY
|
25
|
+
current = stack.pop
|
26
|
+
when ENTRY
|
27
|
+
current.entries << parse_line(line)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
sie_file
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parse_line(line)
|
37
|
+
LineParser.new(line).parse
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Sie
|
2
|
+
class Parser
|
3
|
+
ENTRY_TYPES = {
|
4
|
+
"adress" => [ 'kontakt', 'utdelningsadr', 'postadr', 'tel' ],
|
5
|
+
"bkod" => [ 'SNI-kod' ],
|
6
|
+
"dim" => [ 'dimensionsnr', 'namn' ],
|
7
|
+
"enhet" => [ 'kontonr', 'enhet' ],
|
8
|
+
"flagga" => [ 'x' ],
|
9
|
+
"fnamn" => [ 'foretagsnamn' ],
|
10
|
+
"fnr" => [ 'foretagsid' ],
|
11
|
+
"format" => [ 'PC8' ],
|
12
|
+
"ftyp" => [ 'foretagstyp' ],
|
13
|
+
"gen" => [ 'datum', 'sign' ],
|
14
|
+
"ib" => [ 'arsnr', 'konto', 'saldo', 'kvantitet' ],
|
15
|
+
"konto" => [ 'kontonr', 'kontonamn' ],
|
16
|
+
"kptyp" => [ 'typ' ],
|
17
|
+
"ktyp" => [ 'kontonr', 'kontotyp' ],
|
18
|
+
"objekt" => [ 'dimensionsnr', 'objektnr', 'objektnamn' ],
|
19
|
+
"oib" => [ 'arsnr', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
|
20
|
+
"omfattn" => [ 'datum' ],
|
21
|
+
"orgnr" => [ 'orgnr', 'forvnr', 'verknr' ],
|
22
|
+
"oub" => [ 'arsnr', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
|
23
|
+
"pbudget" => [ 'arsnr', 'period', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
|
24
|
+
"program" => [ 'programnamn', 'version' ],
|
25
|
+
"prosa" => [ 'text' ],
|
26
|
+
"psaldo" => [ 'arsnr', 'period', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
|
27
|
+
"rar" => [ 'arsnr', 'start', 'slut' ],
|
28
|
+
"res" => [ 'ars', 'konto', 'saldo', 'kvantitet' ],
|
29
|
+
"sietyp" => [ 'typnr' ],
|
30
|
+
"sru" => [ 'konto', 'SRU-kod' ],
|
31
|
+
"taxar" => [ 'ar' ],
|
32
|
+
"trans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
|
33
|
+
"rtrans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
|
34
|
+
"btrans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
|
35
|
+
"ub" => [ 'arsnr', 'konto', 'saldo', 'kvantitet' ],
|
36
|
+
"underdim" => [ 'dimensionsnr', 'namn', 'superdimension' ],
|
37
|
+
"valuta" => [ 'valutakod' ],
|
38
|
+
"ver" => [ 'serie', 'vernr', 'verdatum', 'vertext', 'regdatum', 'sign' ]
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "sie/parser/tokenizer"
|
2
|
+
require "sie/parser/entry"
|
3
|
+
require "sie/parser/sie_file"
|
4
|
+
|
5
|
+
module Sie
|
6
|
+
class Parser
|
7
|
+
class LineParser
|
8
|
+
pattr_initialize :line
|
9
|
+
|
10
|
+
def parse
|
11
|
+
tokens = tokenize(line)
|
12
|
+
first_token = tokens.shift
|
13
|
+
|
14
|
+
entry = Entry.new(first_token.label)
|
15
|
+
entry_type = first_token.entry_type
|
16
|
+
|
17
|
+
entry_type.each_with_index do |entry_type, i|
|
18
|
+
break if i >= tokens.size
|
19
|
+
|
20
|
+
if entry_type.is_a?(Hash)
|
21
|
+
skip_array(tokens, i)
|
22
|
+
next
|
23
|
+
else
|
24
|
+
label = entry_type
|
25
|
+
entry.attributes[label] = tokens[i].value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
entry
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def tokenize(line)
|
35
|
+
Tokenizer.new(line).tokenize
|
36
|
+
end
|
37
|
+
|
38
|
+
def skip_array(tokens, i)
|
39
|
+
if tokens[i].is_a?(Tokenizer::BeginArrayToken) &&
|
40
|
+
!tokens[i+1].is_a?(Tokenizer::EndArrayToken)
|
41
|
+
raise "We currently don't support metadata within entries as we haven't had a need for it yet (the data between {} in #{line})."
|
42
|
+
end
|
43
|
+
|
44
|
+
tokens.reject! { |token| token.is_a?(Tokenizer::EndArrayToken) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "strscan"
|
2
|
+
require "sie/parser/tokenizer/token"
|
3
|
+
require "sie/parser/tokenizer/character"
|
4
|
+
|
5
|
+
module Sie
|
6
|
+
class Parser
|
7
|
+
class Tokenizer
|
8
|
+
pattr_initialize :line
|
9
|
+
|
10
|
+
def tokenize
|
11
|
+
@tokens = []
|
12
|
+
@consume = false
|
13
|
+
@quoted = false
|
14
|
+
|
15
|
+
loop do
|
16
|
+
move_to_next_character
|
17
|
+
break unless current_character.value
|
18
|
+
|
19
|
+
if consume?
|
20
|
+
if quoted?
|
21
|
+
consume_quoted_value
|
22
|
+
else
|
23
|
+
consume_unquoted_value
|
24
|
+
end
|
25
|
+
else
|
26
|
+
add_new_token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
tokens
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_query :consume?, :quoted?
|
36
|
+
attr_private :consume, :quoted, :tokens, :current_character
|
37
|
+
|
38
|
+
def move_to_next_character
|
39
|
+
@current_character = Character.new(scanner.getch)
|
40
|
+
end
|
41
|
+
|
42
|
+
def consume_quoted_value
|
43
|
+
if current_character.quote?
|
44
|
+
@quoted = false
|
45
|
+
else
|
46
|
+
add_to_current_token current_character
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def consume_unquoted_value
|
51
|
+
if current_character.unquoted_data?
|
52
|
+
add_to_current_token current_character
|
53
|
+
else
|
54
|
+
@consume = false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_new_token
|
59
|
+
if current_character.entry?
|
60
|
+
@consume = true
|
61
|
+
add_token EntryToken.new
|
62
|
+
elsif current_character.beginning_of_array?
|
63
|
+
add_token BeginArrayToken.new
|
64
|
+
elsif current_character.end_of_array?
|
65
|
+
add_token EndArrayToken.new
|
66
|
+
elsif current_character.quote?
|
67
|
+
@consume = @quoted = true
|
68
|
+
add_token StringToken.new
|
69
|
+
elsif current_character.non_whitespace?
|
70
|
+
@consume = true
|
71
|
+
add_token StringToken.new(current_character.value)
|
72
|
+
elsif current_character.value != " "
|
73
|
+
raise "Unhandled character: #{current_character.value}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_token(token)
|
78
|
+
tokens << token
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_to_current_token(character)
|
82
|
+
tokens.last.value += character.value
|
83
|
+
end
|
84
|
+
|
85
|
+
def scanner
|
86
|
+
@scanner ||= StringScanner.new(line)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Sie
|
2
|
+
class Parser
|
3
|
+
class Tokenizer
|
4
|
+
class Character
|
5
|
+
pattr_initialize :value
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def unquoted_data?
|
9
|
+
non_whitespace? && !end_of_array?
|
10
|
+
end
|
11
|
+
|
12
|
+
def entry?
|
13
|
+
value == "#"
|
14
|
+
end
|
15
|
+
|
16
|
+
def beginning_of_array?
|
17
|
+
value == "{"
|
18
|
+
end
|
19
|
+
|
20
|
+
def end_of_array?
|
21
|
+
value == "}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def quote?
|
25
|
+
value == '"'
|
26
|
+
end
|
27
|
+
|
28
|
+
def non_whitespace?
|
29
|
+
value != " " && value != "\t"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "sie/parser/entry_types"
|
2
|
+
|
3
|
+
module Sie
|
4
|
+
class Parser
|
5
|
+
class Tokenizer
|
6
|
+
class Token
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def initialize(value = "")
|
10
|
+
@value = value
|
11
|
+
end
|
12
|
+
|
13
|
+
def entry_type
|
14
|
+
Sie::Parser::ENTRY_TYPES.fetch(label)
|
15
|
+
end
|
16
|
+
|
17
|
+
def label
|
18
|
+
value.sub(/^#/, '').downcase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class EntryToken < Token; end
|
23
|
+
class BeginArrayToken < Token; end
|
24
|
+
class EndArrayToken < Token; end
|
25
|
+
class StringToken < Token; end
|
26
|
+
class ArrayToken < Token; end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/sie/version.rb
ADDED
data/script/bootstrap
ADDED
data/script/ci/tests.sh
ADDED
data/script/refresh
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
printf "\e[00;34mGetting the latest version\e[00m... "
|
4
|
+
|
5
|
+
(git status|grep 'nothing to commit' 1> /dev/null)
|
6
|
+
|
7
|
+
if [ ! $? -eq 0 ]
|
8
|
+
then
|
9
|
+
set -e
|
10
|
+
git stash 1> /dev/null
|
11
|
+
git pull --rebase 1> /dev/null
|
12
|
+
git stash pop 1> /dev/null
|
13
|
+
else
|
14
|
+
set -e
|
15
|
+
git pull --rebase 1> /dev/null
|
16
|
+
fi
|
17
|
+
|
18
|
+
echo "done"
|
19
|
+
|
20
|
+
printf "\e[00;34mBundling\e[00m... "
|
21
|
+
bundle 1> /dev/null
|
22
|
+
echo "done"
|
23
|
+
echo
|
24
|
+
|
25
|
+
rake
|
data/script/turbux_rspec
ADDED
data/sie.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'sie/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "sie"
|
8
|
+
spec.version = Sie::VERSION
|
9
|
+
spec.authors = ["Barsoom AB"]
|
10
|
+
spec.email = ["all@barsoom.se"]
|
11
|
+
spec.description = %q{SIE parser and generator}
|
12
|
+
spec.summary = %q{Parses and generates SIE files (http://sie.se/)}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "attr_extras"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#FLAGGA 0
|
2
|
+
#PROGRAM "fooconomic" "1.0"
|
3
|
+
#FORMAT PC8
|
4
|
+
#GEN 20130101
|
5
|
+
#SIETYP 4
|
6
|
+
#ORGNR 555555-5555
|
7
|
+
#FNAMN "Foocorp"
|
8
|
+
#ADRESS "Foocorp" "" "12345 City" "555-12345"
|
9
|
+
#KONTO 1510 "Accounts receivable"
|
10
|
+
#KTYP 1510 T
|
11
|
+
#SRU 1510 204
|
12
|
+
#KONTO 1930 "Bank account"
|
13
|
+
#KTYP 1930 T
|
14
|
+
#SRU 1930 200
|
15
|
+
#KONTO 2440 Supplier
|
16
|
+
#KTYP 2440 S
|
17
|
+
#SRU 2440 300
|
18
|
+
#VER A 1 20130101 "Invoice" 20120105
|
19
|
+
{
|
20
|
+
#TRANS 1510 {} -200 20130101 "Coffee machine"
|
21
|
+
#TRANS 4100 {} 180 20130101 "Coffee machine"
|
22
|
+
#TRANS 2611 {} -20 20130101 "VAT"
|
23
|
+
}
|
24
|
+
#VER A 2 20130102 "Payment" 20120105
|
25
|
+
{
|
26
|
+
#TRANS 1510 {} -200 20130101 "Payment: Coffee machine"
|
27
|
+
#TRANS 1970 {} 200 20130101 "Payment: Coffee machine"
|
28
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
|
5
|
+
describe Sie::Parser do
|
6
|
+
it "parses a file" do
|
7
|
+
parser = Sie::Parser.new
|
8
|
+
|
9
|
+
open_file("fixtures/sie_file.se") do |f|
|
10
|
+
sie_file = parser.parse(f)
|
11
|
+
|
12
|
+
expect(sie_file.entries_with_label("fnamn").first.attributes["foretagsnamn"]).to eq("Foocorp")
|
13
|
+
|
14
|
+
account = sie_file.entries_with_label("konto").first
|
15
|
+
expect(account.attributes["kontonr"]).to eq("1510")
|
16
|
+
expect(account.attributes["kontonamn"]).to eq("Accounts receivable")
|
17
|
+
expect(sie_file.entries_with_label("ver").size).to eq(2)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def open_file(fixture_file)
|
22
|
+
File.open(File.join(File.dirname(__FILE__), "../#{fixture_file}"))
|
23
|
+
end
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sie::Document::FinancialYears, ".between" do
|
4
|
+
it "gives us the financial years between from_date and to_date" do
|
5
|
+
Sie::Document::FinancialYears.between(
|
6
|
+
1,
|
7
|
+
Date.new(2011, 9, 1),
|
8
|
+
Date.new(2012, 12, 31)
|
9
|
+
).should == [
|
10
|
+
Date.new(2011, 9, 1)..Date.new(2011, 12, 31),
|
11
|
+
Date.new(2012, 1, 1)..Date.new(2012, 12, 31),
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "gives us the financial years between from_date and to_date" do
|
16
|
+
Sie::Document::FinancialYears.between(
|
17
|
+
1,
|
18
|
+
Date.new(2011, 9, 1),
|
19
|
+
Date.new(2013, 12, 31)
|
20
|
+
).should == [
|
21
|
+
Date.new(2011, 9, 1)..Date.new(2011, 12, 31),
|
22
|
+
Date.new(2012, 1, 1)..Date.new(2012, 12, 31),
|
23
|
+
Date.new(2013, 1, 1)..Date.new(2013, 12, 31),
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "gives us the financial years between from_date and to_date with a different financial year start month" do
|
28
|
+
Sie::Document::FinancialYears.between(
|
29
|
+
5,
|
30
|
+
Date.new(2011, 9, 1),
|
31
|
+
Date.new(2014, 1, 10)
|
32
|
+
).should == [
|
33
|
+
Date.new(2011, 9, 1)..Date.new(2012, 4, 30),
|
34
|
+
Date.new(2012, 5, 1)..Date.new(2013, 4, 30),
|
35
|
+
Date.new(2013, 5, 1)..Date.new(2014, 1, 10),
|
36
|
+
]
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Sie::Document::VoucherSeries, ".for" do
|
4
|
+
subject(:series) { Sie::Document::VoucherSeries.for(creditor, type) }
|
5
|
+
|
6
|
+
let(:type) { :invoice }
|
7
|
+
|
8
|
+
context "when on the creditor side" do
|
9
|
+
let(:creditor) { true }
|
10
|
+
|
11
|
+
context "with an invoice" do
|
12
|
+
let(:type) { :invoice }
|
13
|
+
it { should == "LF" }
|
14
|
+
end
|
15
|
+
|
16
|
+
context "with a payment" do
|
17
|
+
let(:type) { :payment }
|
18
|
+
it { should == "KB" }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "when on the debtor side" do
|
23
|
+
let(:creditor) { false }
|
24
|
+
|
25
|
+
context "with an invoice" do
|
26
|
+
let(:type) { :invoice }
|
27
|
+
it { should == "KF" }
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with a payment" do
|
31
|
+
let(:type) { :payment }
|
32
|
+
it { should == "KI" }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when neither a payment or invoice" do
|
37
|
+
let(:creditor) { false }
|
38
|
+
let(:type) { :manual_bookable }
|
39
|
+
it { should == "LV" }
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
require "sie"
|
4
|
+
require "active_support/core_ext/date/calculations"
|
5
|
+
|
6
|
+
describe Sie::Document, "#render" do
|
7
|
+
let(:from_date) { Date.new(2011, 9, 1) }
|
8
|
+
let(:to_date) { Date.new(2013, 12, 31) }
|
9
|
+
let(:generated_on) { Date.yesterday }
|
10
|
+
let(:accounts) {
|
11
|
+
[
|
12
|
+
number: 1500, description: "Customer ledger"
|
13
|
+
]
|
14
|
+
}
|
15
|
+
let(:vouchers) {
|
16
|
+
[
|
17
|
+
{
|
18
|
+
creditor: false, type: :invoice, number: 1, booked_on: from_date + 2, description: "Invoice 1",
|
19
|
+
voucher_lines: [
|
20
|
+
{ account_number: "1500", amount: 512.0, booked_on: from_date + 2, description: "Item 1" },
|
21
|
+
{ account_number: "3100", amount: -512.0, booked_on: from_date + 2, description: "Item 1" },
|
22
|
+
]
|
23
|
+
},
|
24
|
+
{
|
25
|
+
creditor: true, type: :payment, number: 2, booked_on: from_date + 365, description: "Payout 1",
|
26
|
+
voucher_lines: [
|
27
|
+
{ account_number: "2400", amount: 256.0, booked_on: from_date + 365, description: "Payout line 1" },
|
28
|
+
{ account_number: "1970", amount: -256.0, booked_on: from_date + 365, description: "Payout line 2" },
|
29
|
+
]
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
33
|
+
|
34
|
+
class TestDataSource
|
35
|
+
attr_accessor :program, :program_version, :generated_on, :company_name,
|
36
|
+
:accounts, :balance_account_numbers, :closing_account_numbers,
|
37
|
+
:vouchers, :from_date, :to_date, :financial_year_start_month
|
38
|
+
|
39
|
+
# vouchers is not part of the expected interface so making it private.
|
40
|
+
#
|
41
|
+
# Sie::Document uses #each_voucher so that you can build documents for huge sets of vouchers
|
42
|
+
# by loading them in batches.
|
43
|
+
private :vouchers
|
44
|
+
|
45
|
+
def initialize(hash)
|
46
|
+
hash.each do |k, v|
|
47
|
+
public_send("#{k}=", v)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def each_voucher(&block)
|
52
|
+
vouchers.each(&block)
|
53
|
+
end
|
54
|
+
|
55
|
+
def balance_before(account_number, date)
|
56
|
+
# Faking a fetch based on date and number
|
57
|
+
account_number.to_i + (date.mday * 100).to_f
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:doc) {
|
62
|
+
data_source = TestDataSource.new(
|
63
|
+
from_date: from_date,
|
64
|
+
to_date: to_date,
|
65
|
+
accounts: accounts,
|
66
|
+
vouchers: vouchers,
|
67
|
+
program: "Foonomic",
|
68
|
+
program_version: "3.11",
|
69
|
+
generated_on: generated_on,
|
70
|
+
company_name: "Foocorp",
|
71
|
+
financial_year_start_month: 1,
|
72
|
+
balance_account_numbers: [ "1500", "2400" ],
|
73
|
+
closing_account_numbers: [ "3100" ]
|
74
|
+
)
|
75
|
+
doc = Sie::Document.new(data_source)
|
76
|
+
}
|
77
|
+
|
78
|
+
let(:sie_file) { Sie::Parser.new.parse(doc.render) }
|
79
|
+
|
80
|
+
it "adds a header" do
|
81
|
+
expect(entry_attribute("flagga", "x")).to eq("0")
|
82
|
+
expect(entry_attribute("program", "programnamn")).to eq("Foonomic")
|
83
|
+
expect(entry_attribute("program", "version")).to eq("3.11")
|
84
|
+
expect(entry_attribute("format", "PC8")).to eq("PC8")
|
85
|
+
expect(entry_attribute("gen", "datum")).to eq(generated_on.strftime("%Y%m%d"))
|
86
|
+
expect(entry_attribute("sietyp", "typnr")).to eq("4")
|
87
|
+
expect(entry_attribute("fnamn", "foretagsnamn")).to eq("Foocorp")
|
88
|
+
end
|
89
|
+
|
90
|
+
it "has accounting years" do
|
91
|
+
expect(indexed_entry_attribute("rar", 0, "arsnr")).to eq("0")
|
92
|
+
expect(indexed_entry_attribute("rar", 0, "start")).to eq("20130101")
|
93
|
+
expect(indexed_entry_attribute("rar", 0, "slut")).to eq("20131231")
|
94
|
+
expect(indexed_entry_attribute("rar", 1, "arsnr")).to eq("-1")
|
95
|
+
expect(indexed_entry_attribute("rar", 1, "start")).to eq("20120101")
|
96
|
+
expect(indexed_entry_attribute("rar", 1, "slut")).to eq("20121231")
|
97
|
+
expect(indexed_entry_attribute("rar", 2, "arsnr")).to eq("-2")
|
98
|
+
expect(indexed_entry_attribute("rar", 2, "start")).to eq("20110901")
|
99
|
+
expect(indexed_entry_attribute("rar", 2, "slut")).to eq("20111231")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "has accounts" do
|
103
|
+
expect(indexed_entry_attributes("konto", 0)).to eq("kontonr" => "1500", "kontonamn" => "Customer ledger")
|
104
|
+
end
|
105
|
+
|
106
|
+
it "has balances brought forward (ingående balans)" do
|
107
|
+
expect(indexed_entry_attributes("ib", 0)).to eq("arsnr" => "0", "konto" => "1500", "saldo" => "1600.0")
|
108
|
+
expect(indexed_entry_attributes("ib", 1)).to eq("arsnr" => "0", "konto" => "2400", "saldo" => "2500.0")
|
109
|
+
expect(indexed_entry_attributes("ib", 2)).to eq("arsnr" => "-1", "konto" => "1500", "saldo" => "1600.0")
|
110
|
+
expect(indexed_entry_attributes("ib", 3)).to eq("arsnr" => "-1", "konto" => "2400", "saldo" => "2500.0")
|
111
|
+
expect(indexed_entry_attributes("ib", 4)).to eq("arsnr" => "-2", "konto" => "1500", "saldo" => "1600.0")
|
112
|
+
expect(indexed_entry_attributes("ib", 5)).to eq("arsnr" => "-2", "konto" => "2400", "saldo" => "2500.0")
|
113
|
+
end
|
114
|
+
|
115
|
+
it "has balances carried forward (utgående balans)" do
|
116
|
+
expect(indexed_entry_attributes("ub", 0)).to eq("arsnr" => "0", "konto" => "1500", "saldo" => "4600.0")
|
117
|
+
expect(indexed_entry_attributes("ub", 1)).to eq("arsnr" => "0", "konto" => "2400", "saldo" => "5500.0")
|
118
|
+
expect(indexed_entry_attributes("ub", 2)).to eq("arsnr" => "-1", "konto" => "1500", "saldo" => "4600.0")
|
119
|
+
expect(indexed_entry_attributes("ub", 3)).to eq("arsnr" => "-1", "konto" => "2400", "saldo" => "5500.0")
|
120
|
+
expect(indexed_entry_attributes("ub", 4)).to eq("arsnr" => "-2", "konto" => "1500", "saldo" => "4600.0")
|
121
|
+
expect(indexed_entry_attributes("ub", 5)).to eq("arsnr" => "-2", "konto" => "2400", "saldo" => "5500.0")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "has closing account balances (saldo för resultatkonto)" do
|
125
|
+
expect(indexed_entry_attributes("res", 0)).to eq("ars" => "0", "konto" => "3100", "saldo" => "6200.0")
|
126
|
+
expect(indexed_entry_attributes("res", 1)).to eq("ars" => "-1", "konto" => "3100", "saldo" => "6200.0")
|
127
|
+
expect(indexed_entry_attributes("res", 2)).to eq("ars" => "-2", "konto" => "3100", "saldo" => "6200.0")
|
128
|
+
end
|
129
|
+
|
130
|
+
it "has vouchers" do
|
131
|
+
expect(indexed_entry("ver", 0).attributes).to eq(
|
132
|
+
"serie" => "KF", "vernr" => "1",
|
133
|
+
"verdatum" => "20110903", "vertext" => "Invoice 1"
|
134
|
+
)
|
135
|
+
expect(indexed_voucher_entries(0)[0].attributes).to eq(
|
136
|
+
"kontonr" => "1500", "belopp" => "512.0",
|
137
|
+
"transdat" => "20110903", "transtext" => "Item 1"
|
138
|
+
)
|
139
|
+
expect(indexed_voucher_entries(0)[1].attributes).to eq(
|
140
|
+
"kontonr" => "3100", "belopp" => "-512.0",
|
141
|
+
"transdat" => "20110903", "transtext" => "Item 1"
|
142
|
+
)
|
143
|
+
|
144
|
+
expect(indexed_entry("ver", 1).attributes).to eq(
|
145
|
+
"serie" => "KB", "vernr" => "2",
|
146
|
+
"verdatum" => "20120831", "vertext" => "Payout 1"
|
147
|
+
)
|
148
|
+
expect(indexed_voucher_entries(1)[0].attributes).to eq(
|
149
|
+
"kontonr" => "2400", "belopp" => "256.0",
|
150
|
+
"transdat" => "20120831", "transtext" => "Payout line 1"
|
151
|
+
)
|
152
|
+
expect(indexed_voucher_entries(1)[1].attributes).to eq(
|
153
|
+
"kontonr" => "1970", "belopp" => "-256.0",
|
154
|
+
"transdat" => "20120831", "transtext" => "Payout line 2"
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def entry_attribute(label, attribute)
|
161
|
+
indexed_entry_attribute(label, 0, attribute)
|
162
|
+
end
|
163
|
+
|
164
|
+
def indexed_entry_attribute(label, index, attribute)
|
165
|
+
indexed_entry_attributes(label, index).fetch(attribute) do
|
166
|
+
raise "Unknown attribute #{ attribute } in #{ entry.attributes.keys.inspect }"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def indexed_entry_attributes(label, index)
|
171
|
+
indexed_entry(label, index).attributes
|
172
|
+
end
|
173
|
+
|
174
|
+
def indexed_voucher_entries(index)
|
175
|
+
indexed_entry("ver", index).entries
|
176
|
+
end
|
177
|
+
|
178
|
+
def indexed_entry(label, index)
|
179
|
+
sie_file.entries_with_label(label)[index] or raise "No entry with label #{ label.inspect } found!"
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "sie/parser/line_parser"
|
3
|
+
|
4
|
+
describe Sie::Parser::LineParser, "parse" do
|
5
|
+
it "parses lines from a sie file" do
|
6
|
+
parser = Sie::Parser::LineParser.new('#TRANS 2400 {} -200 20130101 "Foocorp expense"')
|
7
|
+
entry = parser.parse
|
8
|
+
expect(entry.label).to eq("trans")
|
9
|
+
expect(entry.attributes).to eq({
|
10
|
+
"kontonr" => "2400",
|
11
|
+
"belopp" => "-200",
|
12
|
+
"transdat" => "20130101",
|
13
|
+
"transtext" => "Foocorp expense"
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
it "fails if you have non empty metadata arrays until there is a need to support that" do
|
18
|
+
parser = Sie::Parser::LineParser.new('#TRANS 2400 { 1 "2" } -200 20130101 "Foocorp expense"')
|
19
|
+
expect(-> { parser.parse }).to raise_error(/don't support metadata/)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "sie/parser/tokenizer"
|
3
|
+
|
4
|
+
describe Sie::Parser::Tokenizer do
|
5
|
+
it "tokenizes the given line" do
|
6
|
+
tokenizer = Sie::Parser::Tokenizer.new('#TRANS 2400 {} -200 20130101 "Foocorp expense"')
|
7
|
+
tokens = tokenizer.tokenize
|
8
|
+
|
9
|
+
expect(token_table_for(tokens)).to eq([
|
10
|
+
[ "EntryToken", "TRANS" ],
|
11
|
+
[ "StringToken", "2400" ],
|
12
|
+
[ "BeginArrayToken", "" ],
|
13
|
+
[ "EndArrayToken", "" ],
|
14
|
+
[ "StringToken", "-200" ],
|
15
|
+
[ "StringToken", "20130101" ],
|
16
|
+
[ "StringToken", "Foocorp expense" ]
|
17
|
+
])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "can parse metadata arrays" do
|
21
|
+
tokenizer = Sie::Parser::Tokenizer.new('#TRANS 2400 { 1 "2" } -200 20130101 "Foocorp expense"')
|
22
|
+
tokens = tokenizer.tokenize
|
23
|
+
|
24
|
+
expect(token_table_for(tokens)).to eq([
|
25
|
+
[ "EntryToken", "TRANS" ],
|
26
|
+
[ "StringToken", "2400" ],
|
27
|
+
[ "BeginArrayToken", "" ],
|
28
|
+
[ "StringToken", "1" ],
|
29
|
+
[ "StringToken", "2" ],
|
30
|
+
[ "EndArrayToken", "" ],
|
31
|
+
[ "StringToken", "-200" ],
|
32
|
+
[ "StringToken", "20130101" ],
|
33
|
+
[ "StringToken", "Foocorp expense" ]
|
34
|
+
])
|
35
|
+
end
|
36
|
+
|
37
|
+
def token_table_for(tokens)
|
38
|
+
tokens.map { |token|
|
39
|
+
[ token.class.name.split("::").last, token.value ]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "sie/parser"
|
3
|
+
|
4
|
+
describe Sie::Parser, "parse" do
|
5
|
+
it "parses sie data that includes arrays" do
|
6
|
+
parser = Sie::Parser.new
|
7
|
+
sie_file = parser.parse(<<DATA
|
8
|
+
#VER "LF" 2222 20130101 "Foocorp expense"
|
9
|
+
{
|
10
|
+
#TRANS 2400 {} -200 20130101 "Foocorp expense"
|
11
|
+
#TRANS 4100 {} 180 20130101 "Widgets from foocorp"
|
12
|
+
#TRANS 2611 {} -20 20130101 "VAT"
|
13
|
+
}
|
14
|
+
DATA
|
15
|
+
)
|
16
|
+
|
17
|
+
voucher_entry = sie_file.entries.first
|
18
|
+
expect(sie_file.entries.size).to eq(1)
|
19
|
+
expect(voucher_entry.attributes["verdatum"]).to eq("20130101")
|
20
|
+
expect(voucher_entry.entries.size).to eq(3)
|
21
|
+
expect(voucher_entry.entries.first.attributes["kontonr"]).to eq("2400")
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Barsoom AB
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: attr_extras
|
16
|
+
requirement: &70103120492320 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70103120492320
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: activesupport
|
27
|
+
requirement: &70103120491900 !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: *70103120491900
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &70103120491400 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.3'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70103120491400
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70103120490980 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70103120490980
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: &70103120490520 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70103120490520
|
69
|
+
description: SIE parser and generator
|
70
|
+
email:
|
71
|
+
- all@barsoom.se
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .rvmrc
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/sie.rb
|
83
|
+
- lib/sie/document.rb
|
84
|
+
- lib/sie/document/financial_years.rb
|
85
|
+
- lib/sie/document/renderer.rb
|
86
|
+
- lib/sie/document/voucher_series.rb
|
87
|
+
- lib/sie/parser.rb
|
88
|
+
- lib/sie/parser/entry.rb
|
89
|
+
- lib/sie/parser/entry_types.rb
|
90
|
+
- lib/sie/parser/line_parser.rb
|
91
|
+
- lib/sie/parser/sie_file.rb
|
92
|
+
- lib/sie/parser/tokenizer.rb
|
93
|
+
- lib/sie/parser/tokenizer/character.rb
|
94
|
+
- lib/sie/parser/tokenizer/token.rb
|
95
|
+
- lib/sie/version.rb
|
96
|
+
- script/bootstrap
|
97
|
+
- script/ci/support/env
|
98
|
+
- script/ci/tests.sh
|
99
|
+
- script/refresh
|
100
|
+
- script/turbux_rspec
|
101
|
+
- sie.gemspec
|
102
|
+
- spec/fixtures/sie_file.se
|
103
|
+
- spec/integration/parser_spec.rb
|
104
|
+
- spec/spec_helper.rb
|
105
|
+
- spec/unit/document/financial_years_spec.rb
|
106
|
+
- spec/unit/document/voucher_series_spec.rb
|
107
|
+
- spec/unit/document_spec.rb
|
108
|
+
- spec/unit/parser/line_parser_spec.rb
|
109
|
+
- spec/unit/parser/tokenizer_spec.rb
|
110
|
+
- spec/unit/parser_spec.rb
|
111
|
+
homepage: ''
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ! '>='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
hash: 1372446967420835703
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
segments:
|
134
|
+
- 0
|
135
|
+
hash: 1372446967420835703
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.8.6
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: Parses and generates SIE files (http://sie.se/)
|
142
|
+
test_files:
|
143
|
+
- spec/fixtures/sie_file.se
|
144
|
+
- spec/integration/parser_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/unit/document/financial_years_spec.rb
|
147
|
+
- spec/unit/document/voucher_series_spec.rb
|
148
|
+
- spec/unit/document_spec.rb
|
149
|
+
- spec/unit/parser/line_parser_spec.rb
|
150
|
+
- spec/unit/parser/tokenizer_spec.rb
|
151
|
+
- spec/unit/parser_spec.rb
|