stupidedi 1.2.20 → 1.3.21

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -1
  3. data/bin/edi-obfuscate +112 -0
  4. data/lib/stupidedi/builder/generation.rb +39 -11
  5. data/lib/stupidedi/builder/navigation.rb +15 -2
  6. data/lib/stupidedi/either.rb +1 -1
  7. data/lib/stupidedi/reader/tokens/component_element_tok.rb +12 -0
  8. data/lib/stupidedi/reader/tokens/composite_element_tok.rb +17 -0
  9. data/lib/stupidedi/reader/tokens/segment_tok.rb +19 -1
  10. data/lib/stupidedi/reader/tokens/simple_element_tok.rb +12 -0
  11. data/lib/stupidedi/values/functional_group_val.rb +1 -1
  12. data/lib/stupidedi/values/interchange_val.rb +1 -1
  13. data/lib/stupidedi/values/loop_val.rb +1 -1
  14. data/lib/stupidedi/values/table_val.rb +1 -1
  15. data/lib/stupidedi/values/transaction_set_val.rb +1 -1
  16. data/lib/stupidedi/values/transmission_val.rb +1 -1
  17. data/lib/stupidedi/version.rb +1 -1
  18. data/lib/stupidedi/versions/functional_groups/002001/element_defs.rb +1 -14
  19. data/lib/stupidedi/versions/functional_groups/002001/element_types/date_val.rb +3 -3
  20. data/lib/stupidedi/versions/functional_groups/002001/element_types/fixnum_val.rb +2 -2
  21. data/lib/stupidedi/versions/functional_groups/002001/element_types/float_val.rb +2 -2
  22. data/lib/stupidedi/versions/functional_groups/002001/element_types/identifier_val.rb +1 -1
  23. data/lib/stupidedi/versions/functional_groups/002001/element_types/string_val.rb +2 -2
  24. data/lib/stupidedi/versions/functional_groups/002001/element_types/time_val.rb +2 -2
  25. data/lib/stupidedi/versions/functional_groups/003010/element_defs.rb +1 -14
  26. data/lib/stupidedi/versions/functional_groups/003010/element_types/date_val.rb +3 -3
  27. data/lib/stupidedi/versions/functional_groups/003010/element_types/fixnum_val.rb +2 -2
  28. data/lib/stupidedi/versions/functional_groups/003010/element_types/float_val.rb +2 -2
  29. data/lib/stupidedi/versions/functional_groups/003010/element_types/identifier_val.rb +2 -2
  30. data/lib/stupidedi/versions/functional_groups/003010/element_types/string_val.rb +2 -2
  31. data/lib/stupidedi/versions/functional_groups/003010/element_types/time_val.rb +2 -2
  32. data/lib/stupidedi/versions/functional_groups/003040/element_defs.rb +1 -14
  33. data/lib/stupidedi/versions/functional_groups/003040/element_types/date_val.rb +3 -3
  34. data/lib/stupidedi/versions/functional_groups/003040/element_types/fixnum_val.rb +2 -2
  35. data/lib/stupidedi/versions/functional_groups/003040/element_types/float_val.rb +2 -2
  36. data/lib/stupidedi/versions/functional_groups/003040/element_types/identifier_val.rb +2 -2
  37. data/lib/stupidedi/versions/functional_groups/003040/element_types/string_val.rb +2 -2
  38. data/lib/stupidedi/versions/functional_groups/003040/element_types/time_val.rb +2 -2
  39. data/lib/stupidedi/versions/functional_groups/003050/element_defs.rb +1 -14
  40. data/lib/stupidedi/versions/functional_groups/003050/element_types/date_val.rb +3 -3
  41. data/lib/stupidedi/versions/functional_groups/003050/element_types/fixnum_val.rb +2 -2
  42. data/lib/stupidedi/versions/functional_groups/003050/element_types/float_val.rb +2 -2
  43. data/lib/stupidedi/versions/functional_groups/003050/element_types/identifier_val.rb +2 -2
  44. data/lib/stupidedi/versions/functional_groups/003050/element_types/string_val.rb +2 -2
  45. data/lib/stupidedi/versions/functional_groups/003050/element_types/time_val.rb +2 -2
  46. data/lib/stupidedi/versions/functional_groups/004010/element_defs.rb +2 -3
  47. data/lib/stupidedi/versions/functional_groups/004010/element_types/date_val.rb +3 -3
  48. data/lib/stupidedi/versions/functional_groups/004010/element_types/fixnum_val.rb +2 -2
  49. data/lib/stupidedi/versions/functional_groups/004010/element_types/float_val.rb +2 -2
  50. data/lib/stupidedi/versions/functional_groups/004010/element_types/string_val.rb +2 -2
  51. data/lib/stupidedi/versions/functional_groups/004010/element_types/time_val.rb +2 -2
  52. data/lib/stupidedi/versions/functional_groups/005010/element_types/date_val.rb +1 -1
  53. data/lib/stupidedi/versions/functional_groups/005010/element_types/fixnum_val.rb +1 -1
  54. data/lib/stupidedi/versions/functional_groups/005010/element_types/float_val.rb +1 -1
  55. data/lib/stupidedi/versions/functional_groups/005010/element_types/string_val.rb +1 -1
  56. data/lib/stupidedi/versions/functional_groups/005010/element_types/time_val.rb +1 -1
  57. data/lib/stupidedi/versions/interchanges/00200/element_defs.rb +1 -1
  58. data/lib/stupidedi/versions/interchanges/00300/element_defs.rb +1 -1
  59. data/lib/stupidedi/versions/interchanges/00400/element_defs.rb +1 -1
  60. data/lib/stupidedi/versions/interchanges/00401/element_defs.rb +1 -1
  61. data/spec/examples/integration/navigating_spec.rb +44 -0
  62. data/spec/examples/integration/nondeterminism_spec.rb +165 -0
  63. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc8ac588bac665066d84b4df50b9641d94835f44671ad43a189bad11c6ad8101
