ledger-rest 2.0.3 → 3.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.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +4 -22
  5. data/{LICENSE.txt → LICENSE} +1 -1
  6. data/README.md +62 -11
  7. data/config.ru +1 -1
  8. data/ledger-rest.gemspec +17 -17
  9. data/ledger-rest.yml.example +3 -0
  10. data/lib/ledger-rest/app.rb +21 -34
  11. data/lib/ledger-rest/core_ext.rb +4 -3
  12. data/lib/ledger-rest/git.rb +8 -7
  13. data/lib/ledger-rest/ledger.rb +29 -24
  14. data/lib/ledger-rest/ledger/balance.rb +48 -15
  15. data/lib/ledger-rest/ledger/budget.rb +35 -16
  16. data/lib/ledger-rest/ledger/entry.rb +1 -4
  17. data/lib/ledger-rest/ledger/parser.rb +41 -37
  18. data/lib/ledger-rest/ledger/register.rb +31 -24
  19. data/lib/ledger-rest/ledger/transaction.rb +33 -28
  20. data/lib/ledger-rest/transactions.py +47 -0
  21. data/lib/ledger-rest/version.rb +2 -1
  22. data/spec/files/append.ledger +13 -0
  23. data/spec/files/budget.ledger +4 -0
  24. data/spec/files/main.ledger +4 -0
  25. data/spec/files/opening_balance.ledger +4 -0
  26. data/spec/files/transactions.ledger +11 -0
  27. data/spec/ledger-rest.yml +3 -0
  28. data/spec/ledger-rest/api/accounts_spec.rb +19 -0
  29. data/spec/ledger-rest/api/balance_spec.rb +117 -0
  30. data/spec/ledger-rest/api/budget_spec.rb +110 -0
  31. data/spec/ledger-rest/api/payees_spec.rb +19 -0
  32. data/spec/ledger-rest/api/register_spec.rb +233 -0
  33. data/spec/ledger-rest/api/transactions_spec.rb +201 -0
  34. data/spec/ledger-rest/api/version_spec.rb +13 -0
  35. data/spec/ledger-rest/ledger/balance_spec.rb +124 -0
  36. data/spec/ledger-rest/ledger/parser_spec.rb +184 -117
  37. data/spec/ledger-rest/ledger/transaction_spec.rb +38 -31
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/support/deep_eq_matcher.rb +149 -0
  40. metadata +52 -31
  41. data/ledger-rest.org +0 -22
@@ -1,23 +1,29 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module LedgerRest
2
3
  class Ledger
3
4
  class Balance
4
-
5
5
  FORMAT = [
6
6
  '{',
7
- ' "total": %(quoted(display_total)),',
8
- ' "name": %(quoted(partial_account)),',
9
- ' "depth": %(depth), "fullname": %(quoted(account))',
7
+ '"total":%(quoted(display_total)),',
8
+ '"name":%(quoted(partial_account)),',
9
+ '"depth":%(depth), "fullname": %(quoted(account))',
10
10
  '},%/',
11
- '{ "total": %(quoted(display_total)) }'
12
- ].join('\\n')
11
+ '{"total":%(quoted(display_total))}'
12
+ ].join
13
13
 
14
14
  class << self
15
+ def get(query = nil, params = {})
16
+ data = JSON.parse(json(query, params), symbolize_names: true)
17
+
18
+ data[:accounts] = expand_accounts(data[:accounts])
19
+ unless query =~ /--flat/
20
+ data[:accounts] = wrap_accounts(data[:accounts])
21
+ end
15
22
 
16
- def get query = nil, params = {}
17
- JSON.parse(json(query, params), :symbolize_names => true)
23
+ data
18
24
  end
19
25
 
20
- def json query = nil, params = {}
26
+ def json(query = nil, params = {})
21
27
  params = { '--format' => FORMAT }.merge(params)
22
28
  result = Ledger.exec("bal #{query}", params)
23
29
 
@@ -25,19 +31,46 @@ module LedgerRest
25
31
  if result.end_with?(',')
26
32
  result = result[0..-2]
27
33
  else
28
- match_total = result.match(/,\n.*?{ +"total": +("[0-9\.A-Za-z ]+") +}\z/)
34
+ match_total = result.match(/,{"total":("[0-9\.A-Za-z ]+")}\z/)
29
35
  if match_total
