dataMetaPii 1.0.1

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