4
- data.tar.gz: e556a6c96516ee2bd4aae9b2fa5c969331e0ea1d1473e2f32e8f130bfda94989
3
+ metadata.gz: dca3cbe38e197d5e24eb127bb9ea501a7417e8ebdef6ce29c0841cd8a7f618f8
4
+ data.tar.gz: d7d967d793ee60bed8b4f4794f1168a102c07369db5deb1814727ff14d6a4b4e
5
5
  SHA512:
6
- metadata.gz: 9ba36f9939ce14183eb3211ca666115ee420f74f63549bbf751714e55a06f07c0eadfe580383209c5a451cb9d9e02d485efb3536a8a352d7a1214322738585fe
7
- data.tar.gz: e68250deed4fc2f0e100cfbf696d3d7d7e419730561fed7cd05e886eccdd44dc450cc133ce9f3015d86a05537d62a99573825137fd05bd4eedddd924b5a1a6d4
6
+ metadata.gz: 075d7961db6721b5e60125c7a1fc98e58ed1a2847c55dce06c9ff138a0a0d745a95ca0d798e6c38923fbdfa250b60b6c026a4242fe308d7df170385b3b44ef9c
7
+ data.tar.gz: a899f00431881d6edd7cbf083c551f70af676bff1cc9b21df8dcbd9bc41723bf8dab402484f3a028b79b05c7abcbec745c3b95dd854c10a0c9337d62e03eb632
data/README.md CHANGED
@@ -233,6 +233,8 @@ Perform validation on a file
233
233
 
234
234
  ### Generating, Writing
235
235
 
236
+ #### X12 Writer
237
+
236
238
  ```ruby
237
239
  require "stupidedi"
238
240
 
@@ -301,7 +303,9 @@ b.machine.zipper.tap do |z|
301
303
  end
302
304
  ```
303
305
 
