sie 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.
- 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
|