baldr 0.0.0 → 0.3.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.
@@ -0,0 +1,16 @@
1
+ module Baldr::Grammar
2
+
3
+ def self.for_version(version)
4
+ case version.length
5
+ when 4
6
+ self.const_get("Version#{version}")
7
+ when 5
8
+ self.const_get("Version#{version[2..4]}0")
9
+ when 6
10
+ self.const_get("Version#{version[2..5]}")
11
+ else
12
+ raise "unknown standard version number: #{version}"
13
+ end
14
+ end
15
+
16
+ end
data/lib/baldr/loop.rb CHANGED
@@ -1,28 +1,26 @@
1
1
  class Baldr::Loop
2
2
 
3
- def initialize(id, builder)
4
- @elements = []
5
- @children = []
6
- @builder = builder
7
- @id = id.upcase.to_s
3
+ attr_reader :id, :segments
4
+
5
+ def initialize(id)
6
+ @segments = []
7
+ @id = id
8
+ end
9
+
10
+ def add(segment)
11
+ @segments << segment
12
+ end
13
+
14
+ def count
15
+ @segments.length
8
16
  end
9
17
 
10
- def method_missing(method, *args, &block)
11
- if method.to_s.start_with?(@id)
12
- element = method.to_s[-2..-1].to_i - 1
13
- @elements[element] = args[0]
14
- else
15
- child = VanillaX12::Segment.new(method, @builder)
16
- child.instance_eval &block if block_given?
17
- @children << child
18
- end
18
+ def number_of_segments
19
+ @segments.map(&:number_of_segments).sum
19
20
  end
20
21
 
21
- def draw
22
- a = [@id] + @elements
23
- s = @builder.separators
24
- result = ["#{a.join(s[:element])}#{s[:segment]}"] + @children.map{ |c| c.draw }
25
- result.join("\n")
22
+ def current_segment
23
+ @segments.last
26
24
  end
27
25
 
28
26
  end