304
- Please note that two writers are available. As shown above `Stupidedi::Writer::Default` will output data encoded in plain x12 format. While `Stupidedi::Writer::Claredi` will output a formatted HTML string.
306
+ #### HTML writer
307
+
308
+ As shown above `Stupidedi::Writer::Default` will output data encoded in plain x12 format. While `Stupidedi::Writer::Claredi` will output a formatted HTML string.
305
309
 
306
310
  `Stupidedi::Writer::Claredi#write` operates on `StringIO`.
307
311
 
@@ -313,6 +317,16 @@ b.machine.zipper.tap do |z|
313
317
  end
314
318
  ```
315
319
 
320
+ #### Json (Hash) Writer
321
+
322
+ Converting the tree to a JSON document is intentionally not included in the library. However this still may be implemented utilizing the stupidedi API.
323
+
324
+ [Here](https://github.com/irobayna/stupidedi/blob/master/notes/json_writer/json.rb) is one of the possible ways to implement this.
325
+
326
+ The shown approach allows to define custom traversing logic for the nodes with ability to change hash keys and values to whatever is needed.
327
+
328
+ Please refer to [this readme](https://github.com/irobayna/stupidedi/blob/master/notes/json_writer/json.MD) and [these nodes implementation](https://github.com/irobayna/stupidedi/blob/master/notes/json_writer/json/) for more information.
329
+
316
330
  ### Reading, Traversing
317
331
 
318
332
  ```ruby
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This is a utility that will strip all free-form text (elements of type AN),
4
+ # dates, and times from an X12 file. Strings are replaced with underscores,
5
+ # dates are replaced with 2015-12-30, and times are replaced with 00:00:00.
6
+ #
7
+ # When -s is given, then unrecognized elements and segments will be suppressed
8
+ # from the output.
9
+ #
10
+ # Otherwise, unrecognized segments or elements will cause an exception since it
11
+ # is not possible to know if these should be obfuscated or not (they are invalid).
12
+ #
13
+ require File.expand_path("../../lib/stupidedi", __FILE__)
14
+
15
+ require "stupidedi"
16
+ require "pp"
17
+
18
+ # Short-lived processes should win some peformance gains here
19
+ GC.disable
20
+
21
+ def main(argv)
22
+ strict = !argv.delete("-s")
23
+ config = Stupidedi::Config.default
24
+ reader = Stupidedi::Reader.build(File.open(argv[0]))
25
+
26
+ # First segment (ISA) specifies separators, used for parsing
27
+ result = reader.read_segment
28
+
29
+ result.tap do
30
+ lookup = result.remainder.segment_dict.push(
31
+ config.interchange.at(result.fetch.element_toks[11].value).segment_dict)
32
+
33
+ reader = result.remainder.copy(segment_dict: lookup)
34
+ delims = result.remainder.separators.copy(component: result.fetch.element_toks[15].value)
35
+ reader = reader.copy(separators: delims)
36
+
37
+ while result.defined?
38
+ result.tap do |token|
39
+ if token.id == :GS
40
+ gs08 = result.fetch.element_toks[7].value.slice(0, 6)
41
+ lookup = lookup.push(config.functional_group.at(gs08).segment_dict)
42
+ reader = reader.copy(segment_dict: lookup)
43
+ end
44
+
45
+ if lookup.defined_at?(token.id)
46
+ segment_def = lookup.at(token.id)
47
+ element_toks = token.element_toks.zip(segment_def.element_uses).map.each_with_index do |(e, u), n|
48
+ if u.nil?
49
+ raise "unrecognized element: #{token.id}-#{"%02d" % n}" if strict
50
+
51
+ # This won't terminate the inner loop immediately, but if this
52
+ # element is unknown, it's because there were too many elements
53
+ # for the given segment. So all the extra elements at the end
54
+ # will still be skipped
55
+ next
56
+ elsif u.simple?
57
+ simple(e, u)
58
+ else
59
+ composite(e, u)
60
+ end
61
+ end.compact
62
+
63
+ puts token.copy(element_toks: element_toks).to_s(delims)
64
+ else
65
+ raise "unknown segment: #{token.id}" if strict
66
+ next
67
+ end
68
+ end
69
+
70
+ result = reader.read_segment
71
+ reader = result.remainder
72
+ end
73
+
74
+ result.explain{|msg| raise msg } if result.fatal?
75
+ end
76
+ end
77
+
78
+ def simple(e, u)
79
+ type = u.definition.class.name
80
+ type = (type || "?").split("::").last
81
+
82
+ # GS-08 and ST-03 are preserved, since these have special meaning to the parser
83
+ if e.blank? or [:E1705, :E480].include?(u.definition.id)
84
+ return e
85
+ end
86
+
87
+ if type.end_with?("AN")
88
+ e.copy(value: "_" * u.definition.min_length)
89
+ elsif type == "TM"
90
+ e.copy(value: "0" * e.value.length)
91
+ elsif type == "DT"
92
+ if e.value.length == 8
93
+ e.copy(value: "20151230")
94
+ else
95
+ e.copy(value: "151230")
96
+ end
97
+ else
98
+ e
99
+ end
100
+ end
101
+
102
+ def composite(e, u)
103
+ component_toks = []
104
+
105
+ e.component_toks.zip(u.definition.component_uses) do |ce, cu|
106
+ component_toks.push(simple(ce, cu))
107
+ end
108
+
109
+ e.copy(component_toks: component_toks)
110
+ end
111
+
112
+ main(ARGV)
@@ -7,21 +7,49 @@ module Stupidedi
7
7
 
