tediparse 2.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 (176) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +319 -0
  3. data/LICENSE +26 -0
  4. data/README.md +308 -0
  5. data/Rakefile +33 -0
  6. data/bin/tediparse +102 -0
  7. data/doc/Defining.md +97 -0
  8. data/doc/Generating-Grammars.md +241 -0
  9. data/doc/Generating.md +332 -0
  10. data/doc/Navigating.md +670 -0
  11. data/doc/Parsing.md +133 -0
  12. data/doc/README.md +51 -0
  13. data/doc/Serializing.md +100 -0
  14. data/doc/Tokenizing.md +136 -0
  15. data/doc/Validating.md +140 -0
  16. data/lib/ruby/array.rb +226 -0
  17. data/lib/ruby/blank.rb +52 -0
  18. data/lib/ruby/exception.rb +14 -0
  19. data/lib/ruby/hash.rb +14 -0
  20. data/lib/ruby/module.rb +60 -0
  21. data/lib/ruby/object.rb +56 -0
  22. data/lib/ruby/string.rb +89 -0
  23. data/lib/ruby/to_d.rb +82 -0
  24. data/lib/ruby/to_date.rb +26 -0
  25. data/lib/ruby/to_time.rb +21 -0
  26. data/lib/ruby/try.rb +46 -0
  27. data/lib/stupidedi/builder.rb +6 -0
  28. data/lib/stupidedi/color.rb +94 -0
  29. data/lib/stupidedi/config/code_list_config.rb +45 -0
  30. data/lib/stupidedi/config/functional_group_config.rb +72 -0
  31. data/lib/stupidedi/config/interchange_config.rb +86 -0
  32. data/lib/stupidedi/config/transaction_set_config.rb +73 -0
  33. data/lib/stupidedi/config.rb +100 -0
  34. data/lib/stupidedi/either.rb +286 -0
  35. data/lib/stupidedi/exceptions/invalid_element_error.rb +7 -0
  36. data/lib/stupidedi/exceptions/invalid_schema_error.rb +7 -0
  37. data/lib/stupidedi/exceptions/missing_grammar_error.rb +50 -0
  38. data/lib/stupidedi/exceptions/output_error.rb +7 -0
  39. data/lib/stupidedi/exceptions/parse_error.rb +7 -0
  40. data/lib/stupidedi/exceptions/stupidedi_error.rb +7 -0
  41. data/lib/stupidedi/exceptions/tokenize_error.rb +7 -0
  42. data/lib/stupidedi/exceptions/zipper_error.rb +7 -0
  43. data/lib/stupidedi/exceptions.rb +13 -0
  44. data/lib/stupidedi/inspect.rb +26 -0
  45. data/lib/stupidedi/interchanges/element_types/separator_val.rb +79 -0
  46. data/lib/stupidedi/interchanges/element_types/special_val.rb +48 -0
  47. data/lib/stupidedi/interchanges/element_types.rb +11 -0
  48. data/lib/stupidedi/interchanges.rb +21 -0
  49. data/lib/stupidedi/parser/builder_dsl.rb +292 -0
  50. data/lib/stupidedi/parser/constraint_table.rb +566 -0
  51. data/lib/stupidedi/parser/generation.rb +147 -0
  52. data/lib/stupidedi/parser/identifier_stack.rb +248 -0
  53. data/lib/stupidedi/parser/instruction.rb +112 -0
  54. data/lib/stupidedi/parser/instruction_table.rb +222 -0
  55. data/lib/stupidedi/parser/navigation.rb +786 -0
  56. data/lib/stupidedi/parser/state_machine.rb +62 -0
  57. data/lib/stupidedi/parser/states/abstract_state.rb +385 -0
  58. data/lib/stupidedi/parser/states/failure_state.rb +69 -0
  59. data/lib/stupidedi/parser/states/functional_group_state.rb +101 -0
  60. data/lib/stupidedi/parser/states/initial_state.rb +62 -0
  61. data/lib/stupidedi/parser/states/interchange_state.rb +96 -0
  62. data/lib/stupidedi/parser/states/loop_state.rb +74 -0
  63. data/lib/stupidedi/parser/states/table_state.rb +90 -0
  64. data/lib/stupidedi/parser/states/transaction_set_state.rb +120 -0
  65. data/lib/stupidedi/parser/states/transmission_state.rb +59 -0
  66. data/lib/stupidedi/parser/tokenization.rb +195 -0
  67. data/lib/stupidedi/parser.rb +32 -0
  68. data/lib/stupidedi/reader/input/abstract_input.rb +137 -0
  69. data/lib/stupidedi/reader/input/delegated_input.rb +112 -0
  70. data/lib/stupidedi/reader/input/file_input.rb +157 -0
  71. data/lib/stupidedi/reader/input.rb +31 -0
  72. data/lib/stupidedi/reader/position.rb +78 -0
  73. data/lib/stupidedi/reader/result.rb +172 -0
  74. data/lib/stupidedi/reader/segment_dict.rb +176 -0
  75. data/lib/stupidedi/reader/separators.rb +90 -0
  76. data/lib/stupidedi/reader/stream_reader.rb +173 -0
  77. data/lib/stupidedi/reader/token_reader.rb +465 -0
  78. data/lib/stupidedi/reader/tokens/component_element_tok.rb +71 -0
  79. data/lib/stupidedi/reader/tokens/composite_element_tok.rb +85 -0
  80. data/lib/stupidedi/reader/tokens/repeated_element_tok.rb +74 -0
  81. data/lib/stupidedi/reader/tokens/segment_tok.rb +74 -0
  82. data/lib/stupidedi/reader/tokens/simple_element_tok.rb +76 -0
  83. data/lib/stupidedi/reader.rb +121 -0
  84. data/lib/stupidedi/schema/abstract_def.rb +76 -0
  85. data/lib/stupidedi/schema/abstract_element_def.rb +35 -0
  86. data/lib/stupidedi/schema/abstract_element_use.rb +47 -0
  87. data/lib/stupidedi/schema/abstract_use.rb +79 -0
  88. data/lib/stupidedi/schema/code_list.rb +99 -0
  89. data/lib/stupidedi/schema/component_element_use.rb +76 -0
  90. data/lib/stupidedi/schema/composite_element_def.rb +103 -0
  91. data/lib/stupidedi/schema/composite_element_use.rb +78 -0
  92. data/lib/stupidedi/schema/element_req.rb +57 -0
  93. data/lib/stupidedi/schema/functional_group_def.rb +124 -0
  94. data/lib/stupidedi/schema/generation/definition_generator.rb +139 -0
  95. data/lib/stupidedi/schema/generation/element_generator.rb +221 -0
  96. data/lib/stupidedi/schema/generation/flat_file_reader.rb +551 -0
  97. data/lib/stupidedi/schema/generation/functional_group_generator.rb +64 -0
  98. data/lib/stupidedi/schema/generation/interchange_generator.rb +145 -0
  99. data/lib/stupidedi/schema/generation/master_loader_generator.rb +121 -0
  100. data/lib/stupidedi/schema/generation/models.rb +85 -0
  101. data/lib/stupidedi/schema/generation/module_loader_generator.rb +64 -0
  102. data/lib/stupidedi/schema/generation/registration_generator.rb +230 -0
  103. data/lib/stupidedi/schema/generation/runner.rb +161 -0
  104. data/lib/stupidedi/schema/generation/segment_generator.rb +130 -0
  105. data/lib/stupidedi/schema/generation/support.rb +78 -0
  106. data/lib/stupidedi/schema/generation/support_modules_generator.rb +126 -0
  107. data/lib/stupidedi/schema/generation/version_modules.rb +35 -0
  108. data/lib/stupidedi/schema/generation.rb +94 -0
  109. data/lib/stupidedi/schema/interchange_def.rb +103 -0
  110. data/lib/stupidedi/schema/loop_def.rb +156 -0
  111. data/lib/stupidedi/schema/repeat_count.rb +86 -0
  112. data/lib/stupidedi/schema/segment_def.rb +122 -0
  113. data/lib/stupidedi/schema/segment_req.rb +46 -0
  114. data/lib/stupidedi/schema/segment_use.rb +99 -0
  115. data/lib/stupidedi/schema/simple_element_def.rb +51 -0
  116. data/lib/stupidedi/schema/simple_element_use.rb +83 -0
  117. data/lib/stupidedi/schema/syntax_note.rb +52 -0
  118. data/lib/stupidedi/schema/table_def.rb +178 -0
  119. data/lib/stupidedi/schema/transaction_set_def.rb +125 -0
  120. data/lib/stupidedi/schema.rb +30 -0
  121. data/lib/stupidedi/sets.rb +42 -0
  122. data/lib/stupidedi/transaction_sets/builder/dsl.rb +192 -0
  123. data/lib/stupidedi/transaction_sets/builder.rb +188 -0
  124. data/lib/stupidedi/transaction_sets/common/implementations/element_reqs.rb +37 -0
  125. data/lib/stupidedi/transaction_sets/common/implementations/segment_reqs.rb +31 -0
  126. data/lib/stupidedi/transaction_sets/common/implementations.rb +11 -0
  127. data/lib/stupidedi/transaction_sets/common.rb +8 -0
  128. data/lib/stupidedi/transaction_sets/validation/ambiguity.rb +395 -0
  129. data/lib/stupidedi/transaction_sets/validation/implementation.rb +12 -0
  130. data/lib/stupidedi/transaction_sets/validation.rb +9 -0
  131. data/lib/stupidedi/transaction_sets.rb +25 -0
  132. data/lib/stupidedi/values/abstract_element_val.rb +19 -0
  133. data/lib/stupidedi/values/abstract_val.rb +133 -0
  134. data/lib/stupidedi/values/composite_element_val.rb +102 -0
  135. data/lib/stupidedi/values/functional_group_val.rb +105 -0
  136. data/lib/stupidedi/values/interchange_val.rb +102 -0
  137. data/lib/stupidedi/values/invalid_envelope_val.rb +61 -0
  138. data/lib/stupidedi/values/invalid_segment_val.rb +89 -0
  139. data/lib/stupidedi/values/loop_val.rb +73 -0
  140. data/lib/stupidedi/values/repeated_element_val.rb +113 -0
  141. data/lib/stupidedi/values/segment_val.rb +105 -0
  142. data/lib/stupidedi/values/segment_val_group.rb +20 -0
  143. data/lib/stupidedi/values/simple_element_val.rb +80 -0
  144. data/lib/stupidedi/values/table_val.rb +69 -0
  145. data/lib/stupidedi/values/transaction_set_val.rb +69 -0
  146. data/lib/stupidedi/values/transmission_val.rb +56 -0
  147. data/lib/stupidedi/values.rb +22 -0
  148. data/lib/stupidedi/version.rb +4 -0
  149. data/lib/stupidedi/versions/common/element_reqs.rb +13 -0
  150. data/lib/stupidedi/versions/common/element_types/an.rb +386 -0
  151. data/lib/stupidedi/versions/common/element_types/dt.rb +572 -0
  152. data/lib/stupidedi/versions/common/element_types/id.rb +304 -0
  153. data/lib/stupidedi/versions/common/element_types/nn.rb +312 -0
  154. data/lib/stupidedi/versions/common/element_types/operators.rb +128 -0
  155. data/lib/stupidedi/versions/common/element_types/r.rb +342 -0
  156. data/lib/stupidedi/versions/common/element_types/simple_element.rb +73 -0
  157. data/lib/stupidedi/versions/common/element_types/tm.rb +347 -0
  158. data/lib/stupidedi/versions/common/element_types.rb +29 -0
  159. data/lib/stupidedi/versions/common/segment_reqs.rb +15 -0
  160. data/lib/stupidedi/versions/common/syntax_notes.rb +172 -0
  161. data/lib/stupidedi/versions/common.rb +11 -0
  162. data/lib/stupidedi/versions.rb +25 -0
  163. data/lib/stupidedi/writer/claredi.rb +178 -0
  164. data/lib/stupidedi/writer/default.rb +119 -0
  165. data/lib/stupidedi/writer.rb +7 -0
  166. data/lib/stupidedi/zipper/abstract_cursor.rb +351 -0
  167. data/lib/stupidedi/zipper/dangling_cursor.rb +103 -0
  168. data/lib/stupidedi/zipper/edited_cursor.rb +157 -0
  169. data/lib/stupidedi/zipper/memoized_cursor.rb +133 -0
  170. data/lib/stupidedi/zipper/path.rb +132 -0
  171. data/lib/stupidedi/zipper/root_cursor.rb +120 -0
  172. data/lib/stupidedi/zipper/stack_cursor.rb +107 -0
  173. data/lib/stupidedi/zipper.rb +45 -0
  174. data/lib/stupidedi.rb +69 -0
  175. data/lib/tediparse.rb +1 -0
  176. metadata +249 -0
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Schema
6
+ #
7
+ # @see X222 B.1.1.3.3 Composite Data Structure
8
+ #
9
+ class CompositeElementDef < AbstractElementDef
10
+ # @return [Array<ComponentElementUse>]
11
+ attr_reader :component_uses
12
+
13
+ # @return [Array<SyntaxNote>]
14
+ attr_reader :syntax_notes
15
+
16
+ # @return [CompositeElementUse]
17
+ attr_reader :parent
18
+
19
+ def initialize(id, name, description, component_uses, syntax_notes, parent)
20
+ @id, @name, @description, @component_uses, @syntax_notes, @parent =
21
+ id, name, description, component_uses, syntax_notes, parent
22
+
23
+ # Delay re-parenting until the entire definition tree has a root
24
+ # to prevent unnecessarily copying objects
25
+ unless parent.nil?
26
+ @component_uses = @component_uses.map{|x| x.copy(:parent => self) }
27
+ end
28
+ end
29
+
30
+ # @return [CompositeElementDef]
31
+ def copy(changes = {})
32
+ CompositeElementDef.new \
33
+ changes.fetch(:id, @id),
34
+ changes.fetch(:name, @name),
35
+ changes.fetch(:description, @description),
36
+ changes.fetch(:component_uses, @component_uses),
37
+ changes.fetch(:syntax_notes, @syntax_notes),
38
+ changes.fetch(:parent, @parent)
39
+ end
40
+
41
+ # @return false
42
+ def simple?
43
+ false
44
+ end
45
+
46
+ # @return true
47
+ def composite?
48
+ true
49
+ end
50
+
51
+ # @return [CompositeElementUse]
52
+ def simple_use(requirement, repeat_count, parent = nil)
53
+ CompositeElementUse.new(self, requirement, repeat_count, parent)
54
+ end
55
+
56
+ # @return [CompositeElementVal]
57
+ def value(component_vals, usage, position)
58
+ Values::CompositeElementVal.new(component_vals, usage)
59
+ end
60
+
61
+ # @return [CompositeElementVal]
62
+ def empty(usage, position)
63
+ Values::CompositeElementVal.new([], usage)
64
+ end
65
+
66
+ # @return [AbstractSet<CodeList>]
67
+ def code_lists
68
+ @component_uses.map(&:code_lists).inject(&:|)
69
+ end
70
+
71
+ # @return [void]
72
+ def pretty_print(q)
73
+ q.text("CompositeElementDef[#{@id}]")
74
+ q.group(2, "(", ")") do
75
+ q.breakable ""
76
+ @component_uses.each do |e|
77
+ unless q.current_group.first?
78
+ q.text ","
79
+ q.breakable
80
+ end
81
+ q.pp e
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ class << CompositeElementDef
88
+ # @group Constructors
89
+ #########################################################################
90
+
91
+ # @return [CompositeElementDef]
92
+ def build(id, name, description, *args)
93
+ component_uses = args.take_while{|x| x.is_a?(AbstractElementUse) }
94
+ syntax_notes = args.drop(component_uses.length)
95
+
96
+ new(id, name, description, component_uses, syntax_notes, nil)
97
+ end
98
+
99
+ # @endgroup
100
+ #########################################################################
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Schema
6
+ class CompositeElementUse < AbstractElementUse
7
+ # @return [CompositeElementDef]
8
+ attr_reader :definition
9
+
10
+ def_delegators :definition, :id, :name, :description
11
+
12
+ # @return [ElementReq]
13
+ attr_reader :requirement
14
+
15
+ # @return [RepeatCount]
16
+ attr_reader :repeat_count
17
+
18
+ # @return [SegmentDef]
19
+ attr_reader :parent
20
+
21
+ def initialize(definition, requirement, repeat_count, parent)
22
+ @definition, @requirement, @repeat_count, @parent =
23
+ definition, requirement, repeat_count, parent
24
+
25
+ # Delay re-parenting until the entire definition tree has a root
26
+ # to prevent unnecessarily copying objects
27
+ unless parent.nil?
28
+ @definition = @definition.copy(:parent => self)
29
+ end
30
+ end
31
+
32
+ # @return [CompositeElementUse]
33
+ def copy(changes = {})
34
+ CompositeElementUse.new \
35
+ changes.fetch(:definition, @definition),
36
+ changes.fetch(:requirement, @requirement),
37
+ changes.fetch(:repeat_count, @repeat_count),
38
+ changes.fetch(:parent, @parent)
39
+ end
40
+
41
+ def descriptor
42
+ n = parent.element_uses.index{|u| u.eql?(self) } + 1
43
+ "element #{parent.id}#{"%02d" % n} #{definition.name}".strip
44
+ end
45
+
46
+ def repeatable?
47
+ @repeat_count.try{|r| r.include?(2) }
48
+ end
49
+
50
+ # @return false
51
+ def simple?
52
+ false
53
+ end
54
+
55
+ # @return false
56
+ def component?
57
+ false
58
+ end
59
+
60
+ # @return [void]
61
+ def pretty_print(q)
62
+ q.text("CompositeElementUse")
63
+ q.group(2, "(", ")") do
64
+ q.breakable ""
65
+ q.pp @definition
66
+ q.text ","
67
+
68
+ q.breakable
69
+ q.pp @requirement
70
+ q.text ","
71
+
72
+ q.breakable
73
+ q.pp @repeat_count
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Schema
6
+ #
7
+ # The 5010 X12 "condition designator"s include
8
+ # M - Mandatory
9
+ # O - Optional
10
+ # X - Relational
11
+ #
12
+ # Where "Relational" indicates this element is subject to relational
13
+ # conditions between two or more elements (encoded as a SyntaxNote)
14
+ #
15
+ # @see X222 B.1.1.3.9 Condition Designator
16
+ #
17
+ # The "Implementation Usage" from the 5010 HIPAA implementation guides
18
+ # have three descriptors:
19
+ # REQUIRED
20
+ # NOT USED
21
+ # SITUATIONAL
22
+ #
23
+ # @see X222.pdf 2.2.1 Industry Usage
24
+ #
25
+ class ElementReq
26
+ def initialize(required, forbidden, to_s)
27
+ @required, @forbidden, @to_s =
28
+ required, forbidden, to_s
29
+ end
30
+
31
+ # True if the element's presence is unconditionally required
32
+ def required?
33
+ @required
34
+ end
35
+
36
+ # True if the element's presence is unconditionally forbidden
37
+ def forbidden?
38
+ @forbidden
39
+ end
40
+
41
+ # True if the element's presence is required conditionally
42
+ def optional?
43
+ not (@required or @forbidden)
44
+ end
45
+
46
+ # @return [void]
47
+ def pretty_print(q)
48
+ q.text @to_s
49
+ end
50
+
51
+ # @return [String]
52
+ def inspect
53
+ @to_s
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+ module Stupidedi
3
+ using Refinements
4
+
5
+ module Schema
6
+ #
7
+ # X12 standards are released three times yearly. The version codes sent in
8
+ # the `GS08` and `ST03` elements encode the version (eg 004, 005), the
9
+ # release (eg 0040, 0050), the subrelease (00501, 00602) and the level
10
+ # (eg 005010)
11
+ #
12
+ # The release schedules look like this:
13
+ #
14
+ # Approved by Available Ballot Results Available for
15
+ # Version Subcommitees for Ballot Approved Distribution
16
+ # ---------------------------------------------------------------------
17
+ # 005010 Feb 03 Jun 03 Oct 03 Jan 04
18
+ # 005011 Jun 03 Oct 04 Feb 04 Mar 04
19
+ # 005012 Oct 03 Feb 04 Jun 04 Jul 04
20
+ #
21
+ # 005020 Feb 04 Jun 04 Oct 04 Jan 05
22
+ # 005021 Jun 04 Oct 04 Feb 05 Mar 05
23
+ # 005022 Oct 04 Feb 05 Jun 05 Jul 05
24
+ #
25
+ # @see http://www.disa.org/x12org/subcommittees/dev/pdf/X12RELEASESCHED.pdf
26
+ #
27
+ # @see X222.pdf B.1.1.3.13 Functional Group
28
+ # @see X222.pdf B.1.1.4.2 Functional Groups
29
+ #
30
+ class FunctionalGroupDef < AbstractDef
31
+ include Inspect
32
+
33
+ # @return [String]
34
+ attr_reader :id
35
+
36
+ # @return [Array<SegmentUses>]
37
+ attr_reader :header_segment_uses
38
+
39
+ # @return [Array<SegmentUses>]
40
+ attr_reader :trailer_segment_uses
41
+
42
+ # @return [Hash, Module]
43
+ abstract :segment_dict
44
+
45
+ def initialize(id, header_segment_uses, trailer_segment_uses)
46
+ @id, @header_segment_uses, @trailer_segment_uses =
47
+ id, header_segment_uses, trailer_segment_uses
48
+
49
+ @header_segment_uses = header_segment_uses.map{|x| x.copy(:parent => self) }
50
+ @trailer_segment_uses = trailer_segment_uses.map{|x| x.copy(:parent => self) }
51
+ end
52
+
53
+ # @return [FunctionalGroupDef]
54
+ def copy(changes = {})
55
+ FunctionalGroupDef.new \
56
+ changes.fetch(:id, @id),
57
+ changes.fetch(:header_segment_uses, @header_segment_uses),
58
+ changes.fetch(:trailer_segment_uses, @trailer_segment_uses)
59
+ end
60
+
61
+ # @return [String]
62
+ def descriptor
63
+ "transaction set #{id}"
64
+ end
65
+
66
+ def required?
67
+ false
68
+ end
69
+
70
+ # @return [SegmentUse]
71
+ def entry_segment_use
72
+ @header_segment_uses.head
73
+ end
74
+
75
+ # @return [Array<SegmentUse>]
76
+ def children
77
+ @header_segment_uses + @trailer_segment_uses
78
+ end
79
+
80
+ # @return [Values::FunctionalGroupVal]
81
+ def empty
82
+ Values::FunctionalGroupVal.new(self, [], [])
83
+ end
84
+
85
+ def functional_group?
86
+ true
87
+ end
88
+
89
+ # @return [AbstractSet<CodeList>
90
+ def code_lists
91
+ children.map(&:code_lists).inject(&:|)
92
+ end
93
+
94
+ # @return [void]
95
+ def pretty_print(q)
96
+ q.text("FunctionalGroupDef")
97
+ q.group(2, "(", ")") do
98
+ q.breakable ""
99
+ @header_segment_uses.each do |e|
100
+ unless q.current_group.first?
101
+ q.text ","
102
+ q.breakable
103
+ end
104
+ q.pp e
105
+ end
106
+
107
+ unless q.current_group.first?
108
+ q.text ","
109
+ q.breakable
110
+ end
111
+ q.text "... (TRANSACTION SETS) ..."
112
+
113
+ @trailer_segment_uses.each do |e|
114
+ unless q.current_group.first?
115
+ q.text ","
116
+ q.breakable
117
+ end
118
+ q.pp e
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stupidedi
4
+ module Schema
5
+ module Generation
6
+ # Generates one transaction-set standard file (e.g. SM204.rb). Walks the
7
+ # transaction set's table definitions and recursively emits loops and
8
+ # segment uses. This is the heart of the grammar.
9
+ class DefinitionGenerator
10
+ include Support
11
+
12
+ AREA_METHODS = {
13
+ "heading" => "header",
14
+ "detail" => "detail",
15
+ "summary" => "summary"
16
+ }.freeze
17
+
18
+ def initialize(transaction_set, namespace: "Edi")
19
+ @transaction_set = transaction_set
20
+ @namespace = namespace
21
+ end
22
+
23
+ def generate
24
+ validate_release!
25
+ build_ruby_content
26
+ end
27
+
28
+ def constant_name
29
+ func_group = transaction_set.func_group
30
+ code = transaction_set.code
31
+ # If func_group is nil/empty, prefix with 'TS' to create a valid Ruby constant
32
+ func_group = "TS" if func_group.nil? || func_group.empty?
33
+ "#{func_group}#{code}"
34
+ end
35
+
36
+ def output_path
37
+ "#{namespace_path}/#{underscore(version_module)}/standards/#{constant_name}.rb"
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :transaction_set
43
+
44
+ def release_code
45
+ transaction_set.release.code
46
+ end
47
+
48
+ def build_ruby_content
49
+ lines = []
50
+ lines << 'require "tediparse"'
51
+ lines << "if !defined?(#{namespace}::#{version_module}::Standards::#{constant_name})"
52
+ lines << " module #{namespace}"
53
+ lines << " module #{version_module}"
54
+ lines << " module Standards"
55
+ lines << " b = Stupidedi::TransactionSets::Builder"
56
+ lines << " d = Stupidedi::Schema"
57
+ lines << " r = #{namespace}::#{version_module}::SegmentReqs"
58
+ lines << " s = #{namespace}::#{version_module}::SegmentDefs"
59
+ lines << build_transaction_set_definition
60
+ lines << " end"
61
+ lines << " end"
62
+ lines << " end"
63
+ lines << "end"
64
+ lines.join("\n") + "\n"
65
+ end
66
+
67
+ def build_transaction_set_definition
68
+ tables = transaction_set.table_definitions.to_a
69
+ table_defs = tables.each_with_index.map do |table, index|
70
+ generate_table_def(table, is_last: index == tables.length - 1)
71
+ end
72
+
73
+ header = %[ #{constant_name} = b.build("#{transaction_set.func_group}", "#{transaction_set.code}", "#{transaction_set.name}",]
74
+ footer = " )"
75
+
76
+ [header, *table_defs, footer].join("\n")
77
+ end
78
+
79
+ def generate_table_def(table, is_last:)
80
+ area_method = AREA_METHODS.fetch(table.area)
81
+ children = table.ordered_children
82
+
83
+ child_lines = children.each_with_index.map do |child, index|
84
+ child_is_last = index == children.length - 1
85
+ generate_child(child, indent_level: 6, is_last: child_is_last)
86
+ end
87
+
88
+ trailing_comma = is_last ? "" : ","
89
+ header = %[#{indent(5)}d::TableDef.#{area_method}("#{table.name}",]
90
+ footer = "#{indent(5)})#{trailing_comma}"
91
+
92
+ [header, *child_lines, footer].join("\n")
93
+ end
94
+
95
+ def generate_child(child, indent_level:, is_last:)
96
+ if child.segment_use?
97
+ generate_segment_use(child, indent_level: indent_level, is_last: is_last)
98
+ else
99
+ generate_loop_def(child, indent_level: indent_level, is_last: is_last)
100
+ end
101
+ end
102
+
103
+ def generate_loop_def(loop, indent_level:, is_last:)
104
+ children = loop.ordered_children
105
+ repeat_count = format_repeat_count(loop.max_reps)
106
+
107
+ child_lines = children.each_with_index.map do |child, index|
108
+ child_is_last = index == children.length - 1
109
+ generate_child(child, indent_level: indent_level + 1, is_last: child_is_last)
110
+ end
111
+
112
+ trailing_comma = is_last ? "" : ","
113
+ header = %[#{indent(indent_level)}d::LoopDef.build("#{loop.identifier}", #{repeat_count},]
114
+ footer = "#{indent(indent_level)})#{trailing_comma}"
115
+
116
+ [header, *child_lines, footer].join("\n")
117
+ end
118
+
119
+ def generate_segment_use(segment_use, indent_level:, is_last:)
120
+ segment_code = segment_use.segment.code
121
+ position = (segment_use.x12_sequence || segment_use.position).to_i
122
+ requirement = format_requirement(segment_use.requirement)
123
+ repeat_count = format_repeat_count(segment_use.max_reps)
124
+
125
+ trailing_comma = is_last ? "" : ","
126
+ "#{indent(indent_level)}s::#{segment_code}.use(#{position}, #{requirement}, #{repeat_count})#{trailing_comma}"
127
+ end
128
+
129
+ def format_repeat_count(max_reps)
130
+ if max_reps.nil?
131
+ "d::RepeatCount.unbounded"
132
+ else
133
+ "d::RepeatCount.bounded(#{max_reps})"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end