30
36
  total = match_total[1]
31
- result = result[0,match_total.offset(0)[0]]
37
+ result = result[0, match_total.offset(0)[0]]
32
38
  end
33
39
  end
34
40
 
35
- json_str = "{"
36
- json_str << " \"accounts\": [ #{result} ]"
37
- json_str << ", \"total\": #{total}" if total
38
- json_str << " }"
41
+ json_str = '{'
42
+ json_str << "\"accounts\":[#{result}]"
43
+ json_str << ",\"total\":#{total}" if total
44
+ json_str << '}'
45
+ end
46
+
47
+ def expand_accounts(accounts)
48
+ accounts.inject([]) do |acc, elem|
49
+ fullname = elem[:fullname].gsub(/:?#{elem[:name]}/, '')
50
+ acc + elem[:name].split(':').map do |name|
51
+ fullname << "#{':' unless fullname.empty?}#{name}"
52
+ parent = elem.dup
53
+ parent[:fullname], parent[:name], parent[:depth] =
54
+ fullname.dup, name.dup, fullname.count(':')+1
55
+ parent
56
+ end
57
+ end
39
58
  end
40
59
 
60
+ def wrap_accounts(accounts)
61
+ stack = []
62
+ accounts.inject([]) do |acc, elem|
63
+ stack.pop while stack.last && stack.last[:depth] >= elem[:depth]
64
+ if stack.empty?
65
+ stack << elem
66
+ acc << elem
67
+ else
68
+ (stack.last[:accounts] ||= []) << elem
69
+ stack << elem
70
+ end
71
+ acc
72
+ end
73
+ end
41
74
  end
42
75
  end
43
76
  end
@@ -1,33 +1,52 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module LedgerRest
2
3
  class Ledger
3
4
  class Budget
4
5
  FORMAT = [
5
- '{',
6
- ' "name": %(quoted(account)),',
7
- ' "amount": %(quoted(get_at(T, 0))),',
8
- ' "budget": %(quoted(-get_at(T, 1)))',
9
- ' },%/',
10
- ' ],',
11
- ' "total_amount": %(quoted(get_at(T, 0))),',
12
- ' "total_budget": %(quoted(-get_at(T, 1)))'
13
- ].join('\\n')
6
+ '%(scrub(get_at(display_total, 0)))\n',
7
+ '%(-scrub(get_at(display_total, 1)))\n',
8
+ '%(scrub(get_at(display_total, 1) + get_at(display_total, 0)))\n',
9
+ '%(get_at(display_total, 1) ? (100% * scrub(get_at(display_total, 0))) / -scrub(get_at(display_total, 1)) : 0)\n',
10
+ '%(account)\n',
11
+ '---\n',
12
+ '%/---\n',
13
+ '%$1\n',
14
+ '%$2\n',
15
+ '%$3\n',
16
+ '%$4\n',
17
+ '%/'
18
+ ].join
14
19
 
15
20
  class << self
16
-
17
21
  def get(query = nil, params = {})
18
- JSON.parse(json(query, params), :symbolize_names => true)
22
+ JSON.parse(json(query, params), symbolize_names: true)
19
23
  end
20
24
 
21
25
  def json(query = nil, params = {})
22
26
  params = { '--format' => FORMAT }.merge(params)
23
27
  result = Ledger.exec("budget #{query}", params)
24
- result.gsub!(/\},\n *?\]/, "}\n ]")
25
-
26
- "{\n \"budget\": {\n \"accounts\": [\n #{result}\n }\n}"
28
+ budget = {}
29
+ accounts, total = result.split("---\n---\n")
30
+ budget['accounts'] = accounts.split("---\n").map do |str|
31
+ val = str.split("\n")
32
+ {
33
+ 'total' => val[0].empty? ? '0' : val[0],
34
+ 'budget' => val[1].empty? ? '0' : val[1],
35
+ 'difference' => val[2].empty? ? '0' : val[2],
36
+ 'percentage' => val[3].empty? ? '0' : val[3],
37
+ 'account' => val[4].empty? ? '0' : val[4]
38
+ }
39
+ end
40
+ if total
41
+ val = total.split("\n")
42
+ budget['total'] = val[0].empty? ? '0' : val[0]
43
+ budget['budget'] = val[1].empty? ? '0' : val[1]
44
+ budget['difference'] = val[2].empty? ? '0' : val[2]
45
+ budget['percentage'] = val[3].empty? ? '0' : val[3]
46
+ end
47
+ budget.to_json
27
48
  end
