dataMetaDom 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/History.md +6 -0
- data/PostInstall.txt +1 -0
- data/README.md +137 -0
- data/Rakefile +13 -0
- data/bin/dataMetaGvExport.rb +97 -0
- data/bin/dataMetaMySqlDdl.rb +27 -0
- data/bin/dataMetaOracleDdl.rb +26 -0
- data/bin/dataMetaPojo.rb +27 -0
- data/bin/dataMetaReVersion.rb +66 -0
- data/bin/dataMetaSameFullJ.rb +13 -0
- data/bin/dataMetaSameIdJ.rb +12 -0
- data/lib/dataMetaDom/converter.rb +70 -0
- data/lib/dataMetaDom/dataType.rb +112 -0
- data/lib/dataMetaDom/docs.rb +122 -0
- data/lib/dataMetaDom/enum.rb +125 -0
- data/lib/dataMetaDom/field.rb +182 -0
- data/lib/dataMetaDom/help.rb +41 -0
- data/lib/dataMetaDom/model.rb +274 -0
- data/lib/dataMetaDom/mySql.rb +256 -0
- data/lib/dataMetaDom/ora.rb +295 -0
- data/lib/dataMetaDom/pojo.rb +1056 -0
- data/lib/dataMetaDom/python.rb +168 -0
- data/lib/dataMetaDom/recAttr.rb +271 -0
- data/lib/dataMetaDom/record.rb +397 -0
- data/lib/dataMetaDom/ref.rb +127 -0
- data/lib/dataMetaDom/sourceFile.rb +150 -0
- data/lib/dataMetaDom/sources.rb +68 -0
- data/lib/dataMetaDom/util.rb +279 -0
- data/lib/dataMetaDom/ver.rb +244 -0
- data/lib/dataMetaDom.rb +141 -0
- data/test/test_dataMetaDom.rb +130 -0
- data/test/test_helper.rb +6 -0
- data/tmpl/java/migrationEntityEnums.erb +172 -0
- data/tmpl/python/entity.erb +50 -0
- metadata +126 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'dataMetaDom/help'
|
6
|
+
require 'dataMetaDom/field'
|
7
|
+
require 'dataMetaDom/pojo' # we borrow some useful methods from the POJO Lexer
|
8
|
+
require 'erb'
|
9
|
+
require 'ostruct'
|
10
|
+
|
11
|
+
module DataMetaDom
|
12
|
+
=begin rdoc
|
13
|
+
Definition for generating Python basic classes that do not relate to serialization layer.
|
14
|
+
|
15
|
+
For command line details either check the new method's source or the README.rdoc file, the usage section.
|
16
|
+
=end
|
17
|
+
module PythonLexer
|
18
|
+
include DataMetaDom
|
19
|
+
|
20
|
+
# Standard header for every __init__.py file
|
21
|
+
INIT_PY_HEADER = %q<
|
22
|
+
# see https://docs.python.org/3/library/pkgutil.html
|
23
|
+
# without this, Python will have trouble finding packages that share some common tree off the root
|
24
|
+
from pkgutil import extend_path
|
25
|
+
__path__ = extend_path(__path__, __name__)
|
26
|
+
|
27
|
+
>
|
28
|
+
|
29
|
+
# The top of each model file.
|
30
|
+
MODEL_HEADER = %q|# This file is generated by DataMeta DOM. Do not edit manually!
|
31
|
+
# package %s
|
32
|
+
|
33
|
+
import re
|
34
|
+
|
35
|
+
from ebay_datameta_core.base import Verifiable, DateTime, Migrator, SemVer
|
36
|
+
from ebay_datameta_core.canned_re import CannedRe
|
37
|
+
|
38
|
+
# noinspection PyCompatibility
|
39
|
+
from enum import Enum
|
40
|
+
|
41
|
+
|
|
42
|
+
|
43
|
+
# Augment the RegExRoster class with Python specifics
|
44
|
+
class PyRegExRoster < RegExRoster
|
45
|
+
# converts the registry to the Python variable -- compiled Re
|
46
|
+
def to_patterns
|
47
|
+
i_to_r.keys.map { |ix|
|
48
|
+
r = i_to_r[ix]
|
49
|
+
rx = r.r.to_s
|
50
|
+
%<#{RegExRoster.ixToVarName(ix)} = re.compile(#{rx.inspect}) # #{r.vars.to_a.sort.join(', ')}>
|
51
|
+
}.join("\n#{INDENT}")
|
52
|
+
end
|
53
|
+
|
54
|
+
# converts the registry to the verification code for the verify() method
|
55
|
+
def to_verifications(baseName)
|
56
|
+
result = (canned.keys.map { |r|
|
57
|
+
r = canned[r]
|
58
|
+
vs = r.vars.to_a.sort
|
59
|
+
vs.map { |v|
|
60
|
+
rx = r.r.to_s
|
61
|
+
%<\n#{INDENT*2}if(#{r.req? ? '' : "self.__#{v} is not None and "}CannedRe.CANNED_RES[#{rx.inspect}].match(self.__#{v}) is None):
|
62
|
+
#{INDENT*3}raise AttributeError("Property \\"#{v}\\" == {{%s}} didn't match canned expression \\"#{rx}\\"" % self.__#{v} )>
|
63
|
+
}
|
64
|
+
}).flatten
|
65
|
+
(result << i_to_r.keys.map { |ix|
|
66
|
+
r = i_to_r[ix]
|
67
|
+
vs = r.vars.to_a.sort
|
68
|
+
rv = "#{baseName}.#{RegExRoster.ixToVarName(ix)}"
|
69
|
+
vs.map { |v|
|
70
|
+
%<\n#{INDENT*2}if(#{r.req? ? '' : "self.__#{v} is not None and "}#{rv}.match(self.__#{v}) is None):
|
71
|
+
#{INDENT*3}raise AttributeError("Property \\"#{v}\\" == {{%s}} didn't match custom expression {{%s}}" %(self.__#{v}, #{rv}))>
|
72
|
+
}
|
73
|
+
}).flatten
|
74
|
+
result.join("\n")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
=begin rdoc
|
79
|
+
Generates Python sources for the model, the "plain" Python part, without fancy dependencies.
|
80
|
+
* Parameters
|
81
|
+
* +parser+ - instance of Model
|
82
|
+
* +outRoot+ - output directory
|
83
|
+
=end
|
84
|
+
def genPy(model, outRoot)
|
85
|
+
firstRecord = model.records.values.first
|
86
|
+
@pyPackage, baseName, packagePath = DataMetaDom::PojoLexer::assertNamespace(firstRecord.name)
|
87
|
+
|
88
|
+
# Next: replace dots with underscores.
|
89
|
+
# The path also adjusted accordingly.
|
90
|
+
#
|
91
|
+
# Rationale for this, quoting PEP 8:
|
92
|
+
#
|
93
|
+
# Package and Module Names
|
94
|
+
#
|
95
|
+
# Modules should have short, all-lowercase names. Underscores can be used in the module name if it improves
|
96
|
+
# readability. Python packages should also have short, all-lowercase names, although the use of underscores
|
97
|
+
# is discouraged.
|
98
|
+
#
|
99
|
+
# Short and all-lowercase names, and improving readability if you have complex system and need long package names,
|
100
|
+
# is "discouraged". Can't do this here, our system is more complicated for strictly religous, "pythonic" Python.
|
101
|
+
# A tool must be enabling, and in this case, this irrational ruling gets in the way.
|
102
|
+
# And dots are a no-no, Python can't find packages with complicated package structures and imports.
|
103
|
+
#
|
104
|
+
# Hence, we opt for long package names with underscores for distinctiveness and readability:
|
105
|
+
@pyPackage = @pyPackage.gsub('.', '_')
|
106
|
+
packagePath = packagePath.gsub('/', '_')
|
107
|
+
destDir = File.join(outRoot, packagePath)
|
108
|
+
FileUtils.mkdir_p destDir
|
109
|
+
# build the package path and create __init__.py files in each package's dir if it's not there.
|
110
|
+
@destDir = packagePath.split(File::SEPARATOR).reduce(outRoot){ |s,v|
|
111
|
+
r = s.empty? ? v : File.join(s, v) # next package dir
|
112
|
+
FileUtils.mkdir_p r # create if not there
|
113
|
+
# create the __init__.py with proper content if it's not there
|
114
|
+
df = File.join(r, '__init__.py'); IO.write(df, INIT_PY_HEADER, mode: 'wb') unless r == outRoot || File.file?(df)
|
115
|
+
r # pass r for the next reduce iteration
|
116
|
+
}
|
117
|
+
vars = OpenStruct.new # for template's local variables. ERB does not make them visible to the binding
|
118
|
+
modelFile = File.join(@destDir, 'model.py')
|
119
|
+
[modelFile].each{|f| FileUtils.rm f if File.file?(f)}
|
120
|
+
IO.write(modelFile, MODEL_HEADER % @pyPackage, mode: 'wb')
|
121
|
+
(model.records.values + model.enums.values).each { |srcE| # it is important that the records render first
|
122
|
+
_, baseName, _ = DataMetaDom::PojoLexer::assertNamespace(srcE.name)
|
123
|
+
|
124
|
+
pyClassName = baseName
|
125
|
+
|
126
|
+
case
|
127
|
+
when srcE.kind_of?(DataMetaDom::Record)
|
128
|
+
fields = srcE.fields
|
129
|
+
rxRoster = PyRegExRoster.new
|
130
|
+
eqHashFields = srcE.identity ? srcE.identity.args : fields.keys.sort
|
131
|
+
reqFields = fields.values.select{|f| f.isRequired }.map{|f| f.name}
|
132
|
+
verCalls = reqFields.map{|r| %<if(self.__#{r} is None): missingFields.append("#{r}");>}.join("\n#{INDENT * 2}")
|
133
|
+
fieldVerifications = ''
|
134
|
+
fields.each_key { |k|
|
135
|
+
f = fields[k]
|
136
|
+
dt = f.dataType
|
137
|
+
rxRoster.register(f) if f.regex
|
138
|
+
if f.trgType # Maps: if either the key or the value is verifiable, do it
|
139
|
+
mainVf = model.records[dt.type] # main data type is verifiable
|
140
|
+
trgVf = model.records[f.trgType.type] # target type is verifiable
|
141
|
+
if mainVf || trgVf
|
142
|
+
fieldVerifications << "\n#{INDENT*2}#{!f.isRequired ? "if(self.__#{f.name} is not None):\n#{INDENT*3}" : '' }for k, v in self.__#{f.name}.iteritems():#{mainVf ? 'k.verify();' : ''} #{trgVf ? 'v.verify()' :''}\n"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if model.records[dt.type] && !f.trgType # maps handled separately
|
147
|
+
fieldVerifications << "\n#{INDENT*2}#{!f.isRequired ? "if(self.__#{f.name} is not None): " : '' }#{f.aggr ? "[v___#{f.name}.verify() for v___#{f.name} in self.__#{f.name} ]" : "self.__#{f.name}.verify()"}"
|
148
|
+
# the Verifiable::verify method reference works just fine, tested it: Java correctly calls the method on the object
|
149
|
+
end
|
150
|
+
}
|
151
|
+
IO::write(File.join(@destDir, 'model.py'),
|
152
|
+
ERB.new(IO.read(File.join(File.dirname(__FILE__), '../../tmpl/python/entity.erb')),
|
153
|
+
$SAFE, '%<>').result(binding), mode: 'ab')
|
154
|
+
when srcE.kind_of?(DataMetaDom::Mappings)
|
155
|
+
# FIXME -- implement!!!
|
156
|
+
when srcE.kind_of?(DataMetaDom::Enum)
|
157
|
+
IO.write(modelFile, %|#{baseName} = Enum("#{baseName}", "#{srcE.rawVals.join(' ')}")\n\n|, mode: 'ab')
|
158
|
+
# # handled below, bundled in one huge file
|
159
|
+
when srcE.kind_of?(DataMetaDom::BitSet)
|
160
|
+
# FIXME -- implement!!!
|
161
|
+
else
|
162
|
+
raise "Unsupported Entity: #{srcE.inspect}"
|
163
|
+
end
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
%w(fileutils set).each { |r| require r }
|
4
|
+
|
5
|
+
module DataMetaDom
|
6
|
+
|
7
|
+
=begin rdoc
|
8
|
+
Record Attribute such as unique fields set, identity information, indexes, references etc
|
9
|
+
the common structure is like this:
|
10
|
+
keyword (hint1, hint2, hint3...) arg1, arg2
|
11
|
+
|
12
|
+
For command line details either check the new method's source or the README.rdoc file, the usage section.
|
13
|
+
=end
|
14
|
+
class RecAttr
|
15
|
+
|
16
|
+
=begin rdoc
|
17
|
+
The keyword for the attribute.
|
18
|
+
=end
|
19
|
+
attr_reader :keyword
|
20
|
+
|
21
|
+
=begin rdoc
|
22
|
+
A Set of hints, empty set if there are no hints on this attribute.
|
23
|
+
=end
|
24
|
+
attr_reader :hints
|
25
|
+
|
26
|
+
=begin rdoc
|
27
|
+
Arguments on this attribute if any, an array in the order as listed in the DataMeta DOM source. Order is important,
|
28
|
+
for example, for an identity.
|
29
|
+
=end
|
30
|
+
attr_reader :args
|
31
|
+
|
32
|
+
=begin rdoc
|
33
|
+
Unique key for the given attribute to distinguish between those and use in a map. Rebuilt by getKey method
|
34
|
+
defined on the subclasses.
|
35
|
+
=end
|
36
|
+
attr_reader :key
|
37
|
+
|
38
|
+
=begin rdoc
|
39
|
+
Determines if this attribute has the given hint.
|
40
|
+
=end
|
41
|
+
def hasHint?(hint)
|
42
|
+
@hints.member?(hint)
|
43
|
+
end
|
44
|
+
|
45
|
+
=begin rdoc
|
46
|
+
Creates an instance for the given keyword.
|
47
|
+
=end
|
48
|
+
def initialize(keyword)
|
49
|
+
@keyword = keyword.to_sym
|
50
|
+
raise "Unsupported keyword #@keyword" unless REC_ATTR_KEYWORDS.member?(@keyword)
|
51
|
+
@args = []; @hints = Set.new
|
52
|
+
end
|
53
|
+
|
54
|
+
=begin rdoc
|
55
|
+
Adds the given argument, updates the key
|
56
|
+
=end
|
57
|
+
def addArg(val)
|
58
|
+
@args << val.to_sym
|
59
|
+
updateKey
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
=begin rdoc
|
64
|
+
Adds the given hint.
|
65
|
+
=end
|
66
|
+
def addHint(val); @hints << val end
|
67
|
+
|
68
|
+
=begin rdoc
|
69
|
+
Adds an array of arguments.
|
70
|
+
=end
|
71
|
+
def addArgs(vals); vals.each { |v| addArg v }; self end
|
72
|
+
|
73
|
+
=begin rdoc
|
74
|
+
Adds a collection of hints.
|
75
|
+
=end
|
76
|
+
def addHints(vals); vals.each { |h| addHint h }; self end
|
77
|
+
|
78
|
+
=begin rdoc
|
79
|
+
Updates the key, returns self for call chaining
|
80
|
+
=end
|
81
|
+
def updateKey; @key = getKey; self end
|
82
|
+
|
83
|
+
=begin rdoc
|
84
|
+
Returns the count of arguments.
|
85
|
+
=end
|
86
|
+
def length; @args.length end
|
87
|
+
|
88
|
+
=begin rdoc
|
89
|
+
Returns the arguments in the given position, zero-based.
|
90
|
+
=end
|
91
|
+
def [](index); @args[index] end
|
92
|
+
|
93
|
+
|
94
|
+
=begin rdoc
|
95
|
+
Joins the arguments with the given delimiter.
|
96
|
+
=end
|
97
|
+
def join(delimiter); @args.join(delimiter) end
|
98
|
+
|
99
|
+
=begin rdoc
|
100
|
+
Parses this instance from the given source.
|
101
|
+
* Parameter:
|
102
|
+
* +source+ - an instance of SourceFile
|
103
|
+
=end
|
104
|
+
def parse(source)
|
105
|
+
@sourceRef = source.snapshot
|
106
|
+
line = source.line
|
107
|
+
recAttrMatch = line.scan(/^\s*(\w*)\s*(\([^\)]+\))?\s+(.+)$/)
|
108
|
+
raise "Invalid record attribute spec '#{line}'" unless recAttrMatch
|
109
|
+
keyw, hintList, argList = recAttrMatch[0]
|
110
|
+
raise "Wrong keyword '#{keyw}', '#@keyword' expected instead" unless keyw && keyw.to_sym == @keyword
|
111
|
+
@args = argList.split(/[\(\)\s\,]+/).map { |a| a.to_sym }
|
112
|
+
if hintList
|
113
|
+
@hints = Set.new hintList.split(/[\(\)\s\,]+/).select { |h| !h.strip.empty? }.map { |h| h.strip.to_sym }
|
114
|
+
else
|
115
|
+
@hints = Set.new
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# textual representation of this instance
|
120
|
+
def to_s; "#@keyword:#@key; #@sourceRef" end
|
121
|
+
|
122
|
+
private :initialize
|
123
|
+
end
|
124
|
+
|
125
|
+
=begin rdoc
|
126
|
+
The record attribute with the unordered set of arguments.
|
127
|
+
See the RecAttrList for the ordered list implementation.
|
128
|
+
=end
|
129
|
+
class RecAttrSet < RecAttr
|
130
|
+
|
131
|
+
# Unordered unique set of the arguments
|
132
|
+
attr_reader :argSet
|
133
|
+
|
134
|
+
# Creates an instance with the given keyword
|
135
|
+
def initialize(keyword); super(keyword); @argSet = Set.new end
|
136
|
+
|
137
|
+
# Engages the super's parse method via the alias
|
138
|
+
alias :recAttrParse :parse
|
139
|
+
|
140
|
+
# Determines if the instance has the given argument
|
141
|
+
def hasArg?(arg); argSet.member?(arg) end
|
142
|
+
|
143
|
+
# Builds textual for the set of the arguments, for diagnostics.
|
144
|
+
def argSetTextual; @argSet.map { |a| a.to_s }.sort.join(':') end
|
145
|
+
|
146
|
+
# Builds the unique key for the set of arguments on the instance
|
147
|
+
def getKey; argSetTextual.to_sym end
|
148
|
+
|
149
|
+
# Adds the given argument to the instance
|
150
|
+
def addArg(val)
|
151
|
+
k = val.to_sym
|
152
|
+
raise "Duplicate arg #{k} in the set of #{argSetTextual}" if @argSet.member?(k)
|
153
|
+
@argSet << k
|
154
|
+
#RecAttr.instance_method(:addArg).bind(self).call k - fortunately, overkill in this case, can do with just:
|
155
|
+
super k
|
156
|
+
end
|
157
|
+
|
158
|
+
=begin rdoc
|
159
|
+
Parses the instance from the given source.
|
160
|
+
* Parameters
|
161
|
+
* +src+ - an instance of SourceFile
|
162
|
+
=end
|
163
|
+
def parse(src)
|
164
|
+
recAttrParse(src)
|
165
|
+
# look if there are any duplicates, if there are it's an error:
|
166
|
+
counterHash = Hash.new(0)
|
167
|
+
args.each { |a| k=a.to_sym; counterHash[k] += 1 }
|
168
|
+
dupes = []; counterHash.each { |k, v| dupes << k if v > 1 }
|
169
|
+
raise "Duplicate arguments for #{self} - [#{dupes.join(',')}]" unless dupes.empty?
|
170
|
+
@argSet = Set.new(args)
|
171
|
+
updateKey
|
172
|
+
self
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
=begin rdoc
|
177
|
+
The record attribute with the ordered list of arguments.
|
178
|
+
See RecAttrSet for the unordered set implementation.
|
179
|
+
=end
|
180
|
+
class RecAttrList < RecAttr
|
181
|
+
|
182
|
+
# Engages the super's parse method via the alias
|
183
|
+
alias :recAttrParse :parse
|
184
|
+
|
185
|
+
# Creates an instance with the given keyword
|
186
|
+
def initialize(keyword); super(keyword) end
|
187
|
+
|
188
|
+
# Builds the unique key for the list of arguments on the instance
|
189
|
+
def getKey; @args.map { |a| a.to_s }.join(':').to_sym end
|
190
|
+
|
191
|
+
=begin rdoc
|
192
|
+
Parses the instance from the given source preserving the order of arguments, returns self for call chaining.
|
193
|
+
* Parameters
|
194
|
+
* +src+ - an instance of SourceFile
|
195
|
+
=end
|
196
|
+
def parse(src)
|
197
|
+
recAttrParse(src)
|
198
|
+
updateKey
|
199
|
+
self
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
=begin rdoc
|
204
|
+
Record attrubute "<tt>unique</tt>"
|
205
|
+
=end
|
206
|
+
class RecUnique < RecAttrSet
|
207
|
+
|
208
|
+
# Creates an instance with the keyword "<tt>unique</tt>"
|
209
|
+
def initialize
|
210
|
+
#noinspection RubyArgCount
|
211
|
+
super(UNIQUE)
|
212
|
+
end
|
213
|
+
|
214
|
+
=begin rdoc
|
215
|
+
Attempts to consume the "<tt>unique</tt>" attribute for the given Record from the given source.
|
216
|
+
* Parameters
|
217
|
+
* +source+ - an instance of SourceFile
|
218
|
+
=end
|
219
|
+
def self.consumed?(source, record)
|
220
|
+
source.line =~ /^#{UNIQUE}\W.+$/ ? record.addUnique(RecUnique.new.parse(source)) : nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
=begin rdoc
|
225
|
+
Record attrubute "<tt>identity</tt>"
|
226
|
+
=end
|
227
|
+
class RecIdentity < RecAttrSet
|
228
|
+
|
229
|
+
# Creates an instance with the keyword "<tt>identity</tt>"
|
230
|
+
def initialize
|
231
|
+
#noinspection RubyArgCount
|
232
|
+
super(IDENTITY)
|
233
|
+
end
|
234
|
+
|
235
|
+
=begin rdoc
|
236
|
+
Attempts to consume the "<tt>identity</tt>" attribute for the given Record from the given source.
|
237
|
+
* Parameters
|
238
|
+
* +source+ - an instance of SourceFile
|
239
|
+
=end
|
240
|
+
def self.consumed?(source, record)
|
241
|
+
source.line =~ /^#{IDENTITY}\W+.+$/ ? record.identity = RecIdentity.new.parse(source) : nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
=begin rdoc
|
246
|
+
Record attrubute "<tt>index</tt>"
|
247
|
+
=end
|
248
|
+
class RecIndex < RecAttrList
|
249
|
+
|
250
|
+
# Creates an instance with the keyword "<tt>index</tt>"
|
251
|
+
def initialize
|
252
|
+
#noinspection RubyArgCount
|
253
|
+
super(INDEX)
|
254
|
+
end
|
255
|
+
|
256
|
+
=begin rdoc
|
257
|
+
Attempts to consume the "<tt>index</tt>" attribute for the given Record from the given source.
|
258
|
+
* Parameters
|
259
|
+
* +source+ - an instance of SourceFile
|
260
|
+
=end
|
261
|
+
def self.consumed?(source, record)
|
262
|
+
source.line =~ /^#{INDEX}\W+.+$/ ? record.addIndex(RecIndex.new.parse(source)) : nil
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
=begin rdoc
|
267
|
+
An array of record level parse token classes, namely RecIdentity, RecIndex, RecUnique
|
268
|
+
=end
|
269
|
+
RECORD_LEVEL_TOKENS=[RecIdentity, RecIndex, RecUnique]
|
270
|
+
|
271
|
+
end
|