@@ -0,0 +1,107 @@
1
+ class Baldr::Parser
2
+
3
+ attr_reader :error, :envelopes, :separators, :input
4
+
5
+ def initialize(input)
6
+ parse(input)
7
+ end
8
+
9
+ def successful?
10
+ @error.nil?
11
+ end
12
+
13
+ protected
14
+
15
+ def parse(input)
16
+ @input = input
17
+ @separators = detect_separators(input)
18
+ @envelopes = build_tree(split_segments(input, separators))
19
+ @envelopes.each { |e| Baldr::Validator.validate! e }
20
+ self
21
+ rescue Baldr::Error => e
22
+ @error = e.message
23
+ end
24
+
25
+ def detect_separators(input)
26
+ io = StringIO.new(input)
27
+
28
+ isa = io.gets(3)
29
+ raise Baldr::Error, "doesn't begin with ISA..." unless isa == 'ISA'
30
+
31
+ element = io.getbyte
32
+
33
+ 15.times { io.bytes { |b| break if b == element } }
34
+ component = io.getbyte
35
+
36
+ segment = []
37
+ io.bytes do |b|
38
+ break if b.chr =~ /[A-Z]/
39
+ segment << b
40
+ end
41
+
42
+ {
43
+ element: element.chr,
44
+ segment: segment,
45
+ component: component.chr
46
+ }
47
+ end
48
+
49
+ def split_segments(input, separators)
50
+ segments = []
51
+ buffer = []
52
+ skip = 0
53
+
54
+ io = StringIO.new(input)
55
+ io.bytes do |b|
56
+ if skip > 0
57
+ skip -= 1
58
+ else
59
+ if b == separators[:segment].first
60
+ segments << buffer.pack('c*').split(separators[:element])
61
+ skip = separators[:segment].length - 1
62
+ buffer = []
63
+ else
64
+ buffer << b
65
+ end
66
+ end
67
+ end
68
+
69
+ raise Baldr::Error, 'invalid characters in the end of interchange' if skip > 0
70
+
71
+ segments
72
+ end
73
+
74
+ def build_tree(source)
75
+ grammar = Baldr::Grammar::Envelope::STRUCTURE
76
+ loop = build_segment(source.to_enum, grammar, nil)
77
+ loop.segments
78
+ end
79
+
80
+ def build_segment(enumerator, grammar, version)
81
+ current = enumerator.peek
82
+
83
+ while grammar[:id] == current[0]
84
+ loop ||= Baldr::Loop.new(current[0])
85
+ segment = Baldr.const_get((grammar[:class] || :segment).to_s.camelize).new(current[0])
86
+ segment.elements = current[1..-1]
87
+ loop.add segment
88
+
89
+ enumerator.next
90
+
91
+ version ||= segment.sub_version
92
+ sub_grammar = segment.sub_grammar(version) || grammar
93
+ sub_grammar.fetch(:level, []).each do |g|
94
+ child = build_segment(enumerator, g, version)
95
+ segment.children << child if child
96
+ end
97
+
98
+ current = enumerator.peek
99
+ end
100
+
101
+ loop
102
+
103
+ rescue StopIteration
104
+ return loop
105
+ end
106
+
107
+ end
@@ -0,0 +1,16 @@
1
+ module Baldr::Renderer::Json
2
+
3
+ extend self
4
+
5
+ def draw(segment, params = {})
6
+ draw_segment(segment).to_json
7
+ end
8
+
9
+ def draw_segment(segment)
10
+ node = ActiveSupport::OrderedHash.new
11
+ segment.elements.each.with_index { |e, i| node["#{segment.id}#{'%02d' % i}"] = e if e.present? }
12
+ segment.children.each { |loop| node[loop.id] = loop.segments.map { |s| draw_segment(s) } }
13
+ node
14
+ end
15
+
16
+ end
@@ -0,0 +1,27 @@
1
+ module Baldr::Renderer::X12
2
+
3
+ extend self
4
+
5
+ DEFAULT_SEPARATORS = {
6
+ element: '*',
7
+ segment: '~',
8
+ component: '>',
9
+ }.freeze
10
+
11
+ def draw(segment, params = {})
12
+ separators = params[:separators] || DEFAULT_SEPARATORS
13
+ separators[:segment] = separators[:segment].pack('c*') if separators[:segment].is_a?(Array)
14
+ draw_segment(segment, separators).join
15
+ end
16
+
17
+ def draw_segment(segment, separators)
18
+ a = [segment.id] + segment.elements
19
+
20
+ ["#{a.join(separators[:element])}#{separators[:segment]}"] + segment.children.map{ |l| draw_loop(l, separators) }
21
+ end
22
+
23
+ def draw_loop(loop, separators)
24
+ loop.segments.map { |s| draw_segment(s, separators) }
25
+ end
26
+
27
+ end
@@ -0,0 +1,3 @@
1
+ module Baldr::Renderer
2
+
3
+ end
data/lib/baldr/segment.rb CHANGED
@@ -1,28 +1,136 @@
1
1
  class Baldr::Segment
2
2
 
3
- def initialize(id, builder)
3
+ attr_accessor :id, :children, :elements
4
+
5
+ def self.helpers_for_elements(hash)
6
+ hash.each do |element, method|
7
+ self.class_eval <<-RUBY
8
+ def #{method}
9
+ self['#{element}']
10
+ end
11
+
12
+ def #{method}=(value)
13
+ self['#{element}'] = value
14
+ end
15
+ RUBY
16
+ end
17
+ end
18
+
19
+ def initialize(id)
4
20
  @elements = []
5
21
  @children = []