28
-
29
49
  end
30
-
31
50
  end
32
51
  end
33
52
  end
@@ -1,13 +1,11 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module LedgerRest
2
3
  class Ledger
3
-
4
4
  # Ledger offers a simple command to create a new entry based on
5
5
  # previous entries in your ledger files. This class abstracts
6
6
  # mentioned functionality for easy integration into ledger-rest.
7
7
  class Entry
8
-
9
8
  class << self
10
-
11
9
  # Return a new transaction object based on previous transactions.
12
10
  def get(desc, options = {})
13
11
  result = Ledger.exec("entry #{desc}", options)
@@ -20,7 +18,6 @@ module LedgerRest
20
18
  transaction.append_to(Ledger.append_file)
21
19
  transaction
22
20
  end
23
-
24
21
  end
25
22
  end
26
23
  end
@@ -1,7 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module LedgerRest
3
3
  class Ledger
4
-
5
4
  # A very simple parser for single legder format transactions.
6
5
  # There has to be a better way ... This does not implement the
7
6
  # whole ledger format. Tragically ... someone told me it´s the
@@ -11,14 +10,11 @@ module LedgerRest
11
10
  #
12
11
  # This works for `ledger entry` with my transactions ...
13
12
  class Parser
14
-
15
13
  class << self
16
-
17
14
  def parse(str)
18
15
  parser = Parser.new
19
16
  parser.parse(str)
20
17
  end
21
-
22
18
  end
23
19
 
24
20
  def initialize
@@ -30,20 +26,20 @@ module LedgerRest
30
26
  @str = str
31
27
  @transaction[:postings] = []
32
28
 
33
- @transaction[:date],str = parse_date(str)
29
+ @transaction[:date], str = parse_date(str)
34
30
 
35
31
  effective_date, str = parse_effective_date(str)
36
32
  @transaction[:effective_date] = effective_date if effective_date
37
33
 
38
- @transaction[:cleared],str = parse_cleared(str)
39
- @transaction[:pending],str = parse_pending(str)
34
+ @transaction[:cleared], str = parse_cleared(str)
35
+ @transaction[:pending], str = parse_pending(str)
40
36
 
41
37
  code, str = parse_code(str)
42
38
  @transaction[:code] = code if code
43
39
 
44
- @transaction[:payee],str = parse_payee(str)
40
+ @transaction[:payee], str = parse_payee(str)
45
41
 
46
- comments,str = parse_comments(str)
42
+ comments, str = parse_comments(str)
47
43
  @transaction[:comments] = comments if comments
48
44
 
49
45
  str.split("\n").each do |line|
@@ -58,60 +54,60 @@ module LedgerRest
58
54
  end
59
55
 
60
56
  def parse_date(str)
61
- if match = str.match(/\A(\d{4}\/\d{1,2}\/\d{1,2})(.*)/m)
62
- [ match[1], match[2] ]
57
+ if match = str.match(%r{\A(\d{4}/\d{1,2}/\d{1,2})(.*)}m)
58
+ [match[1], match[2]]
63
59
  else
64
- raise "Date was expected."
60
+ fail 'Date was expected.'
65
61
  end
66
62
  end
67
63
 
68
64
  def parse_effective_date(str)
69
- if match = str.match(/\A=(\d{4}\/\d{1,2}\/\d{1,2})(.*)/m)
70
- [ match[1], match[2] ]
65
+ if match = str.match(%r{\A=(\d{4}/\d{1,2}/\d{1,2})(.*)}m)
66
+ [match[1], match[2]]
71
67
  else
72
- [ nil, str ]
68
+ [nil, str]
73
69
  end
74
70
  end
75
71
 
76
72
  def parse_pending(str)
77
73
  if match = str.match(/\A ! (.*)/m)
78
- [ true, match[1] ]
74
+ [true, match[1]]
79
75
  else
80
- [ false, str]
76
+ [false, str]
81
77
  end
82
78
  end
83
79
 
84
80
  def parse_cleared(str)
