x12-lite 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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +11 -0
- data/lib/x12-lite.rb +858 -0
- data/x12-lite.gemspec +15 -0
- metadata +47 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: beb279f2d650c4542ef4e8d1c1c7776bd186f60fa68285b7b3cdba327b8dda6f
|
4
|
+
data.tar.gz: 68a9267df83e0a11f3f27aafde4d6215e710d758a15b80ff37ddf3db0674f8ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b781293ca7d307daa74db041c84beed21cd73db1ed9890674611c3ed6997d34f05e196a0d7f96d281ee65016b6526302f0de1e1ac59629548fe7bf16b50eddb8
|
7
|
+
data.tar.gz: 61dcd151d94c6e109e10a1053038a21ac5801b896035e951e88d6a0096ed0a29e4ee96c5b9ba94863c2182231d0c24512076bcc5c6f500d28fd3849449a12e85
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Steve Shreeve
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/lib/x12-lite.rb
ADDED
@@ -0,0 +1,858 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# ==============================================================================
|
4
|
+
# x12-lite.rb: X12 library for Ruby
|
5
|
+
#
|
6
|
+
# Author: Steve Shreeve <steve.shreeve@trusthealth.com>
|
7
|
+
# Date: October 7, 2024
|
8
|
+
#
|
9
|
+
# Legal: All rights reserved.
|
10
|
+
# ==============================================================================
|
11
|
+
|
12
|
+
require "enumerator"
|
13
|
+
require "find"
|
14
|
+
|
15
|
+
class Object
|
16
|
+
def blank?
|
17
|
+
respond_to?(:empty?) or return !self
|
18
|
+
empty? or respond_to?(:strip) && strip.empty?
|
19
|
+
end unless defined? blank?
|
20
|
+
end
|
21
|
+
|
22
|
+
# ==[ ANSI colors ]=============================================================
|
23
|
+
|
24
|
+
def hex(str=nil)
|
25
|
+
($hex ||= {})[str] ||= begin
|
26
|
+
str =~ /\A#?(?:(\h\h)(\h\h)(\h\h)|(\h)(\h)(\h))\z/ or return
|
27
|
+
r, g, b = $1 ? [$1, $2, $3] : [$4*2, $5*2, $6*2]
|
28
|
+
[r.hex, g.hex, b.hex] * ";"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def fg(rgb=nil); rgb ? "\e[38;2;#{hex(rgb)}m" : "\e[39m"; end
|
33
|
+
def bg(rgb=nil); rgb ? "\e[48;2;#{hex(rgb)}m" : "\e[49m"; end
|
34
|
+
|
35
|
+
def ansi(str, f=nil, b=nil)
|
36
|
+
[
|
37
|
+
(fg(f) if f),
|
38
|
+
(bg(b) if b),
|
39
|
+
str,
|
40
|
+
(bg if b),
|
41
|
+
(fg if f),
|
42
|
+
].compact.join
|
43
|
+
end
|
44
|
+
|
45
|
+
# ==[ X12 ]=====================================================================
|
46
|
+
|
47
|
+
class X12
|
48
|
+
VERSION="0.2.0"
|
49
|
+
|
50
|
+
include Enumerable
|
51
|
+
|
52
|
+
# ISA field widths
|
53
|
+
LEN = [3, 2, 10, 2, 10, 2, 15, 2, 15, 6, 4, 1, 5, 9, 1, 1]
|
54
|
+
|
55
|
+
# Basic Character Set (also adds '#' from the Extended Character Set)
|
56
|
+
BCS = <<~'end'.gsub(/\s+/, '').concat(' ').split('')
|
57
|
+
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
58
|
+
0 1 2 3 4 5 6 7 8 9
|
59
|
+
! " # & ' ( ) * + , - . / : ; = ?
|
60
|
+
end
|
61
|
+
|
62
|
+
# delimiter pos chr
|
63
|
+
# ---------- --- ---
|
64
|
+
# field 4 (*)
|
65
|
+
# composite 105 (:)
|
66
|
+
# repetition 83 (^)
|
67
|
+
# segment 106 (~)
|
68
|
+
|
69
|
+
def initialize(obj=nil, *etc)
|
70
|
+
if obj.is_a?(String) && !etc.empty?
|
71
|
+
obj = etc.dup.unshift(obj)
|
72
|
+
elsif obj
|
73
|
+
obj = obj.dup # does this need to be a deep clone?
|
74
|
+
end
|
75
|
+
case obj
|
76
|
+
when nil
|
77
|
+
when String then @str = obj unless obj.empty?
|
78
|
+
when Array
|
79
|
+
when Hash
|
80
|
+
when IO then @str = obj = obj.read
|
81
|
+
when X12 then @str = obj.to_s
|
82
|
+
else raise "unable to handle #{arg.class} objects"
|
83
|
+
end
|
84
|
+
@str ||= isa_widths!("ISA*00**00**ZZ**ZZ****^*00501**0*P*:~")
|
85
|
+
@str =~ /\AISA(.).{78}(.).{21}(.)(.)/ or raise "malformed X12"
|
86
|
+
@fld, @com, @rep, @seg = $~.captures.values_at(0, 2, 1, 3)
|
87
|
+
@rep = "^" if @rep == "U"
|
88
|
+
@sep = [@fld, @com, @rep, @seg]
|
89
|
+
@bad = regex_chars!(BCS + @sep) # invalid in txn bytes #!# BARELY USED NOW???
|
90
|
+
@chr = regex_chars!(BCS - @sep) # invalid in user data #!# NOT USED RIGHT NOW
|
91
|
+
case obj
|
92
|
+
when String, nil then to_a; @str = nil
|
93
|
+
when Array then set(obj.shift, obj.shift) until obj.empty?
|
94
|
+
when Hash then obj.each {|k, v| set(k, v) unless v.nil?}
|
95
|
+
end
|
96
|
+
to_s unless @str
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.load(file)
|
100
|
+
str = File.open(file, "r:bom|utf-8", &:read) rescue "unreadable file"
|
101
|
+
new(str)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.[](*args)
|
105
|
+
new(*args)
|
106
|
+
end
|
107
|
+
|
108
|
+
def regex_chars(ary, invert=false)
|
109
|
+
chrs = ary.sort.uniq # ordered list of given characters
|
110
|
+
.chunk_while {|prev, curr| curr.ord == prev.ord + 1 } # find runs
|
111
|
+
.map do |chunk| # build character ranges
|
112
|
+
(chunk.length > 1 ? [chunk.first, chunk.last] : [chunk.first])
|
113
|
+
.map {|chr| "^[]-\\".include?(chr) ? Regexp.escape(chr) : chr }
|
114
|
+
.join("-")
|
115
|
+
end
|
116
|
+
.join # join ranges together
|
117
|
+
.prepend("[#{'^' if invert}") # invert or not
|
118
|
+
.concat("]") # reject these character ranges
|
119
|
+
/#{chrs}/ # return as a regex
|
120
|
+
end
|
121
|
+
|
122
|
+
def regex_chars!(ary, invert=true)
|
123
|
+
regex_chars(ary, invert)
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_a
|
127
|
+
@ary ||= @str.strip.split(/[#{Regexp.escape(@seg)}\r\n]+/).map {|str| str.split(@fld, -1)}
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_a!
|
131
|
+
to_a
|
132
|
+
@str = nil
|
133
|
+
@ary
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_s
|
137
|
+
@str ||= @ary.inject("") {|str, seg| str << seg.join(@fld) << "#{@seg}\n"}.chomp
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s!
|
141
|
+
to_s
|
142
|
+
@ary = nil
|
143
|
+
@str
|
144
|
+
end
|
145
|
+
|
146
|
+
def raw
|
147
|
+
to_s.delete("\n").upcase #!# NOTE: Fix these... should all sets be checked?
|
148
|
+
end
|
149
|
+
|
150
|
+
def show!
|
151
|
+
to_a.each {|r| puts ansi(r.inspect, "fff", "369") }
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def show(*opts)
|
156
|
+
full = opts.include?(:full) # show body at top
|
157
|
+
deep = opts.include?(:deep) # dive into repeats
|
158
|
+
down = opts.include?(:down) # show segments in lowercase
|
159
|
+
list = opts.include?(:list) # give back a list or print it
|
160
|
+
hide = opts.include?(:hide) # hide output
|
161
|
+
only = opts.include?(:only) # only show first of each segment type
|
162
|
+
left = opts.grep(Integer).first || 15 # left justify size
|
163
|
+
|
164
|
+
out = full ? [to_s] : []
|
165
|
+
|
166
|
+
unless hide
|
167
|
+
out << "" if full
|
168
|
+
nums = Hash.new(0)
|
169
|
+
segs = to_a
|
170
|
+
segs.each_with_index do |flds, i|
|
171
|
+
seg = down ? flds.first.downcase : flds.first.upcase
|
172
|
+
num = (nums[seg] += 1)
|
173
|
+
flds.each_with_index do |fld, j|
|
174
|
+
next if !fld || fld.empty? || j == 0
|
175
|
+
if deep
|
176
|
+
reps = fld.split(@rep)
|
177
|
+
if reps.size > 1
|
178
|
+
reps.each_with_index do |set, k|
|
179
|
+
tag = "#{seg}%s-#{j}(#{k + 1})" % [num > 1 && !only ? "(#{num})" : ""]
|
180
|
+
out << (tag.ljust(left) + set)
|
181
|
+
end
|
182
|
+
next
|
183
|
+
end
|
184
|
+
end
|
185
|
+
tag = "#{seg}%s-#{j}" % [num > 1 && !only ? "(#{num})" : ""]
|
186
|
+
out << (tag.ljust(left) + wrap(fld, "fff", "369"))
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
list ? out : (puts out)
|
192
|
+
end
|
193
|
+
|
194
|
+
def normalize(obj)
|
195
|
+
if Array === obj
|
196
|
+
obj.each_with_index do |elt, i|
|
197
|
+
str = (String === elt) ? elt : (obj[i] = elt.to_s)
|
198
|
+
str.upcase!
|
199
|
+
str.gsub!(@bad, ' ')
|
200
|
+
end
|
201
|
+
else
|
202
|
+
str = (String === obj) ? obj : obj.to_s
|
203
|
+
str.upcase!
|
204
|
+
str.gsub!(@bad, ' ')
|
205
|
+
str
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def isa_widths(row)
|
210
|
+
row.each_with_index do |was, i|
|
211
|
+
len = LEN[i]
|
212
|
+
was.replace(was.ljust(len)[...len]) if was && len && was.size != len
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def isa_widths!(str)
|
217
|
+
sep = str[3] or return str
|
218
|
+
isa_widths(str.split(sep)).join(sep)
|
219
|
+
end
|
220
|
+
|
221
|
+
def data(*args)
|
222
|
+
len = args.size; return update(*args) if len > 2
|
223
|
+
pos = args[0] or return @str
|
224
|
+
val = args[1]
|
225
|
+
|
226
|
+
# Syntax: seg(num)-fld(rep).com
|
227
|
+
pos =~ /^(..[^-.(]?)?(?:\((\d*|[+!?*]?)\))?[-.]?(\d+)?(?:\((\d*|[+!?*]?)\))?[-.]?(\d+)?$/
|
228
|
+
seg = $1 or return ""; want = /^#{seg}[^#{Regexp.escape(@seg)}\r\n]*/i
|
229
|
+
num = $2 && $2.to_i; new_num = $2 == "+"; ask_num = $2 == "?"; all_num = $2 == "*"
|
230
|
+
rep = $4 && $4.to_i; new_rep = $4 == "+"; ask_rep = $4 == "?"; all_rep = $4 == "*"
|
231
|
+
fld = $3 && $3.to_i; len > 1 && fld == 0 and raise "zero index on field"
|
232
|
+
com = $5 && $5.to_i; len > 1 && com == 0 and raise "zero index on component"
|
233
|
+
|
234
|
+
# NOTE: When doing a get, a missing num or rep means get the first
|
235
|
+
# NOTE: When doing a set, a missing num or rep means set the last
|
236
|
+
# NOTE: ask_num and ask_rep are mutually exclusive, how should we handle?
|
237
|
+
# NOTE: all_num and all_rep are mutually exclusive, how should we handle?
|
238
|
+
# NOTE: ask_* is only for get
|
239
|
+
# NOTE: all_* is only for get and set [is this correct?]
|
240
|
+
|
241
|
+
if len == 1 # get
|
242
|
+
to_s unless @str
|
243
|
+
return @str.scan(want).size if ask_num && !ask_rep
|
244
|
+
return @str.scan(want).inject([]) do |ary, out|
|
245
|
+
out = loop do
|
246
|
+
out = out.split(@fld)[fld ] or break if fld
|
247
|
+
break out.split(@rep).size if ask_rep
|
248
|
+
out = out.split(@rep)[rep - 1] or break if rep || (com && (rep ||= 1))
|
249
|
+
out = out.split(@com)[com - 1] or break if com
|
250
|
+
break out
|
251
|
+
end
|
252
|
+
ary << out if out
|
253
|
+
ary
|
254
|
+
end if all_num
|
255
|
+
out = @str.scan(want)[num - 1] or return "" if num ||= 1
|
256
|
+
out = out.split(@fld)[fld ] or return "" if fld
|
257
|
+
return out.split(@rep).size if ask_rep
|
258
|
+
out = out.split(@rep)[rep - 1] or return "" if rep || (com && (rep ||= 1))
|
259
|
+
out = out.split(@com)[com - 1] or return "" if com
|
260
|
+
out
|
261
|
+
else # set
|
262
|
+
to_a unless @ary
|
263
|
+
@str &&= nil
|
264
|
+
our = @ary.select {|now| now[0] =~ want}
|
265
|
+
unless all_num
|
266
|
+
num ||= 0 # default to last
|
267
|
+
row = our[num - 1] or pad = num - our.size
|
268
|
+
pad = 1 if (num == 0 && our.size == 0 || new_num)
|
269
|
+
pad and pad.times { @ary.push(row = [seg.upcase]) }
|
270
|
+
val = our.size + pad if new_num && val == :num # auto-number
|
271
|
+
our = [row]
|
272
|
+
end
|
273
|
+
|
274
|
+
# prepare the source and decide how to update
|
275
|
+
val ||= ""
|
276
|
+
how = case
|
277
|
+
when !rep && !com # replace fields
|
278
|
+
val = val.join(@fld) if Array === val
|
279
|
+
val = val.to_s.split(@fld, -1)
|
280
|
+
:fld
|
281
|
+
when fld && rep && !com # replace repeats
|
282
|
+
val = val.join(@rep) if Array === val
|
283
|
+
val.include?(@fld) and raise "invalid separator for repeats"
|
284
|
+
val = val.to_s.split(@rep, -1)
|
285
|
+
:rep
|
286
|
+
when fld && com # replace components
|
287
|
+
val = val.join(@com) if Array === val
|
288
|
+
val.include?(@fld) and raise "invalid separator for repeats"
|
289
|
+
val.include?(@rep) and raise "invalid separator for repeats"
|
290
|
+
val = val.to_s.split(@com, -1)
|
291
|
+
:com
|
292
|
+
end or raise "invalid fld/rep/com: #{[fld, rep, com].inspect}"
|
293
|
+
val = [""] if val.empty?
|
294
|
+
|
295
|
+
#!# TODO: val.dup to prevent sharing issues???
|
296
|
+
|
297
|
+
# replace the target
|
298
|
+
our.each do |row|
|
299
|
+
case how
|
300
|
+
when :fld
|
301
|
+
if fld
|
302
|
+
pad = fld - row.size
|
303
|
+
pad.times { row.push("") } if pad > 0
|
304
|
+
row[fld, val.size] = val
|
305
|
+
else
|
306
|
+
row[1..-1] = val
|
307
|
+
end
|
308
|
+
when :rep
|
309
|
+
if (was = row[fld] ||= "").empty?
|
310
|
+
was << @rep * (rep - 1) if rep > 1
|
311
|
+
was << val.join(@rep)
|
312
|
+
else
|
313
|
+
ufr = was.split(@rep, -1) # unpacked repeats
|
314
|
+
pad = rep - ufr.size
|
315
|
+
pad = 1 if new_rep || rep == 0 && ufr.empty?
|
316
|
+
pad.times { ufr.push("") } if pad > 0
|
317
|
+
ufr[rep - 1, val.size] = val
|
318
|
+
was.replace(ufr.join(@rep)) # repacked repeats
|
319
|
+
end
|
320
|
+
when :com
|
321
|
+
rep ||= 0 # default to last
|
322
|
+
|
323
|
+
if (one = row[fld] ||= "").empty?
|
324
|
+
one << @rep * (rep - 1) if rep > 1
|
325
|
+
one << @com * (com - 1) if com > 1
|
326
|
+
one << val.join(@com)
|
327
|
+
else
|
328
|
+
ufr = one.split(@rep, -1) # unpacked repeats
|
329
|
+
pad = rep - ufr.size
|
330
|
+
pad = 1 if new_rep || rep == 0 && ufr.empty?
|
331
|
+
pad.times { ufr.push("") } if pad > 0
|
332
|
+
|
333
|
+
if (two = ufr[rep - 1] ||= "").empty?
|
334
|
+
two << @com * (com - 1) if com > 1
|
335
|
+
two << val.join(@com)
|
336
|
+
else
|
337
|
+
ucr = two.split(@com, -1) # unpacked components
|
338
|
+
pad = com - ucr.size
|
339
|
+
pad.times { ucr.push("") } if pad > 0
|
340
|
+
ucr[com - 1, val.size] = val
|
341
|
+
two.replace(ucr.join(@com)) # repacked components
|
342
|
+
end
|
343
|
+
one.replace(ufr.join(@rep)) # repacked repeats
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# enforce ISA field widths
|
349
|
+
isa_widths(row) if seg =~ /isa/i
|
350
|
+
|
351
|
+
nil
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
alias_method :get, :data
|
356
|
+
alias_method :set, :data
|
357
|
+
alias_method :[], :data
|
358
|
+
alias_method :[]=, :data
|
359
|
+
|
360
|
+
def update(*etc)
|
361
|
+
etc = etc.first if etc.size == 1
|
362
|
+
case etc
|
363
|
+
when nil
|
364
|
+
when Array then etc.each_slice(2) {|pos, val| data(pos, val) if val }
|
365
|
+
when Hash then etc.each {|pos, val| data(pos, val) if val }
|
366
|
+
else raise "unable to update X12 objects with #{etc.class} types"
|
367
|
+
end
|
368
|
+
self
|
369
|
+
end
|
370
|
+
|
371
|
+
# def each(seg=nil)
|
372
|
+
# to_a.each do |item|
|
373
|
+
# next if seg && !(seg === item[0])
|
374
|
+
# yield item
|
375
|
+
# end
|
376
|
+
# end
|
377
|
+
#
|
378
|
+
# # means this each may change @ary, so clear @str in case
|
379
|
+
# def each!(...)
|
380
|
+
# out = each(...)
|
381
|
+
# @str &&= nil
|
382
|
+
# out
|
383
|
+
# end
|
384
|
+
#
|
385
|
+
# def grep(who)
|
386
|
+
# inject([]) do |ary, row|
|
387
|
+
# ary.push(block_given? ? yield(row) : row) if who === row.first
|
388
|
+
# ary
|
389
|
+
# end
|
390
|
+
# end
|
391
|
+
#
|
392
|
+
# def now(fmt="%Y%m%d%H%M%S")
|
393
|
+
# Time.now.strftime(fmt)
|
394
|
+
# end
|
395
|
+
#
|
396
|
+
# def guid
|
397
|
+
# ("%9.6f" % Time.now.to_f).to_s.sub(".", "")
|
398
|
+
# end
|
399
|
+
#
|
400
|
+
# def each_pair
|
401
|
+
# nums = Hash.new(0)
|
402
|
+
# segs = to_a
|
403
|
+
# segs.each_with_index do |flds, i|
|
404
|
+
# seg = flds.first.downcase
|
405
|
+
# num = nums[seg] += 1
|
406
|
+
# msh = seg == "msh"
|
407
|
+
# adj = msh ? 1 : 0
|
408
|
+
# flds.each_with_index do |fld, j|
|
409
|
+
# next if !fld || fld.empty? || j == 0
|
410
|
+
# if !msh and (reps = fld.split(@rep)).size > 1
|
411
|
+
# reps.each_with_index do |set, k|
|
412
|
+
# tag = "#{seg}%s-#{j + adj}(#{k + 1})" % [num > 1 ? "(#{num})" : ""]
|
413
|
+
# yield(tag, set)
|
414
|
+
# end
|
415
|
+
# next
|
416
|
+
# else
|
417
|
+
# tag = "#{seg}%s-#{j + adj}" % [num > 1 ? "(#{num})" : ""]
|
418
|
+
# yield(tag, fld)
|
419
|
+
# end
|
420
|
+
# end
|
421
|
+
# end
|
422
|
+
# end
|
423
|
+
#
|
424
|
+
# def to_pairs
|
425
|
+
# ary = []
|
426
|
+
# saw = Hash.new(0)
|
427
|
+
#
|
428
|
+
# to_a.each_with_index do |row, i|
|
429
|
+
# seg = row.first.downcase
|
430
|
+
# num = saw[seg] += 1
|
431
|
+
# msh = seg.upcase == "MSH"
|
432
|
+
# adj = msh ? 1 : 0
|
433
|
+
# row.each_with_index do |val, j|
|
434
|
+
# next if val.blank? || j == 0
|
435
|
+
# tag = "#{seg}%s-#{j + adj}" % [num > 1 ? "(#{num})" : ""]
|
436
|
+
# if !msh && val.include?(@rep)
|
437
|
+
# val.split(@rep).each_with_index do |val, k|
|
438
|
+
# ary << [tag + "(#{k + 1})", val] unless val.blank?
|
439
|
+
# end
|
440
|
+
# else
|
441
|
+
# ary << [tag, val]
|
442
|
+
# end
|
443
|
+
# end
|
444
|
+
# end
|
445
|
+
#
|
446
|
+
# ary
|
447
|
+
# end
|
448
|
+
#
|
449
|
+
# def pluck(row, *ask)
|
450
|
+
# return if ask.empty?
|
451
|
+
#
|
452
|
+
# str = (String === row) ? row : row.join(@fld)
|
453
|
+
# say = []
|
454
|
+
#
|
455
|
+
# msh = str =~ /^MSH\b/i # is this an MSH segment?
|
456
|
+
#
|
457
|
+
# ask.each do |pos|
|
458
|
+
# say.push(nil) && next if pos.nil?
|
459
|
+
# pos = pos.to_s unless pos.is_a?(String)
|
460
|
+
# pos =~ /^([A-Z]..)?(?:\((\d*)\))?[-.]?(\d+)?(?:\((\d*)\))?[-.]?(\d+)?[-.]?(\d+)?$/i
|
461
|
+
# seg = $1 && $1.upcase; raise "asked for a segment of #{seg}, but given #{str}" if (seg && seg != str[0, seg.size].upcase)
|
462
|
+
# num = $2 && $2.to_i; # this will be ignored
|
463
|
+
# fld = $3 && $3.to_i
|
464
|
+
# rep = $4 && $4.to_i
|
465
|
+
# com = $5 && $5.to_i
|
466
|
+
# sub = $6 && $6.to_i
|
467
|
+
#
|
468
|
+
# fld -= 1 if msh && fld # MSH fields are offset by one
|
469
|
+
#
|
470
|
+
# out = str.dup
|
471
|
+
# out = out.split(@fld)[fld ] if out && fld
|
472
|
+
# out = out.split(@rep)[rep - 1] if out && rep || (com && (rep ||= 1)) # default to first
|
473
|
+
# out = out.split(@com)[com - 1] if out && com
|
474
|
+
# out = out.split(@sub)[sub - 1] if out && sub
|
475
|
+
# say << (out || "")
|
476
|
+
# end
|
477
|
+
#
|
478
|
+
# say.size > 1 ? say : say.first
|
479
|
+
# end
|
480
|
+
#
|
481
|
+
# def populate(hash, want)
|
482
|
+
# list = find(*want.values)
|
483
|
+
# keys = want.keys
|
484
|
+
# keys.size == list.size or raise "mismatch (#{keys.size} keys, but #{list.size} values)"
|
485
|
+
# keys.each {|item| hash[item] = list.shift }
|
486
|
+
# hash
|
487
|
+
# end
|
488
|
+
#
|
489
|
+
# def glean(want, *rest)
|
490
|
+
# row = rest.pop if Array === rest.last || Hash === rest.last
|
491
|
+
# want = rest.unshift(want) if String === want || Numeric === want
|
492
|
+
# want, row = row, want if Array === want && Hash === row
|
493
|
+
#
|
494
|
+
# case row
|
495
|
+
# when Array
|
496
|
+
# case want
|
497
|
+
# when String, Array
|
498
|
+
# vals = pluck(row, *want)
|
499
|
+
# when Hash
|
500
|
+
# keys = want.keys
|
501
|
+
# vals = pluck(row, *want.values)
|
502
|
+
# hash = keys.zip(vals).to_h
|
503
|
+
# else raise "unable to glean X12 segments with #{want.class} types"
|
504
|
+
# end
|
505
|
+
# when nil
|
506
|
+
# case want
|
507
|
+
# when String, Array
|
508
|
+
# vals = find(*want)
|
509
|
+
# when Hash
|
510
|
+
# keys = want.keys
|
511
|
+
# vals = Array(find(*want.values)) # ensure we get an array
|
512
|
+
# hash = keys.zip(vals).to_h
|
513
|
+
# else raise "unable to glean X12 segments with #{want.class} types"
|
514
|
+
# end
|
515
|
+
# else raise "unable to glean X12 objects from #{row.class} types"
|
516
|
+
# end
|
517
|
+
# end
|
518
|
+
#
|
519
|
+
# # NOTE: this could be merged with grep()
|
520
|
+
# def slice(who, *ask)
|
521
|
+
# return if ask.empty?
|
522
|
+
#
|
523
|
+
# all = ask.map do |pos|
|
524
|
+
# pos.to_s =~ /^([A-Z]..)?(?:\((\d*)\))?[-.]?(\d+)?(?:\((\d*)\))?[-.]?(\d+)?[-.]?(\d+)?$/i
|
525
|
+
# seg = $1 && $1.upcase
|
526
|
+
# num = $2 && $2.to_i; # this will be ignored
|
527
|
+
# fld = $3 && $3.to_i or raise "invalid field specifier in '#{pos}'"
|
528
|
+
# rep = $4 && $4.to_i
|
529
|
+
# com = $5 && $5.to_i
|
530
|
+
# sub = $6 && $6.to_i
|
531
|
+
# [seg, num, fld, rep, com, sub]
|
532
|
+
# end
|
533
|
+
#
|
534
|
+
# grep(who).map do |row|
|
535
|
+
# str ||= row[0]
|
536
|
+
# msh ||= str == "msh" # is this an MSH segment?
|
537
|
+
# all.inject([]) do |ary, (seg, num, fld, rep, com, sub)|
|
538
|
+
# raise "scanning #{str} segments, but asked for #{seg}" if (seg && seg != str)
|
539
|
+
# out = row[msh ? fld - 1 : fld].dup
|
540
|
+
# out = out.split(@rep)[rep - 1] if out && rep || (com && (rep ||= 1)) # default to first
|
541
|
+
# out = out.split(@com)[com - 1] if out && com
|
542
|
+
# out = out.split(@sub)[sub - 1] if out && sub
|
543
|
+
# ary << (out || "")
|
544
|
+
# end
|
545
|
+
# end
|
546
|
+
# end
|
547
|
+
#
|
548
|
+
# def find(*ask)
|
549
|
+
# return if ask.empty?
|
550
|
+
#
|
551
|
+
# str = to_s
|
552
|
+
# say = []
|
553
|
+
#
|
554
|
+
# seg = nil
|
555
|
+
#
|
556
|
+
# ask.each do |pos|
|
557
|
+
# say.push(nil) && next if pos.nil?
|
558
|
+
# pos = pos.to_s unless pos.is_a?(String)
|
559
|
+
# pos =~ /^([A-Z]..)?(?:\((\d*|\?|\*)\))?[-.]?(\d+)?(?:\((\d*|\?|\*)\))?[-.]?(\d+)?[-.]?(\d+)?$/i or raise "bad query #{pos.inspect}"
|
560
|
+
# seg = $1 if $1; want = /^#{seg}.*/i
|
561
|
+
# num = $2 && $2.to_i; ask_num = $2 == "?"; all_num = $2 == "*"
|
562
|
+
# rep = $4 && $4.to_i; ask_rep = $4 == "?"; all_rep = $4 == "*"
|
563
|
+
# fld = $3 && $3.to_i
|
564
|
+
# com = $5 && $5.to_i
|
565
|
+
# sub = $6 && $6.to_i
|
566
|
+
#
|
567
|
+
# # p [seg, num, rep, fld, com, sub]
|
568
|
+
#
|
569
|
+
# msh = seg =~ /^MSH$/i # is this an MSH segment?
|
570
|
+
# fld -= 1 if msh && fld # MSH fields are offset by one
|
571
|
+
#
|
572
|
+
# if all_num
|
573
|
+
# raise "multi query allows only one selector" if ask.size > 1
|
574
|
+
# return str.scan(want).inject([]) do |ary, out|
|
575
|
+
# out = loop do
|
576
|
+
# out = out.split(@fld)[fld ] or break if fld
|
577
|
+
# break out.split(@rep).size if ask_rep
|
578
|
+
# out = out.split(@rep)[rep - 1] or break if rep || (com && (rep ||= 1)) # default to first
|
579
|
+
# out = out.split(@com)[com - 1] or break if com
|
580
|
+
# out = out.split(@sub)[sub - 1] or break if sub
|
581
|
+
# break out
|
582
|
+
# end
|
583
|
+
# ary << out if out
|
584
|
+
# ary
|
585
|
+
# end
|
586
|
+
# end
|
587
|
+
#
|
588
|
+
# say << loop do
|
589
|
+
# out = ""
|
590
|
+
# break str.scan( want).size if ask_num && !ask_rep
|
591
|
+
# out = str.scan( want)[num - 1] or break "" if num ||= 1 # default to first
|
592
|
+
# out = out.split(@fld)[fld ] or break "" if fld
|
593
|
+
# break out.split(@rep).size if ask_rep
|
594
|
+
# out = out.split(@rep)[rep - 1] or break "" if rep || (com && (rep ||= 1)) # default to first
|
595
|
+
# out = out.split(@com)[com - 1] or break "" if com
|
596
|
+
# out = out.split(@sub)[sub - 1] or break "" if sub
|
597
|
+
# break out
|
598
|
+
# end
|
599
|
+
# end
|
600
|
+
#
|
601
|
+
# say.size > 1 ? say : say.first
|
602
|
+
# end
|
603
|
+
end
|
604
|
+
|
605
|
+
if __FILE__ == $0
|
606
|
+
|
607
|
+
require "optparse"
|
608
|
+
|
609
|
+
trap("INT" ) { abort "\n" }
|
610
|
+
trap("PIPE") { abort "\n" } rescue nil
|
611
|
+
|
612
|
+
opts = {
|
613
|
+
# "lower" => true,
|
614
|
+
# "ignore" => true,
|
615
|
+
# "message" => true,
|
616
|
+
# "spacer" => true,
|
617
|
+
}
|
618
|
+
|
619
|
+
OptionParser.new.instance_eval do
|
620
|
+
@banner = "usage: #{program_name} [options] <file> <file> ..."
|
621
|
+
|
622
|
+
on "-a", "--after <date>" , "After (date as 'YYYYMMDD' or time as 'YYYYMMDD HHMMSS')"
|
623
|
+
on "-c", "--count" , "Count messages at the end"
|
624
|
+
on "-d", "--dive" , "Dive into directories recursively"
|
625
|
+
# on "--delim <char>" , "Delimiter to use"
|
626
|
+
# on "-f", "--fields" , "Show fields"
|
627
|
+
# on "-F", "--fields-only" , "Show fields only, not repeat indicators"
|
628
|
+
on "-h", "--help" , "Show help and command usage" do Kernel.abort to_s; end
|
629
|
+
on "-i", "--ignore" , "Ignore malformed X12 files"
|
630
|
+
on "-l", "--lower" , "Show segment names in lowercase"
|
631
|
+
on "-m", "--message" , "Show message body"
|
632
|
+
on "-p", "--path" , "Show path for each message"
|
633
|
+
# on "-q", "--query <value>" , "Query a specific value"
|
634
|
+
# on "-r", "--repeats" , "Show field repeats on their own line"
|
635
|
+
on "-s", "--spacer" , "Show an empty line between messages"
|
636
|
+
# on "-t", "--tsv" , "Tab-delimit output (tsv format)"
|
637
|
+
# on "-w", "--width <width>", "Width of segment names", Integer
|
638
|
+
|
639
|
+
Kernel.abort to_s if ARGV.empty?
|
640
|
+
self
|
641
|
+
end.parse!(into: opts) rescue abort($!.message)
|
642
|
+
|
643
|
+
opts.transform_keys!(&:to_s) # stringify keys
|
644
|
+
|
645
|
+
require "time" if opts["after"]
|
646
|
+
|
647
|
+
time = Time.parse(opts["after"]) if opts["after"]
|
648
|
+
dive = opts["dive"]
|
649
|
+
# only = opts["fields-only"] and opts["fields"] = true
|
650
|
+
# quer = opts["query"].split(',').map(&:strip) if opts["query"]
|
651
|
+
# from = quer.delete("-") if quer.is_a?(Array)
|
652
|
+
# delm = opts["tsv"] ? "\t" : (opts["delim"] || "|")
|
653
|
+
# delm = {"\\n" => "\n", "\\t" => "\t"}[delm] || delm
|
654
|
+
|
655
|
+
args = []
|
656
|
+
# args << :deep if opts["repeats"]
|
657
|
+
args << :down if opts["lower"]
|
658
|
+
args << :full if opts["message"] || opts.empty?
|
659
|
+
# args << :hide if !opts["fields"]
|
660
|
+
# args << :only if only
|
661
|
+
# args << (opts["width"].to_i.between?(1, 50) ? opts["width"].to_i : 12) if opts["width"]
|
662
|
+
|
663
|
+
msgs = 0
|
664
|
+
|
665
|
+
list = []
|
666
|
+
ARGV.push(".") if dive && ARGV.empty?
|
667
|
+
ARGV.each do |path|
|
668
|
+
if File.directory?(path)
|
669
|
+
ours = []
|
670
|
+
if dive
|
671
|
+
Find.find(path) do |item|
|
672
|
+
if File.file?(item)
|
673
|
+
if time
|
674
|
+
ours << item if File.mtime(item) > time
|
675
|
+
else
|
676
|
+
ours << item
|
677
|
+
end
|
678
|
+
end
|
679
|
+
end
|
680
|
+
else
|
681
|
+
Dir[File.join(path, "*")].each do |item|
|
682
|
+
if File.file?(item)
|
683
|
+
if time
|
684
|
+
ours << item if File.mtime(item) > time
|
685
|
+
else
|
686
|
+
ours << item
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
691
|
+
list.concat(ours.sort!)
|
692
|
+
elsif File.file?(path)
|
693
|
+
if time
|
694
|
+
list << path if File.mtime(path) > time
|
695
|
+
else
|
696
|
+
list << path
|
697
|
+
end
|
698
|
+
else
|
699
|
+
warn "WARNING: unknown item in list: #{path.inspect}"
|
700
|
+
next
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
list.each do |file|
|
705
|
+
puts if opts["spacer"] && msgs > 0
|
706
|
+
if opts["path"]
|
707
|
+
# if quer && quer.size == 1
|
708
|
+
# print "#{file}:"
|
709
|
+
# else
|
710
|
+
puts "\n==[ #{file} ]==\n\n"
|
711
|
+
# end
|
712
|
+
end
|
713
|
+
|
714
|
+
begin
|
715
|
+
str = File.open(file, "r:bom|utf-8", &:read) rescue abort("ERROR: unable to read file: \"#{file}\"")
|
716
|
+
begin
|
717
|
+
x12 = X12.new(str)
|
718
|
+
rescue
|
719
|
+
abort "ERROR: malformed X12 file: \"#{file}\" (#{$!})" unless opts["ignore"]
|
720
|
+
next
|
721
|
+
end
|
722
|
+
# if quer
|
723
|
+
# hits = *x12.find(*quer)
|
724
|
+
# hits.unshift file if opts["path"] || from
|
725
|
+
# puts hits.join(delm)
|
726
|
+
# puts if opts["path"]
|
727
|
+
# next
|
728
|
+
# end
|
729
|
+
x12.show(*args)
|
730
|
+
msgs += 1
|
731
|
+
rescue => e
|
732
|
+
warn "WARNING: #{e.message}"
|
733
|
+
end
|
734
|
+
end
|
735
|
+
|
736
|
+
if opts["count"] && msgs > 0
|
737
|
+
puts "\nTotal messages: #{msgs}"
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
__END__
|
742
|
+
|
743
|
+
x12 = X12.new
|
744
|
+
|
745
|
+
# fields
|
746
|
+
x12["seg"] = nil
|
747
|
+
x12["seg"] = ""
|
748
|
+
x12["seg"] = "a"
|
749
|
+
x12["seg"] = "a*b"
|
750
|
+
x12["seg"] = "**c*d"
|
751
|
+
x12["seg"] = "**c:e^f*d"
|
752
|
+
|
753
|
+
x12["seg"] = [nil ]
|
754
|
+
x12["seg"] = ["" ]
|
755
|
+
x12["seg"] = ["","","","" ]
|
756
|
+
x12["seg"] = ["a" ]
|
757
|
+
x12["seg"] = ["a", "b" ]
|
758
|
+
x12["seg"] = ["", "", "c", "d" ]
|
759
|
+
x12["seg"] = ["", "", "c:e^f", "d"]
|
760
|
+
x12["seg"] = ["**c:e^f", "d"]
|
761
|
+
|
762
|
+
# repeats
|
763
|
+
x12["seg-2(3)"] = "^^c:e^^"
|
764
|
+
x12["seg-2(3)"] = ""
|
765
|
+
x12["seg-2(3)"] = nil
|
766
|
+
x12["seg-2(5)"] = nil
|
767
|
+
x12["seg-2(3)"] = "a"
|
768
|
+
x12["seg-2(3)"] = "a^b"
|
769
|
+
x12["seg-2(3)"] = "^^c^d"
|
770
|
+
|
771
|
+
x12["seg-2(3)"] = [nil ]
|
772
|
+
x12["seg-2(3)"] = ["" ]
|
773
|
+
x12["seg-2(3)"] = ["a" ]
|
774
|
+
x12["seg-2(3)"] = ["a", "b" ]
|
775
|
+
x12["seg-2(3)"] = ["", "", "c", "d" ]
|
776
|
+
x12["seg-2(3)"] = ["", "", "c:e^f", "d"]
|
777
|
+
x12["seg-2(3)"] = ["c:e^f", "d"]
|
778
|
+
|
779
|
+
# components
|
780
|
+
x12["seg-2(3).1"] = "c:e"
|
781
|
+
x12["seg-2(3).4"] = ""
|
782
|
+
x12["seg-2(3).1"] = nil
|
783
|
+
x12["seg-2(5).4"] = nil
|
784
|
+
x12["seg-2(3).1"] = "a"
|
785
|
+
x12["seg-2(5).2"] = "a:b"
|
786
|
+
x12["seg-2(5).4"] = "::c:d"
|
787
|
+
|
788
|
+
x12["seg-2.1"] = [nil ]
|
789
|
+
x12["seg-2.4"] = ["" ]
|
790
|
+
x12["seg-2.1"] = ["a" ]
|
791
|
+
x12["seg-2.4"] = ["a", "b" ]
|
792
|
+
x12["seg-2.1"] = ["", "", "c", "d" ]
|
793
|
+
x12["seg-2.2"] = ["", "", "c:e:f", "d"]
|
794
|
+
x12["seg-2.4"] = ["c:e:f", "d" ]
|
795
|
+
|
796
|
+
# p x12.to_a
|
797
|
+
|
798
|
+
__END__
|
799
|
+
|
800
|
+
position fld rep com
|
801
|
+
======== === === ===
|
802
|
+
|
803
|
+
# fields
|
804
|
+
seg nil nil nil # replace fields, complete
|
805
|
+
seg-1 1 nil nil # replace fields, starting from field 1
|
806
|
+
seg-2 2 nil nil # replace fields, starting from field 2
|
807
|
+
|
808
|
+
# repeats
|
809
|
+
seg-1(1) 1 1 nil # replace repeats for field 1, starting from repeat 1
|
810
|
+
seg-1(3) 1 3 nil # replace repeats for field 1, starting from repeat 3
|
811
|
+
seg-2(1) 2 1 nil # replace repeats for field 2, starting from repeat 1
|
812
|
+
seg-2(3) 2 3 nil # replace repeats for field 2, starting from repeat 3
|
813
|
+
|
814
|
+
# components
|
815
|
+
seg-1.1 1 nil 1 # replace components for field 1, starting from component 1
|
816
|
+
seg-1.4 1 nil 4 # replace components for field 1, starting from component 4
|
817
|
+
seg-2.1 2 nil 1 # replace components for field 2, starting from component 1
|
818
|
+
seg-2.4 2 nil 4 # replace components for field 2, starting from component 4
|
819
|
+
seg-1(1).1 1 1 1 # replace components for field 1, repeat 1, starting from component 1
|
820
|
+
seg-1(1).4 1 1 4 # replace components for field 1, repeat 1, starting from component 4
|
821
|
+
seg-2(1).1 2 1 1 # replace components for field 2, repeat 1, starting from component 1
|
822
|
+
seg-2(1).4 2 1 4 # replace components for field 2, repeat 1, starting from component 4
|
823
|
+
seg-1(3).1 1 3 1 # replace components for field 1, repeat 3, starting from component 1
|
824
|
+
seg-1(3).4 1 3 4 # replace components for field 1, repeat 3, starting from component 4
|
825
|
+
seg-2(3).1 2 3 1 # replace components for field 2, repeat 3, starting from component 1
|
826
|
+
seg-2(3).4 2 3 4 # replace components for field 2, repeat 3, starting from component 4
|
827
|
+
|
828
|
+
x12 = X12.new [
|
829
|
+
"gs-2", "...",
|
830
|
+
"gs-8", "...",
|
831
|
+
"foo", "wow",
|
832
|
+
]
|
833
|
+
|
834
|
+
x12 = X12.new <<~""
|
835
|
+
ISA*00* *00* *ZZ*HT009382-001 *ZZ*HT000004-001 *240626*0906*^*00501*000923871*0*P*:~
|
836
|
+
GS*HS*HT009382-001*HT000004-001*20240626*0906*923871*X*005010X279A1~
|
837
|
+
|
838
|
+
x12.show(:down)
|
839
|
+
|
840
|
+
__END__
|
841
|
+
|
842
|
+
class DefaultArray < Array
|
843
|
+
def initialize(default_value = nil)
|
844
|
+
@default_value = default_value
|
845
|
+
super()
|
846
|
+
end
|
847
|
+
|
848
|
+
def [](index)
|
849
|
+
self[index] = @default_value if index >= size
|
850
|
+
super
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
# Usage
|
855
|
+
arr = DefaultArray.new(0) # Create a new array with default value 0
|
856
|
+
|
857
|
+
puts arr[2] # => 0 (accessing non-existent element sets it to default 0)
|
858
|
+
puts arr.inspect # => [0, 0, 0] (array is now filled with default values)
|
data/x12-lite.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "x12-lite"
|
5
|
+
s.version = `grep -m 1 '^\s*VERSION' lib/x12.rb | head -1 | cut -f 2 -d '"'`
|
6
|
+
s.author = "Steve Shreeve"
|
7
|
+
s.email = "steve.shreeve@gmail.com"
|
8
|
+
s.summary = "A " +
|
9
|
+
s.description = "Ruby gem to parse and generate X.12 transactions"
|
10
|
+
s.homepage = "https://github.com/shreeve/x12-lite"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.platform = Gem::Platform::RUBY
|
13
|
+
s.files = `git ls-files`.split("\n") - %w[.gitignore]
|
14
|
+
s.required_ruby_version = Gem::Requirement.new(">= 3.0") if s.respond_to? :required_ruby_version=
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: x12-lite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Shreeve
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ruby gem to parse and generate X.12 transactions
|
14
|
+
email: steve.shreeve@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- Gemfile
|
20
|
+
- LICENSE
|
21
|
+
- README.md
|
22
|
+
- lib/x12-lite.rb
|
23
|
+
- x12-lite.gemspec
|
24
|
+
homepage: https://github.com/shreeve/x12-lite
|
25
|
+
licenses:
|
26
|
+
- MIT
|
27
|
+
metadata: {}
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3.0'
|
37
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
requirements: []
|
43
|
+
rubygems_version: 3.5.21
|
44
|
+
signing_key:
|
45
|
+
specification_version: 4
|
46
|
+
summary: A Ruby gem to parse and generate X.12 transactions
|
47
|
+
test_files: []
|