dataMetaPii 1.0.1
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 +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
|