85
81
  if match = str.match(/\A \* (.*)/m)
86
- [ true, match[1] ]
82
+ [true, match[1]]
87
83
  else
88
- [ false, str]
84
+ [false, str]
89
85
  end
90
86
  end
91
87
 
92
88
  def parse_code(str)
93
89
  if match = str.match(/\A *?\(([^\(\)]+)\) (.*)/m)
94
- [ match[1], match[2] ]
90
+ [match[1], match[2]]
95
91
  else
96
- [ nil, str ]
92
+ [nil, str]
97
93
  end
98
94
  end
99
95
 
100
96
  def parse_payee(str)
101
97
  if match = str.match(/\A *?([^ ][^\n]+)\n(.*)/m)
102
- [ match[1], match[2] ]
98
+ [match[1], match[2]]
103
99
  else
104
- raise "No payee given."
100
+ fail 'No payee given.'
105
101
  end
106
102
  end
107
103
 
108
104
  def parse_comments(str)
109
- comments = ""
105
+ comments = ''
110
106
  while str && match = str.match(/\A +;(.*?)(\n|$)(.*)/m)
111
107
  comments << match[1].strip << "\n"
112
108
  str = match[3]
113
109
  end
114
- [ comments.empty? ? nil : comments, str ]
110
+ [comments.empty? ? nil : comments, str]
115
111
  end
116
112
 
117
113
  # parses a ledger posting line
@@ -124,9 +120,9 @@ module LedgerRest
124
120
  posting[:balanced] = balanced if balanced
125
121
 
126
122
  amount, posting_cost, per_unit_cost, str = parse_amount(str)
127
- posting[:amount] = amount if amount
128
- posting[:posting_cost] = posting_cost if posting_cost
129
- posting[:per_unit_cost] = per_unit_cost if per_unit_cost
123
+ posting[:amount], posting[:commodity] = parse_amount_parts(amount) if amount
124
+ posting[:posting_cost], posting[:posting_cost_commodity] = parse_amount_parts(posting_cost) if posting_cost
125
+ posting[:per_unit_cost], posting[:per_unit_commodity] = parse_amount_parts(per_unit_cost) if per_unit_cost
130
126
 
131
127
  comment, actual_date, effective_date = parse_posting_comment(str)
132
128
  posting[:comment] = comment if comment
@@ -137,28 +133,36 @@ module LedgerRest
137
133
  end
138
134
 
139
135
  def parse_account(str)
140
- return [] if str.nil? or str.empty?
136
+ return [] if str.nil? || str.empty?
141
137
  if match = str.match(/\A +([\w:]+)(\n|$| )(.*)/m)
142
- [ match[1], false, false , match[3] ]
138
+ [match[1], false, false , match[3]]
143
139
  elsif match = str.match(/\A +\(([\w:]+)\)(\n|$| )(.*)/m)
144
- [ match[1], true, false , match[3] ]
140
+ [match[1], true, false , match[3]]
145
141
  elsif match = str.match(/\A +\[([\w:]+)\](\n|$| )(.*)/m)
146
- [ match[1], true, true , match[3] ]
142
+ [match[1], true, true , match[3]]
147
143
  else
148
- [ nil, false, false, str ]
144
+ [nil, false, false, str]
149
145
  end
150
146
  end
151
147
 
152
148
  def parse_amount(str)
153
149
  if match = str.match(/\A(.*?)@@([^;]*?)(;(.*)|\n(.*)|$(.*))/m)
154
150
  amount = match[1].strip
155
- [ amount.empty? ? nil : amount, nil, match[2].strip, match[3] ]
151
+ [amount.empty? ? nil : amount, nil, match[2].strip, match[3]]
156
152
  elsif match = str.match(/\A(.*?)@([^;]*)(;(.*)|\n(.*)|$(.*))/m)
157
153
  amount = match[1].strip
158
- [ amount.empty? ? nil : amount, match[2].strip, nil, match[3] ]
154
+ [amount.empty? ? nil : amount, match[2].strip, nil, match[3]]
159
155
  elsif match = str.match(/\A([^;]*?)(;(.*)|\n(.*)|$(.*))/m)
160
156
  amount = match[1].strip