8
8
  module Generation
9
9
 
10
- # @return [(StateMachine, Either<Reader::TokenReader>)]
11
- def read(reader)
12
- machine = self
13
- reader = reader.read_segment
14
-
15
- while reader.defined?
16
- reader = reader.flatmap do |segment_tok, reader1|
17
- machine, reader =
18
- machine.insert(segment_tok, reader1)
10
+ # Consumes all input from `reader` and returns the updated
11
+ # {StateMachine} along with the result of the last attempt
12
+ # to read a segment.
13
+ #
14
+ # The `nondeterminism` argument specifies a limit on how many
15
+ # parse trees can be built simultaneously due to ambiguity in
16
+ # the input and/or specification. This prevents runaway memory
17
+ # CPU consumption (see GH-129), and will return a {Result.failure}
18
+ # once exceeded.
19
+ #
20
+ # The default value is 1, resulting in an error if any input
21
+ # is ambiguous.
22
+ #
23
+ # NOTE: The error is detected *after* the resources are already
24
+ # been consumed. The extra parse trees are returned (in memory)
25
+ # via the {StateMachine} to aide diagnosis.
26
+ #
27
+ # @return [(StateMachine, Reader::Result)]
28
+ def read(reader, options = {})
29
+ limit = options.fetch(:nondeterminism, 1)
30
+ machine = self
31
+ reader_e = reader.read_segment
32
+
33
+ while reader_e.defined?
34
+ reader_e = reader_e.flatmap do |segment_tok, reader|
35
+ machine, reader_ =
36
+ machine.insert(segment_tok, reader)
37
+
38
+ if machine.active.length <= limit
39
+ reader_.read_segment
40
+ else
41
+ matches = machine.active.map do |m|
42
+ segment_def = m.node.zipper.node.definition
43
+ "#{segment_def.id} #{segment_def.name}"
44
+ end.join(", ")
19
45
 
20
- reader.read_segment
46
+ return machine,
47
+ Reader::Result.failure("too much non-determinism: #{matches}", reader_.input, true)
48
+ end
21
49
  end
22
50
  end
23
51
 
24
- return machine, reader
52
+ return machine, reader_e
25
53
  end
26
54
 
27
55
  # @return [(StateMachine, Reader::TokenReader)]
@@ -453,7 +453,7 @@ module Stupidedi
453
453
  # @return [Either<Array<Object>>]
454
454
  def iterate(id, *elements)
455
455
  a = []