6
- @builder = builder
7
- @id = id.upcase.to_s
22
+ @id = id.to_s.upcase
23
+ end
24
+
25
+ def fetch(key, default = nil)
26
+ self[key] || default
27
+ end
28
+
29
+ def []=(key, value)
30
+ key = key.to_s
31
+ if key.start_with?(@id) && key.length == @id.length + 2
32
+ element = key[-2..-1].to_i - 1
33
+ @elements[element] = value
34
+ elsif !key.start_with?(@id) && key.length > 3 && key =~ /[0-9]{2}$/
35
+ loop = @children.select { |l| l.id == key[0..-3] }.first
36
+ if loop
37
+ if loop.segments.length == 1
38
+ loop.segments.first[key] = value
39
+ else
40
+ raise Buldr::Error, 'there are more than 1 segment in loop. you can\'t assign from here' if loop.segments.length > 1
41
+ end
42
+ else
43
+ raise Buldr::Error, "segment #{key[0..-3]} not found"
44
+ end
45
+ else
46
+ raise Buldr::Error, "unable to assign #{key}"
47
+ end
48
+ end
49
+
50
+ def [](key)
51
+ key = key.to_s
52
+ if key.start_with?(@id) && key.length == @id.length + 2
53
+ element = key[-2..-1].to_i - 1
54
+ @elements[element]
55
+ elsif !key.start_with?(@id) && key.length > 3 && key =~ /[0-9]{2}$/
56
+ loop = @children.select { |l| l.id == key[0..-3] }.first
57
+ if loop
58
+ if loop.segments.length == 1
59
+ loop.segments.first[key]
60
+ else
61
+ raise Buldr::Error, 'there are more than 1 segment in loop. use it as enum' if loop.segments.length > 1
62
+ end
63
+ end
64
+ elsif key =~ /^[A-z][A-Z0-9]{1,2}$/
65
+ loop = @children.select { |l| l.id == key }.first
66
+ if loop
67
+ loop.segments.to_enum
68
+ end
69
+ end
8
70
  end
9
71
 
10
72
  def method_missing(method, *args, &block)
11
- if method.to_s.start_with?(@id)
12
- element = method.to_s[-2..-1].to_i - 1
73
+ m = method.to_s
74
+ if m.start_with?(@id) && m.length == @id.length + 2
75
+ element = m[-2..-1].to_i - 1
13
76
  @elements[element] = args[0]
77
+ elsif m =~ /^[A-Z][A-Z0-9]{1,2}$/
78
+ segment = Baldr::Segment.new(m)
79
+ segment.instance_eval &block if block_given?
80
+ get_loop(m).add segment
14
81
  else
15
- child = VanillaX12::Loop.new(method, @builder)
16
- child.instance_eval &block if block_given?
17
- @children << child
82
+ super
18
83
  end
19
84
  end
20
85
 
21
- def draw
22
- a = [@id] + @elements
23
- s = @builder.separators
24
- result = ["#{a.join(s[:element])}#{s[:segment]}"] + @children.map{ |c| c.draw }
25
- result.join("\n")
86
+ def sub_version
87
+
88
+ end
89
+
90
+ def sub_grammar(version)
91
+
92
+ end
93
+
94
+ def prepare!
95
+
96
+ end
97
+
98
+ def custom_validate!(version)
99
+
100
+ end
101
+
102
+ def number_of_segments
103
+ 1 + @children.map(&:number_of_segments).sum
104
+ end
105
+
106
+ def add(segment)
107
+ get_loop(segment.id).add segment
108
+ end
109
+
110
+ protected
111
+
112
+ def generate_control_number(digits)
113
+ "%0#{digits}d" % Random.rand(10 ** (digits + 1))
114
+ end
115
+
116
+ def get_trailer(trailer_id)
117
+ if @children.last && @children.last.segments.first.id == trailer_id
118
+ trailer = @children.last.segments.first
119
+ else
120
+ trailer = Baldr::Segment.new(trailer_id)
121
+ add trailer
122
+ end
123
+ trailer
124
+ end
125
+
126
+ def get_loop(loop_id)
127
+ if @children.last && @children.last.id.to_s == loop_id
128
+ @children.last
129
+ else
130
+ loop = Baldr::Loop.new(loop_id)
131
+ @children << loop
132
+ loop
133
+ end
26
134
  end
27
135
 
28
136
  end