161
- [ amount.empty? ? nil : amount, nil, nil, match[2] ]
157
+ [amount.empty? ? nil : amount, nil, nil, match[2]]
158
+ end
159
+ end
160
+
161
+ def parse_amount_parts(str)
162
+ if match = str.match(/^(.*?)(\d+(\.\d+)?)(.*?)$/)
163
+ [match[2].to_f, match[1].strip + match[4].strip]
164
+ else
165
+ [str, nil]
162
166
  end
163
167
  end
164
168
 
@@ -171,7 +175,7 @@ module LedgerRest
171
175
  comment = match[1]
172
176
  end
173
177
 
174
- [ comment, actual_date, effective_date ]
178
+ [comment, actual_date, effective_date]
175
179
  end
176
180
  end
177
181
  end
@@ -1,38 +1,45 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module LedgerRest
2
3
  class Ledger
3
4
  class Register
4
-
5
- FORMAT = [
6
- "{",
7
- ' "date": %(quoted(date)),',
8
- ' "effective_date": %(effective_date ? quoted(effective_date) : "null"),',
9
- ' "code": %(code ? quoted(code) : "null"),',
10
- ' "cleared": %(cleared ? "true" : "false"),',
11
- ' "pending": %(pending ? "true" : "false"),',
12
- ' "payee": %(quoted(payee)),',
13
- ' "postings":',
14
- ' [',
15
- ' { "account": %(quoted(display_account)), "amount": %(quoted(amount)) },%/',
16
- ' { "account": %(quoted(display_account)), "amount": %(quoted(amount)) },%/',
17
- ' ]',
18
- '},'
19
- ].join('\\n')
20
-
21
5
  class << self
6
+ def format(query)
7
+ format = '{'
8
+ if query =~ /-D|--daily|-W|--weekly|-M|--monthly|--quarterly|-Y|--yearly/
9
+ format << '"beginning": %(quoted(format_date(date))),'
10
+ format << '"end": %(quoted(payee)),'
11
+ elsif query =~ /--by-payee/
12
+ format << '"payee": %(quoted(payee)),'
13
+ else
14
+ format << '"date": %(quoted(format_date(date))),'
15
+ format << '"effective_date": %(effective_date ? quoted(format_date(effective_date)) : "null"),'
16
+ format << '"code": %(code ? quoted(code) : "null"),'
17
+ format << '"cleared": %(cleared ? "true" : "false"),'
18
+ format << '"pending": %(pending ? "true" : "false"),'
19
+ format << '"payee": %(quoted(payee)),'
20
+ end
21
+ format << '"postings": ['
22
+ format << '{ "account": %(quoted(display_account)), "amount": %(quoted(quantity(scrub(display_amount)))), "total": %(quoted(quantity(scrub(display_amount)))), "commodity": %(quoted(commodity)) },%/'
23
+ format << '{ "account": %(quoted(display_account)), "amount": %(quoted(quantity(scrub(display_amount)))), "total": %(quoted(quantity(scrub(display_amount)))), "commodity": %(quoted(commodity)) },%/'
24
+ format << ']},'
25
+ format
26
+ end
22
27
 
23
28
  def get(query = nil, params = {})
24
- JSON.parse(json(query, params), :symbolize_names => true)
29
+ JSON.parse(json(query, params), symbolize_names: true)
25
30
  end
26
31
 
27
32
  def json(query = nil, params = {})
28
- params = { '--format' => FORMAT }.merge(params)
33
+ params = {
34
+ '--format' => format(query),
35
+ '--date-format' => '%Y/%m/%d'
36
+ }.merge(params)
29
37
  result = Ledger.exec("reg #{query}", params)
30
- result << "\n]\n}"
31
- result.gsub! /\},\n *?\]/m, "}\n \]"
32
-
33
- "{\"transactions\":[#{result}]}"
38
+ result << "]}"
39
+ result.gsub! '"end": "- ', '"end": "'
40
+ result.gsub! /\},\n? *?\]/m, "}]"
41
+ "[#{result}]"
34
42
  end
35
-
36
43
  end
37
44
  end
38
45
  end
@@ -2,14 +2,11 @@
2
2
  module LedgerRest
3
3
  class Ledger
4
4
  class Transaction < Hash
5
-
6
5
  class << self
7
-
8
6
  # Parse a ledger transaction string into a `Transaction` object.
9
7
  def parse(str)