456
- m = find(id, *elements)
456
+ m = __find(false, id, elements, true)
457
457
  return m unless m.defined?
458
458
 
459
459
  while m.defined?
@@ -485,10 +485,16 @@ module Stupidedi
485
485
  private
486
486
 
487
487
  # @return [Either<StateMachine>]
488
- def __find(invalid, id, elements)
488
+ def __find(invalid, id, elements, assert_repeatable = false)
489
489
  reachable = false
490
490
  matches = []
491
491
 
492
+ # Note op.segment_use.nil? is true when searching for ISA,
493
+ # GS, and ST, because we can't know the SegmentUse until we
494
+ # deconstruct the token and looked up the versions numbers
495
+ # in the Config. Nonetheless, we know ISA, GS, and ST can repeat
496
+ repeatable = [:ISA, :GS, :ST].include?(id)
497
+
492
498
  @active.each do |zipper|
493
499
  matched = false
494
500
  filter_tok = mksegment_tok(zipper.node.segment_dict, id, elements, nil)
@@ -515,6 +521,8 @@ module Stupidedi
515
521
  state = zipper
516
522
  value = zipper.node.zipper
517
523
 
524
+ repeatable ||= op_.segment_use.try(:repeatable?)
525
+
518
526
  # 1. Move upward (possibly zero times)
519
527
  op_.pop_count.times do
520
528
  value = value.up
@@ -625,6 +633,11 @@ module Stupidedi
625
633
  end
626
634
  end
627
635
 
636
+ if assert_repeatable and not repeatable
637
+ raise Exceptions::ParseError,
638
+ "#{id} segment is not repeatable"
639
+ end
640
+
628
641
  if not reachable
629
642
  raise Exceptions::ParseError,
630
643
  "#{id} segment cannot be reached from the current state"
@@ -131,7 +131,7 @@ module Stupidedi
131
131
 
132
132
  # @return [Boolean]
133
133
  def ==(other)
134
- other.is_a?(Either) and other.select{|x| x == @value }.defined?
134
+ other.is_a?(self.class) and other.select{|x| x == @value }.defined?
135
135
  end
136
136
 
137
137
  # @return [void]
@@ -21,6 +21,14 @@ module Stupidedi
21
21
  value, position, remainder
22
22
  end
23
23
 
24
+ # @return [CompositeElementTok]
25
+ def copy(changes = {})
26
+ ComponentElementTok.new \
27
+ changes.fetch(:value, @value),
28
+ changes.fetch(:position, @position),
29
+ changes.fetch(:remainder, @remainder)
30
+ end
31
+
24
32
  def pretty_print(q)
25
33
  q.pp(:component.cons(@value.cons))
26
34
  end
@@ -40,6 +48,10 @@ module Stupidedi
40
48
  def composite?
41
49
  false
42
50
  end
51
+
52
+ def to_s(separators)
53
+ @value
54
+ end
43
55
  end
44
56
 
45
57
  class << ComponentElementTok
@@ -21,6 +21,14 @@ module Stupidedi
21
21
  component_toks, position, remainder
22
22
  end
23
23
 
24
+ # @return [CompositeElementTok]
25
+ def copy(changes = {})
26
+ CompositeElementTok.new \
27
+ changes.fetch(:component_toks, @component_toks),
28
+ changes.fetch(:position, @position),
29
+ changes.fetch(:remainder, @remainder)
30
+ end
31
+
24
32
  def pretty_print(q)
25
33
  q.pp(:composite.cons(@component_toks))
26
34
  end
@@ -48,6 +56,15 @@ module Stupidedi
48
56
  def composite?
49
57
  true
50
58
  end
59
+
60
+ def to_s(separators)
61
+ if blank?
62
+ ""
63
+ else
64
+ cs = @component_toks.map{|x| x.to_s(separators) }
65
+ cs.join(separators.component)
66
+ end
67
+ end
51
68
  end
52
69
 
53
70
  class << CompositeElementTok