@@ -0,0 +1,40 @@
1
+ class Baldr::Transaction < Baldr::Segment
2
+
3
+ helpers_for_elements(
4
+ 'ST01' => :transaction_set_code,
5
+ 'ST02' => :transaction_control_number,
6
+ )
7
+
8
+ def initialize(id = 'ST')
9
+ super(id)
10
+ end
11
+
12
+ def prepare!
13
+ trailer = get_trailer('SE')
14
+
15
+ trailer['SE01'] = number_of_segments.to_s
16
+
17
+ self.transaction_control_number ||= generate_control_number(9)
18
+ trailer['SE02'] = transaction_control_number
19
+ end
20
+
21
+ def sub_grammar(version)
22
+ version.for_transaction_set(transaction_set_code)::STRUCTURE
23
+ end
24
+
25
+ def functional_group(version)
26
+ version.for_transaction_set(transaction_set_code)::FUNCTIONAL_GROUP
27
+ end
28
+
29
+ def custom_validate!(version)
30
+ trailer = @children.last.segments.first
31
+ total_number = number_of_segments
32
+ if trailer['SE01'].to_i != total_number
33
+ raise "wrong segments number: #{trailer['SE01']} in SE01, but real number is #{total_number}"
34
+ end
35
+ if trailer['SE02'] != transaction_control_number
36
+ raise "transaction set control numbers don't match: #{trailer['SE02']} in SE02 and #{transaction_control_number} in ST02"
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,93 @@
1
+ module Baldr::Validator
2
+
3
+ extend self
4
+
5
+ def validate!(envelope)
6
+ grammar = Baldr::Grammar::Envelope::STRUCTURE
7
+ record_defs = Baldr::Grammar::Envelope::RECORD_DEFS
8
+ validate_tree!(envelope, grammar, record_defs, nil)
9
+ end
10
+
11
+ protected
12
+
13
+ def validate_tree!(segment, grammar, record_defs, version)
14
+ raise Baldr::Error, "unknown segment #{segment.id}" unless record_defs[segment.id]
15
+
16
+ record_defs[segment.id].each.with_index do |r, i|
17
+ element = segment.elements[i]
18
+ check_required(r, element)
19
+ self.send("check_#{r[:type]}", r, element) unless element.nil?
20
+ end
21
+
22
+ version ||= segment.sub_version
23
+ record_defs = version::RECORD_DEFS if version
24
+ sub_grammar = segment.sub_grammar(version) || grammar
25
+
26
+ l = 0
27
+ sub_grammar.fetch(:level, []).each do |g|
28
+ loop = segment.children[l]
29
+ if loop && loop.id.to_s == g[:id]
30
+ check_loop_count(loop, g)
31
+
32
+ loop.segments.each { |child| validate_tree!(child, g, record_defs, version) }
33
+
34
+ l += 1
35
+ else
36
+ raise Baldr::Error, "segment #{g[:id]} is required, but #{loop.id} was found" if g[:min] > 0
37
+ end
38
+ end
39
+
40
+ segment.custom_validate!(version)
41
+ end
42
+
43
+ def check_loop_count(loop, grammar)
44
+ raise Baldr::Error, "#{loop.id} loop is too long: #{loop.count} segments, maximum #{grammar[:max]}" if loop.count > grammar[:max]
45
+ raise Baldr::Error, "#{loop.id} loop is too short: #{loop.count} segments, minimum #{grammar[:min]}" if loop.count < grammar[:min]
46
+ end
47
+
48
+ def check_required(r, element)
49
+ if r[:required] && (element.nil? || element.size == 0)
50
+ raise Baldr::Error, "element #{r[:id]} is required"
51
+ end
52
+ end
53
+
54
+ def check_string(r, element)
55
+ check_max_and_min_for_string(r, element)
56
+ end
57
+
58
+ def check_id(r, element)
59
+ check_max_and_min_for_string(r, element)
60
+
61
+ end
62
+
63
+ def check_max_and_min_for_string(r, element)
64
+ if r[:max] && element.length > r[:max]
65
+ raise Baldr::Error, "#{r[:id]} is too long: #{element.length} characters, maximum #{r[:max]}"
66
+ end
67
+
68
+ if r[:min] && element.length < r[:min]
69
+ raise Baldr::Error, "#{r[:id]} is too short: #{element.length} characters, minimum #{r[:min]}"
70
+ end
71
+ end
72
+
73
+ def check_time(r, element)
74
+
75
+ end
76
+
77
+ def check_date(r, element)
78
+
79
+ end
80
+
81
+ def check_number(r, element)
82
+
83
+ end
84
+
85
+ def check_real(r, element)
86
+
87
+ end
88
+
89
+ def check_complex(r, element)
90
+
91
+ end
92
+
93
+ end
data/lib/baldr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Baldr
2
- VERSION = '0.0.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/baldr.rb CHANGED
@@ -1 +1,29 @@
1
- require 'baldr/version'
1
+ require 'baldr/version'
2
+
3
+ require 'baldr/builder'
4
+ require 'baldr/parser'
5
+ require 'baldr/segment'
6
+ require 'baldr/envelope'
7
+ require 'baldr/transaction'
8
+ require 'baldr/functional_group'
9
+ require 'baldr/loop'
10
+ require 'baldr/validator'
11
+
12
+ require 'baldr/error'
13
+
14
+ require 'baldr/renderer'
15
+ require 'baldr/renderer/x12'
16
+ require 'baldr/renderer/json'
17
+
18
+ require 'baldr/grammar'
19
+ require 'baldr/grammar/envelope'
20
+ require 'baldr/grammar/version4010'
21
+ require 'baldr/grammar/version4010/set204'
22
+ require 'baldr/grammar/version4010/set210'
23
+ require 'baldr/grammar/version4010/set214'
24
+ require 'baldr/grammar/version4010/set810'
25
+ require 'baldr/grammar/version4010/set850'
26
+ require 'baldr/grammar/version4010/set855'
27
+ require 'baldr/grammar/version4010/set856'
28
+ require 'baldr/grammar/version4010/set990'
29
+ require 'baldr/grammar/version4010/set997'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: baldr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,23 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2013-02-06 00:00:00.000000000 Z
13
- dependencies: []
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
14
30
  description: Lightweight EDI X12 translator
