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,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stupidedi
4
+ module Schema
5
+ module Generation
6
+ # Generates element_defs.rb for a release: every simple and composite
7
+ # element definition. ID elements with code lists emit a CodeList; control
8
+ # /separator elements (e.g. I15, I65) that carry no x12_type are emitted
9
+ # with the Separator type so #separator? is true.
10
+ class ElementGenerator
11
+ include Support
12
+
13
+ TYPE_MAPPING = {
14
+ "AN" => :AN,
15
+ "ID" => :ID,
16
+ "DT" => :DT,
17
+ "TM" => :TM,
18
+ "R" => :R,
19
+ "N0" => :Nn,
20
+ "N1" => :Nn,
21
+ "N2" => :Nn,
22
+ "N4" => :Nn,
23
+ "N6" => :Nn,
24
+ "N" => :Nn,
25
+ "B" => :AN # Binary as AN
26
+ }.freeze
27
+
28
+ def initialize(release, namespace: "Edi")
29
+ @release = release
30
+ @namespace = namespace
31
+ end
32
+
33
+ def generate
34
+ validate_release!
35
+ build_ruby_content
36
+ end
37
+
38
+ def output_path
39
+ "#{namespace_path}/#{underscore(version_module)}/element_defs.rb"
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :release
45
+
46
+ def release_code
47
+ release.code
48
+ end
49
+
50
+ def build_ruby_content
51
+ lines = []
52
+ lines << 'require "tediparse"'
53
+ lines << "module #{namespace}"
54
+ lines << " module #{version_module}"
55
+ lines << " module ElementDefs"
56
+ lines << " t = #{namespace}::#{version_module}::ElementTypes"
57
+ lines << " r = #{namespace}::#{version_module}::ElementReqs"
58
+ lines << " s = Stupidedi::Schema"
59
+ lines << ""
60
+ lines << build_simple_element_definitions
61
+ lines << ""
62
+ lines << "#{indent(3)}# Composite Elements"
63
+ lines << build_composite_element_definitions
64
+ lines << " end"
65
+ lines << " end"
66
+ lines << "end"
67
+ lines.join("\n") + "\n"
68
+ end
69
+
70
+ def build_simple_element_definitions
71
+ elements = release.elements.reject(&:is_composite).reject { |e| blank_type?(e) }
72
+
73
+ sorted_elements = sort_elements(elements)
74
+ simple_defs = sorted_elements.map { |element| generate_element_def(element) }
75
+
76
+ # Add special control/separator elements (e.g. I15, I65, I69) that have
77
+ # no x12_type but are referenced by segments in this release.
78
+ control_defs = control_elements.map { |element| generate_control_element_def(element) }
79
+
80
+ (simple_defs + control_defs).join("\n")
81
+ end
82
+
83
+ def build_composite_element_definitions
84
+ composites = release.elements.select(&:is_composite)
85
+
86
+ sorted_composites = sort_elements(composites)
87
+ sorted_composites.map { |composite| generate_composite_def(composite) }.join("\n")
88
+ end
89
+
90
+ # Simple elements with no x12_type that are referenced by some segment's
91
+ # element use (the ISA control/separator elements).
92
+ def control_elements
93
+ referenced_codes = release.segments
94
+ .flat_map(&:element_uses)
95
+ .map(&:element)
96
+ .select { |el| !el.is_composite && blank_type?(el) }
97
+ .map(&:code)
98
+ .uniq
99
+
100
+ release.elements.select do |el|
101
+ !el.is_composite && blank_type?(el) && referenced_codes.include?(el.code)
102
+ end
103
+ end
104
+
105
+ def blank_type?(element)
106
+ element.x12_type.nil? || element.x12_type.empty?
107
+ end
108
+
109
+ def sort_elements(elements)
110
+ # Sort numeric codes numerically, then alphanumeric codes alphabetically
111
+ elements.sort_by do |element|
112
+ code = element.code
113
+ if code.match?(/^\d+$/)
114
+ [0, code.to_i, ""]
115
+ else
116
+ [1, 0, code]
117
+ end
118
+ end
119
+ end
120
+
121
+ def generate_element_def(element)
122
+ stupidedi_type = stupidedi_type_for(element)
123
+
124
+ if element.x12_type == "ID" && element.element_codes.any?
125
+ generate_id_element(element, stupidedi_type)
126
+ elsif stupidedi_type == :Nn
127
+ generate_numeric_element(element, stupidedi_type)
128
+ else
129
+ generate_simple_element(element, stupidedi_type)
130
+ end
131
+ end
132
+
133
+ # The X12 Nn type carries 0-9 implied decimals, so any "N<digits>" is a
134
+ # valid numeric type even when not enumerated in TYPE_MAPPING (e.g. N3,
135
+ # N5, N7, N8, N9). The engine's Nn accepts arbitrary precision, so map
136
+ # them all to :Nn rather than raising.
137
+ def stupidedi_type_for(element)
138
+ type = element.x12_type
139
+ return TYPE_MAPPING[type] if TYPE_MAPPING.key?(type)
140
+ return :Nn if type.to_s =~ /\AN\d+\z/
141
+
142
+ raise ArgumentError, "Unknown x12_type: #{type} for element #{element.code}"
143
+ end
144
+
145
+ def generate_composite_def(composite)
146
+ component_uses = composite.component_uses.sort_by(&:position)
147
+
148
+ lines = []
149
+ lines << "#{indent(3)}#{composite.code} ||= s::CompositeElementDef.build(:#{composite.code},"
150
+ lines << "#{indent(4)}#{escape_string(composite.name)},"
151
+ lines << "#{indent(4)}#{escape_string(composite.description || '')},"
152
+
153
+ component_uses.each_with_index do |component_use, index|
154
+ is_last = index == component_uses.length - 1
155
+ lines << generate_component_use(component_use, is_last: is_last)
156
+ end
157
+
158
+ lines << "#{indent(3)})"
159
+ lines.join("\n")
160
+ end
161
+
162
+ def generate_component_use(component_use, is_last:)
163
+ element = component_use.element
164
+ requirement = format_requirement(component_use.requirement)
165
+ trailing_comma = is_last ? "" : ","
166
+
167
+ "#{indent(4)}E#{element.code}.component_use(#{requirement})#{trailing_comma}"
168
+ end
169
+
170
+ def generate_simple_element(element, stupidedi_type)
171
+ max_length = element.max_length || element.min_length
172
+ "#{indent(3)}E#{element.code} ||= t::#{stupidedi_type}.new(:E#{element.code}, #{escape_string(element.name)}, #{element.min_length}, #{max_length})"
173
+ end
174
+
175
+ # Separator elements (I15, I65) have no x12_type - use Tediparse's
176
+ # Separator type so that #separator? returns true and the writer excludes
177
+ # them from data character checks.
178
+ def generate_control_element_def(element)
179
+ max_length = element.max_length || element.min_length
180
+ "#{indent(3)}E#{element.code} ||= Stupidedi::Interchanges::ElementTypes::Separator.new(:E#{element.code}, #{escape_string(element.name)}, #{element.min_length}, #{max_length})"
181
+ end
182
+
183
+ def generate_numeric_element(element, stupidedi_type)
184
+ max_length = element.max_length || element.min_length
185
+ digits = extract_decimal_places(element)
186
+ "#{indent(3)}E#{element.code} ||= t::#{stupidedi_type}.new(:E#{element.code}, #{escape_string(element.name)}, #{element.min_length}, #{max_length}, #{digits})"
187
+ end
188
+
189
+ def generate_id_element(element, stupidedi_type)
190
+ codes = element.element_codes.sort_by { |c| [c.paragraph, c.code] }
191
+
192
+ return generate_simple_element(element, stupidedi_type) if codes.empty?
193
+
194
+ max_length = element.max_length || element.min_length
195
+ lines = []
196
+ lines << "#{indent(3)}E#{element.code} ||= t::#{stupidedi_type}.new(:E#{element.code}, #{escape_string(element.name)}, #{element.min_length}, #{max_length},"
197
+ lines << "#{indent(4)}s::CodeList.build("
198
+
199
+ code_lines = codes.map do |code|
200
+ "#{indent(5)}#{escape_string(code.code)} => #{escape_string(code.name)}"
201
+ end
202
+ lines << code_lines.join(",\n") + "))"
203
+
204
+ lines.join("\n")
205
+ end
206
+
207
+ def extract_decimal_places(element)
208
+ case element.x12_type
209
+ when "N0", "N" then 0
210
+ when "N1" then 1
211
+ when "N2" then 2
212
+ when "N4" then 4
213
+ when "N6" then 6
214
+ else
215
+ element.digits || 0
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end