@@ -24,17 +24,35 @@ module Stupidedi
24
24
  id, element_toks, position, remainder
25
25
  end
26
26
 
27
+ # @return [SegmentTok]
28
+ def copy(changes = {})
29
+ SegmentTok.new \
30
+ changes.fetch(:id, @id),
31
+ changes.fetch(:element_toks, @element_toks),
32
+ changes.fetch(:position, @position),
33
+ changes.fetch(:remainder, @remainder)
34
+ end
35
+
27
36
  def pretty_print(q)
28
37
  q.pp(:segment.cons(@id.cons(@element_toks)))
29
38
  end
30
39
 
31
40
  def blank?
32
- @element_toks.all(&:blank?)
41
+ @element_toks.all?(&:blank?)
33
42
  end
34
43
 
35
44
  def present?
36
45
  not blank?
37
46
  end
47
+
48
+ def to_s(separators)
49
+ if blank?
50
+ "#{id}#{separators.segment}"
51
+ else
52
+ es = @element_toks.map{|x| x.to_s(separators) }
53
+ id.cons(es).join(separators.element) + separators.segment
54
+ end
55
+ end
38
56
  end
39
57
 
40
58
  class << SegmentTok
@@ -21,6 +21,14 @@ module Stupidedi
21
21
  value, position, remainder
22
22
  end
23
23
 
24
+ # @return [SimpleElementTok]
25
+ def copy(changes = {})
26
+ SimpleElementTok.new \
27
+ changes.fetch(:value, @value),
28
+ changes.fetch(:position, @position),
29
+ changes.fetch(:remainder, @remainder)
30
+ end
31
+
24
32
  def pretty_print(q)
25
33
  q.pp(:simple.cons(@value.cons))
26
34
  end
@@ -48,6 +56,10 @@ module Stupidedi
48
56
  def composite?
49
57
  false
50
58
  end
59
+
60
+ def to_s(separators)
61
+ @value
62
+ end
51
63
  end
52
64
 
53
65
  class << SimpleElementTok
@@ -92,7 +92,7 @@ module Stupidedi
92
92
 
93
93
  # @return [String]
94
94
  def inspect
95
- ansi.envelope("Group") << "(#{@children.map(&:inspect).join(', ')})"
95
+ ansi.envelope("Group") + "(#{@children.map(&:inspect).join(', ')})"
96
96
  end
97
97
 
98
98
  def ==(other)
@@ -88,7 +88,7 @@ module Stupidedi
88
88
 
89
89
  # @return [String]
90
90
  def inspect
91
- ansi.envelope("Interchange") << "(#{@children.map(&:inspect).join(', ')})"
91
+ ansi.envelope("Interchange") + "(#{@children.map(&:inspect).join(', ')})"
92
92
  end
93
93
 
94
94
  # @return [Boolean]
@@ -59,7 +59,7 @@ module Stupidedi
59
59
 
60
60
  # @return [String]
61
61
  def inspect
62
- ansi.loop("Loop") << "(#{@children.map(&:inspect).join(', ')})"
62
+ ansi.loop("Loop") + "(#{@children.map(&:inspect).join(', ')})"
63
63
  end
64
64
 
65
65
  # @return [Boolean]
@@ -55,7 +55,7 @@ module Stupidedi
55
55
 
56
56
  # @return [String]
57
57
  def inspect
58
- ansi.table("Table") << "(#{@children.map(&:inspect).join(', ')})"
58
+ ansi.table("Table") + "(#{@children.map(&:inspect).join(', ')})"
59
59
  end
60
60
 
61
61
  # @return [Boolean]
@@ -55,7 +55,7 @@ module Stupidedi
55
55
 
56
56
  # @return [String]
57
57
  def inspect
58
- ansi.envelope("Transaction") << "(#{@children.map(&:inspect).join(', ')})"
58
+ ansi.envelope("Transaction") + "(#{@children.map(&:inspect).join(', ')})"
59
59
  end
60
60
 
61
61
  # @return [Boolean]