15
31
  email: stanislav@spiridonov.pro
16
32
  executables: []
@@ -19,9 +35,29 @@ extra_rdoc_files: []
19
35
  files:
20
36
  - lib/baldr.rb
21
37
  - lib/baldr/builder.rb
22
- - lib/baldr/grammar/set997.rb
38
+ - lib/baldr/envelope.rb
39
+ - lib/baldr/error.rb
40
+ - lib/baldr/functional_group.rb
41
+ - lib/baldr/grammar.rb
42
+ - lib/baldr/grammar/envelope.rb
43
+ - lib/baldr/grammar/version4010.rb
44
+ - lib/baldr/grammar/version4010/set204.rb
45
+ - lib/baldr/grammar/version4010/set210.rb
46
+ - lib/baldr/grammar/version4010/set214.rb
47
+ - lib/baldr/grammar/version4010/set810.rb
48
+ - lib/baldr/grammar/version4010/set850.rb
49
+ - lib/baldr/grammar/version4010/set855.rb
50
+ - lib/baldr/grammar/version4010/set856.rb
51
+ - lib/baldr/grammar/version4010/set990.rb
52
+ - lib/baldr/grammar/version4010/set997.rb
23
53
  - lib/baldr/loop.rb
54
+ - lib/baldr/parser.rb
55
+ - lib/baldr/renderer.rb
56
+ - lib/baldr/renderer/json.rb
57
+ - lib/baldr/renderer/x12.rb
24
58
  - lib/baldr/segment.rb
59
+ - lib/baldr/transaction.rb
60
+ - lib/baldr/validator.rb
25
61
  - lib/baldr/version.rb
26
62
  homepage: https://github.com/spiridonov/baldr
27
63
  licenses: []