stupidedi 1.2.20 → 1.3.21

Sign up to get free protection for your applications and to get access to all the features.
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]