10
8
  LedgerRest::Ledger::Parser.parse(str)
11
9
  end
12
-
13
10
  end
14
11
 
15
12
  def initialize(params = {})
@@ -18,31 +15,39 @@ module LedgerRest
18
15
 
19
16
  # Return true if the `Transaction#to_ledger` is a valid ledger string.
20
17
  def valid?
21
- result = IO.popen("#{settings.ledger_bin} -f - stats 2>&1", "r+") do |f|
22
- f.write self.to_ledger
18
+ result = IO.popen("#{Ledger.bin} -f - stats 2>&1", 'r+') do |f|
19
+ f.write to_ledger
23
20
  f.close_write
24
21
  f.readlines
25
22
  end
26
23
 
27
- $?.success? and not result.empty?
24
+ $?.success? && !result.empty?
25
+ end
26
+
27
+ def check
28
+ IO.popen("#{Ledger.bin} -f - stats 2>&1", 'r+') do |f|
29
+ f.write to_ledger
30
+ f.close_write
31
+ f.readlines
32
+ end
28
33
  end
29
34
 
30
35
  def to_ledger
31
- if self[:date].nil? or
32
- self[:payee].nil? or
33
- self[:postings].nil?
36
+ if self[:date].nil? || self[:payee].nil? || self[:postings].nil?
34
37
  return nil
35
38
  end
36
39
 
37
- result = ""
40
+ result = ''
38
41
 
39
42
  result << self[:date]
40
- result << "=#{self[:effective_date]}" if self[:effective_date]
43
+ if self[:effective_date]
44
+ result << "=#{self[:effective_date]}"
45
+ end
41
46
 
42
47
  if self[:cleared]
43
- result << " *"
48
+ result << ' *'
44
49
  elsif self[:pending]
45
- result << " !"
50
+ result << ' !'
46
51
  end
47
52
 
48
53
  result << " (#{self[:code]})" if self[:code]
@@ -51,7 +56,7 @@ module LedgerRest
51
56
 
52
57
  self[:postings].each do |posting|
53
58
  if posting[:comment]
54
- result << " ; #{posting[:comment]}\n"
59
+ result << " ; #{posting[:comment]}\n"
55
60
  next
56
61
  end
57
62
 
@@ -59,12 +64,12 @@ module LedgerRest
59
64
 
60
65
  if posting[:virtual]
61
66
  if posting[:balance]
62
- result << " [#{posting[:account]}]"
67
+ result << " [#{posting[:account]}]"
63
68
  else
64
- result << " (#{posting[:account]})"
69
+ result << " (#{posting[:account]})"
65
70
  end
66
71
  else
67
- result << " #{posting[:account]}"
72
+ result << " #{posting[:account]}"
68
73
  end
69
74
 
70
75
  if posting[:amount].nil?
@@ -72,26 +77,26 @@ module LedgerRest
72
77
  next
73
78
  end
74
79
 
75
- result << " #{posting[:amount]}"
80
+ result << " #{'%.2f' % posting[:amount]}#{posting[:commodity]}"
76
81
 
77
- if(posting[:per_unit_cost])
78
- result << " @@ #{posting[:per_unit_cost]}"
79
- elsif(posting[:posting_cost])
80
- result << " @ #{posting[:posting_cost]}"
82
+ if posting[:per_unit_cost]
83
+ result << " @@ #{'%.2f' % posting[:per_unit_cost]}#{posting[:per_unit_commodity]}"
84
+ elsif posting[:posting_cost]
85
+ result << " @ #{'%.2f' % posting[:posting_cost]}#{posting[:posting_cost_commodity]}"
81
86
  end
82
87
 
83
- if posting[:actual_date] or posting[:effective_date]
84
- result << " ; ["
88
+ if posting[:actual_date] || posting[:effective_date]
89
+ result << ' ; ['
85
90
  result << posting[:actual_date] if posting[:actual_date]
86
- result << "=#{posting[:effective_date]}" if posting[:effective_date]
87
- result << "]"
91
+ if posting[:effective_date]
92
+ result << "=#{posting[:effective_date]}"
93
+ end
94
+ result << ']'
88
95
  end
89
96
 
90
97
  result << "\n"
91
98
  end
92
99
 
93
- result << "\n"
94
-
95
100
  result
96
101
  end
97
102
  end