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
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+ abspath = Pathname.new(File.dirname(__FILE__)).expand_path
4
+ relpath = abspath.relative_path_from(Pathname.pwd)
5
+
6
+ task :default => :spec
7
+
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.verbose = false
11
+ t.rspec_opts = "-w -rspec_helper"
12
+
13
+ if ENV.include?("CI")
14
+ t.rspec_opts += " --format progress"
15
+ else
16
+ t.rspec_opts += " --format documentation"
17
+ end
18
+ end
19
+
20
+ # Note options are loaded from .yardopts
21
+ require "yard"
22
+ YARD::Rake::YardocTask.new(:yard => :clobber_yard)
23
+ task :clobber_yard do
24
+ # Call FileUtils directly rather than Rake's DSL shim: on Ruby 3.x the
25
+ # rake-12.3 FileUtilsExt wrapper forwards its verbose/noop flags as a
26
+ # positional argument, which the keyword-only FileUtils.rm_rf rejects.
27
+ FileUtils.rm_rf "#{relpath}/build/generated/doc"
28
+ FileUtils.mkdir_p "#{relpath}/build/generated/doc/images"
29
+ end
30
+
31
+ task :console do
32
+ exec(*%w(irb -I lib -r stupidedi))
33
+ end
data/bin/tediparse ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "optparse"
5
+
6
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
7
+ require "stupidedi"
8
+
9
+ options = {
10
+ namespace: "Edi",
11
+ out: ".",
12
+ write: true
13
+ }
14
+
15
+ parser = OptionParser.new do |opts|
16
+ opts.banner = <<~BANNER
17
+ tediparse - generate Stupidedi grammar definitions from ASC X12 Table Data.
18
+
19
+ The X12 Table Data is licensed X12 IP that you supply; the generated grammar
20
+ files are a derivative you own. This tool ships no X12 content.
21
+
22
+ Usage:
23
+ tediparse generate --release CODE --table-data DIR [--out DIR] [options]
24
+ tediparse register --out DIR [--namespace NAME]
25
+
26
+ `register` rebuilds the whole-tree artifacts (stupidedi_registration.rb, and
27
+ the master loader if present) by scanning an existing generated tree.
28
+
29
+ Options:
30
+ BANNER
31
+
32
+ opts.on("-r", "--release CODE", "X12 release code (e.g. 005010)") { |v| options[:release] = v }
33
+ opts.on("-t", "--table-data DIR", "Directory of ASC X12 Table Data .TXT files") { |v| options[:table_data] = v }
34
+ opts.on("-o", "--out DIR", "Output directory (default: current dir)") { |v| options[:out] = v }
35
+ opts.on("-n", "--namespace NAME", "Root Ruby module for generated code (default: Edi)") { |v| options[:namespace] = v }
36
+ opts.on("--master-loader", "Also emit a single-require entry file (e.g. edi.rb)") { options[:master_loader] = true }
37
+ opts.on("--dry-run", "Preview only; write nothing") { options[:write] = false }
38
+ opts.on("-h", "--help", "Show this help") do
39
+ puts opts
40
+ exit
41
+ end
42
+ end
43
+
44
+ command = ARGV.shift
45
+ parser.parse!(ARGV)
46
+
47
+ case command
48
+ when "generate"
49
+ missing = %i[release table_data].reject { |k| options[k] }
50
+ unless missing.empty?
51
+ warn "Missing required option(s): #{missing.map { |k| "--#{k.to_s.tr('_', '-')}" }.join(', ')}"
52
+ warn parser.help
53
+ exit 1
54
+ end
55
+
56
+ unless Dir.exist?(options[:table_data])
57
+ warn "Table data directory not found: #{options[:table_data]}"
58
+ exit 1
59
+ end
60
+
61
+ begin
62
+ results = Stupidedi::Schema::Generation.run(
63
+ table_data: options[:table_data],
64
+ release: options[:release],
65
+ out: options[:out],
66
+ namespace: options[:namespace],
67
+ write: options[:write],
68
+ master_loader: options.fetch(:master_loader, false),
69
+ logger: ->(msg) { puts msg }
70
+ )
71
+ rescue ArgumentError => e
72
+ warn "Error: #{e.message}"
73
+ exit 1
74
+ end
75
+
76
+ puts "Done: #{results.size} files (#{options[:write] ? 'written' : 'preview'})."
77
+ when "register"
78
+ unless options[:out] && Dir.exist?(options[:out])
79
+ warn "register requires --out DIR pointing at an existing generated tree"
80
+ exit 1
81
+ end
82
+
83
+ begin
84
+ paths = Stupidedi::Schema::Generation.register(
85
+ out: options[:out],
86
+ namespace: options[:namespace],
87
+ write: options[:write],
88
+ logger: ->(msg) { puts msg }
89
+ )
90
+ rescue ArgumentError => e
91
+ warn "Error: #{e.message}"
92
+ exit 1
93
+ end
94
+
95
+ puts "Done: rebuilt #{paths.size} file(s)#{options[:write] ? '' : ' (preview)'}."
96
+ when nil, "help", "-h", "--help"
97
+ puts parser.help
98
+ else
99
+ warn "Unknown command: #{command}"
100
+ warn parser.help
101
+ exit 1
102
+ end
data/doc/Defining.md ADDED
@@ -0,0 +1,97 @@
1
+ > This page covers transcribing a grammar **by hand**. If you license the ASC
2
+ > X12 Table Data flat files, tediparse can generate the whole definition tree
3
+ > for a release instead — see
4
+ > [Generating-Grammars.md](Generating-Grammars.md). Hand-authoring as below is
5
+ > still the right approach for a small custom grammar or for understanding what
6
+ > the generator emits.
7
+
8
+ Be sure you start with the right documentation, which must be purchased from [ASC
9
+ X12](http://x12.org/). Documentation from a trading partner might be usable in a
10
+ pinch, but is often missing details or has details relevant only to that
11
+ partner. If you are unsure, the cover page for the document should look like
12
+ this:
13
+
14
+ ![](images/transcribing-cover.png)
15
+
16
+ ## Tables
17
+
18
+ The overall structure of the grammar is given in section 2.2 of the
19
+ documentation (be sure it is marked `IMPLEMENTATION` and not `STANDARD`). The
20
+ top-level tables can be written as `TableDef.header`, `TableDef.detail`, and
21
+ `TableDef.summary`.
22
+
23
+ ![](images/transcribing-table.png)
24
+
25
+ > Note for some reason, some grammars show the `SE` segment at the end of a
26
+ > detail table, rather than in its own summary table. The `SE` segment is the
27
+ > end of the document, so when you code up the grammar, you should create a
28
+ > `TableDef.summary` to contain the `SE` segment (see 837P). This is because
29
+ > a detail table can be repeated, while a summary table cannot, and the `SE`
30
+ > segment should only occur once in a document.
31
+
32
+ ## Loops
33
+
34
+ Loops are groups of segments which have a particular starting segment that
35
+ indicates the start of the group, followed by other segments. Loops may be
36
+ nested, and each loop may be repeated a specified number of times. You can
37
+ translate each loop in the documentation to a `LoopDef.build(name, repeat_count,
38
+ *children)` where children are either segments or child loops.
39
+
40
+ In the example above, the `"2000C PATIENT HIERARCHY"` loop has a repeat count
41
+ of `RepeatCount.unbounded`, child segments `HL` and `PAT`, and many child loops
42
+ beginning with `"2000CA PATIENT NAME"`. Since `HL` is the first segment in the
43
+ loop, when the parser reads an `HL` in the right context, it will then create
44
+ the corresponding loop in the parse tree.
45
+
46
+ ## Segments
47
+
48
+ Segments are sort of like a `struct` in the C programming language, where the
49
+ fields are called elements. Unlike structs, each time a segment occurs in the
50
+ grammar, it can be given a different name, different flags to indicate which
51
+ fields are required or optional, different set of allowed values in each field,
52
+ and a different number of allowed repeats.
53
+
54
+ One of the most commonly used segments is `NM1`, which is generally some kind of
55
+ name. In the above example, you an `NM1 Patient Name`, but there are many others
56
+ in the grammar for different people.
57
+
58
+ ![](images/transcribing-segment-detail.png)
59
+
60
+ The important parts of the image above have probably already been transcribed in
61
+ an existing `SegmentDef` (unless you find a segment that hasn't been defined), which
62
+ are the same across all grammars (all the X12 attributes). The "loop", "usage", and
63
+ "repeat count" were described earlier, but are again shown here.
64
+
65
+ Segments can be described using `BuilderDsl.Segment(offset, id, name,
66
+ usage, repeat_count, *elements)`. The first argument comes from the `Pos. #`
67
+ column in the table diagram above. For instance, the `NM1 Patient Name` segment
68
+ has an offset of 150. The usage argument is denoted in a few places: on the
69
+ above diagrams, it is the labeled `Usage`; this indicates if the segment is
70
+ required or optional, or if its presence depends on other conditions.
71
+
72
+ ## Elements
73
+
74
+ Elements are (apart from composite elements), some kind of atomic data type,
75
+ like a string, number, date, etc. Each place a segment is specified in the
76
+ grammar, its element properties are also specified. Beware that each place that
77
+ a segment appears can have different details about its elements, per-occurence.
78
+
79
+ ![](images/transcribing-segment-diagram.png)
80
+
81
+ This diagram indicates some of the properties of each element, in this
82
+ particular instance of the `NM1` segemnt. The bold border around an element
83
+ indicates that it is `Required`, while strike-through text indicates a
84
+ `NotUsed`. If you encounter a segment that hasn't already been defined, some of
85
+ the other properties needed to define the segment are also included on the
86
+ diagram, but we'll ignore those for now.
87
+
88
+ ![](images/transcribing-element-detail.png)
89
+
90
+ The third section of each element detail gives a name to each element,
91
+ usually in the gray section labeled `IMPLEMENTATION NAME`, and also specifies
92
+ its usage, allowed values, and minimum and maximum lengths. The usage indicator
93
+ is in the first column, name and allowed values in the fourth. Minimum and
94
+ maximum length, and number of decimal places are given in the last column, but
95
+ these rarely need to be specified as they are usually the same as the default
96
+ declared in the `ElementDef`.
97
+
@@ -0,0 +1,241 @@
1
+ Generating Grammars from X12 Table Data
2
+ =======================================
3
+
4
+ > **"Generating" means two different things in this project.** This page is
5
+ > about *generating the grammar* — turning the ASC X12 standard into the Ruby
6
+ > definition files the engine walks. [Generating.md](Generating.md) is about
7
+ > *generating X12 documents* — emitting EDI with the writer once you already
8
+ > have a grammar. If you want to produce an 837 or a 204, you want the other
9
+ > page.
10
+
11
+ There are two ways to get a grammar that the engine can use:
12
+
13
+ 1. **Author it by hand** — transcribe a purchased implementation guide into
14
+ `SegmentDef` / `LoopDef` / `TransactionSetDef` values. See
15
+ [Defining.md](Defining.md). This is fine for a handful of segments, but a
16
+ full transaction set is hundreds of definitions.
17
+ 2. **Generate it from ASC X12 Table Data** — feed the official flat-file
18
+ distribution to the generator and it emits the whole definition tree for a
19
+ release. This page covers that path.
20
+
21
+ Both produce the same thing: Ruby source that builds `Stupidedi::Schema`
22
+ objects, registered against a `Stupidedi::Config`. Generation is just
23
+ automation over the hand-authoring you would otherwise do.
24
+
25
+ The IP boundary
26
+ ---------------
27
+
28
+ **The gem ships the generation *machine* only.** The X12 Table Data it reads
29
+ is licensed X12 IP that you supply, and the grammar files it writes are a
30
+ derivative of that IP that belong to you. Neither the input nor the output
31
+ ships with tediparse.
32
+
33
+ The committed fixture under `spec/support/generation/table_data/` is a small
34
+ *synthetic* grammar in the same flat-file format — fabricated structure used
35
+ to test the generator — not real X12 content.
36
+
37
+ Input: the ASC X12 Table Data distribution
38
+ -------------------------------------------
39
+
40
+ To obtain the Table Data you need an **ASC X12 license**; with one in hand you
41
+ can download the distribution from <https://ecommerce.x12.org/downloads>.
42
+
43
+ The Table Data is a set of CSV-shaped `.TXT` files, one header/detail pair per
44
+ kind of definition, plus a free-form file:
45
+
46
+ | File | Holds |
47
+ | -------------- | -------------------------------------------------- |
48
+ | `ELEHEAD` / `ELEDETL` | simple data elements (type, lengths, code lists) |
49
+ | `COMHEAD` / `COMDETL` | composite elements and their component uses |
50
+ | `SEGHEAD` / `SEGDETL` | segments and their ordered element uses |
51
+ | `SETHEAD` / `SETDETL` | transaction sets and their table/loop/segment structure |
52
+ | `FREEFORM` | longer prose (segment/element notes) |
53
+
54
+ Point `--table-data` at the directory holding these files for one release.
55
+
56
+ The CLI
57
+ -------
58
+
59
+ The gem ships one executable, `bin/tediparse`:
60
+
61
+ ```sh
62
+ # Generate the full grammar tree for a release into ./lib
63
+ tediparse generate --release 005010 \
64
+ --table-data vendor/x12/table_data/005010 \
65
+ --out lib
66
+
67
+ # Preview without writing anything
68
+ tediparse generate --release 005010 \
69
+ --table-data vendor/x12/table_data/005010 \
70
+ --out lib --dry-run
71
+
72
+ # Emit into a custom root module (default is Edi)
73
+ tediparse generate --release 005010 \
74
+ --table-data vendor/x12/table_data/005010 \
75
+ --out lib --namespace Acme
76
+
77
+ # Also emit a single-require entry file (for non-autoloader setups)
78
+ tediparse generate --release 005010 \
79
+ --table-data vendor/x12/table_data/005010 \
80
+ --out lib --master-loader
81
+
82
+ # Rebuild the whole-tree aggregation files for an existing tree
83
+ tediparse register --out lib
84
+ ```
85
+
86
+ Supported releases: `003060`, `004010`, `004060`, `005010`, `006010`,
87
+ `007010`, `008010`. An unsupported release code is rejected before anything
88
+ is read.
89
+
90
+ From Ruby
91
+ ---------
92
+
93
+ The CLI is a thin wrapper over a facade you can call directly — useful from a
94
+ Rake task:
95
+
96
+ ```ruby
97
+ Stupidedi::Schema::Generation.run(
98
+ table_data: "vendor/x12/table_data/005010",
99
+ release: "005010",
100
+ out: "lib",
101
+ namespace: "Edi", # root module for the emitted code (default: "Edi")
102
+ master_loader: false, # also emit a single-require entry file?
103
+ write: true, # false = dry run (returns results, writes nothing)
104
+ logger: ->(msg) { puts msg }
105
+ )
106
+
107
+ # Rebuild only the whole-tree artifacts (registration + master loader if present)
108
+ Stupidedi::Schema::Generation.register(out: "lib")
109
+ ```
110
+
111
+ `run` returns one `Result` per file (`path`, `relative_path`, `content`,
112
+ `written`), so a dry run gives you the full generated source to diff or
113
+ inspect without touching disk. The whole release is staged in a tempdir first
114
+ and copied into `out` in one step only on success, so a failure partway
115
+ through never leaves a half-written tree.
116
+
117
+ The output tree
118
+ ---------------
119
+
120
+ For `--release 005010 --namespace Edi --out lib`, the generator writes
121
+ (`005010` → the `FiftyTen` version module → the `fifty_ten` path):
122
+
123
+ ```
124
+ lib/
125
+ edi/
126
+ fifty_ten/
127
+ element_reqs.rb # aliases Mandatory/Optional/Relational
128
+ element_types.rb # aliases the element type primitives
129
+ segment_reqs.rb # aliases the segment requirement enums
130
+ syntax_notes.rb # aliases the P/R/C/E/L syntax-note builders
131
+ element_defs.rb # every simple + composite element for the release
132
+ segment_defs.rb # every segment, incl. ISA/IEA/GS/GE/TA1
133
+ functional_group_def.rb # the GS/GE envelope
134
+ standards/
135
+ <transaction_set>.rb # one file per transaction set
136
+ fifty_ten.rb # version loader: requires this version's files
137
+ interchanges/
138
+ five_oh_one.rb # the ISA/IEA envelope (named by ISA version code)
139
+ stupidedi_registration.rb # wires every release in the tree onto a Config
140
+ edi.rb # master loader — only with --master-loader
141
+ ```
142
+
143
+ - The four small `*_reqs` / `*_types` / `syntax_notes` modules just alias
144
+ common `Stupidedi` types into the version namespace so the bulky files can
145
+ use short names.
146
+ - `element_defs.rb` and `segment_defs.rb` are the bulk of the grammar.
147
+ - `standards/*.rb` are the transaction-set definitions — these are what you
148
+ register per `(version, functional id, transaction code)`.
149
+ - `interchanges/` is **shared across releases** in the tree and named by the
150
+ ISA12 version code (`00501` → `five_oh_one`), not by the release module.
151
+ - `stupidedi_registration.rb` is the entry point that builds a populated
152
+ `Config` for everything in the tree (see "Using the grammar" below).
153
+
154
+ Loading the generated code
155
+ ---------------------------
156
+
157
+ By default the tree is meant to be picked up by your application's autoloader
158
+ (Rails / Zeitwerk): the file/constant layout follows the `edi/fifty_ten/...`
159
+ convention, so `Edi::FiftyTen::SegmentDefs` resolves on demand.
160
+
161
+ If you are **not** using an autoloader, pass `--master-loader` (or
162
+ `master_loader: true`). That emits a single `edi.rb` (named after the
163
+ namespace) which `require`s the whole tree in dependency order, so a plain
164
+ `require "edi"` loads everything.
165
+
166
+ Multiple releases in one tree
167
+ -----------------------------
168
+
169
+ You can keep several releases side by side under one `out`. Generating an
170
+ additional release **preserves** the ones already present:
171
+ `stupidedi_registration.rb` (and the master loader, if any) are whole-tree
172
+ artifacts — every run rebuilds them to cover every release found in `out`, not
173
+ just the one you just generated. The regenerated release's own directory is
174
+ replaced wholesale (so a shrunk transaction-set list doesn't leave orphans),
175
+ while other releases and the shared `interchanges/` directory are left alone.
176
+
177
+ If you add or remove a release by other means and need to refresh just the
178
+ aggregation files, run `tediparse register --out lib` (or
179
+ `Stupidedi::Schema::Generation.register(out: "lib")`).
180
+
181
+ Using the generated grammar
182
+ ----------------------------
183
+
184
+ The generated `stupidedi_registration.rb` does the wiring for you: it returns
185
+ a `Stupidedi::Config` with every release's interchange, functional group, and
186
+ transaction sets registered. Load the tree, get the config, and hand it to the
187
+ parser or writer exactly as you would a hand-authored one:
188
+
189
+ ```ruby
190
+ require "edi" # or rely on your autoloader
191
+
192
+ config = Edi.config # populated Stupidedi::Config from stupidedi_registration.rb
193
+
194
+ parser = Stupidedi::Parser.build(config)
195
+ # ... parse, navigate (see Navigating.md)
196
+
197
+ b = Stupidedi::Parser::BuilderDsl.build(config)
198
+ # ... generate documents (see Generating.md)
199
+ ```
200
+
201
+ From here the generated grammar is indistinguishable from one you wrote by
202
+ hand. Continue with [Generating.md](Generating.md) to emit documents and
203
+ [Navigating.md](Navigating.md) to read and traverse them.
204
+
205
+ How it works
206
+ ------------
207
+
208
+ Generation is two layers with a plain-struct seam between them
209
+ (`lib/stupidedi/schema/generation/`):
210
+
211
+ - **Layer A — `flat_file_reader.rb`** parses the `.TXT` distribution into the
212
+ plain value objects in `models.rb` (`Release`, `Element`, `Segment`,
213
+ `TransactionSet`, `LoopDefinition`, …). It carries no database or Ruby-source
214
+ concerns — any source that builds objects with the same shape can drive the
215
+ next layer.
216
+ - **Layer B — the generators** (`element_generator.rb`, `segment_generator.rb`,
217
+ `definition_generator.rb`, `functional_group_generator.rb`,
218
+ `interchange_generator.rb`, `module_loader_generator.rb`,
219
+ `registration_generator.rb`, `support_modules_generator.rb`) consume those
220
+ structs and emit the Ruby source above. `runner.rb` orchestrates a full
221
+ release; `bin/tediparse` is the CLI over it.
222
+
223
+ `spec/lib/stupidedi/schema/generation_spec.rb` round-trips the synthetic
224
+ fixture through the reader, the generators, and a child-process load that
225
+ builds real engine objects — so it's also a worked example of what each layer
226
+ produces.
227
+
228
+ Fidelity notes
229
+ --------------
230
+
231
+ A few details the generator carries through from the standard that are easy to
232
+ miss:
233
+
234
+ - **Element repeat counts** come from the SEGDETL repetition column: a numeric
235
+ count becomes `RepeatCount.bounded(n)`, the unbounded marker (`>1`) becomes
236
+ `RepeatCount.unbounded`, and a non-repeating use is `bounded(1)`.
237
+ - **The repetition separator** (ISA11 / element I65) is re-stamped in the
238
+ generated interchange's `replace_separators` on releases that use one, so the
239
+ writer round-trips it.
240
+ - **Higher-precision numeric types** (`N3`, `N5`, `N7`, `N8`, `N9`, …) all map
241
+ to the engine's `Nn`, which accepts arbitrary implied-decimal precision.