dataMetaPii 1.0.1
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/.yardopts +1 -0
- data/History.md +10 -0
- data/PostInstall.txt +2 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/bin/dmPiiGenCode.rb +65 -0
- data/grammar/appLink.treetop +214 -0
- data/grammar/piiCommons.treetop +46 -0
- data/grammar/registry.treetop +66 -0
- data/lib/dataMetaPii.rb +577 -0
- data/test/appLinkFull.dmPii +71 -0
- data/test/appLinkNoReusables.dmPii +35 -0
- data/test/appLinkShowcase.dmPii +73 -0
- data/test/registryShowcase.dmPii +28 -0
- data/test/test_dataMetaPii.rb +42 -0
- data/test/test_grammars.rb +152 -0
- data/test/test_helper.rb +24 -0
- data/tpl/java/master.erb +64 -0
- data/tpl/json/master.erb +7 -0
- data/tpl/python/master.erb +25 -0
- data/tpl/scala/master.erb +31 -0
- metadata +106 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# PII Master Registry DSL grammar
|
2
|
+
# to debug, uncomment next line and add this anywhere: &{|s| debugger; true }
|
3
|
+
#require 'ruby-debug'
|
4
|
+
|
5
|
+
grammar PiiRegistry
|
6
|
+
|
7
|
+
# Load the commons from the DataMetaParse which lives in:
|
8
|
+
# binary: http://FIXME
|
9
|
+
# documentation: http://FIXME
|
10
|
+
# source: https://FIXME
|
11
|
+
#include DataMetaCommonsRoot
|
12
|
+
include PiiCommons
|
13
|
+
|
14
|
+
# Master rule
|
15
|
+
rule piiDef
|
16
|
+
piiWss? verDef:version piiWss? fields:piiFields piiWs?
|
17
|
+
end
|
18
|
+
|
19
|
+
# PII fields definitions - not much, just a list of PII keys with attributes one or more.
|
20
|
+
rule piiFields
|
21
|
+
piiAttrbs+ {
|
22
|
+
# the type method makes it simple to distinguish between the rules; the Treetop standard API does not provide such facility yet
|
23
|
+
def type; 'piiFields' end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# The PII key with attributes (one or more), i.e. key=value, optionally ornamented/separated by comments, whitespaces including newlines
|
28
|
+
rule piiAttrbs
|
29
|
+
piiWs? fldKey:piiKey piiWs? '{' piiWs? attrbLst:attrbList piiWs? '}' {
|
30
|
+
def type
|
31
|
+
'piiAttrbs'
|
32
|
+
end
|
33
|
+
def pk; fldKey.text_value end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
# most complicated rule in this grammar: attribute list, i.e. key=value, optionally ornamented/separated by comments, whitespaces including newlines
|
38
|
+
rule attrbList
|
39
|
+
attrb (',' w attrb)* {
|
40
|
+
def type; 'attrbList' end
|
41
|
+
|
42
|
+
# Main method for the AST traversal to collect the attributes
|
43
|
+
def attrbs
|
44
|
+
DataMetaPii::digAstElsType('keySym', elements)
|
45
|
+
end
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
# attribute: for now, kvSym only
|
50
|
+
rule attrb
|
51
|
+
kvs:kvSym {
|
52
|
+
def type; 'attrb' end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
# Key-Value, where value is a symbol
|
57
|
+
# surrounding quotes.
|
58
|
+
rule kvSym
|
59
|
+
key:symbol w '=' w val:symbol
|
60
|
+
{
|
61
|
+
def type; 'keySym' end
|
62
|
+
def k; key.text_value end
|
63
|
+
def v; val.text_value end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
data/lib/dataMetaPii.rb
ADDED
@@ -0,0 +1,577 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'dataMetaDom'
|
4
|
+
require 'dataMetaDom/ver'
|
5
|
+
require 'treetop'
|
6
|
+
require 'dataMetaParse'
|
7
|
+
require 'bigdecimal'
|
8
|
+
require 'set'
|
9
|
+
require 'logger'
|
10
|
+
require 'erb'
|
11
|
+
require 'fileutils'
|
12
|
+
require 'pathname'
|
13
|
+
|
14
|
+
=begin rdoc
|
15
|
+
PII support for DataMeta
|
16
|
+
|
17
|
+
For command line details either check the new method's source or the README file, the usage section.
|
18
|
+
|
19
|
+
""
|
20
|
+
=end
|
21
|
+
module DataMetaPii
|
22
|
+
# Logger to use for this module
|
23
|
+
L = Logger.new("#{File.basename(__FILE__)[0..-4]}.log", 0, 10_000_000)
|
24
|
+
|
25
|
+
L.datetime_format = '%Y-%m-%d %H:%M:%S'
|
26
|
+
|
27
|
+
# Determine the gem root in the local Filesystem
|
28
|
+
GEM_ROOT = Pathname.new(File.join(File.dirname(__FILE__), '..')).cleanpath
|
29
|
+
# Advance further to determine the root of the grammars
|
30
|
+
GRAMMAR_ROOT = File.join(GEM_ROOT, 'grammar')
|
31
|
+
|
32
|
+
# Current version
|
33
|
+
VERSION = '1.0.1'
|
34
|
+
# Load base rules from the DataMeta Parsing Commons
|
35
|
+
BASE_RULES = DataMetaParse.loadBaseRulz
|
36
|
+
# Load PII specific common rules from this very gem's codebase
|
37
|
+
PII_COMMONS = Treetop.load(File.join(GRAMMAR_ROOT, 'piiCommons'))
|
38
|
+
# Load the PII Registry grammar
|
39
|
+
REGISTRY = Treetop.load(File.join(GRAMMAR_ROOT, 'registry'))
|
40
|
+
# Load the PII Applications Link grammar
|
41
|
+
APP_LINK = Treetop.load(File.join(GRAMMAR_ROOT, 'appLink'))
|
42
|
+
L.info %<
|
43
|
+
Loaded base rules: #{DataMetaPii::BASE_RULES}
|
44
|
+
Loaded PII Commons Rules: #{DataMetaPii::PII_COMMONS.inspect}
|
45
|
+
Loaded Regstry Rules: #{DataMetaPii::REGISTRY}
|
46
|
+
Loaded AppLink Rules: #{DataMetaPii::APP_LINK}
|
47
|
+
>
|
48
|
+
|
49
|
+
# Create all parsers, it's not expensive. First the Registry (Abstract Defs) grammar parser:
|
50
|
+
REGISTRY_PARSER = PiiRegistryParser.new
|
51
|
+
# And the App Link grammar parser:
|
52
|
+
APP_LINK_PARSER = PiiAppLinkParser.new
|
53
|
+
|
54
|
+
# Value Object Class Key
|
55
|
+
VO_CLASS_KEY = :voClass
|
56
|
+
# Value Reference Key
|
57
|
+
REF_KEY = :ref
|
58
|
+
# Constant value key
|
59
|
+
CONST_KEY = :const
|
60
|
+
# One step for indentation of the output
|
61
|
+
INDENT = ' ' * 4
|
62
|
+
# Constant Data type: string
|
63
|
+
STR_CONST_DT = :str
|
64
|
+
# Constant Data type:
|
65
|
+
INT_CONST_DT = :int
|
66
|
+
# Constant Data type:
|
67
|
+
DECIMAL_CONST_DT = :dec
|
68
|
+
|
69
|
+
# AST Node type
|
70
|
+
ATTRB_LIST_NODE_TYPE = :attrbList
|
71
|
+
# AST Node type
|
72
|
+
ATTRB_DEF_NODE_TYPE = :attrbDef
|
73
|
+
|
74
|
+
# Module-holder of the impact constants
|
75
|
+
# In a language that support enums, that'd be an enum.
|
76
|
+
module Impact
|
77
|
+
# Impact Level: Public
|
78
|
+
PUBLIC = :public
|
79
|
+
# Impact Level: Confidential
|
80
|
+
CONFIDENTIAL = :confidential
|
81
|
+
# Impact Level: Internal
|
82
|
+
INTERNAL = :internal
|
83
|
+
# Impact Level: Restricted
|
84
|
+
RESTRICTED = :restricted
|
85
|
+
# Impact Level: Taboo
|
86
|
+
TABOO = :taboo
|
87
|
+
end
|
88
|
+
|
89
|
+
# Supported export (generation) formats
|
90
|
+
module ExportFmts
|
91
|
+
# Code generation format: Java
|
92
|
+
JAVA = :java
|
93
|
+
# Code generation format: Scala
|
94
|
+
SCALA = :scala
|
95
|
+
# Code generation format: Python
|
96
|
+
PYTHON = :python
|
97
|
+
# Code generation format: JSON
|
98
|
+
JSON = :json
|
99
|
+
end
|
100
|
+
|
101
|
+
# Collect all the constants from the module ExportFmts, that's all supported formats
|
102
|
+
ALL_FMTS = ExportFmts.constants.map{|c| ExportFmts.const_get(c)}
|
103
|
+
|
104
|
+
# All supported impacts collected from the module Impact
|
105
|
+
ALL_IMPACTS = Impact.constants.map{|c| Impact.const_get(c)}
|
106
|
+
|
107
|
+
# PII field definition scope: master vs application
|
108
|
+
module Scope
|
109
|
+
# Abstract fields scope
|
110
|
+
ABSTRACT = :abstract
|
111
|
+
# Application scope
|
112
|
+
APPLICATION = :app
|
113
|
+
end
|
114
|
+
|
115
|
+
# All supported scopes collected from the module Scope
|
116
|
+
ALL_SCOPES = Scope.constants.map{|c| Scope.const_get(c)}
|
117
|
+
|
118
|
+
=begin rdoc
|
119
|
+
PII Registry Key Value Object, encapsulates the PII Registry Key with attributes
|
120
|
+
@!attribute [r] key
|
121
|
+
@return [String] unique key for the given enum
|
122
|
+
|
123
|
+
@!attribute [r] level
|
124
|
+
@return [Symbol] the impact level
|
125
|
+
|
126
|
+
=end
|
127
|
+
class RegKeyVo
|
128
|
+
# The map key for the level
|
129
|
+
LEVEL = 'level'
|
130
|
+
|
131
|
+
attr_accessor :key, :level, :attrs
|
132
|
+
=begin rdoc
|
133
|
+
Creates an instance for the given parameters, see the properties with the same names.
|
134
|
+
=end
|
135
|
+
def initialize(key, attrs)
|
136
|
+
@key, @attrs = key, attrs
|
137
|
+
# single the level out:
|
138
|
+
levelSpec = attrs[LEVEL]
|
139
|
+
|
140
|
+
raise ArgumentError, %<Impact level missing or empty in #{@attrs.inspect}> unless levelSpec && !levelSpec.empty?
|
141
|
+
|
142
|
+
@level = levelSpec.to_sym
|
143
|
+
|
144
|
+
raise ArgumentError, %<Unsupported Impact Level #{@attrs.inspect}. Supported levels are: #{
|
145
|
+
ALL_IMPACTS.map(&:to_s).join(', ')}> unless ALL_IMPACTS.member?(@level)
|
146
|
+
|
147
|
+
raise ArgumentError, %<Invalid PII key: "#{@key}"> unless @key =~ /^\w+$/
|
148
|
+
end
|
149
|
+
|
150
|
+
# textual representation of this instance
|
151
|
+
def to_s; "#{key}(#{@level})" end
|
152
|
+
|
153
|
+
# Builds a textual tree image for logging and/or console output
|
154
|
+
def to_tree_image(indent = '')
|
155
|
+
next_ident = indent + DataMetaPii::INDENT
|
156
|
+
%<#{indent}#{@key}:
|
157
|
+
#{next_ident}#{@attrs.keys.map{|k| "#{k}=#{@attrs[k]}"}.join("\n#{next_ident}")}>
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Versioned Value Object common ancestor class
|
162
|
+
class VersionedVo
|
163
|
+
attr_accessor :ver
|
164
|
+
def initialize(verDef)
|
165
|
+
@ver = case verDef.class
|
166
|
+
when String.class
|
167
|
+
DataMetaDom::SemVer.new(verDef)
|
168
|
+
when DataMetaDom::SemVer.class
|
169
|
+
verDef
|
170
|
+
else
|
171
|
+
raise ArgumentError, %<Unsupported verDefsion type: #{verDef.class} == #{verDef.inspect}>
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Registry Value Object - a wrap around the PII Key to attributes map
|
178
|
+
class RegVo < VersionedVo
|
179
|
+
attr_accessor :keyVos
|
180
|
+
def initialize(ver, vos)
|
181
|
+
super(ver)
|
182
|
+
@keyVos = vos
|
183
|
+
end
|
184
|
+
|
185
|
+
# Builds a textual tree image for logging and/or console output
|
186
|
+
def to_tree_image(indent = '')
|
187
|
+
next_ident = indent + DataMetaPii::INDENT
|
188
|
+
%<#{@keyVos.keys.map{|k| @keyVos[k].to_tree_image(next_ident)}.join("\n")}>
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Attribute definition ancestor - should be abstract but Ruby does not let define abstract classes easily.
|
193
|
+
class AlAttrDef
|
194
|
+
attr_accessor :key, :val
|
195
|
+
def initialize(key, val)
|
196
|
+
@key, @val = key, val
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Defines a value for a VO Class
|
201
|
+
class AlAttrVoClass < AlAttrDef
|
202
|
+
def initialize(voClass)
|
203
|
+
super(VO_CLASS_KEY, voClass)
|
204
|
+
raise ArgumentError, %<Wrong type for VO Class: #{voClass.class}=#{voClass.inspect}> unless voClass.is_a?(String)
|
205
|
+
end
|
206
|
+
# String representation
|
207
|
+
def to_s; %<#{self.class.name}{#{val}}> end
|
208
|
+
end
|
209
|
+
# Attribute reference
|
210
|
+
class AttrRef
|
211
|
+
attr_accessor :key
|
212
|
+
def initialize(key); @key = key end
|
213
|
+
# String representation
|
214
|
+
def to_s; %<#{self.class.name}{#{@key}}> end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Attribute section with constants, references and a VO class optionally defined
|
218
|
+
class AttrSect
|
219
|
+
attr_reader :key, :refs, :consts, :voClass
|
220
|
+
def initialize(key)
|
221
|
+
@key = key
|
222
|
+
@refs = Hash.new(*[])
|
223
|
+
@consts = Hash.new(*[])
|
224
|
+
@voClass = nil
|
225
|
+
end
|
226
|
+
|
227
|
+
# Add a new value to this section, depending on the incoming type
|
228
|
+
def +(val)
|
229
|
+
case val
|
230
|
+
when AlAttrVoClass
|
231
|
+
raise RuntimeError, %<Attempt to redefine VO Class on "#{@key}"> if @voClass
|
232
|
+
@voClass = val
|
233
|
+
|
234
|
+
when AttrRef
|
235
|
+
raise RuntimeError,
|
236
|
+
%<Reference to "#{val.key}" specified more than once on the attribute section "#{
|
237
|
+
@key}"> if @refs.has_key?(val.key.to_sym)
|
238
|
+
|
239
|
+
@refs[val.key.to_sym] = val
|
240
|
+
|
241
|
+
when AlAttrStr, AlAttrInt, AlAttrDec
|
242
|
+
raise RuntimeError,
|
243
|
+
%<Constant "#{val.key}" specified more than once on "#{@key}"> if @consts.has_key?(val.key.to_sym)
|
244
|
+
|
245
|
+
@consts[val.key.to_sym] = val
|
246
|
+
else
|
247
|
+
raise ArgumentError, %<Unsupported attribute type #{val.class} = #{val.inspect}>
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# String representation
|
252
|
+
def to_s
|
253
|
+
%<#{self.class.name}{VO=#{@voClass}, Refs=#{@refs.inspect}, Const=#{@consts.inspect}}>
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Defines a value for a String constant
|
258
|
+
class AlAttrStr < AlAttrDef
|
259
|
+
def initialize(key, val)
|
260
|
+
super(key, val)
|
261
|
+
raise ArgumentError, %<Wrong type for a String: #{val.class}=#{val.inspect}> unless val.is_a?(String)
|
262
|
+
end
|
263
|
+
# String representation
|
264
|
+
def to_s; %<#{self.class.name}{#{key}=#{val.inspect}}> end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Defines a value for an Int constant
|
268
|
+
class AlAttrInt < AlAttrDef
|
269
|
+
def initialize(key, val)
|
270
|
+
super(key, val)
|
271
|
+
raise ArgumentError, %<Wrong type for an Int: #{val.class}=#{val.inspect}> unless val.is_a?(Fixnum)
|
272
|
+
end
|
273
|
+
# String representation
|
274
|
+
def to_s; %<#{self.class.name}{#{key}=#{val}}> end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Defines a value for an Decimal constant
|
278
|
+
class AlAttrDec < AlAttrDef
|
279
|
+
def initialize(key, val)
|
280
|
+
super(key, val)
|
281
|
+
raise ArgumentError, %<Wrong type for an Decimal: #{val.class}=#{val.inspect}> unless val.is_a?(BigDecimal)
|
282
|
+
end
|
283
|
+
# String representation
|
284
|
+
def to_s; %<#{self.class.name}{#{key}=#{val.to_s('F')}}> end
|
285
|
+
end
|
286
|
+
|
287
|
+
# AppLink Attribute VO
|
288
|
+
class AlAttrVo # - FIXME - not needed?
|
289
|
+
attr_accessor :key, :attrs
|
290
|
+
=begin rdoc
|
291
|
+
Creates an instance for the given parameters, see the properties with the same names.
|
292
|
+
=end
|
293
|
+
def initialize(key, attrs)
|
294
|
+
@key, @attrs = key, attrs
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Application Link VO - FIXME - not needed?
|
299
|
+
class PiiAlVo
|
300
|
+
attr_accessor :key, :attrs
|
301
|
+
=begin rdoc
|
302
|
+
Creates an instance for the given parameters, see the properties with the same names.
|
303
|
+
=end
|
304
|
+
def initialize(key, attrs)
|
305
|
+
@key, @attrs = key, attrs
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
=begin
|
311
|
+
AppLink Attribute Division VO, i.e. full Application Link Definition
|
312
|
+
|
313
|
+
@!attribute [rw] sectVos
|
314
|
+
@return [Hash] the Hash keyed by the application name symbol pointing to a Hash keyed by the Abstract
|
315
|
+
PII field key pointing to the instance of AttrSect.
|
316
|
+
|
317
|
+
@!attribute [rw] reusables
|
318
|
+
@return [Hash] +nil+ or the Hash keyed by reusable var name pointing to the instance of AttrSect
|
319
|
+
PII field key pointing to the instance of AttrSect.
|
320
|
+
=end
|
321
|
+
class AppLink < VersionedVo
|
322
|
+
|
323
|
+
# Use same ident as the main class:
|
324
|
+
INDENT = DataMetaPii::INDENT
|
325
|
+
|
326
|
+
attr_accessor :sectVos, :reusables
|
327
|
+
=begin rdoc
|
328
|
+
Creates an instance for the given parameters, see the properties with the same names.
|
329
|
+
=end
|
330
|
+
def initialize(ver, vos, reusables = nil)
|
331
|
+
super(ver)
|
332
|
+
@sectVos, @reusables = vos, reusables
|
333
|
+
end
|
334
|
+
|
335
|
+
# Resolves reusable variable references, reports errors
|
336
|
+
def resolveRefs()
|
337
|
+
raise ArgumentError, 'Sections are not set yet on this instance' unless @sectVos
|
338
|
+
return self unless @reusables # no reusables defined, all vars should be accounted for
|
339
|
+
@reusables.keys.each { |uk|
|
340
|
+
ref = @reusables[uk].refs
|
341
|
+
ref.keys.each { |rk|
|
342
|
+
raise ArgumentError, %<Reusable "#{uk}" references "#{rk}" which is not defined> unless @reusables[rk]
|
343
|
+
}
|
344
|
+
}
|
345
|
+
@sectVos.keys.each { |ak|
|
346
|
+
app = @sectVos[ak]
|
347
|
+
app.keys.each { |sk|
|
348
|
+
sect = app[sk]
|
349
|
+
sect.refs.keys.each { |rk|
|
350
|
+
raise ArgumentError, %<In the app "#{ak}": the field "#{sk}" references "#{
|
351
|
+
rk}" which is not defined> unless @reusables[rk]
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
}
|
356
|
+
self
|
357
|
+
end
|
358
|
+
|
359
|
+
# String representation
|
360
|
+
def to_s
|
361
|
+
%<#{self.class.name}{apps=#{@sectVos.inspect}>
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# Builds the AppLink CST from the given Registry Grammar Parser's AST
|
366
|
+
def buildAlCst(ast, logString = nil)
|
367
|
+
# parse the ad first
|
368
|
+
reusables = {}
|
369
|
+
log = -> (what) {logString << what if logString}
|
370
|
+
|
371
|
+
log.call("AppLink CST\n")
|
372
|
+
if ast.ad&.elements
|
373
|
+
log.call("#{INDENT}#{ast.ad.type}:#{ast.ad.a.elements.size}\n")
|
374
|
+
ast.ad.a.elements.each { |as| # attrbSection
|
375
|
+
raise RuntimeError, %<The attributes set "#{as.pk} is defined more than once"> if reusables.has_key?(as.pk.to_sym)
|
376
|
+
keyVals = digAstElsType(ATTRB_DEF_NODE_TYPE, as.a.elements)
|
377
|
+
log.call(%<#{INDENT * 2}#{as.pk}:#{keyVals.size}\n>)
|
378
|
+
aSect = AttrSect.new(as.pk.to_sym)
|
379
|
+
keyVals.each { |kv|
|
380
|
+
kvVal = kv.val
|
381
|
+
log.call(%<#{INDENT * 3}#{kv.nodeType}:#{kvVal}\\#{kvVal.class}>)
|
382
|
+
log.call(" (#{kv.node.key}==#{kv.node.nodeVal})//#{kv.node.type}\\#{kv.node.dataType}") if(kv.nodeType == CONST_KEY)
|
383
|
+
log.call("\n")
|
384
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
385
|
+
aSect + case kv.nodeType # else case is caught by the AST parser
|
386
|
+
when CONST_KEY
|
387
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
388
|
+
klass = case kv.node.dataType # else case is caught by the AST parser
|
389
|
+
when STR_CONST_DT
|
390
|
+
AlAttrStr
|
391
|
+
when DECIMAL_CONST_DT
|
392
|
+
AlAttrDec
|
393
|
+
when INT_CONST_DT
|
394
|
+
AlAttrInt
|
395
|
+
end
|
396
|
+
klass.new(kv.node.key.to_sym, kv.node.nodeVal)
|
397
|
+
when REF_KEY
|
398
|
+
AttrRef.new(kvVal)
|
399
|
+
when VO_CLASS_KEY
|
400
|
+
AlAttrVoClass.new(kvVal)
|
401
|
+
end
|
402
|
+
}
|
403
|
+
reusables[as.pk.to_sym] = aSect
|
404
|
+
}
|
405
|
+
log.call(%<#{INDENT * 2}#{reusables}\n>)
|
406
|
+
else
|
407
|
+
log.call("#{INDENT * 2}No reusables\n")
|
408
|
+
end
|
409
|
+
apps = {}
|
410
|
+
if ast.al&.elements
|
411
|
+
log.call("#{INDENT}#{ast.al.type}:#{ast.al.a.elements.size}\n")
|
412
|
+
ast.al.a.elements.each { |as| # appLinkApps
|
413
|
+
log.call(%<#{INDENT * 3}#{as.ak}:#{as.a.elements.size}\n>)
|
414
|
+
appKey = as.ak.to_sym
|
415
|
+
raise RuntimeError, %<Application "#{appKey}" defined more than once> if apps.has_key?(appKey)
|
416
|
+
attrbs = {}
|
417
|
+
as.a.elements.each { |ala| #appLinkAttrbs
|
418
|
+
alis = digAstElsType(DataMetaPii::ATTRB_DEF_NODE_TYPE, ala.a.elements)
|
419
|
+
log.call(%<#{INDENT * 4}#{ala.pk} (#{ala.type}): #{alis.size}\n>)
|
420
|
+
aSect = AttrSect.new(ala.pk.to_sym)
|
421
|
+
alis.each { |ali|
|
422
|
+
kvVal = ali.val
|
423
|
+
log.call(%<#{INDENT * 5}#{ali.nodeType}: >)
|
424
|
+
if ali.nodeType == DataMetaPii::CONST_KEY
|
425
|
+
log.call(%<(#{ali.node.dataType}):: #{ali.node.key}=#{ali.node.nodeVal}>)
|
426
|
+
else
|
427
|
+
log.call(%<#{ali.val}>)
|
428
|
+
end
|
429
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
430
|
+
aSect + case ali.nodeType # else case is caught by the AST parser
|
431
|
+
when CONST_KEY
|
432
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
433
|
+
klass = case ali.node.dataType # else case is caught by the AST parser
|
434
|
+
when STR_CONST_DT
|
435
|
+
AlAttrStr
|
436
|
+
when DECIMAL_CONST_DT
|
437
|
+
AlAttrDec
|
438
|
+
when INT_CONST_DT
|
439
|
+
AlAttrInt
|
440
|
+
end
|
441
|
+
klass.new(ali.node.key.to_sym, ali.node.nodeVal)
|
442
|
+
when REF_KEY
|
443
|
+
AttrRef.new(kvVal)
|
444
|
+
when VO_CLASS_KEY
|
445
|
+
AlAttrVoClass.new(kvVal)
|
446
|
+
end
|
447
|
+
log.call("\n")
|
448
|
+
}
|
449
|
+
attrbs[ala.pk.to_sym] = aSect
|
450
|
+
}
|
451
|
+
log.call(%<#{INDENT}#{attrbs}\n>)
|
452
|
+
apps[appKey] = attrbs
|
453
|
+
}
|
454
|
+
else
|
455
|
+
raise ArgumentError, 'No Applink Division'
|
456
|
+
end
|
457
|
+
AppLink.new(ast.verDef.ver, apps, reusables)
|
458
|
+
end
|
459
|
+
|
460
|
+
# Builds the Registry CST from the given Registry Grammar Parser's AST
|
461
|
+
def buildRegCst(ast)
|
462
|
+
|
463
|
+
resultMap = {}
|
464
|
+
|
465
|
+
ast.fields.elements.each { |f|
|
466
|
+
fKey = f.pk
|
467
|
+
raise ArgumentError, %<The PII field #{fKey} is defined more than once> if resultMap.keys.member?(fKey)
|
468
|
+
attrs = {}
|
469
|
+
f.attrbLst.attrbs.each { |a|
|
470
|
+
raise ArgumentError, %<Attribute "#{a.k}" is defined more than once for #{fKey}> if attrs.keys.member?(a.k)
|
471
|
+
attrs[a.k] = a.v
|
472
|
+
}
|
473
|
+
attrVo = RegKeyVo.new(fKey, attrs)
|
474
|
+
resultMap[fKey] = attrVo
|
475
|
+
}
|
476
|
+
|
477
|
+
RegVo.new(ast.verDef.ver, resultMap)
|
478
|
+
end
|
479
|
+
|
480
|
+
# Helper method for the AST traversal to collect the attributes
|
481
|
+
# Because of the Treetop AST design, can not just flatten the elements and select of those of the needed type in one call, hence the tree traversal
|
482
|
+
def digAstElsType(type, els, result=[])
|
483
|
+
if els.nil? # is it a leaf?
|
484
|
+
nil # not a leaf - return nil
|
485
|
+
else
|
486
|
+
els.each { |e|
|
487
|
+
if e.respond_to?(:type) && e.type == type # actual attribute Key/Value?
|
488
|
+
result << e # add it
|
489
|
+
else
|
490
|
+
digAstElsType(type, e.elements, result) # dig deeper into the AST
|
491
|
+
end
|
492
|
+
}
|
493
|
+
result
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
def errNamespace(outFmt)
|
498
|
+
raise ArgumentError, %<For output format "#{outFmt}", the Namespace is required>
|
499
|
+
end
|
500
|
+
|
501
|
+
# API method: generate the PII code
|
502
|
+
def genCode(scope, outFmt, outDirName, source, namespace = nil)
|
503
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
504
|
+
codeIndent = ' ' * 2
|
505
|
+
raise ArgumentError, %Q<Unsupported scope definition "#{scope}", supported scopes are: #{
|
506
|
+
DataMetaPii::ALL_SCOPES.map(&:to_s).join(', ')}> unless ALL_SCOPES.member?(scope)
|
507
|
+
|
508
|
+
raise ArgumentError, %Q<Unsupported output format definition "#{outFmt}", supported formats are: #{
|
509
|
+
DataMetaPii::ALL_FMTS.map(&:to_s).join(', ')}> unless ALL_FMTS.member?(outFmt)
|
510
|
+
|
511
|
+
raise ArgumentError, %<For safety purposes, absolute path names like "#{
|
512
|
+
outDirName}" are not supported> if outDirName.start_with?('/')
|
513
|
+
|
514
|
+
raise ArgumentError, %<The output dir "#{outDirName}" is not a directory> unless File.directory?(outDirName)
|
515
|
+
|
516
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
517
|
+
case scope # else case caught up there, on argument validation
|
518
|
+
when Scope::ABSTRACT
|
519
|
+
reg = DataMetaPii.buildRegCst(DataMetaParse.parse(REGISTRY_PARSER, source))
|
520
|
+
L.info(%<PII Registry:
|
521
|
+
#{reg.to_tree_image(INDENT)}>)
|
522
|
+
tmpl = ERB.new(IO.read(File.join(GEM_ROOT, 'tpl', outFmt.to_s, 'master.erb')), $SAFE, '%<>>')
|
523
|
+
className = "PiiAbstractDef_#{reg.ver.toVarName}"
|
524
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
525
|
+
case outFmt
|
526
|
+
when ExportFmts::JAVA, ExportFmts::SCALA
|
527
|
+
errNamespace(outFmt) unless namespace.is_a?(String) && !namespace.empty?
|
528
|
+
pkgDir = namespace.gsub('.', '/')
|
529
|
+
classDest =File.join(outDirName, pkgDir)
|
530
|
+
FileUtils.mkpath classDest
|
531
|
+
|
532
|
+
IO.write(File.join(classDest, "#{className}.#{outFmt}"),
|
533
|
+
tmpl.result(binding).gsub(/\n\n+/, "\n\n"), mode: 'wb') # collapse multiple lines in 2
|
534
|
+
|
535
|
+
when ExportFmts::JSON
|
536
|
+
IO.write(File.join(outDirName, "#{className}.#{outFmt}"),
|
537
|
+
tmpl.result(binding).gsub(/\n\n+/, "\n\n"), mode: 'wb') # collapse multiple lines in 2
|
538
|
+
|
539
|
+
when ExportFmts::PYTHON
|
540
|
+
pkgDir = namespace.gsub('.', '_')
|
541
|
+
classDest =File.join(outDirName, pkgDir)
|
542
|
+
FileUtils.mkpath classDest
|
543
|
+
IO.write(File.join(classDest, "#{className[0].downcase + className[1..-1]}.py"),
|
544
|
+
tmpl.result(binding).gsub(/\n\n+/, "\n\n"), mode: 'wb') # collapse multiple lines in 2
|
545
|
+
IO.write(File.join(classDest, '__init__.py'), %q<
|
546
|
+
# see https://docs.python.org/3/library/pkgutil.html
|
547
|
+
# without this, Python will have trouble finding packages that share some common tree off the root
|
548
|
+
from pkgutil import extend_path
|
549
|
+
__path__ = extend_path(__path__, __name__)
|
550
|
+
|
551
|
+
>, mode: 'wb')
|
552
|
+
end
|
553
|
+
|
554
|
+
when DataMetaPii::Scope::APPLICATION
|
555
|
+
raise NotImplementedError, 'There is no generic code gen for AppLink, each app/svc should have their own'
|
556
|
+
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
=begin
|
561
|
+
Turns the given text into the instance of the AppLink object.
|
562
|
+
=end
|
563
|
+
def parseAppLink(source)
|
564
|
+
|
565
|
+
piiAppLinkParser = PiiAppLinkParser.new
|
566
|
+
ast = DataMetaParse.parse(piiAppLinkParser, source)
|
567
|
+
raise SyntaxError, 'AppLink parse unsuccessful' unless ast
|
568
|
+
if ast.is_a?(DataMetaParse::Err)
|
569
|
+
raise %<#{ast.parser.failure_line}
|
570
|
+
ast.parser.failure_reason}>
|
571
|
+
end
|
572
|
+
|
573
|
+
DataMetaPii.buildAlCst(ast).resolveRefs
|
574
|
+
end
|
575
|
+
|
576
|
+
module_function :digAstElsType, :buildRegCst, :buildAlCst, :genCode, :errNamespace, :parseAppLink
|
577
|
+
end
|