roll-right 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4005 @@
1
+ const {PathManager} = require('extra-file-class')
2
+
3
+ const fos = require('extra-file-class')()
4
+
5
+
6
+ const TESTING = true
7
+
8
+ const {base_patterns_mod} = require('../lib/html_directives')
9
+ const {bundle_inclusion_transform,link_inclusion_transform} = require('../lib/bundle_directives')
10
+
11
+ let page_or_worker_context = {} // maps skeletons "skeleton_src" to contexts for links in hmtl or javascript inclusion mechanisms
12
+
13
+ const crypto = require('crypto')
14
+
15
+
16
+ let ParseUtils = require('../lib/utils')
17
+ let parse_util = new ParseUtils()
18
+
19
+ /**
20
+ * This class is made for organizing the code in this utility.
21
+ *
22
+ * This class has to do with phase 1 operations.
23
+ *
24
+ * Using the direction of an input file, such as generate.json in [websites]/template-configs/,
25
+ * this method will read skeleton files describing the structure of web pages and output templates.
26
+ * It will also attempt to move javascript files into collections for rollup into bundles to be loaded
27
+ * via deferred loading into a browser like contexts.
28
+ *
29
+ */
30
+ class SkelToTemplate {
31
+
32
+ /**
33
+ *
34
+ * Takes in the configuration object, which describes which files are to be loaded as skeletons
35
+ * and which files are to be output to the directories of concerns.
36
+ *
37
+ * The constructor calls upon the PathManager obect to expand out a set of abbreviations used
38
+ * for file names in the skeleton processing.
39
+ *
40
+ * Default directories for finding types of file are expanded to absolute paths and placed in their maps.
41
+ * Top level directories, those with special keys (part of the skeleton language) are expanded as well.
42
+ *
43
+ * @param {object} conf
44
+ */
45
+ constructor(conf) {
46
+ //
47
+ this.paths = null
48
+ if ( typeof conf.inputs === 'object' ) {
49
+ this.paths = new PathManager(conf.inputs)
50
+ }
51
+ //
52
+ this.remove_block_comments = true
53
+ if ( typeof conf.remove_block_comments === 'boolean' ) {
54
+ this.remove_block_comments = conf.remove_block_comments
55
+ }
56
+
57
+ //
58
+ this.global_variable_values = conf.global_variable_values ? conf.global_variable_values : {}
59
+ this.vars_unset_in_run = {}
60
+ this.app_skel_vars = conf.app_skel_vars ? conf.app_skel_vars : {}
61
+ //
62
+ this.top_level_parsed = conf.top_level_parsed
63
+ //
64
+ this.ext_default_dirs = Object.assign({},conf.ext_default_dirs)
65
+ this.top_dir_locations = Object.assign({},conf.top_dir_locations)
66
+ this.find_concerns = Object.assign({},conf["use_case<[targets.dir]>"])
67
+ this.target_dir_key = "targets.dir"
68
+ this.find_in_group = {
69
+ "targets.dir" : (obj) => obj.targets.dir
70
+ }
71
+ //
72
+ this.created_dir = conf['@target'] // will often appear in the top level of project directory
73
+ if ( typeof this.created_dir !== 'string' ) {
74
+ let msg = "missging terminal (leaf) dir name in field '@target'"
75
+ console.log(msg)
76
+ throw new Error(msg)
77
+ }
78
+ //
79
+ // expand the directories
80
+ // these directories are assumed to exist
81
+ let ext_default_dir = this.ext_default_dirs
82
+ let top_dir_locations = this.top_dir_locations
83
+
84
+ for ( let [ext,edd] of Object.entries(ext_default_dir) ) {
85
+ let edd_path = this.paths.compile_one_path(edd)
86
+ ext_default_dir[ext] = edd_path
87
+ }
88
+ //
89
+ for ( let [script_key,tdl] of Object.entries(top_dir_locations) ) {
90
+ let tdl_path = this.paths.compile_one_path(tdl)
91
+ top_dir_locations[script_key] = tdl_path
92
+ }
93
+ //
94
+ this.outputs = false
95
+ let outputs = conf.outputs
96
+ if ( outputs && Array.isArray(outputs) ) {
97
+ outputs = JSON.parse(JSON.stringify(outputs))
98
+ for ( let ogroup of outputs ) {
99
+ let targets = ogroup.targets // targets
100
+ if ( typeof targets !== 'object' || Array.isArray(targets) ) {
101
+ let msg = "each output group in the 'output' array is expected to have a map 'targets' specifying asset name to asset directory"
102
+ console.log(msg)
103
+ throw new Error(msg)
104
+ }
105
+ let skeletons = ogroup.skeletons // skeletons
106
+ if ( typeof skeletons !== 'object' || Array.isArray(skeletons) ) {
107
+ let msg = "each output group in the 'output' array is expected to have a map 'skeletons' specifying termplace file names to source skeletons"
108
+ console.log(msg)
109
+ throw new Error(msg)
110
+ }
111
+ }
112
+ this.outputs = outputs
113
+ }
114
+
115
+ this.range_pattern = new RegExp(/.*\<(\d),(\d+)\>\<\</)
116
+ this.list_pattern = new RegExp(/.*\<(\w+)\>\<\</)
117
+ //
118
+ this.var_spec_pattern = new RegExp(/^\$\@\{(\w+)\}\$files\:\:(.*)$/)
119
+ this.var_spec_copy_pattern = new RegExp(/^\$\@\{(\w+)\}\$verbatim$/)
120
+ this.var_spec_value_pattern = new RegExp(/^\$\@\{(\w+)\}\$\<\<(.+)$/)
121
+ //
122
+ this.var_spec_other_pattern = new RegExp(/^\$\@\{(\w+)\}\$(\w+)\:\:(.*)$/)
123
+
124
+ // ||||--------------------------build_param_tree $@{mushroom}$icons::mushroom-menu-icon.svg
125
+ // build_param_tree files::params::nav_bar_V.smplt $@{mushroom}$icons::mushroom-menu-icon.svg<<
126
+
127
+
128
+ this.cross_type_directory = new RegExp(/^(\w+)\<(\w+)\>\:\:(.*)$/)
129
+ this.executable_pattern = new RegExp(/\>\>.*\<\</)
130
+ this.entry_starter = new RegExp(/^(\w+)\:\:/)
131
+ this.sibling_type_directory_match = new RegExp(/^\[([\w-]+)\]\/(.*)$/)
132
+
133
+ this.concerns_directory_redirect_match = new RegExp(/^\[(([\w-]+)\<([\w]+)\>)\]\/(.*)$/)
134
+
135
+ this.start_of_list = new RegExp(/^\@list\<(\w+)\>\<\{/)
136
+
137
+ this.ternary_check = new RegExp(/^(.+)\?(.+)\:(.*)/)
138
+ this.var_pattern = new RegExp(/\@\{([\w\%]+)\}/g)
139
+ this.var_set_expr_pattern = new RegExp(/\{([\w\=\+\-\d]+)\}/)
140
+
141
+ this.basic_function_call_match = new RegExp(/f\@(\w[\w\d]*)\{(\w[\w\d]*)\}/)
142
+ this.import_entry_match = new RegExp(/\$\$(\w+)\:\:.+\.\w+\<\</)
143
+
144
+ this.generic_ogroup_target_dotted_path = new RegExp(/\{\{\{\s*(\@\{\[(\w+)\.(\w+)\]\}).*/)
145
+
146
+ this.entry_remap_map = {
147
+ "files" : { "source" : "html", "type" : "tmplt"},
148
+ "css" : { "source" : "css", "type" : "css"}
149
+ }
150
+
151
+ this.name_drops_db = {}
152
+ //
153
+ this.delay_file_loading_queue = []
154
+ //
155
+ this.tracking_skel_calc_usage = {}
156
+ }
157
+
158
+ // {{{ @{[targets.dir]}/scripts/site_app.js }}}
159
+ pull_parent_dir_dotted_target_dir(form,ogroup) {
160
+ let parts_holder = this.generic_ogroup_target_dotted_path.exec(form)
161
+ if ( parts_holder ) {
162
+ //console.dir(parts_holder)
163
+ let substkey = parts_holder[1]
164
+ let f1 = parts_holder[2]
165
+ let f2 = parts_holder[3]
166
+ let otarget = ogroup[f1]
167
+ let abstract_path = otarget[f2]
168
+ //
169
+ abstract_path = this.paths.compile_one_path(abstract_path)
170
+ //
171
+ let rform = form.replace(substkey,abstract_path)
172
+ //
173
+ if ( otarget ) return rform
174
+ }
175
+ return false
176
+ }
177
+
178
+ /**
179
+ *
180
+ * @param {string} project_dir
181
+ */
182
+ set_project_directory(project_dir) {
183
+ this.project_dir = project_dir
184
+ }
185
+
186
+ /**
187
+ *
188
+ * @param {string} entry_directive
189
+ * @returns {pair} - a two element array with ary[0] being the true directory name and ary[1] being the file type (extension)
190
+ */
191
+ entry_directive_location_remap(entry_directive) {
192
+ let remap = this.entry_remap_map[entry_directive]
193
+ if ( remap ) {
194
+ return Object.values(remap)
195
+ }
196
+ return [entry_directive,entry_directive]
197
+ }
198
+
199
+ /**
200
+ *
201
+ * @param {string} script_line
202
+ * @returns
203
+ */
204
+ script_specialization(script_line) {
205
+ let after_def = script_line.split("<<")[1]
206
+ after_def = after_def.trim()
207
+ return after_def
208
+ }
209
+
210
+
211
+ /**
212
+ *
213
+ * If a script has some variable from configuration.
214
+ * Then, there should be a substitution variable that will be set
215
+ * as a special case in the use of the skeleton.
216
+ *
217
+ * The specialization will for the script to be inlined.
218
+ *
219
+ * @param {string} script_line
220
+ * @returns {boolean}
221
+ */
222
+ script_no_bundle(script_line) {
223
+ let after_def = this.script_specialization(script_line)
224
+ if ( after_def.length ) return false
225
+ return true
226
+ }
227
+
228
+ /**
229
+ *
230
+ * @param {*} script_line
231
+ * @param {*} script
232
+ * @returns
233
+ */
234
+ structural_substitution(script_line,script) {
235
+ let after_def = this.script_specialization(script_line)
236
+ if ( after_def[0] === '@' ) {
237
+ let value = ""
238
+ let config_var = after_def.substring(after_def.indexOf('{') + 1,after_def.lastIndexOf('}')-1)
239
+ let dat_struct = this.get_data(config_var)
240
+ if ( after_def[1] === '#' ) {
241
+ value = 0
242
+ if ( Array.isArray(dat_struct) ) {
243
+ value = dat_struct.length
244
+ } else {
245
+ value = Object.keys(dat_struct).length
246
+ }
247
+ } else {
248
+ //
249
+ }
250
+ script = script.replace(`{{${config_var}}}`,value)
251
+ }
252
+ return script
253
+ }
254
+
255
+
256
+ /**
257
+ * For each concern, makes sure that the output directory exists under the directory for the concern
258
+ * determined by the scheme estabished by the target's 'dir_form'
259
+ *
260
+ * @param {object} targets - a set of directory
261
+ * @param {object} skeletons - a map of skeletons path names to skeleton forms to be expanded
262
+ * @param {string} created_dir - the leaf directory to be created
263
+ */
264
+ async ensure_all_concerns_template_directories(targets,skeletons) {
265
+ //
266
+ let dir_form = targets.dir_form
267
+ let concerns = targets.concerns
268
+ //
269
+ dir_form = dir_form.replace("@target",this.created_dir)
270
+ //
271
+ // Here, the directories which will receive template copies for an
272
+ // asset stack will be created if they do not exists.
273
+ for ( let concern of concerns ) {
274
+ //
275
+ let top_out_dir = dir_form.replace("@concern",concern)
276
+ //
277
+ let promises = []
278
+ for ( let [opath,skeleton] of Object.entries(skeletons) ) {
279
+ let skel_output_path = `${top_out_dir}${opath}`
280
+ let sko_path = this.paths.compile_one_path(skel_output_path)
281
+ let sk_path = this.paths.compile_one_path(skeleton)
282
+ skeletons[opath] = sk_path
283
+ promises.push(fos.ensure_directories(sko_path,"",true))
284
+ }
285
+ await Promise.all(promises)
286
+ //
287
+ }
288
+ //
289
+ }
290
+
291
+
292
+ /**
293
+ * makes sure that directories receiving the generated assets exists
294
+ * and are structured according to the configuration.
295
+ *
296
+ * The configuration loaded from a file such as "generate.json" has fields describing directories
297
+ * and concerns and global variables. Global variables reflect the specialization for a run on skeletons.
298
+ *
299
+ * There is one field "outputs" which describes directories where templates and final renditions will be stored
300
+ * for different concern/file pairings. Each element of the outputs has three main fields for grouping
301
+ * this information:
302
+ *
303
+ * targets - description of output directory schemes and a list of a concerns (webstites/apps) that will use the outputs
304
+ * skeletons - a map of out template names to
305
+ * name_parameters -- variations on skeletal structure from a database of these structures, with defintions for
306
+ * final substitutions
307
+ *
308
+ * this method makes use of targets and skeletons
309
+ *
310
+ */
311
+ async prepare_directories() {
312
+ //
313
+ let outputs = this.outputs
314
+ for ( let ogroup of outputs ) {
315
+ let targets = ogroup.targets // targets
316
+ let skeletons = ogroup.skeletons // skeletons
317
+ await this.ensure_all_concerns_template_directories(targets,skeletons)
318
+ }
319
+ }
320
+
321
+
322
+ /**
323
+ *
324
+ * Loads the skeleton files. Creates a map from output names to skeleton source names.
325
+ * Returns the map.
326
+ *
327
+ * @returns {pair}
328
+ */
329
+ async load_skeletons() {
330
+ //
331
+ let outputs = this.outputs
332
+ let all_skeletons = {}
333
+ //
334
+ for ( let ogroup of outputs ) {
335
+ let skeletons = ogroup.skeletons // skeletons mentioned in the ogroup
336
+ ogroup.skels_processed = {} // will get the unique skels in this ogroup and create a place to store their processed parts
337
+ //
338
+ for ( let [fky,file] of Object.entries(skeletons) ) { // the field key is the desired output
339
+ //
340
+ let fpath = this.paths.compile_one_path(file)
341
+ let p = all_skeletons[fpath] // a skeleton absolute path name
342
+ if ( p === undefined ) {
343
+ all_skeletons[fpath] = {
344
+ "original" : "",
345
+ "ops" : { // ops map
346
+ }
347
+ }
348
+ }
349
+ //
350
+ if ( ogroup.skels_processed[file] === undefined ) { // one for any of the selected skeletons (may select in duplicate)
351
+ let config_vars = ogroup.targets.uses_config_vars
352
+ config_vars = (config_vars ? config_vars : "default") // the skeleton ops apply to an ogroup
353
+ ogroup.skels_processed[file] = { // allows to map from template to output
354
+ "ablsolute_path" : fpath, // a key to the loaded data as well
355
+ "skel_vars_key" : config_vars,
356
+ "final" : "" // final output not yet determined
357
+ }
358
+ all_skeletons[fpath].ops[config_vars] = "" // on preparation across many entries retrievable from ogroup by:
359
+ // all_skeletons[ogroup.skels_processed[file].ablsolute_path][ogroup.skels_processed[file].skel_vars_key]
360
+ }
361
+ }
362
+ //
363
+ let tdir = ogroup.targets.dir_form
364
+ ogroup.concerns_to_out_dirs = {}
365
+ //
366
+ for ( let crn of ogroup.targets.concerns ) {
367
+ let crn_tdir = tdir.replace("@concern",crn)
368
+ crn_tdir = crn_tdir.replace("@target",this.created_dir)
369
+ ogroup.concerns_to_out_dirs[crn] = crn_tdir
370
+ }
371
+ //
372
+ }
373
+ //
374
+ let data_promises = []
375
+ let skel_keys = Object.keys(all_skeletons)
376
+ for ( let fpath of skel_keys ) {
377
+ data_promises.push(fos.load_data_at_path(fpath))
378
+ }
379
+ let data_list = await Promise.all(data_promises)
380
+ let n = data_list.length
381
+ //
382
+ for ( let i = 0; i < n; i++ ) {
383
+ all_skeletons[skel_keys[i]].original = data_list[i]
384
+ }
385
+ //
386
+ return all_skeletons
387
+ }
388
+
389
+
390
+ /**
391
+ *
392
+ * @param {string} file
393
+ * @returns {object}
394
+ */
395
+ async load_name_drops_db(file) {
396
+ this.name_drops_db = {}
397
+ let name_db_loc = this.paths.get_path("[names]")
398
+ let name_db_file = file ? file : `${name_db_loc}/name-drop.db`
399
+ if ( file ) {
400
+ return await fos.load_json_data_at_path(name_db_file)
401
+ }
402
+ this.name_drops_db = await fos.load_json_data_at_path(name_db_file)
403
+ return this.name_drops_db
404
+ }
405
+
406
+
407
+ /**
408
+ * The data provided is expected to be a string which may or may not include subfiles or the kind of
409
+ * variable used in skeleton processing.
410
+ *
411
+ * This looks to see if there is at least one instance of some structured text indicating that
412
+ * further procesing may be done (especially in a recursive fashion).
413
+ *
414
+ * @param {string} data
415
+ * @returns {number}
416
+ */
417
+ check_recursive_data(data) {
418
+ if ( !data ) return false
419
+ //
420
+ if ( data.indexOf("$$file") >= 0 ) {
421
+ return 1
422
+ }
423
+ if ( data.indexOf("$$icons") >= 0 ) {
424
+ return 2
425
+ }
426
+ if ( data.indexOf("$$css") >= 0 ) {
427
+ return 3
428
+ }
429
+ if ( data.indexOf("@{") >= 0 ) {
430
+ return 4
431
+ }
432
+ if ( this.executable_pattern.test(data) ) {
433
+ return 5
434
+ }
435
+ return false
436
+ }
437
+
438
+
439
+ /**
440
+ * The range parameter is a structure that will have lb (lower bound)
441
+ * and ub (upper bound) filled out if the control string `ctrl_str`
442
+ * matches the pattern. The pattern will be of the fomr `prefix`<lb,ub>`postfix`
443
+ *
444
+ * @param {string} ctrl_str
445
+ * @param {object} range
446
+ * @returns {boolean}
447
+ */
448
+ has_range_expr(ctrl_str,range) {
449
+ let result = this.range_pattern.exec(ctrl_str)
450
+ if ( result ) {
451
+ range.lb = parseInt(result[1])
452
+ range.ub = parseInt(result[2])
453
+ return true
454
+ }
455
+ return false
456
+ }
457
+
458
+
459
+ /**
460
+ * The `var_holder` parameter is an array that will
461
+ * contain the bracket identifier
462
+ * if the control string `ctrl_str`
463
+ * matches the pattern. The pattern will be of the form `prefix`<identifier>`postfix`
464
+ *
465
+ * @param {string} ctrl_str
466
+ * @param {Array} var_holder
467
+ * @returns {boolean}
468
+ */
469
+ has_list_expr(ctrl_str,var_holder) {
470
+ let result = this.list_pattern.exec(ctrl_str)
471
+ if ( result ) {
472
+ var_holder[0] = result[1]
473
+ return true
474
+ }
475
+ return false
476
+ }
477
+
478
+
479
+
480
+ /**
481
+ *
482
+ * @param {string} part_key
483
+ * @param {string} param_str
484
+ * @returns {object}
485
+ */
486
+
487
+ // console.log("||||--------------------------build_param_tree",key_part)
488
+ // console.log("build_param_tree",part_key,param_str)
489
+
490
+ async build_param_tree(part_key,param_str) {
491
+ //
492
+ if ( param_str[0] === '{' ) {
493
+ param_str = param_str.substring(1,param_str.lastIndexOf('}'))
494
+ }
495
+
496
+ let var_tree = {}
497
+ let easy_access_parse = {}
498
+
499
+ let sep_point = param_str.indexOf("<<")
500
+ if ( sep_point > 0 ) {
501
+ //
502
+ let key_part = param_str.substring(0,sep_point)
503
+ let rest = param_str.substring(sep_point+2)
504
+ //console.log("||||--------------------------build_param_tree",key_part)
505
+ //
506
+ do {
507
+ let check = this.var_spec_pattern.exec(key_part)
508
+ if ( check ) {
509
+ let sub_tree = {}
510
+ if ( rest.length ) {
511
+ rest = await this.capture_param_sub_call(part_key,rest,sub_tree)
512
+ }
513
+ if ( Object.keys(sub_tree).length === 0 ) {
514
+ sub_tree = false
515
+ }
516
+ let extracted_var = check[1]
517
+ let the_file = check[2]
518
+ //
519
+ easy_access_parse[extracted_var] = "%file%"
520
+ //
521
+ let data = ""
522
+ let parts_of_key = part_key.split('::')
523
+ if ( parts_of_key.length > 1 ) {
524
+ let [path_finder,ftype] = this.entry_directive_location_remap(parts_of_key[0])
525
+ let locus = this.top_dir_locations[path_finder]
526
+ if ( locus ) {
527
+ if ( the_file.indexOf('::') > 0 ) {
528
+ the_file = the_file.substring(the_file.indexOf('::') + 2)
529
+ }
530
+ let file_path = `${locus}/${the_file}`
531
+ data = await fos.load_data_at_path(file_path)
532
+ }
533
+ }
534
+ var_tree[extracted_var] = {
535
+ "file" : the_file,
536
+ "tree" : sub_tree,
537
+ "data" : data
538
+ }
539
+ if ( this.check_recursive_data(data) ) {
540
+ let map_value = var_tree[extracted_var]
541
+ map_value.recursive = await this.get_files_and_vars(part_key,data)
542
+ }
543
+ } else if ( check = this.var_spec_copy_pattern.exec(key_part) ) {
544
+ //
545
+ let data = ""
546
+ if ( rest.length ) {
547
+ let next = this.find_next(rest)
548
+ if ( next ) {
549
+ data = rest.substring(0,next)
550
+ rest = rest.substring(next)
551
+ data = data.substring(1,data.lastIndexOf('}'))
552
+ }
553
+ }
554
+ let extracted_var = check[1]
555
+ easy_access_parse[extracted_var] = "%string%"
556
+ var_tree[extracted_var] = {
557
+ "file" : false,
558
+ "tree" : false,
559
+ "data" : data
560
+ }
561
+ //
562
+ } else if ( check = this.var_spec_value_pattern.exec(param_str) ) {
563
+ let data = ""
564
+ if ( rest.length ) {
565
+ let next = this.find_next(rest)
566
+ if ( next ) {
567
+ data = rest.substring(0,next)
568
+ rest = rest.substring(next)
569
+ data = data.substring(1,data.lastIndexOf('}'))
570
+ }
571
+ }
572
+ //
573
+ let extracted_var = check[1]
574
+ data = check[2]
575
+ let val_type = isNaN(parseInt(data)) ? "%string%" : "%number%"
576
+ easy_access_parse[extracted_var] = val_type
577
+ var_tree[extracted_var] = {
578
+ "file" : false,
579
+ "tree" : false,
580
+ "data" : data
581
+ }
582
+ //
583
+ } else {
584
+ if ( rest.length ) {
585
+ let next = this.find_next(rest)
586
+ if ( next ) {
587
+ rest = rest.substring(next)
588
+ }
589
+ }
590
+
591
+ check = this.var_spec_other_pattern.exec(key_part)
592
+ if ( check = this.var_spec_other_pattern.exec(key_part) ) {
593
+ let extracted_var = check[1]
594
+ let path_finder = check[2]
595
+ let the_file = check[3]
596
+
597
+ let data = ""
598
+ let locus = this.top_dir_locations[path_finder]
599
+
600
+ if ( locus ) {
601
+ if ( the_file.indexOf('::') > 0 ) {
602
+ the_file = the_file.substring(the_file.indexOf('::') + 2)
603
+ }
604
+ let file_path = `${locus}/${the_file}`
605
+ data = await fos.load_data_at_path(file_path)
606
+ }
607
+ var_tree[extracted_var] = {
608
+ "file" : the_file,
609
+ "tree" : false,
610
+ "data" : data
611
+ }
612
+ }
613
+
614
+ }
615
+ //
616
+ if ( rest.length ) {
617
+ sep_point = rest.indexOf("<<")
618
+ if ( sep_point ) {
619
+ key_part = rest.substring(0,sep_point)
620
+ rest = rest.substring(sep_point+2)
621
+ } else break
622
+ } else break;
623
+
624
+ } while ( key_part.length )
625
+ //
626
+ }
627
+
628
+ if ( Object.keys(var_tree).length === 0 ) {
629
+ return false
630
+ }
631
+
632
+ let par_state = `_params<${Object.keys(easy_access_parse).join(',')}>`
633
+ var_tree[par_state] = easy_access_parse
634
+ return var_tree
635
+ }
636
+
637
+
638
+ /**
639
+ *
640
+ * @param {string} rest
641
+ * @returns {Number} -- the location of the next
642
+ */
643
+ find_next(rest) {
644
+ if ( rest[0] === '{' ) { // handle recursive object searchs
645
+ let depth = 0
646
+ let index = 1
647
+ while ( depth > -1 ) {
648
+ let c = rest[index]
649
+ if ( c === '{' ) {
650
+ depth++
651
+ } else if ( c === '}' ) {
652
+ depth--
653
+ }
654
+ index++
655
+ }
656
+ return index
657
+ } else {
658
+ return 0
659
+ }
660
+ }
661
+
662
+
663
+
664
+ /**
665
+ *
666
+ * @param {string} rest
667
+ * @param {object} sub_tree -- gets populated with object information
668
+ */
669
+ async capture_param_sub_call(part_key,rest,sub_tree) {
670
+ let next = this.find_next(rest)
671
+ if ( next ) {
672
+ let param_block = rest.substring(0,next)
673
+ rest = rest.substring(next)
674
+ let a_tree = await this.build_param_tree(part_key,param_block)
675
+ for ( let ky in a_tree ) {
676
+ sub_tree[ky] = a_tree[ky]
677
+ }
678
+ }
679
+ return rest
680
+ }
681
+
682
+
683
+ /**
684
+ * remove syntax markers that must not occur in the final output
685
+ *
686
+ * @param {string} str
687
+ * @returns {string}
688
+ */
689
+ clean_verbatim(str) {
690
+ str = str.trim()
691
+ if ( str[0] === '{' ) {
692
+ str = str.substring(1)
693
+ let i = str.lastIndexOf('}')
694
+ if ( i > 0 ) {
695
+ str = str.substring(0,i)
696
+ }
697
+ }
698
+ return str
699
+ }
700
+
701
+ /**
702
+ *
703
+ * This method is a map call on the array, that filters 'files::loop::' into parsing operations and returns all else
704
+ * in its original state.
705
+ *
706
+ * There are two types of loop structure supported, ranges and lists.
707
+ *
708
+ * The range construct maps the input into an expanded list of elements indexed from start to end of the range.
709
+ * Before returning, this method flattens the list to incorporate the exanded range. The range construct is identified
710
+ * by a pair within backets `<range_pair>`, where `range_pair` is a comma separated sequence of two numbers,
711
+ * lower bound to upper bound.
712
+ *
713
+ * The list construct provides a variable (expected to be used in the list expansion file) instead of a range.
714
+ * That is, instead of a `range_pair` inside brackets `<>`, there will be a variable name, e.g. '<el>', where
715
+ * 'el' would mean an element of the list. The list construct will provide a list in a JSON parseable structure
716
+ * listed after '<<', the end of step specifier symbol. The struct must be a complete structure prior to the next
717
+ * '$$' in the table. The structure will be held for later use in the `maybe_params` structure. A 'part key' will
718
+ * be used to index the data list for this sort of element. (Note: the part key will be a sub skeleton that takes
719
+ * a list as input.)
720
+ *
721
+ * Note: this method stops short of loading subtemplate files, so the list construct will not expand the list.
722
+ *
723
+ * Note: Sequence expansion conceptually assigns space to a sequence of elements in markup. These are structural changes,
724
+ * expansions, of a skeleton. These are step elements that claim space, but do not produce a string to be input to anything.
725
+ * These space grabbers can take input from items that produce strings. (See parameters for string producing items).
726
+ *
727
+ * @param {Array} data_parts
728
+ * @param {object} maybe_params
729
+ * @returns {Array}
730
+ */
731
+ sequence_expansion(data_parts,maybe_params) {
732
+ data_parts = data_parts.map((a_part) => {
733
+ if ( a_part.indexOf("files::") === 0 ) {
734
+ if ( a_part.indexOf("files::loop::") === 0 ) {
735
+ let range = {
736
+ lb: 0,
737
+ ub: 0
738
+ }
739
+ a_part = parse_util.remove_spaces(a_part)
740
+ a_part = parse_util.remove_white(a_part)
741
+ // RANGE
742
+ if ( this.has_range_expr(a_part.substring("files::loop::".length),range) ) {
743
+ let replacer = `<${range.lb},${range.ub}>`
744
+ let lines = []
745
+ for ( let i = range.lb; i <= range.ub; i++ ) {
746
+ let this_part = a_part.replace(replacer,`${i}`)
747
+ this_part = this_part.replace("::loop","") // removes the loop specification leaving behind other types (e.g. calc)
748
+ lines.push(this_part)
749
+ }
750
+ return lines
751
+ }
752
+ } else if ( a_part.indexOf("files::list::") === 0 ) {
753
+ let var_holder = []
754
+ if ( this.has_list_expr(a_part.substring("files::list::".length,a_part.indexOf('[')),var_holder) ) {
755
+ // ELEMENTS
756
+ let parts = a_part.split('<<')
757
+ let part_key = parts.shift()
758
+ part_key = part_key.replace("::list","")
759
+ part_key = part_key.substring(0,part_key.indexOf('<'))
760
+ let part_def = parts.shift()
761
+ let def = part_def
762
+ try {
763
+ def = JSON.parse(part_def)
764
+ } catch (e){}
765
+ //
766
+ let pars = {}
767
+ pars[var_holder[0]] = "list"
768
+ pars[`_type<${var_holder[0]}>`] = def
769
+ maybe_params[part_key] = pars
770
+ return part_key
771
+ }
772
+ }
773
+ }
774
+ //
775
+ return a_part
776
+ })
777
+ data_parts = parse_util.flatten(data_parts)
778
+ return data_parts
779
+ }
780
+
781
+
782
+ /**
783
+ *
784
+ * @param {string} a_part
785
+ * @returns {boolean}
786
+ */
787
+ has_verbatim_params(a_part) {
788
+ return a_part.indexOf('$verbatim') > 0
789
+ }
790
+
791
+ /**
792
+ *
793
+ * @param {string} a_part
794
+ * @param {object} verbatim_blocks
795
+ * @returns
796
+ */
797
+ block_verabtim_parameters(a_part,verbatim_blocks) {
798
+ let var_lines = a_part.split("$@{")
799
+ let n = var_lines.length
800
+ for ( let l = 1; l < n; l++ ) {
801
+ let maybe_verbatim = var_lines[l]
802
+ if ( this.has_verbatim_params(maybe_verbatim) ) {
803
+ let verby = maybe_verbatim.split("<<")
804
+ let vdata = verby[1].trim()
805
+ let [s,e] = [0,0]
806
+ if ( vdata[0] === '{' ) {
807
+ s = 1
808
+ e = vdata.lastIndexOf('}')
809
+ vdata = vdata.substring(s,e)
810
+ }
811
+ let ky = `v${l}`
812
+ verbatim_blocks[ky] = vdata.trim()
813
+ var_lines[l] = maybe_verbatim.replace(vdata,`@{${ky}}`)
814
+ }
815
+ }
816
+ return var_lines.join("$@{")
817
+ }
818
+
819
+ /**
820
+ *
821
+ * @param {string} a_part
822
+ * @param {object} verbatim_blocks
823
+ * @returns {string}
824
+ */
825
+ unblock_verabtim_parameters(a_part,verbatim_blocks) {
826
+ for ( let ky in verbatim_blocks ) {
827
+ let data = verbatim_blocks[ky]
828
+ a_part = a_part.replace(`@{${ky}}`,data)
829
+ }
830
+ return a_part
831
+ }
832
+
833
+ /**
834
+ * This method is a map call on the array, that filters 'files::params::' into parsing operations and returns all else
835
+ * in its original state.
836
+ *
837
+ * A custom sytnax is used for the passing of parameters to a subskeleton and should occur after the end of step specifier symbol, '<<'.
838
+ * The parameter specification looks a little bit like JSON, but it is not.
839
+ * The specifier is demarcated by braces, '{}', and within the braces, sub-step specifiers are given.
840
+ * The sub-step has similar structure to a step, but instead of starting with a 'start of section' symbol, i.e. "$$", it starts
841
+ * with variable assignment of the sub-section. The variable assignment step indicator includes a subsitution variable identifier
842
+ * between the '$'s of '$$' pair. For example, the form `$@{lr_div}$` will assign the rest of the step specifier to the variable
843
+ * 'lr_div` to be found in the sub template file requiring the variables.
844
+ *
845
+ * The variable assigned steps are listed sequentially for the parameterized sub template. Each variable assigned step
846
+ * may be any one of the step types indicating a file to be loaded into skeletons. As such the parameterized step
847
+ * may be recursive (maximally what is typed into the top level skeleton). So, a tree of parameterizations is constructed
848
+ * and returned.
849
+ *
850
+ * Note: Parameter files demand string input from a calling file. The step version takes up structural space, while
851
+ * the `variable assignment step indicator` version produces a string for input.
852
+ *
853
+ * @param {Array} data_parts
854
+ * @param {object} maybe_params
855
+ * @returns {Array}
856
+ */
857
+ async capture_parameters(data_parts,maybe_params) {
858
+
859
+ let promisory = data_parts.map(async (a_part) => { // map and return an array of promises
860
+ if ( (a_part.indexOf("files::") === 0) || (a_part.indexOf("script::") === 0) ) { ///
861
+ if ( (a_part.indexOf("files::params::") === 0) || (a_part.indexOf("script::params::") === 0) ) { //
862
+ let verbatim_blocks = false
863
+ if ( this.has_verbatim_params(a_part) ) {
864
+ verbatim_blocks = {}
865
+ a_part = this.block_verabtim_parameters(a_part,verbatim_blocks)
866
+ }
867
+ a_part = parse_util.remove_white(a_part)
868
+ if ( verbatim_blocks !== false ) {
869
+ a_part = this.unblock_verabtim_parameters(a_part,verbatim_blocks)
870
+ }
871
+ let parts = a_part.split('<<{')
872
+ let part_key = parts.shift()
873
+ let part_def = parts.join('<<{')
874
+ //
875
+ maybe_params[part_key] = await this.build_param_tree(part_key,part_def.substring(0,part_def.lastIndexOf('}')))
876
+ //
877
+ return part_key
878
+ }
879
+ }
880
+ return a_part
881
+ })
882
+ data_parts = await Promise.all(promisory)
883
+ return data_parts
884
+ }
885
+
886
+
887
+ /**
888
+ * pull_out_files
889
+ * @param {Array} data_parts
890
+ * @returns {Array}
891
+ */
892
+ pull_out_files(data_parts) {
893
+
894
+ let just_files = data_parts.filter((a_part) => {
895
+ return (a_part.indexOf("files::") === 0)
896
+ })
897
+ return just_files.map((a_part) => {
898
+ return ( a_part.substring("files::".length) )
899
+ })
900
+
901
+ }
902
+
903
+
904
+ /**
905
+ *
906
+ * @param {*} data_parts
907
+ * @returns
908
+ */
909
+ pull_out_css(data_parts) {
910
+
911
+ let just_files = data_parts.filter((a_part) => {
912
+ return (a_part.indexOf("css::") === 0)
913
+ })
914
+ return just_files.map((a_part) => {
915
+ return ( a_part.substring("css::".length) )
916
+ })
917
+ }
918
+
919
+
920
+ /**
921
+ * pull_out_script
922
+ * @param {Array} data_parts
923
+ * @returns {Array}
924
+ */
925
+ pull_out_script(data_parts) {
926
+ let just_scripts = data_parts.filter((a_part) => {
927
+ return (a_part.indexOf("script::") === 0)
928
+ })
929
+ return just_scripts.map((a_part) => {
930
+ return ( a_part.substring("script::".length) )
931
+ })
932
+ }
933
+
934
+
935
+ /**
936
+ *
937
+ * @param {Array} data_parts
938
+ * @returns {Array}
939
+ */
940
+ pull_out_bundles(data_parts) {
941
+ let just_bundles = data_parts.filter((a_part) => {
942
+ return (a_part.indexOf("bundle::") === 0)
943
+ })
944
+ return just_bundles.map((a_part) => {
945
+ return ( a_part.substring("bundle::".length) )
946
+ })
947
+ }
948
+
949
+
950
+ /**
951
+ * pull_out_html_directives
952
+ * @param {Array} data_parts
953
+ * @returns {Array}
954
+ */
955
+ pull_out_html_directives(data_parts) {
956
+ let just_html_d = data_parts.filter((a_part) => {
957
+ return (a_part.indexOf("html:") === 0)
958
+ })
959
+ return just_html_d
960
+ }
961
+
962
+
963
+ /**
964
+ * Returns an esimation of the size of the object.
965
+ *
966
+ * @param {object|string|Array} o_value
967
+ * @returns {number}
968
+ */
969
+ figure_size(o_value) {
970
+ if ( typeof o_value === "string" ) {
971
+ return o_value.length
972
+ } else if ( Array.isArray(o_value) ) {
973
+ return o_value.length
974
+ } else if ( typeof o_value === "object" ) {
975
+ return Object.keys(o_value).length
976
+ }
977
+ return 0
978
+ }
979
+
980
+
981
+ /**
982
+ *
983
+ * @param {Array} data_parts -- array of sections describing file to include along with alfterations related to configuration
984
+ * @param {string} which_config_vars
985
+ * @returns {Array}
986
+ */
987
+ config_var_transformations(data_parts,which_config_vars) {
988
+ //
989
+ let vars = this.app_skel_vars[which_config_vars] // a single key `uses_config_vars` from the header identifies the variable/value group for the particular skeleton
990
+ if ( vars && (typeof vars === "object")) {
991
+ for ( let [vname,value] of Object.entries(vars) ) {
992
+ let o_value = value // original
993
+ if ( typeof value !== "string" ) {
994
+ value = JSON.stringify(value)
995
+ }
996
+ let finder = `@!{${vname}}`
997
+ let count_finder = `@#{${vname}}`
998
+ //
999
+ data_parts = data_parts.map((el) => { // alter the skeleton (an update)
1000
+ if ( el.indexOf(finder) > 0 ) {
1001
+ el = el.replace(finder,value)
1002
+ }
1003
+ if ( el.indexOf(count_finder) > 0 ) {
1004
+ el = el.replace(count_finder,this.figure_size(o_value))
1005
+ }
1006
+ return el
1007
+ })
1008
+ //
1009
+ }
1010
+ }
1011
+ //
1012
+ return data_parts
1013
+ }
1014
+
1015
+
1016
+ /**
1017
+ *
1018
+ * @param {object} data_parts
1019
+ * @returns
1020
+ */
1021
+ async skeleton_parts_extraction(data_parts) {
1022
+ let files = []
1023
+ let css = []
1024
+ let scripts = []
1025
+ let bundles = []
1026
+ let maybe_params = {}
1027
+ //
1028
+ // The defaults can update the basic skeleton structure prior to skeleton processing (parsing, evaluating)
1029
+ // structure expansions of parts that will be replicated
1030
+ data_parts = await this.sequence_expansion(data_parts,maybe_params)
1031
+ // structure selected by operating on parameters specifically evaluated by configuration assignments
1032
+ data_parts = await this.capture_parameters(data_parts,maybe_params)
1033
+ //
1034
+ files = this.pull_out_files(data_parts) // look for the files that will be expanded beneath the top skeleton
1035
+ css = this.pull_out_css(data_parts) // look for the files that will be expanded beneath the top skeleton
1036
+ scripts = this.pull_out_script(data_parts) // pull out script files (remaining after bundle preparation)
1037
+ bundles = this.pull_out_bundles(data_parts)
1038
+ //
1039
+ return [data_parts,files,css,scripts,bundles,maybe_params]
1040
+ }
1041
+
1042
+
1043
+ /**
1044
+ *
1045
+ * Walks through the map of all loaded skeletons, parsing them into sections.
1046
+ * Sections are demarcated by the beginning of section symbol '$$'.
1047
+ * This method splits the string made from the skel file data (mapped by a key in all_skeletons) into sections,
1048
+ * splitting on '$$', and processes each section separately for structure only.
1049
+ *
1050
+ * This method parses each skeleton file by first creating an array of sections.
1051
+ * After the first split, the sections array is the skeleton as a list of parseable file specifications and uses.
1052
+ *
1053
+ * This method operates on each section in order to determine if further primary parsing is needed.
1054
+ * In some cases, the sections describe loops or the inclusions of files with parameters. This method will expand loops,
1055
+ * and create data structure to line up parameters with future evaluations, handled by methods called after this one.
1056
+ *
1057
+ * The first alteration of the sections is a variable substitution.
1058
+ * The first section of the file is often a specialization of the project configuration file read in by the main command line.
1059
+ * The configuration may specify a set of variable subsitutions that may be performed prior to any other section processing.
1060
+ * This method updates each section (elements in `data_parts`) with a variable subsitution.
1061
+ *
1062
+ * After taking care of any variable substitutions, this method removes comments from the sections.
1063
+ *
1064
+ * Next, the loops and parameters are processed. (note `maybe_params` is an empty object used to capcture the parsing data)
1065
+ * These processes may alter the skeletal structure by altering the array `data_parts`. In the case of loops, elements
1066
+ * may be added to the array. In the case of parameters, the section is returned with all spaces removed, and the
1067
+ * structure `maybe_params` will contain an entire parameter tree, needed for passing values down into a sub-skeleton template.
1068
+ *
1069
+ * Finally, this method makes arrays of markup imports ('files') and language imports ('scripts') for later access and processing.
1070
+ *
1071
+ * All the arrays and structures created for a skeleton file are returned as part of a map of the file names to the
1072
+ * a structure with a field for each one of the arrays or structures.
1073
+ *
1074
+ * `skel_to_concerns` maps a skeleton file to a list of concerns that use it.
1075
+ * Each concern will be identified in an ogroup, where the configuration `app_skel_vars` entry
1076
+ * will be be called out for the variables values used in calculating the skeleton for the concerns
1077
+ * sharing the ogroup. The ogroup.targets.uses_config_vars will identify the variable evaluations
1078
+ * to apply to the skeletons they use.
1079
+ *
1080
+ *
1081
+ * @param {object} all_skeletons - `skel file path` to file contents.
1082
+ * @param {object} skel_to_concerns - allows for determining splits due to structure preferences
1083
+ * @returns {object} - a map of `skel file path` to objects where each object contains parsing data structures
1084
+ */
1085
+ async section_parsing(all_skeletons) {
1086
+ let section_data = {}
1087
+ //
1088
+ for ( let [sky,data_m] of Object.entries(all_skeletons) ) { // data is a string (the contents of the skeleton file)
1089
+ //
1090
+ let data = data_m.original
1091
+ // data_parts an array
1092
+ let data_parts = data.split('$$') // all sectional definitions start with $$ (split makes leading section empty)
1093
+ let no_data = (data_parts.length < 2) //
1094
+ if ( no_data ) continue
1095
+ //
1096
+ section_data[sky] = {}
1097
+ let skel_ops = data_m.ops
1098
+ //
1099
+ data_parts.shift() // the first element is expected to be empty or white space or some comment (throw away)
1100
+ data_m.shifted_data = data_parts
1101
+ //
1102
+ data_parts = data_parts.map((el) => { return parse_util.clear_comments(el.trim()).trim() })
1103
+ //
1104
+ //
1105
+ let def_check = data_parts[0]
1106
+ let def_defaults = false
1107
+ if ( def_check.indexOf("defs:") === 0 ) { // if present, this becomes a structure.
1108
+ def_defaults = def_check
1109
+ data_parts.shift() // will process defaults
1110
+ data_m.shifted_data = data_parts
1111
+ //
1112
+ data_parts = [].concat(data_parts) // make a copy
1113
+ //
1114
+ try {
1115
+ def_defaults = JSON.parse(def_defaults.substring("defs:".length))
1116
+ } catch(e) {
1117
+ def_defaults = undefined
1118
+ }
1119
+ // make a default entry for this skeleton
1120
+ if ( def_defaults ) { // defaults from the skeleton file $$defs
1121
+ let cvars_ky = def_defaults?.uses_config_vars // a name found in a configuration
1122
+ if ( typeof cvars_ky === "string" ) {
1123
+ //
1124
+ // use configuration variable values to put in values for skeleton level variables
1125
+ data_parts = this.config_var_transformations(data_parts,cvars_ky)
1126
+ // next expand sequences and get separate imports for markup and script
1127
+ let [dps,files,css,scripts,bundles,maybe_params] = await this.skeleton_parts_extraction(data_parts)
1128
+ data_parts = dps
1129
+ section_data[sky]["defaults"] = {
1130
+ "defaults" : def_defaults,
1131
+ "final" : {
1132
+ "markup" : false,
1133
+ "scripts" : false
1134
+ },
1135
+ "skeleton" : data_parts,
1136
+ "files" : files,
1137
+ "css" : css,
1138
+ "scripts" : scripts,
1139
+ "bundles" : bundles,
1140
+ "parameterized" : maybe_params
1141
+ }
1142
+ //
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ // all the ops assigned to this skeleton from configuration...
1148
+ for ( let op_ky in skel_ops) {
1149
+ if ( section_data[sky][op_ky] === undefined ) {
1150
+ //
1151
+ data_parts = data_m.shifted_data
1152
+ //
1153
+ // use configuration variable values to put in values for skeleton level variables
1154
+ data_parts = this.config_var_transformations(data_parts,op_ky)
1155
+ // next expand sequences and get separate imports for markup and script
1156
+ let [dps,files,css,scripts,bundles,maybe_params] = await this.skeleton_parts_extraction(data_parts)
1157
+ data_parts = dps
1158
+ section_data[sky][op_ky] = {
1159
+ "defaults" : def_defaults,
1160
+ "final" : {
1161
+ "markup" : false,
1162
+ "scripts" : false
1163
+ },
1164
+ "skeleton" : data_parts,
1165
+ "files" : files,
1166
+ "css" : css,
1167
+ "scripts" : scripts,
1168
+ "bundles" : bundles,
1169
+ "parameterized" : maybe_params
1170
+ }
1171
+ //
1172
+ }
1173
+ }
1174
+ }
1175
+
1176
+ // console.dir(section_data,{depth : 6})
1177
+ // process.exit(0)
1178
+
1179
+ return section_data // return the whole table
1180
+ }
1181
+
1182
+
1183
+ /**
1184
+ *
1185
+ * Outputs to each concern the variable forms table, yielding default substitutions.
1186
+ * If the developer chooses to change these before final template generation he may.
1187
+ *
1188
+ *
1189
+ * Does not output an entire DB for each page that is being generated.
1190
+ * This outputs a selection for each template.
1191
+ *
1192
+ *
1193
+ *
1194
+ * `
1195
+ "name_parameters" : {
1196
+ "db" : "[names]/name-drop.db",
1197
+ "parameter_values" : "[websites]/@concern/@target/name-drop.json",
1198
+ "index.tmplt" : [ "contact_box", "about_box", "topicBox_<1,3>", "thankyou_box", "register" ],
1199
+ "login.tmplt" : [ "contact_box", "thankyou_box", "login" ]
1200
+ }
1201
+ `
1202
+ *
1203
+ */
1204
+ async name_parameters_output() {
1205
+ //
1206
+ let outputs = this.outputs
1207
+ for ( let ogroup of outputs ) {
1208
+ //
1209
+ let name_parameters = ogroup.name_parameters ? true : false
1210
+ if ( name_parameters ) {
1211
+ let concerns = ogroup.targets.concerns
1212
+ //
1213
+ for ( let concern of concerns ) {
1214
+ name_parameters = Object.assign({},ogroup.name_parameters) // actually its an object
1215
+ let drops_db = this.name_drops_db // The template db that allows for specializations to be figured for the concerns output
1216
+ if ( name_parameters.db ) {
1217
+ let name_db = this.paths.compile_one_path(name_parameters.db)
1218
+ drops_db = await this.load_name_drops_db(name_db) // loading this each time (useful after specialization)
1219
+ delete name_parameters.db
1220
+ }
1221
+ let output = name_parameters.parameter_values ? this.paths.compile_one_path(name_parameters.parameter_values) : false
1222
+ if ( output ) {
1223
+ output = output.replace('@concern',concern)
1224
+ output = output.replace("@target",this.created_dir)
1225
+ delete name_parameters.parameter_values // delete one more non file name key
1226
+ let value_object = {} // collect for each template
1227
+ for ( let [tmplt_ky,db_keys] of Object.entries(name_parameters) ) {
1228
+ if ( tmplt_ky === 'db' ) continue // added this for clarity during validation
1229
+ if ( tmplt_ky === 'parameter_values' ) continue // added this for clarity during validation
1230
+ if ( tmplt_ky.indexOf('.tmplt') < 0 ) continue // handle case not planned for at this time
1231
+ // tmplt_ky has to be the name of a tempalte file
1232
+ value_object[tmplt_ky] = {} // vars we are getting for this template
1233
+ for ( let form_name of db_keys ) { // maybe it's a form or some display
1234
+ value_object[tmplt_ky][form_name] = drops_db[form_name]
1235
+ }
1236
+ }
1237
+ output = this.paths.resolve(output)
1238
+ await fos.write_out_pretty_json(output,value_object,4)
1239
+ }
1240
+ }
1241
+ //
1242
+ }
1243
+ //
1244
+ }
1245
+ //
1246
+ }
1247
+
1248
+
1249
+
1250
+
1251
+ /**
1252
+ *
1253
+ * This is for gathering stats about script usage.
1254
+ *
1255
+ * @param {object} transform_1
1256
+ * @returns {object}
1257
+ */
1258
+ coalesce_scripts(transform_1) { // now has a nother level (concerns)
1259
+ //
1260
+ let scripts_occurences = {}
1261
+ for ( let op_map of Object.values(transform_1) ) {
1262
+ for ( let dpart of Object.values(op_map) ) {
1263
+ let scripts = dpart.scripts
1264
+ if ( scripts.length ) {
1265
+ for ( let script of scripts ) {
1266
+ let p = scripts_occurences[script]
1267
+ scripts_occurences[script] = p ? p + 1 : 1
1268
+ }
1269
+ }
1270
+ }
1271
+ }
1272
+ //
1273
+ return scripts_occurences
1274
+ }
1275
+
1276
+
1277
+ /**
1278
+ *
1279
+ * @param {object} sorted_stats
1280
+ * @returns {object}
1281
+ */
1282
+ partition_stats(sorted_stats) {
1283
+ let partitions = []
1284
+ //
1285
+ let bdiff = Object.values(sorted_stats)
1286
+ let n = bdiff.length
1287
+ let diffs = []
1288
+ for ( let i = 0; i < n-1; i++ ) {
1289
+ let v0 = bdiff[i]
1290
+ let v1 = bdiff[i+1]
1291
+ diffs.push(v1-v0)
1292
+ }
1293
+ let last_big_diff = 0
1294
+ partitions.push([])
1295
+ let p_index = 0
1296
+ let skeys = Object.keys(sorted_stats)
1297
+ for ( let i = 0; i < n-1; i++ ) {
1298
+ let d = diffs[i]
1299
+ if ( d > last_big_diff ) {
1300
+ d = last_big_diff
1301
+ partitions.push([])
1302
+ p_index++
1303
+ }
1304
+ partitions[p_index].push(skeys[i])
1305
+ }
1306
+ partitions[p_index].push(skeys[n-1])
1307
+ return partitions
1308
+ }
1309
+
1310
+
1311
+ /**
1312
+ *
1313
+ * @param {object} script_stats
1314
+ * @param {number} threshold
1315
+ * @returns {object}
1316
+ */
1317
+ partition(script_stats) {
1318
+
1319
+ let sorted_stats = {}
1320
+ //
1321
+ // sort by key prefix to get a grouping of functional parts
1322
+ sorted_stats = parse_util.key_sort(script_stats,(ky) => {
1323
+ let slash = ky.indexOf('/')
1324
+ if ( slash > 0 ) {
1325
+ let tester = ky.substring(0,ky.indexOf('/'));
1326
+ return tester
1327
+ } else return ky
1328
+ })
1329
+ //
1330
+
1331
+ // now put common prefixes into buckets
1332
+ let prefix_buckets = {}
1333
+ let bucket_keys = Object.keys(sorted_stats)
1334
+ for ( let ky of bucket_keys ) {
1335
+ if ( ky[0] === '[' ) {
1336
+ let bucket_ky = ky.substring(0,ky.indexOf('/'))
1337
+ let bucket = prefix_buckets[bucket_ky]
1338
+ if ( bucket === undefined ) {
1339
+ bucket = {}
1340
+ prefix_buckets[bucket_ky] = bucket
1341
+ }
1342
+ bucket[ky] = sorted_stats[ky]
1343
+ } else {
1344
+ let bucket = prefix_buckets["[script]"]
1345
+ if ( bucket === undefined ) {
1346
+ bucket = {}
1347
+ prefix_buckets["[script]"] = bucket
1348
+ }
1349
+ bucket[ky] = sorted_stats[ky]
1350
+ }
1351
+ }
1352
+ // before partitioning the sorted stats buckets,
1353
+ // make sure they are sorted by score
1354
+ //
1355
+ //console.log("sorted_stats")
1356
+ let nested_partitions = {}
1357
+
1358
+ for ( let [ky,bucket] of Object.entries(prefix_buckets) ) {
1359
+ //
1360
+ let keys = Object.keys(bucket)
1361
+ keys.sort((k1,k2) => {
1362
+ let v1 = bucket[k1]
1363
+ let v2 = bucket[k2]
1364
+ return v1 - v2
1365
+ })
1366
+ let sorted_bucket = {}
1367
+ for ( let bky of keys ) {
1368
+ sorted_bucket[bky] = bucket[bky]
1369
+ }
1370
+ //console.log(ky)
1371
+ //console.dir(sorted_bucket)
1372
+ //
1373
+ nested_partitions[ky] = this.partition_stats(sorted_bucket)
1374
+ }
1375
+ //
1376
+
1377
+ return nested_partitions
1378
+ }
1379
+
1380
+
1381
+ /**
1382
+ * Partitions files into directories for later use in putting code into
1383
+ * bundles, which will be requested in headers.
1384
+ *
1385
+ * prep_script_directories is one of the first operations in skeleton skeleton_parsing.
1386
+ *
1387
+ * Each skeleton lists a number of scripts that make resulting applications work in general.
1388
+ *
1389
+ * Previously, all of the scripts were placed into the HTML file. But, that results in inefficient loading
1390
+ * and management.
1391
+ *
1392
+ * @param {object} partitions
1393
+ */
1394
+ async prep_script_directories(partitions) {
1395
+
1396
+ for ( let pky in partitions ) {
1397
+ console.log("prep_script_directories",pky)
1398
+ if ( pky.indexOf(">") > 1 ) {
1399
+ console.log("\tskipping")
1400
+ } else {
1401
+ console.log("DATA")
1402
+ let bare_ky = pky.replace('[','').replace(']','')
1403
+ let script_src_dir = this.top_dir_locations[bare_ky]
1404
+ console.log("script source: ",script_src_dir,">> bare_ky:: ",bare_ky)
1405
+ let script_grouping_dir = `${this.project_dir}bundle_src/${bare_ky}`
1406
+ script_grouping_dir = this.paths.resolve(script_grouping_dir)
1407
+ console.log("script output: ", script_grouping_dir)
1408
+ await fos.ensure_directories(script_grouping_dir)
1409
+ let subdr = 'A'
1410
+ for ( let file_list of partitions[pky] ) {
1411
+ if ( file_list.length ) {
1412
+ let part_dir = `${script_grouping_dir}/${subdr}`
1413
+ await fos.ensure_directories(part_dir)
1414
+ for ( let file of file_list ) {
1415
+ file = file.replace(`${pky}/`,'').replace('<<','')
1416
+ let fpath = `${part_dir}/${file}`
1417
+ let spath = `${script_src_dir}/${file}`
1418
+ if ( file.indexOf('/') > 0 ) {
1419
+ console.log("ENSURE:",part_dir)
1420
+ let parts = file.split('/')
1421
+ parts.pop()
1422
+ parts = parts.join('/')
1423
+ await fos.ensure_directories(`${part_dir}/${parts}`)
1424
+ }
1425
+ console.log(spath)
1426
+ console.log(fpath)
1427
+ await fos.file_copier(spath,fpath)
1428
+ }
1429
+ subdr = parse_util.next_char(subdr)
1430
+ }
1431
+ }
1432
+ }
1433
+ }
1434
+ }
1435
+
1436
+
1437
+ /**
1438
+ *
1439
+ * @param {object} transformed
1440
+ * @param {object} script_stats
1441
+ */
1442
+ update_script_stats_usage(transformed,script_stats) {
1443
+ //
1444
+ let outputs = this.outputs
1445
+
1446
+ let top_level_skel_counts = {}
1447
+
1448
+ for ( let output of outputs ) {
1449
+ let n_concerns = output.targets.concerns.length
1450
+ let skeletons = output.skeletons
1451
+ let mid_level_skel_counts = {}
1452
+ for ( let skel of Object.values(skeletons) ) {
1453
+ let p = mid_level_skel_counts[skel]
1454
+ mid_level_skel_counts[skel] = p ? p + 1 : 1
1455
+ }
1456
+ for ( let [skel,count] of Object.entries(mid_level_skel_counts) ) {
1457
+ let p = top_level_skel_counts[skel]
1458
+ top_level_skel_counts[skel] = p ? p + count*n_concerns : count*n_concerns
1459
+ }
1460
+ }
1461
+
1462
+
1463
+ for ( let [skel,count] of Object.entries(top_level_skel_counts) ) {
1464
+
1465
+ let usages = transformed[skel]
1466
+ if ( usages ) {
1467
+
1468
+ for ( let script of usages.scripts ) {
1469
+ let p = script_stats[script]
1470
+ if ( p ) {
1471
+ script_stats[script] = p*count
1472
+ }
1473
+ }
1474
+ }
1475
+ }
1476
+ }
1477
+
1478
+
1479
+ /**
1480
+ *
1481
+ * @param {string} step_entry
1482
+ * @param {Array} cross_directory_check - optional
1483
+ * @returns {Array} - a tripple [file name, token indicating a compiled path, the type of file]
1484
+ */
1485
+ get_file_data_descriptor(step_entry,cross_directory_check) {
1486
+ //
1487
+ let file_name = false
1488
+ let path_finder = false
1489
+ let entry_type = false
1490
+ let entry_directive = false
1491
+ //
1492
+ let file_entry_starter = !(cross_directory_check) ? this.entry_starter.exec(step_entry) : false
1493
+ if ( !file_entry_starter ) {
1494
+ file_entry_starter = cross_directory_check ? cross_directory_check : this.cross_type_directory.exec(step_entry)
1495
+ if ( file_entry_starter ) {
1496
+ entry_directive = file_entry_starter[1]
1497
+ entry_type = file_entry_starter[2]
1498
+ file_name = file_entry_starter[3]
1499
+ if ( file_name.indexOf('.') < 0 ) {
1500
+ file_name = `${file_name}.${entry_type}`
1501
+ }
1502
+ if ( entry_type === 'js' ) entry_type = "script"
1503
+ if ( entry_type === 'tjs' ) entry_type = "script"
1504
+ }
1505
+ } else {
1506
+ entry_directive = file_entry_starter[1]
1507
+ file_name = step_entry.substring((entry_directive).length + 2)
1508
+ }
1509
+ if ( entry_directive ) {
1510
+ let [pf,et] = this.entry_directive_location_remap(entry_directive)
1511
+ path_finder = pf
1512
+ entry_type = (entry_type && (et !== entry_type)) ? entry_type : et
1513
+ }
1514
+ //
1515
+ return [file_name,path_finder,entry_type]
1516
+ }
1517
+
1518
+
1519
+ /**
1520
+ * Looks at the entry specifiers and determines the absolute location of a file to load.
1521
+ * If successful, this returns an a key-value object with three keys, type, file, data, corresponding to
1522
+ * the type of the file, the file path, and the data stored in the file (a string)
1523
+ *
1524
+ * @param {string} step_entry
1525
+ * @param {Array} cross_directory_check - optional .. the result of a RegExp,exec if it is provide
1526
+ * @returns {object|string}
1527
+ */
1528
+ async entry_loading(step_entry,cross_directory_check) {
1529
+ //
1530
+ let map_value = {}
1531
+ let [file_name,path_finder,entry_type] = this.get_file_data_descriptor(step_entry,cross_directory_check)
1532
+ //
1533
+ //
1534
+ if ( file_name ) {
1535
+ let file_path = this.top_dir_locations[path_finder]
1536
+ if ( file_name[0] === '[' ) {
1537
+ let entry_match = this.sibling_type_directory_match.exec(file_name)
1538
+ if ( entry_match ) {
1539
+ let path_f = entry_match[1]
1540
+ file_path = this.top_dir_locations[path_f]
1541
+ file_name = entry_match[2]
1542
+ } else {
1543
+ // handle special cases where the developer has custom leaf code
1544
+ // use the syntax to get information for constructing the directory name.
1545
+ // Do not load a file. That will be done in a second phase.
1546
+ if ( file_name.indexOf('<') > 0 ) {
1547
+ entry_match = this.concerns_directory_redirect_match.exec(file_name)
1548
+ if ( entry_match ) {
1549
+ let search_form = entry_match[1]
1550
+ let use_case = entry_match[2]
1551
+ let p_finder = entry_match[3]
1552
+ let file_form = this.find_concerns[search_form]
1553
+ let file_path = file_name.substring(file_name.indexOf(']')+1)
1554
+ let data = `{{{ @{${file_form}}${file_path} }}}`
1555
+ //console.log(file_name,path_finder,entry_type,file_form,use_case,p_finder,data)
1556
+ map_value = {
1557
+ "search_form" : search_form,
1558
+ "file" : file_path,
1559
+ "use_case" : use_case,
1560
+ "kernel" : p_finder,
1561
+ "type" : entry_type,
1562
+ "data" : data
1563
+ }
1564
+ return map_value
1565
+ }
1566
+ } else {
1567
+ return "not_handled - a"
1568
+ }
1569
+ }
1570
+ }
1571
+ file_path = `${file_path}/${file_name}`
1572
+ let data = await fos.load_data_at_path(file_path)
1573
+ let datawc = ""
1574
+ if ( data ) {
1575
+ datawc = "" + data
1576
+ data = parse_util.clear_comments(data)
1577
+ } else {
1578
+ data = "NO DATA"
1579
+ datawc = data
1580
+ }
1581
+ map_value = {
1582
+ "type" : entry_type,
1583
+ "file" : file_path,
1584
+ "data" : data
1585
+ }
1586
+ if ( this.check_recursive_data(data) ) {
1587
+ map_value.recursive = await this.get_files_and_vars(step_entry,data)
1588
+ }
1589
+ //
1590
+ this.shared_entries[step_entry] = Object.assign({},map_value)
1591
+ return map_value
1592
+ }
1593
+ return "not hanlded - b"
1594
+ }
1595
+
1596
+
1597
+
1598
+ /**
1599
+ *
1600
+ * @param {string} data
1601
+ * @param {object} carrier - an object with the filed `var`
1602
+ * @returns
1603
+ */
1604
+ list_start(data,carrier) {
1605
+ let dat_match = this.start_of_list.exec(data)
1606
+ if ( dat_match ) {
1607
+ carrier.var = dat_match[1]
1608
+ return true
1609
+ }
1610
+ return false
1611
+ }
1612
+
1613
+
1614
+ /**
1615
+ *
1616
+ * @param {object | boolean} params_def
1617
+ * @param {string} data
1618
+ * @returns {object}
1619
+ */
1620
+ errant_variable_extraction(params_def,data) {
1621
+ //
1622
+ let eve_results = {}
1623
+ let occurrences = data.match(this.var_pattern)
1624
+ //
1625
+ if ( !occurrences ) return undefined
1626
+ //
1627
+ occurrences = occurrences.map((occ) => {
1628
+ return occ.replace("@{","").replace("}","")
1629
+ })
1630
+ //
1631
+ if ( occurrences.length ) {
1632
+ for ( let occur of occurrences ) {
1633
+ let atype = ""
1634
+ if ( occur.indexOf('%') > 0 ) {
1635
+ atype = occur.split('%')[1]
1636
+ atype = '%' + atype + '%'
1637
+ }
1638
+ eve_results[occur] = atype
1639
+ }
1640
+ if ( !params_def ) {
1641
+ params_def = eve_results
1642
+ } else {
1643
+ for ( let ky of Object.keys(eve_results) ) {
1644
+ if ( !(ky in params_def) ) {
1645
+ params_def[ky] = eve_results[ky]
1646
+ }
1647
+ }
1648
+ }
1649
+ return params_def
1650
+ }
1651
+ //
1652
+ return undefined
1653
+ }
1654
+
1655
+
1656
+ /**
1657
+ * Handles the inclusion of recursive scripts call by skeletons and templates or s-templates
1658
+ * Some sub files may start with parameter definitions or element definitions
1659
+ * Others may have executables
1660
+ *
1661
+ * This method pulls out the definitions which must be associated with values, structures or basic values.
1662
+ * The final roster of executable, variables, parameter defs, list element defs, are returned in a structure
1663
+ * which will be used later during an evaluation phase.
1664
+ *
1665
+ * @param {string} step_entry
1666
+ * @param {string} data
1667
+ * @returns {object}
1668
+ */
1669
+ async get_files_and_vars(step_entry,data) {
1670
+ //
1671
+ if ( data.indexOf('//') >= 0 ) {
1672
+ data = parse_util.clear_comments(data)
1673
+ }
1674
+
1675
+ let params_def = false
1676
+ let carrier = {}
1677
+ let _is_loop = false
1678
+
1679
+ let lines = []
1680
+ if ( data.startsWith("@params<") ) { // will be at the top of a file (parameter defs for matching calling skeleton)
1681
+ //
1682
+ lines = data.split("\n")
1683
+ let first_line = lines.shift()
1684
+ first_line = parse_util.remove_spaces(first_line)
1685
+ let end_def = first_line.indexOf("}>")
1686
+ while ( (end_def < 0) && lines.length ) {
1687
+ let next_line = lines.shift()
1688
+ next_line = parse_util.remove_spaces(next_line)
1689
+ first_line += '\n' + next_line
1690
+ end_def = next_line.indexOf("}>")
1691
+ }
1692
+ //
1693
+ let flines = first_line.split('\n')
1694
+ first_line = flines.join("")
1695
+
1696
+ // @params<{lr_div:file,logout:file}>
1697
+ let var_defs = first_line.replace("@params<","")
1698
+ var_defs = var_defs.replace(">","")
1699
+ var_defs = var_defs.replace("{",'{\"')
1700
+ var_defs = var_defs.replace("}",'\"}')
1701
+ //
1702
+ let colon_split = var_defs.split(":")
1703
+ var_defs = colon_split.join('\":\"')
1704
+ let comma_split = var_defs.split(",")
1705
+ var_defs = comma_split.join('\",\"')
1706
+ //
1707
+ try {
1708
+ params_def = JSON.parse(var_defs)
1709
+ } catch(e) {}
1710
+ //
1711
+ } else if ( this.list_start(data,carrier) ) { // will be at the top of file for matching list element components (one at a time)
1712
+ //
1713
+ lines = data.split("\n")
1714
+ let first_line = lines.shift()
1715
+ first_line = parse_util.remove_spaces(first_line)
1716
+ let end_def = first_line.indexOf("}>")
1717
+ while ( (end_def < 0) && lines.length ) {
1718
+ let next_line = lines.shift()
1719
+ next_line = parse_util.remove_spaces(next_line)
1720
+ first_line += '\n' + next_line
1721
+ end_def = next_line.indexOf("}>")
1722
+ }
1723
+ let var_defs = first_line.substring(first_line.indexOf('<{') + 1,first_line.indexOf('}>') + 1)
1724
+ //
1725
+ var_defs = var_defs.replace("{",'{\"')
1726
+ var_defs = var_defs.replace("}",'\"}')
1727
+ //
1728
+ let colon_split = var_defs.split("<-")
1729
+ var_defs = colon_split.join('\":\"')
1730
+ let comma_split = var_defs.split(",")
1731
+ var_defs = comma_split.join('\",\"')
1732
+ try {
1733
+ params_def = JSON.parse(var_defs)
1734
+ } catch(e) {}
1735
+ //
1736
+ _is_loop = true
1737
+ params_def._var_name = carrier.var
1738
+ //
1739
+ } else {
1740
+ // no parameter or list defs, so just pull in any variables, schemes and the like
1741
+ // that may be needed to decide on final structure or explicit values
1742
+ // when a template is being generated
1743
+ let executables = this.extract_excecutables(data)
1744
+ params_def = this.errant_variable_extraction(params_def,data)
1745
+ return { _is_loop, params_def, executables }
1746
+ }
1747
+
1748
+ // parameters or loop assignments have been found, so
1749
+ // finish the collection of variables and possible executable operations.
1750
+ params_def = this.errant_variable_extraction(params_def,data)
1751
+ //
1752
+ let executables = []
1753
+ if ( lines.length ) {
1754
+ let rest = lines.join('\n')
1755
+ executables = this.extract_excecutables(rest)
1756
+ }
1757
+ //
1758
+ return { _is_loop, params_def, executables }
1759
+ }
1760
+
1761
+
1762
+
1763
+ /**
1764
+ * A ternary conditional is a type of executable
1765
+ * with a means for picking a particular path of construction.
1766
+ * This method creates the structure that evaluation may use
1767
+ * to yield a particular structure given the setting of
1768
+ * configuration variables.
1769
+ * @param {string} parseable
1770
+ * @param {object} exec_report
1771
+ * @returns {boolean}
1772
+ */
1773
+ ternary_conditional(parseable,exec_report) {
1774
+
1775
+ let ternary = this.ternary_check.exec(parseable)
1776
+ if ( ternary ) {
1777
+ let cond = ternary[1].trim()
1778
+ //
1779
+ let first = ""
1780
+ let second = ""
1781
+ let third = ""
1782
+ //
1783
+ if ( parseable.indexOf("::") < 0 ) {
1784
+ first = ternary[2]
1785
+ second = ternary[3]
1786
+ third = false
1787
+ } else {
1788
+ let prest = parseable.substring(parseable.indexOf('?') + 1)
1789
+ prest = prest.split('::')
1790
+ //
1791
+ first = prest.shift()
1792
+ second = prest.shift()
1793
+ third = prest.shift()
1794
+ //
1795
+ if ( first.indexOf(':') > 0 ) {
1796
+ let fparts = first.split(':')
1797
+ first = fparts[0]
1798
+ second = fparts[1] + "::" + second
1799
+ if ( third ) {
1800
+ second = second + "::" + third
1801
+ }
1802
+ } else if ( second && (second.indexOf(':') >= 0) ) {
1803
+ let sparts = second.split(':')
1804
+ first = first + "::" + sparts[0]
1805
+ second = sparts[1]
1806
+ if ( third ) {
1807
+ second = second + "::" + third
1808
+ }
1809
+ }
1810
+ }
1811
+ //
1812
+ let pos = first.trim()
1813
+ let neg = second.trim()
1814
+
1815
+ if ( pos === "@nothing" ) pos = ""
1816
+ if ( neg === "@nothing" ) neg = ""
1817
+
1818
+ let variable = parse_util.extract_var(cond)
1819
+
1820
+ exec_report.condition = {cond,variable}
1821
+ exec_report.positive_exec = {pos}
1822
+ exec_report.negative_exec = {neg}
1823
+ //
1824
+ return true
1825
+ }
1826
+ return false
1827
+ }
1828
+
1829
+
1830
+
1831
+
1832
+ /**
1833
+ *
1834
+ * @param {string} parseable
1835
+ * @returns
1836
+ */
1837
+ parse_executable(parseable) {
1838
+ //
1839
+ //console.log("parse_executable",parseable)
1840
+ let exec = {
1841
+ "replace" : parseable,
1842
+ "condition" : true,
1843
+ "positive_exec" : parseable.trim(),
1844
+ "negative_exec" : "",
1845
+ }
1846
+ parseable = parseable.replace(">>","").trim()
1847
+ parseable = parseable.substring(0,parseable.lastIndexOf("<<")).trim()
1848
+ if ( this.ternary_conditional(parseable,exec) ) {
1849
+ //
1850
+ let an_import = this.seek_imports(exec.positive_exec.pos)
1851
+ if ( an_import ) {
1852
+ exec.positive_exec.replace = exec.positive_exec.neg
1853
+ exec.positive_exec.file = an_import
1854
+ }
1855
+ an_import = this.seek_imports(exec.negative_exec.neg)
1856
+ if ( an_import ) {
1857
+ exec.negative_exec.replace = exec.negative_exec.neg
1858
+ exec.negative_exec.file = an_import
1859
+ }
1860
+ //
1861
+ }
1862
+ return exec
1863
+ }
1864
+
1865
+
1866
+ /**
1867
+ * seek_imports
1868
+ *
1869
+ * @param {string} maybe_imports
1870
+ * @returns {Array | boolean}
1871
+ */
1872
+ seek_imports(maybe_imports) {
1873
+ let type = this.check_recursive_data(maybe_imports)
1874
+ let rest = maybe_imports
1875
+ //
1876
+ let imports = []
1877
+ let i = 0
1878
+ while ( (type >= 1) && (type <= 3) && rest.length ) {
1879
+ let entry_loc = rest.indexOf('$$')
1880
+ if ( entry_loc < 0 ) break;
1881
+ //
1882
+ let replacer = rest.indexOf("<<") >= 0 ? rest.substring(entry_loc,rest.indexOf("<<") + 2) : undefined
1883
+ let step_entry = rest.substring(entry_loc + 2)
1884
+ rest = rest.substring(entry_loc + 2 + step_entry.indexOf("::"))
1885
+
1886
+ if ( step_entry.length ) {
1887
+ if ( step_entry.indexOf("<<") > 0 ) {
1888
+ step_entry = step_entry.substring(0,step_entry.indexOf("<<"))
1889
+ rest = rest.substring(rest.indexOf("<<")+2)
1890
+ }
1891
+ //
1892
+ let [file_name, path_finder, entry_type] = this.get_file_data_descriptor(step_entry)
1893
+ //
1894
+ let map_value = {
1895
+ "replace" : replacer,
1896
+ "type" : entry_type,
1897
+ "file" : file_name,
1898
+ "path_finder" : path_finder,
1899
+ "data" : false
1900
+ }
1901
+ //
1902
+ imports.push(map_value)
1903
+ this.delay_file_loading_queue.push(map_value)
1904
+ }
1905
+ if ( rest.indexOf('<<') > 0 ) {
1906
+ type = this.check_recursive_data(rest)
1907
+ } else break
1908
+ }
1909
+ if ( imports.length ) return imports
1910
+ return false
1911
+ }
1912
+
1913
+ /**
1914
+ * extract_excecutables
1915
+ *
1916
+ * builds up the lists of executables and imports that are called out in a file.
1917
+ *
1918
+ *
1919
+ * @param {string} data_form
1920
+ * @returns {object}
1921
+ */
1922
+ extract_excecutables(data_form) {
1923
+ //
1924
+ let execs = []
1925
+ let imports = []
1926
+ if ( data_form.indexOf('>>') >= 0 ) {
1927
+ let parts = data_form.split('>>')
1928
+ for ( let i = 1; i < parts.length; i++ ) {
1929
+ let p = parts[i]
1930
+ let skip = 2
1931
+ let reattach_end = "<<"
1932
+ if ( p.indexOf("<<<<") > 0 ) { // executable with import
1933
+ p = p.substring(0,p.indexOf("<<<<"))
1934
+ reattach_end = "<<<<"
1935
+ skip = 4
1936
+ } else {
1937
+ p = p.substring(0,p.indexOf("<<"))
1938
+ }
1939
+ parts[i] = p.substring(p.indexOf("<<") + skip)
1940
+ p = this.parse_executable(">>" + p + reattach_end)
1941
+ execs.push(p)
1942
+ }
1943
+ for ( let i = 1; i < parts.length; i++ ) {
1944
+ let an_import = this.seek_imports(parts[i])
1945
+ if ( an_import ) {
1946
+ imports = imports.concat(an_import)
1947
+ }
1948
+ }
1949
+ } else {
1950
+ imports = this.seek_imports(data_form)
1951
+ }
1952
+ return {execs,imports}
1953
+ }
1954
+
1955
+ //
1956
+ // case 1:
1957
+ // @params<{lr_div:file,logout:file}>
1958
+ //
1959
+ // case 2:
1960
+ // @list<el><{ group_name <- el[1], SOURCE-LINK <- el[2], FRAME-ACTIONS <- el[3]}>
1961
+ // ...
1962
+ // <@el>
1963
+ // ...
1964
+ // </@el>
1965
+ //
1966
+ // case 3:
1967
+ // >>@{FRAME-ACTIONS} ? @{FRAME-ACTIONS} : @nothing <<
1968
+ //
1969
+ // case 4:
1970
+ // @{group_name}
1971
+ //
1972
+ // case 5:
1973
+ // >> any action at all <<
1974
+ // EXAMPLE: >> @p = 2 + 4; put @p here; put @p after next div; <<
1975
+ // EXAMPLE: >> @p = 2 + 4; @bubble = @p; << //late @bubble appears in the html (text)
1976
+ //
1977
+ // case 6:
1978
+ // $$icons::mushroom-menu-icon.svg
1979
+ // OR $$`path-finder`::`file-stem`.`ext`
1980
+ //
1981
+
1982
+
1983
+ is_language_section_control(step_entry) {
1984
+ return false
1985
+ }
1986
+
1987
+ /**
1988
+ * // generalization for later
1989
+ * @param {string} step_entry
1990
+ * @returns {string}
1991
+ */
1992
+ extract_lang_controller_key(step_entry) {
1993
+ return step_entry.substring(0,step_entry.indexOf(':'))
1994
+ }
1995
+
1996
+
1997
+
1998
+ /**
1999
+ * has_calc(entry_data)
2000
+ */
2001
+
2002
+ has_calc(entry_data) {
2003
+ //
2004
+ if ( entry_data?.key_values ) {
2005
+ //
2006
+ for ( let fcall of Object.values(entry_data.key_values) ) {
2007
+ if ( (fcall === "f@incr{$}") || (fcall === "f@init{$}") ) {
2008
+ continue
2009
+ }
2010
+ let parse_call = this.basic_function_call_match.exec(fcall)
2011
+ if ( parse_call ) {
2012
+ return true
2013
+ }
2014
+ }
2015
+ //
2016
+ } else {
2017
+ // console.log("has_calc")
2018
+ // console.dir(entry_data)
2019
+ }
2020
+ //
2021
+ return false
2022
+ }
2023
+
2024
+
2025
+ /**
2026
+ *
2027
+ * @param {object} entry_data
2028
+ * @returns {Array}
2029
+ */
2030
+ gather_calculations(entry_data) {
2031
+ let all_calls = []
2032
+ for ( let [vname,fcall] of Object.entries(entry_data.key_values) ) {
2033
+ if ( (fcall === "f@incr{$}") || (fcall === "f@init{$}") ) {
2034
+ continue
2035
+ }
2036
+ let parse_call = this.basic_function_call_match.exec(fcall)
2037
+ if ( parse_call ) {
2038
+ let one_call = {}
2039
+ one_call.set_var = vname
2040
+ one_call.func = parse_call[1]
2041
+ one_call.param = parse_call[2]
2042
+ //
2043
+ all_calls.push(one_call)
2044
+ } else {
2045
+ let one_call = {}
2046
+ one_call.set_var = vname
2047
+ one_call.func = "copy"
2048
+ one_call.param = fcall // actually just a string
2049
+ //
2050
+ all_calls.push(one_call)
2051
+ }
2052
+ }
2053
+ return all_calls
2054
+ }
2055
+
2056
+
2057
+ /**
2058
+ *
2059
+ * @param {object} entry_data
2060
+ * @param {string} fcall
2061
+ * @param {string} caller_stem
2062
+ */
2063
+ make_call(entry_data,fcall,caller_stem) {
2064
+ let func = fcall.func
2065
+ if ( (typeof entry_data.evaluations) !== "object" ) {
2066
+ entry_data.evaluations = {}
2067
+ }
2068
+ switch ( func ) {
2069
+ case "name" : {
2070
+ let target = fcall.param
2071
+ if ( target === "parent" ) {
2072
+ entry_data.evaluations[fcall.set_var] = caller_stem
2073
+ }
2074
+ break;
2075
+ }
2076
+ case "copy" : {
2077
+ entry_data.evaluations[fcall.set_var] = fcall.param
2078
+ break
2079
+ }
2080
+ default: {
2081
+ break
2082
+ }
2083
+ }
2084
+ }
2085
+
2086
+
2087
+
2088
+ /**
2089
+ *
2090
+ * @param {object} entry_data
2091
+ * @returns {boolean}
2092
+ */
2093
+ has_incrementer(entry_data) {
2094
+ //
2095
+ if ( typeof entry_data.key_values === "object" ) {
2096
+ let values = Object.values(entry_data.key_values)
2097
+ for ( let val of values ) {
2098
+ if ( (val === "f@incr{$}") || (val === "f@init{$}") ) {
2099
+ return true
2100
+ }
2101
+ }
2102
+ }
2103
+ //
2104
+ return false
2105
+ }
2106
+
2107
+
2108
+ /**
2109
+ *
2110
+ * @param {object} incrementer_set
2111
+ * @param {string} step_entry
2112
+ * @param {object} entry_data
2113
+ */
2114
+ add_to_incrementers(incrementer_set,entry_data,var_set_expr) {
2115
+ //
2116
+ let incr_vname = false
2117
+ let start_val = false
2118
+ let op = ""
2119
+ if ( var_set_expr.length ) {
2120
+ let unloader = this.var_set_expr_pattern.exec(var_set_expr)
2121
+ if ( unloader ) {
2122
+ let vexpr = unloader[1]
2123
+ if ( vexpr.indexOf("=") > 0 ) {
2124
+ let pieces = vexpr.split('=')
2125
+ incr_vname = pieces[0]
2126
+ start_val = parseInt(pieces[1].trim())
2127
+ op = '='
2128
+ } else if ( vexpr.indexOf("++") > 0 ) {
2129
+ incr_vname = vexpr.substring(0,vexpr.indexOf("++"))
2130
+ op = "++"
2131
+ } else if ( vexpr.indexOf("--") > 0 ) {
2132
+ incr_vname = vexpr.substring(0,vexpr.indexOf("--"))
2133
+ op = "--"
2134
+ }
2135
+ }
2136
+ }
2137
+ if ( (typeof entry_data === "object") && (typeof entry_data.key_values === "object") ) {
2138
+ for ( let [vname, form] of Object.entries(entry_data.key_values) ) {
2139
+ if ( (form === "f@incr{$}") || (form === "f@init{$}") ) {
2140
+ let ky_vname = incr_vname ? incr_vname : vname
2141
+ entry_data.op = op
2142
+ if ( ky_vname in incrementer_set ) {
2143
+ let incr_descr = incrementer_set[ky_vname]
2144
+ if ( form === "f@init{$}" ) {
2145
+ incr_descr.starter = entry_data
2146
+ incr_descr.start_val = start_val
2147
+ } else {
2148
+ incr_descr.list.push(entry_data)
2149
+ }
2150
+ } else {
2151
+ incrementer_set[ky_vname] = {
2152
+ "print_vname" : ky_vname,
2153
+ "apply_to" : vname,
2154
+ "start_val" : start_val,
2155
+ "starter" : entry_data,
2156
+ "list" : [entry_data]
2157
+ }
2158
+ }
2159
+ return true
2160
+ }
2161
+ }
2162
+ }
2163
+ return false
2164
+ //
2165
+ }
2166
+
2167
+
2168
+ /**
2169
+ *
2170
+ * @param {string} sk_key
2171
+ * @param {string} step_entry
2172
+ */
2173
+ add_tracking_for_calc(sk_key,step_entry) {
2174
+ let track_ky = step_entry.substring(step_entry.lastIndexOf(':') + 1,step_entry.indexOf("${"))
2175
+ let calc_entries = this.tracking_skel_calc_usage[sk_key]
2176
+ if ( !calc_entries ) {
2177
+ calc_entries = {}
2178
+ this.tracking_skel_calc_usage[sk_key] = calc_entries
2179
+ calc_entries[track_ky] = 0
2180
+ }
2181
+ if ( calc_entries[track_ky] === undefined ) {
2182
+ calc_entries[track_ky] = 0
2183
+ }
2184
+ calc_entries[track_ky]++
2185
+ }
2186
+
2187
+
2188
+ /**
2189
+ {
2190
+ $@{scroll_section_count}$::@#{groups}
2191
+ }
2192
+ *
2193
+ * @param {string} params
2194
+ */
2195
+ evaluation_map(params) {
2196
+ params = params.substring(params.indexOf('{')+1,params.lastIndexOf('}')).trim()
2197
+ let parlist = params.split('\n').map( el => el.trim() )
2198
+ parlist = parlist.map( (el) => {
2199
+ return el.split('<<')
2200
+ })
2201
+ let evals = {}
2202
+ for ( let ev_pair of parlist ) {
2203
+ let [ky_form, val] = ev_pair
2204
+ let ky = ky_form.replace('$@{',"")
2205
+ ky = ky.replace('}$',"")
2206
+ if ( (val.indexOf('@!{') === 0) || (val.indexOf('@#{') === 0) ) { val = '0' }
2207
+ evals[ky] = val
2208
+ }
2209
+ return evals
2210
+ }
2211
+
2212
+
2213
+ /**
2214
+ *
2215
+ * @param {object} script_spec
2216
+ */
2217
+ early_evaluations(script_spec) {
2218
+ // @params<{scroll_section_count : %number%}>
2219
+ let data = script_spec.data
2220
+
2221
+ data = data.substring(data.indexOf("@params<{") + "@params<{".length)
2222
+ data = data.substring(data.indexOf('}>') + 2)
2223
+ script_spec.data = data.trim()
2224
+
2225
+ this.conditionless_evaluations_substitution(script_spec)
2226
+ }
2227
+
2228
+
2229
+ /**
2230
+ * Builds the data structures describing the entry
2231
+ * updates sk_map, where data stuctures are stored for a particular skeleton.
2232
+ *
2233
+ * This method parses a skeleton file, creating as a result a data structure that captures the intent of
2234
+ * structural defition which may be some sort of markup (e.g. HTML). The output of this method may be
2235
+ * used subsequently by methods that prepare a template in the markup language.
2236
+ *
2237
+ * Need to add $$bundle section
2238
+ *
2239
+ * A skeleton file is broken down into sections. Each section is demarcated by a start of section token
2240
+ * which indicates section type as well as some parameters that may go into the calculation of the section
2241
+ * structure.
2242
+ *
2243
+ * A section starts with syntactic separator `$$`.
2244
+ * The '$$' symbol must be followed by a type directive, which is then followed by one or two ':' (colons).
2245
+ * One colon indicates a named element of the chosen markup language which will be substituted by the tool.
2246
+ * Two colons indicates that the type directive and information in the section will be part of some calculation
2247
+ * specified by the section that is used to generate the final template markup. In many cases, the calculation
2248
+ * indicates that a file should be loaded, where the file is a pre-template which will structurally expanded
2249
+ * according to the section parameters.
2250
+ *
2251
+ * Following colons are further speciations of the section indicating parametarization and actions.
2252
+ * Each of these has a type name and may be followed by colons as well. The final specifier must be followed by
2253
+ * the syntactic indicator `<<`. This is an end of directive indicator. This line terminator may be followed by
2254
+ * more information encapsulated within braces '{','}'
2255
+ *
2256
+ * The information in braces may specify calculations, parameter substitutions, or loop calculations.
2257
+ *
2258
+ * Information after the final top level brace '}' and the start of the next section '$$' is likely to be ignored
2259
+ * or treated as comments.
2260
+ *
2261
+ *
2262
+ * Adds fields skeleton_map and incrementer_set
2263
+ *
2264
+ *
2265
+ * Here are the type of sections supported by the program to date:
2266
+ *
2267
+ * $$defs:: -- These are variable definitions and overrides relative to the configuration for the run
2268
+ * $$bundle:: -- One of these indicates a bundled script that needs to be included in the header as a deferred file.
2269
+ * $$html: -- These are HTML markups indicating start of larger sections such as hearders, bodies, scripts, style, etc.
2270
+ * $$css:: -- The file name following this is a css file stored in the css directory of the alphas
2271
+ * $$files:: -- These are files to be found in the directory for the markup, e.g. html for HTML
2272
+ * $$files<*>:: -- These are files of a different type than the markup by in the same directory. For instance '*' may be replace with 'js'
2273
+ * $$script:: -- These are files in the scripting language. Files that remain in a skeleton after preprocessing for bundling will be written into the template.
2274
+ * $$verbatim:: -- A section that will be left alone and included precisely as is
2275
+ * $$template:: -- Similar to verbatim, but it expected to contain variables for use in substitutions, some of which may be files loaded recursively
2276
+ * $$icon::
2277
+ *
2278
+ * The $$files:: section may either be a simple directive indicating a file to put into the place of the file, or it may be
2279
+ * a directive indicating how a file will be used:
2280
+ *
2281
+ * $$files::|file path|<<
2282
+ * $$files::params::|file path|<<{|parameter descriptor|}
2283
+ *
2284
+ * $$files::calc::|file path|<<${|calculation code|}
2285
+ * $$files::calc::|file path|<<${|calculation code|}
2286
+ *
2287
+ * $$files::calc::contact_box<<${box_i=100} -- this is an example of a calculation that initializes a variable
2288
+ * $$files::calc::another_box<<${box_i++} -- this is an example of a calculation that advances a variable
2289
+ *
2290
+ * $$files::loop:: -- Loops cause sections of markup to be generated in number, sequentially and may be iterates of a loop index of lists of elements
2291
+ *
2292
+ * $$files::loop::elements::|file path with an element indicator, e.g. '<el>' |<<${|an expression itemizing loop elements|}
2293
+ * $$files::loop::calc::|file path with an index range, e.g. '<start,stop>'|><<${ a calculation advancing the loop index }
2294
+ *
2295
+ * $$javascript:start_worker<<
2296
+ * $$javascript:end_worker<<
2297
+ *
2298
+ * // the following is a `skel_def` ::
2299
+ * `{
2300
+ "defaults" : def_defaults,
2301
+ "final" : {
2302
+ "markup" : false,
2303
+ "scripts" : false
2304
+ },
2305
+ "skeleton" : data_parts,
2306
+ "files" : files,
2307
+ "scripts" : scripts,
2308
+ "bundles" : bundles,
2309
+ "parameterized" : maybe_params
2310
+ }`
2311
+ *
2312
+ * @param {object} skeleton
2313
+ * @param {object} sk_map
2314
+ * @param {object} incrementer_set
2315
+ */
2316
+ async build_entry_parsing(sk_key,skeleton,skeleton_src,sk_map,incrementer_set,concern_rewrites) {
2317
+ let lang_spec_count = 0
2318
+ for ( let step_entry of skeleton ) {
2319
+ step_entry = step_entry.replace('<<','')
2320
+ // now get its value depending on its tyle
2321
+ // HTML
2322
+ if ( step_entry.startsWith('html:') ) { // handle the import of html snippets that start parts of the file
2323
+ lang_spec_count++
2324
+ let html_map = base_patterns_mod['html:']
2325
+ let ky = step_entry.substring('html:'.length)
2326
+ step_entry = step_entry.replace('html:',`html(${lang_spec_count}):`)
2327
+ sk_map[step_entry] = html_map[ky]
2328
+ // OTHER THAN HTML
2329
+ } else if ( this.is_language_section_control(step_entry) ) { // a generalization of html: for later
2330
+ lang_spec_count++
2331
+ let lang_key = this.extract_lang_controller_key(step_entry)
2332
+ let lang_map = base_patterns_mod[lang_key]
2333
+ let ky = step_entry.substring(lang_key.length)
2334
+ step_entry = step_entry.replace(lang_key,`${lang_key}(${lang_spec_count}):`)
2335
+ sk_map[step_entry] = lang_map[ky]
2336
+ // BUNDLE - a link definition (CDN connection perhaps)
2337
+ } else if ( step_entry.startsWith('bundle::') ) { // a bundled file -- used to generate the <link deferred... construct
2338
+ let str = step_entry.substring(('bundle::').length)
2339
+ let hashed = crypto.hash('sha1',str) // cryp]==to
2340
+ sk_map[`bundle::${hashed}`] = bundle_inclusion_transform(str,page_or_worker_context[skeleton_src])
2341
+ // LINK - a link definition (possibly with compression)
2342
+ } else if ( step_entry.startsWith('link<') ) { // a bundled file -- used to generate the <link deferred... construct
2343
+ let str = step_entry.substring(('link').length)
2344
+ let hashed = crypto.hash('sha1',str) // cryp]==to
2345
+ sk_map[`link::${hashed}`] = link_inclusion_transform(str,page_or_worker_context[skeleton_src])
2346
+ // JAVASCRIPT -- sepcial directives for JavaScript in special modules, workers, etc.
2347
+ } else if ( step_entry.startsWith('javascript:')) {
2348
+ let str = step_entry.substring(('bundle::').length)
2349
+ sk_map[str] = "" // for now they just disappear
2350
+ // VERBATIM -- text does not change
2351
+ } else if ( step_entry.startsWith('verbatim::') ) { // parts of the file to leave alone and include in the final
2352
+ let str = step_entry.substring(('verbatim::').length)
2353
+ let hashed = crypto.hash('sha1',str) // crypto
2354
+ sk_map[`verbatim::${hashed}`] = this.clean_verbatim(str)
2355
+ // ELSE
2356
+ } else {
2357
+ let entry = this.shared_entries[step_entry]
2358
+ if ( entry && typeof entry === "object" ) { // previously created informaton -- just copy it
2359
+ sk_map[step_entry] = structuredClone(entry)
2360
+ } else {
2361
+ // FILES with operations -- resulting in targeted markdown in template files
2362
+ if ( step_entry.startsWith('files::') || step_entry.startsWith('files<') ||step_entry.startsWith('css::') ) {
2363
+ // FILES::CALC - custom calculation for file use
2364
+ if ( step_entry.startsWith('files::calc::') ) {
2365
+ //
2366
+ sk_map[step_entry] = "name" // the default is to indicate that the entry names a type of section
2367
+ //
2368
+ if ( step_entry.indexOf("_<") < 0 ) { // looking for a specific syntax associated with a file name
2369
+ // this syntax tells processing that a range will be used in replicating a region
2370
+ let var_set_expr = ""
2371
+ let db = this.name_drops_db // handle the syntax (specific to this rep here) for variable management
2372
+ let db_ky = step_entry.substring("files::calc::".length)
2373
+ if ( db_ky.indexOf("$") > 0 ) { // says that there is an incrementer variable perhaps previously defined
2374
+ let db_ky_parts = db_ky.split("$")
2375
+ db_ky = db_ky_parts[0] // expose the key into the name_drop db
2376
+ var_set_expr = db_ky_parts[1] // get the variable update expressions
2377
+ }
2378
+ let entry_data = db[db_ky] // the db stores operational characteristics of named skeletal parts
2379
+ let back_ref_ky = db_ky
2380
+ if ( typeof entry_data === "object" ) { // In the db the key may be vanilla like the one in use
2381
+ sk_map[step_entry] = Object.assign({},entry_data)
2382
+ } else {
2383
+ back_ref_ky = db_ky // otherwise, it may indicate an abstraction of the range.
2384
+ db_ky = db_ky.substring(0,db_ky.lastIndexOf('_') + 1)
2385
+ entry_data = db[db_ky]
2386
+ sk_map[step_entry] = Object.assign({},entry_data)
2387
+ }
2388
+ // add a tracker for this calculator, which may be used in other steps
2389
+ this.add_tracking_for_calc(sk_key,step_entry)
2390
+ //
2391
+ entry_data = sk_map[step_entry]
2392
+ if ( entry_data.file ) { // delay loading the file ... the field "file" will likely refer to a '.tmplt' file.
2393
+ entry_data.path_finder = "html" // add this field, it is the path to a directoy in alpha-copious
2394
+ this.delay_file_loading_queue.push(entry_data)
2395
+ }
2396
+ // makes a link between the structure generation operations and incrementer operations
2397
+ if ( this.has_incrementer(entry_data) ) { // keep structures of incrementers for updates
2398
+ this.add_to_incrementers(incrementer_set,entry_data,var_set_expr)
2399
+ }
2400
+ // calculations taken from the DB provide delay_file_loadingoperations more complex than increment
2401
+ if ( this.has_calc(entry_data) ) {
2402
+ let calls = this.gather_calculations(entry_data) // gets parameters and op components together
2403
+ for ( let a_call of calls ) { // for this skeletal step make all the calls gathered -- uses local data
2404
+ this.make_call(entry_data,a_call,back_ref_ky)
2405
+ }
2406
+ }
2407
+ //
2408
+ }
2409
+ //
2410
+ //
2411
+ // FILES::PARAMS - load files taking parameters
2412
+ // CSS::PARAMS
2413
+ } else if ( step_entry.startsWith('files::params::') || step_entry.startsWith('css::params::') ) {
2414
+ let loadable_entry = step_entry.replace("::params","")
2415
+ sk_map[step_entry] = await this.entry_loading(loadable_entry)
2416
+ // console.log(step_entry)
2417
+ // console.dir(sk_map[step_entry],{ depth : 6})
2418
+ // FILES::ELEMENTS - files operating on lists of elements
2419
+ // CSS::ELEMENTS
2420
+ } else if ( step_entry.startsWith('files::elements::') || step_entry.startsWith('css::elements::') ) {
2421
+ let loadable_entry = step_entry.replace("::elements","")
2422
+ sk_map[step_entry] = await this.entry_loading(loadable_entry)
2423
+ // FILES
2424
+ // CSS -- nothing special = copy and paste
2425
+ } else {
2426
+ // console.log("CALLING ENTRY LOADING",step_entry)
2427
+ sk_map[step_entry] = await this.entry_loading(step_entry)
2428
+ // console.log("DONE CALL -- ENTRY LOADING",step_entry)
2429
+ }
2430
+ // TEMPLATE
2431
+ } else if ( step_entry.startsWith('template::') ) {
2432
+ // similar to verbatim in that the script won't be changed much.
2433
+ // however, templates may be loaded recursively, resulting in a tree for future assembly
2434
+ let data = step_entry.substring(('template::').length)
2435
+ data = data.trim()
2436
+ let brace_i = data.indexOf('{')
2437
+ let brace_n = data.lastIndexOf('}')
2438
+ data = data.substring(brace_i,brace_n-1)
2439
+ data = parse_util.clear_comments(data) // take out this human readable info that interferes with machine processing
2440
+ //
2441
+ sk_map[step_entry] = {
2442
+ "type" : "template",
2443
+ "data" : data
2444
+ }
2445
+ if ( this.check_recursive_data(data) ) { // handle recursion -- build tree
2446
+ sk_map[step_entry].recursive = await this.get_files_and_vars(step_entry,data) // subfile processing
2447
+ }
2448
+ // SCRIPT
2449
+ } else if ( step_entry.startsWith('script::') ) { // parse out the script section
2450
+ // scripts are preprocessed for bundling
2451
+ // bundled scripts will be removed from the skeleton and the bundle line will be added
2452
+ // for each bundle created from the scripts of the current skeleton
2453
+ // remaining scripts will be added to the template files generated from the skeleton
2454
+ // Scripts that remain may contain variables for customization and final substitution
2455
+ //
2456
+ if ( step_entry.startsWith('script::params') ) {
2457
+ let loadable_entry = step_entry.replace("::params","")
2458
+ sk_map[step_entry] = await this.entry_loading(loadable_entry)
2459
+ console.log(step_entry)
2460
+ console.dir(sk_map[step_entry],{ depth : 6})
2461
+
2462
+ // let loadable_entry = step_entry.replace("::params","")
2463
+ // loadable_entry = loadable_entry.substring(0,loadable_entry.indexOf("{"))
2464
+ // //
2465
+ // let script_spec = await this.entry_loading(loadable_entry) // load the script (it may be output in the template)
2466
+ // let params = step_entry.substring(step_entry.indexOf("{")).trim()
2467
+ // script_spec.evaluations = this.evaluation_map(params)
2468
+ // //
2469
+ // this.early_evaluations(script_spec)
2470
+ // //
2471
+ // console.log("SCRIPT WITH PARAMETERS:: ",step_entry)
2472
+ // console.dir(script_spec)
2473
+ // sk_map[step_entry] = script_spec
2474
+ } else {
2475
+ let script_spec = await this.entry_loading(step_entry) // load the script (it may be output in the template)
2476
+ let data_form = script_spec.data
2477
+ if ( (typeof script_spec.kernel !== "undefined") ) {
2478
+ concern_rewrites[script_spec.file] = structuredClone(script_spec)
2479
+ concern_rewrites[script_spec.file].entry = step_entry
2480
+ console.log("FIX UP KERNEL",script_spec.kernel,sk_key,data_form)
2481
+ // let sk_c = Object.assign({},this.skel_to_concerns[sk_key].concerns)
2482
+ // for ( let [crn,usages] of Object.entries(sk_c) ) {
2483
+ // let dr = usages.dir
2484
+ // dr = dr.replace('@kernel',script_spec.kernel)
2485
+ // dr = this.paths.compile_one_path(dr)
2486
+ // usages.dir = dr
2487
+ // let dir_key = '@{[targets.dir]}'
2488
+ // let dat = data_form.replace(dir_key,dr)
2489
+ // if ( dat === data_form ) {
2490
+ // dir_key = `@{[targets.dir]/${script_spec.use_case}}`
2491
+ // dat = data_form.replace(dir_key,dr)
2492
+ // }
2493
+ // usages.data = dat
2494
+ // }
2495
+
2496
+ // script_spec.customizations = sk_c
2497
+ // //
2498
+ }
2499
+ sk_map[step_entry] = script_spec
2500
+ }
2501
+ //
2502
+ } else {
2503
+ console.log("NOT HANDLED YET: ",step_entry)
2504
+ sk_map[step_entry] = ""
2505
+ }
2506
+ }
2507
+ }
2508
+ }
2509
+ }
2510
+
2511
+
2512
+ /**
2513
+ *
2514
+ * @param {*} skeleton_src
2515
+ */
2516
+ async leaf_html_directives(skeleton_src) { // map: file name -> structured skeleton information
2517
+ //
2518
+ //
2519
+ // console.log("leaf_html_directives ------------------------------------------------------------------->> ")
2520
+ // console.dir(skeleton_src,{ depth: 6})
2521
+ //
2522
+ this.shared_entries = {}
2523
+ // `skeleton_src` -- previously loaded `sk_key` is the file name, `skel_def` is the file contents (structured)
2524
+ // adding an `sk_map` to each `sk_def`
2525
+ for ( let [sk_key, op_map ] of Object.entries(skeleton_src) ) {
2526
+ for ( let [op, skel_def ] of Object.entries(op_map) ) {
2527
+ let sk_map = {}
2528
+ let incrementer_set = {} // keep track of the variables used to increment repeated sections
2529
+ let skeleton = skel_def.skeleton
2530
+ let concern_rewrites = {}
2531
+ await this.build_entry_parsing(sk_key,skeleton,skeleton_src,sk_map,incrementer_set,concern_rewrites)
2532
+ skel_def.skeleton_map = sk_map
2533
+ skel_def.incrementer_set = incrementer_set
2534
+ skel_def.concern_rewrites = concern_rewrites
2535
+ }
2536
+ }
2537
+ }
2538
+
2539
+
2540
+
2541
+ /**
2542
+ * Called after other processing during the preparation phase, but before writing out the results of preparation.
2543
+ *
2544
+ * There is a queue, `delay_file_loading_queue`, that hold structures with empty data components and with file names.
2545
+ * The elements of the queue were processed during synchronous analysis of text and time was not alloted to
2546
+ * loading the data.
2547
+ *
2548
+ * `delay_file_loading_queue` receives elements from `seek_imports` and from parsing `files::calc` sections,
2549
+ * which refer to the name_drop.dbs.
2550
+ *
2551
+ * This loads the data asynchronously from the files on the queue, parallelizing the loading as much as possible
2552
+ * with a Promise.all.
2553
+ *
2554
+ */
2555
+ async delay_file_loading() {
2556
+ let q = this.delay_file_loading_queue
2557
+ let name_to_data = {}
2558
+ //
2559
+ if ( q.length ) {
2560
+ for ( let el of q ) {
2561
+ if ( el.file ) {
2562
+ name_to_data[el.file] = el.path_finder
2563
+ }
2564
+ }
2565
+ //
2566
+ let loader_promises = []
2567
+ for ( let [file,path_finder] of Object.entries(name_to_data) ) {
2568
+ let file_path = this.top_dir_locations[path_finder]
2569
+ let file_name = `${file_path}/${file}`
2570
+ let p = fos.load_data_at_path(file_name)
2571
+ loader_promises.push(p)
2572
+ }
2573
+ let loader_data = await Promise.all(loader_promises)
2574
+ let keys = Object.keys(name_to_data)
2575
+ for ( let dat of loader_data ) {
2576
+ let key = keys.shift()
2577
+ name_to_data[key] = dat
2578
+ }
2579
+ //
2580
+ }
2581
+
2582
+ return name_to_data
2583
+ }
2584
+
2585
+
2586
+
2587
+ /**
2588
+ * Support (helper) for evaluating the delayed queue.
2589
+ * (see delayed queue documentation)
2590
+ *
2591
+ * Handles simple subsitutions that can be done with reducing forms.
2592
+ *
2593
+ * @param {object} entry_data
2594
+ * @returns {boolean}
2595
+ */
2596
+ conditionless_evaluations_substitution(entry_data) {
2597
+ let data = entry_data.data
2598
+
2599
+ //console.log("conditionless_evaluations_substitution", data)
2600
+
2601
+ if ( !data ) return false
2602
+ if ( data.indexOf("?") > 0 ) {
2603
+ if ( this.ternary_check.test(data) ) {
2604
+ return false
2605
+ }
2606
+ }
2607
+
2608
+ let evals = entry_data.evaluations
2609
+
2610
+ if ( (typeof evals === "object") && Object.keys(evals).length ) {
2611
+ for ( let [vky, vval] of Object.entries(evals) ) {
2612
+ let var_form = `@{${vky}}`
2613
+ data = parse_util.subst(data,var_form,vval)
2614
+ }
2615
+ entry_data.data = data
2616
+ }
2617
+ return true
2618
+ }
2619
+
2620
+
2621
+
2622
+ /**
2623
+ * Goes through the delayed file queue looking for evaluations that
2624
+ * can be substituted into the data.
2625
+ *
2626
+ * Makes a backup copy of the substitution form. The backup will be made if there are previously
2627
+ * analyzed recursions or if there are reductions.
2628
+ *
2629
+ * If there are evaluations, the `conditionless_evaluations_substitution` will be applied.
2630
+ *
2631
+ * @param {object} name_to_data
2632
+ */
2633
+ evaluate_delayed_queue(name_to_data) {
2634
+ let q = this.delay_file_loading_queue
2635
+ if ( q && q.length ) {
2636
+ for ( let el of q ) {
2637
+ el.data = "" + name_to_data[el.file] // data first enters into the entry object here
2638
+ if ( el.evaluations || el.recursive ) { // only backup files that have skeletal level evaluations
2639
+ el.backup_data = "" + el.data // if the user runs an intermediate step, then the original data will be utilized
2640
+ }
2641
+ if ( el.evaluations ) {
2642
+ this.conditionless_evaluations_substitution(el)
2643
+ }
2644
+ }
2645
+ }
2646
+ }
2647
+
2648
+
2649
+
2650
+ /**
2651
+ * Given a list of conditions and variable evaluations,
2652
+ * this method selects a branch of the condition's
2653
+ * antecedant to be used as the substitution for the condition's test variable
2654
+ * in the data of the containing element.
2655
+ *
2656
+ * @param {Array} conds
2657
+ * @param {object} params
2658
+ * @returns {Array}
2659
+ */
2660
+ conds_reduction(conds,params) {
2661
+ //
2662
+ //
2663
+ let reduced_conds = []
2664
+ for ( let cond of conds ) {
2665
+ //
2666
+ let vname = cond.condition.variable
2667
+ let val = params[vname]
2668
+ let vform = cond.condition.cond
2669
+ let replacer = ""
2670
+ if ( val && (typeof val === "string") && val.length > 0 ) {
2671
+ replacer = cond.positive_exec.pos
2672
+ replacer = parse_util.subst(replacer,vform,val)
2673
+ if ( cond.positive_exec.file && cond.positive_exec.file[0].data ) {
2674
+ replacer = cond.positive_exec.file[0].data
2675
+ }
2676
+ } else {
2677
+ replacer = cond.negative_exec.neg
2678
+ replacer = parse_util.subst(replacer,vform,val)
2679
+ if ( cond.negative_exec.file && cond.negative_exec.file[0].data ) {
2680
+ replacer = cond.negative_exec.file[0].data
2681
+ }
2682
+ }
2683
+ //
2684
+ let a_reduction = {
2685
+ "replace" : cond.replace,
2686
+ "replacer" : replacer
2687
+ }
2688
+ reduced_conds.push(a_reduction)
2689
+ }
2690
+
2691
+ return reduced_conds
2692
+ }
2693
+
2694
+
2695
+ /**
2696
+ *
2697
+ * @param {string} data
2698
+ * @param {Array} subst_list
2699
+ * @param {string} index_var
2700
+ */
2701
+ list_map_to_substs(data,subst_list,index_var) {
2702
+ //
2703
+ let loop_start_len = index_var.length + "<@>".length
2704
+ let loop_body_start = data.indexOf(`<@${index_var}>`) + loop_start_len
2705
+ let loop_body_end = data.indexOf(`</@${index_var}>`)
2706
+
2707
+ for ( let asubst of subst_list ) {
2708
+ //
2709
+ let loop_body = data.substring(loop_body_start,loop_body_end)
2710
+ //
2711
+ let params = asubst.params_def
2712
+ let conds = asubst.conds
2713
+ //
2714
+ for ( let acond of conds ) {
2715
+ loop_body = parse_util.subst(loop_body,acond.replace,acond.replacer)
2716
+ }
2717
+ for ( let param in params ) {
2718
+ let var_form = `@{${param}}`
2719
+ let val = params[param]
2720
+ loop_body = parse_util.subst(loop_body,var_form,val)
2721
+ }
2722
+ //
2723
+ asubst.data = loop_body
2724
+ }
2725
+
2726
+ }
2727
+
2728
+ /**
2729
+ *
2730
+ * @param {string} val
2731
+ */
2732
+ value_is_custom_type(val) {
2733
+ if ( val === '%config%' ) {
2734
+ return true
2735
+ }
2736
+ return false
2737
+ }
2738
+
2739
+ /**
2740
+ *
2741
+ * @param {string} var_form
2742
+ * @param {string} val
2743
+ * @returns {string}
2744
+ */
2745
+ value_from_custom_source(var_form,val) {
2746
+ if ( this.global_variable_values ) {
2747
+ if ( val === '%config%' ) {
2748
+ let global_conf_map = this.global_variable_values.var_to_value
2749
+ let vky = var_form.replace(val,"").replace("@{","").replace("}","")
2750
+ val = global_conf_map[vky]
2751
+ return val
2752
+ }
2753
+ }
2754
+ return "TESTVAL"
2755
+ }
2756
+
2757
+
2758
+ /**
2759
+ * map_to_substs
2760
+ *
2761
+ * @param {string} data
2762
+ * @param {Array} subst_list
2763
+ */
2764
+ map_to_substs(data,subst_list) {
2765
+ //
2766
+ for ( let asubst of subst_list ) {
2767
+ //
2768
+ let params = asubst.params_def
2769
+ let conds = asubst.conds
2770
+ //
2771
+ let changed_data = "" + data
2772
+ //
2773
+ for ( let acond of conds ) {
2774
+ changed_data = parse_util.subst(changed_data,acond.replace,acond.replacer)
2775
+ }
2776
+ for ( let param in params ) {
2777
+ let var_form = `@{${param}}`
2778
+ let val = params[param]
2779
+ if ( this.value_is_custom_type(val) ) {
2780
+ val = this.value_from_custom_source(var_form,val)
2781
+ }
2782
+ changed_data = parse_util.subst(changed_data,var_form,val)
2783
+ }
2784
+ //
2785
+ if ( parse_util.has_parameter_block(changed_data) ) {
2786
+ changed_data = parse_util.remove_parameter_block(changed_data)
2787
+ }
2788
+ //
2789
+ asubst.data = changed_data
2790
+ }
2791
+ //
2792
+ }
2793
+
2794
+
2795
+ /**
2796
+ *
2797
+ * @param {string} entry_ky
2798
+ * @param {object} param_descr
2799
+ */
2800
+ go_deep(entry_ky,param_descr) {
2801
+ //
2802
+ param_descr.backup_data = "" + param_descr.data
2803
+ //console.dir(param_descr.tree)
2804
+ //
2805
+ let keys = Object.keys(param_descr.tree)
2806
+
2807
+ //
2808
+ let parameter_key = keys.find((ky) => { // find all list types
2809
+ if ( ky.startsWith("_params<") ) {
2810
+ return true
2811
+ } else {
2812
+ return false
2813
+ }
2814
+ })
2815
+ //
2816
+ if ( parameter_key ) {
2817
+ let pars = parameter_key.replace("_params<","").replace(">","").split(",")
2818
+ for ( let par of pars ) {
2819
+ if ( typeof param_descr.tree[par] === "object" ) {
2820
+ let pdescr = param_descr.tree[par]
2821
+ if ( typeof pdescr.tree === "object" ) {
2822
+ // console.log("go_deep",entry_ky)
2823
+ // console.dir(param_descr)
2824
+
2825
+ this.go_deep(entry_ky,pdescr)
2826
+ }
2827
+ if ( pdescr.recursive ) {
2828
+ this.executable_condition_processing(pdescr)
2829
+ this.tree_conditional_reduction(pdescr)
2830
+ pdescr.data = this.join_executables(pdescr)
2831
+ }
2832
+ }
2833
+ }
2834
+ }
2835
+ }
2836
+
2837
+
2838
+ /**
2839
+ *
2840
+ * @param {object} pdescr
2841
+ * @returns {string}
2842
+ */
2843
+ join_executables(pdescr) {
2844
+ let reductions = pdescr.subst_recursive
2845
+ if ( reductions && Array.isArray(reductions) ) {
2846
+ let data_only = reductions.map((red) => {
2847
+ let next_data = red.data
2848
+ if ( !next_data || (typeof next_data !== 'string')) return ""
2849
+ else return next_data.trim()
2850
+ })
2851
+ return data_only.join("\n")
2852
+ }
2853
+ return ""
2854
+ }
2855
+
2856
+
2857
+ /**
2858
+ *
2859
+ * @param {object} transformed
2860
+ */
2861
+ conditional_evaluations(transformed) {
2862
+ for ( let [sk_name, op_map ] of Object.entries(transformed) ) {
2863
+ for ( let [op, skel ] of Object.entries(op_map) ) {
2864
+ let sk_map = skel.skeleton_map
2865
+ if ( typeof sk_map !== "object" ) continue
2866
+ if ( skel.parameterized ) {
2867
+ for ( let [entry_ky,desciptor] of Object.entries(skel.parameterized) ) {
2868
+ let keys = Object.keys(desciptor)
2869
+ let list_key = keys.find((ky) => { // find all list types
2870
+ if ( ky.startsWith("_type<") ) {
2871
+ return true
2872
+ } else {
2873
+ return false
2874
+ }
2875
+ })
2876
+ let parameter_key = keys.find((ky) => { // find all list types
2877
+ if ( ky.startsWith("_params<") ) {
2878
+ return true
2879
+ } else {
2880
+ return false
2881
+ }
2882
+ })
2883
+ //
2884
+ if ( list_key ) {
2885
+ let entry = sk_map[entry_ky]
2886
+ if ( entry?.subst_recursive && entry?.conds ) {
2887
+ for ( let evalr of entry.subst_recursive ) {
2888
+ let params = evalr.params_def
2889
+ evalr.conds = this.conds_reduction(entry.conds,params)
2890
+ }
2891
+ let data = entry.data
2892
+ entry.backup_data = "" + data
2893
+ this.list_map_to_substs(data,entry.subst_recursive,entry.recursive.params_def._var_name)
2894
+ } else {
2895
+ continue
2896
+ }
2897
+ }
2898
+ if ( parameter_key ) {
2899
+ let pars = parameter_key.replace("_params<","").replace(">","").split(",")
2900
+ for ( let par of pars ) {
2901
+ let pdescr = desciptor[par]
2902
+ if ( typeof pdescr.tree === "object" ) {
2903
+ this.go_deep(entry_ky,pdescr)
2904
+ }
2905
+ if ( pdescr.recursive ) {
2906
+ this.executable_condition_processing(pdescr)
2907
+ this.tree_conditional_reduction(pdescr)
2908
+ pdescr.data = this.join_executables(pdescr)
2909
+ }
2910
+ }
2911
+ }
2912
+ }
2913
+ }
2914
+ }
2915
+ }
2916
+ }
2917
+
2918
+
2919
+
2920
+ /**
2921
+ *
2922
+ * @param {string} data
2923
+ * @returns
2924
+ */
2925
+ has_import_def(data) {
2926
+ let match_def = this.import_entry_match.exec(data)
2927
+ if ( match_def ) {
2928
+ return match_def[1]
2929
+ }
2930
+ return false
2931
+ }
2932
+
2933
+ /**
2934
+ *
2935
+ * @param {object} transformed
2936
+ */
2937
+ lift_remaining_imports(transformed) {
2938
+ for ( let [sk_name, op_map ] of Object.entries(transformed) ) {
2939
+ for ( let [op, skel ] of Object.entries(op_map) ) {
2940
+
2941
+ let sk_map = skel.skeleton_map
2942
+ if ( typeof sk_map !== "object" ) continue
2943
+ for ( let [step_entry,entry] of Object.entries(sk_map) ) {
2944
+
2945
+
2946
+ let data = entry?.data
2947
+ if ( typeof data === "string" ) {
2948
+ let import_type = this.has_import_def(data)
2949
+ if ( typeof import_type === "string" ) {
2950
+ //
2951
+ if ( entry.recursive && entry.recursive.executables && entry.recursive.executables.imports ) {
2952
+ let imps = entry.recursive.executables.imports
2953
+ for ( let imp of imps ) {
2954
+ let repl = imp.replace
2955
+ let value = imp.data // should already be loaded
2956
+ //
2957
+ data = parse_util.subst(data,repl,value)
2958
+ //
2959
+ }
2960
+ entry.data = data
2961
+ }
2962
+ //
2963
+ }
2964
+ } else {
2965
+ if ( entry === undefined ) {
2966
+ console.log("lift_remaining_imports",sk_name,step_entry)
2967
+ }
2968
+ }
2969
+ }
2970
+ }
2971
+ }
2972
+ //
2973
+ }
2974
+
2975
+
2976
+ /**
2977
+ *
2978
+ * @param {*} entry
2979
+ * @param {*} data
2980
+ * @returns
2981
+ */
2982
+ entry_is_list(entry,data) {
2983
+ if ( entry.recursive && entry.recursive._is_loop ) {
2984
+ return true
2985
+ }
2986
+ let found_loop = data.startsWith("@list<")
2987
+ return found_loop
2988
+ }
2989
+
2990
+ /**
2991
+ *
2992
+ * @param {*} data
2993
+ * @param {*} ref
2994
+ * @returns
2995
+ */
2996
+ remove_loop_header(data,ref) {
2997
+ let stop_marker = `<@${ref}>`
2998
+ let stop_point = data.indexOf(stop_marker)
2999
+ if ( stop_point > 0 ) {
3000
+ let update_data = data.substring(stop_point + stop_marker.length)
3001
+ let end_marker = `</@${ref}>`
3002
+ update_data = update_data.replace(end_marker,"")
3003
+ return update_data
3004
+ }
3005
+
3006
+ return data
3007
+ }
3008
+
3009
+ /**
3010
+ *
3011
+ * @param {object} transformed
3012
+ */
3013
+ loop_finalization(transformed) {
3014
+ for ( let [sk_name, op_map ] of Object.entries(transformed) ) {
3015
+ for ( let [op, skel ] of Object.entries(op_map) ) {
3016
+ let sk_map = skel.skeleton_map
3017
+ if ( typeof sk_map !== "object" ) continue
3018
+ for ( let [step_entry,entry] of Object.entries(sk_map) ) {
3019
+ let data = entry.data
3020
+ if ( typeof data === "string" ) {
3021
+ if ( this.entry_is_list(entry,data) && skel.parameterized ) {
3022
+ //
3023
+ let skel_loop_vars = skel.parameterized[step_entry]
3024
+ let itr_name = entry.recursive.params_def._var_name
3025
+ let ref = skel_loop_vars[itr_name]
3026
+ if ( ref === "list" ) {
3027
+ let list_ky = `_type<${itr_name}>`
3028
+ let loop_list = skel_loop_vars[list_ky]
3029
+ let dat_tmplt = this.remove_loop_header(data,itr_name)
3030
+ let total_data = ""
3031
+ for ( let el of loop_list ) {
3032
+ let el_data = "" + dat_tmplt
3033
+ for ( let ky in el ) {
3034
+ let value = el[ky]
3035
+ let finder = entry.recursive.params_def[ky]
3036
+ el_data = parse_util.subst(el_data,finder,value)
3037
+ }
3038
+ total_data += el_data
3039
+ }
3040
+ entry.data = total_data
3041
+ }
3042
+ //
3043
+ }
3044
+ }
3045
+ }
3046
+ }
3047
+ }
3048
+ //
3049
+ }
3050
+
3051
+
3052
+
3053
+ /**
3054
+ *
3055
+ * @param {Array} exec_list
3056
+ */
3057
+
3058
+ find_conditionals(exec_list) {
3059
+ //
3060
+ if ( exec_list && Array.isArray(exec_list) ) {
3061
+ let conds_only = exec_list.filter((el) => {
3062
+ if ( el.condition && (typeof el.condition === "object") ) {
3063
+ return true
3064
+ }
3065
+ return false
3066
+ })
3067
+ return conds_only
3068
+ }
3069
+ return false
3070
+ //
3071
+ }
3072
+
3073
+
3074
+ /**
3075
+ *
3076
+ * @param {object} entry
3077
+ */
3078
+ executable_condition_processing(entry) {
3079
+ let par_src = entry.recursive.params_def
3080
+ entry.subst_recursive = []
3081
+ //
3082
+ let subst_vals = Object.assign({},par_src)
3083
+ //
3084
+ entry.subst_recursive.push({ "params_def" : subst_vals })
3085
+ entry.conds = this.find_conditionals(entry.recursive.executables.execs)
3086
+ }
3087
+
3088
+
3089
+
3090
+ /**
3091
+ *
3092
+ * @param {object} entry
3093
+ */
3094
+ tree_conditional_reduction(entry) {
3095
+ if ( entry.subst_recursive && entry.conds ) {
3096
+ for ( let evalr of entry.subst_recursive ) {
3097
+ let params = evalr.params_def
3098
+ evalr.conds = this.conds_reduction(entry.conds,params)
3099
+ }
3100
+ let data = entry.data
3101
+ entry.backup_data = "" + data
3102
+ this.map_to_substs(data,entry.subst_recursive)
3103
+ }
3104
+ }
3105
+
3106
+
3107
+ /**
3108
+ *
3109
+ * @param {string} ptype
3110
+ */
3111
+ is_tree_type(ptype) {
3112
+ if ( ptype[0] === '%' ) {
3113
+ if ( ptype === "%file%" ) {
3114
+ return true
3115
+ }
3116
+ }
3117
+ return false
3118
+ }
3119
+
3120
+
3121
+ /**
3122
+ *
3123
+ * @param {string} ptype
3124
+ */
3125
+ is_const_type(ptype) {
3126
+ if ( ptype[0] === '%' ) {
3127
+ if ( ptype === "%number%" ) {
3128
+ return true
3129
+ }
3130
+ if ( ptype === "%string%" ) {
3131
+ return true
3132
+ }
3133
+ }
3134
+ return false
3135
+ }
3136
+
3137
+ /**
3138
+ *
3139
+ * This method gets the parameter def from the skeleton
3140
+ * by mapping with the entry text and the parameter name, two levels.
3141
+ * The data for the parameter will be taken out. (The data is likely data loaded from a file,
3142
+ * a sub skeleton.) If the data is a string, and the parameter is not a tree, the data will
3143
+ * be returned. Otherwise, the tree will be processed.
3144
+ *
3145
+ * The tree will be processed if recursive data has been figured for the tree.
3146
+ * The data object supplied by the recursive section may contain parameters and/or
3147
+ * executables.
3148
+ *
3149
+ *
3150
+ * When the data is returned, and the data is know to be from a file, any parameter block introduced
3151
+ * by the file will be removed. The parameter block is removed after substitutions for variables in
3152
+ * the data.
3153
+ *
3154
+ *
3155
+ * @param {object} skel
3156
+ * @param {string} entry_ky
3157
+ * @param {string} pname
3158
+ * @returns {string}
3159
+ */
3160
+ data_from_parameter(skel,entry_ky,pname) {
3161
+ let p_entry = skel.parameterized[entry_ky]
3162
+ let will_return_data = ""
3163
+ if ( p_entry ) {
3164
+ let p_def = p_entry[pname]
3165
+ let data = p_def.data
3166
+ if ( typeof data === "string" ) {
3167
+ if ( p_def.tree ) {
3168
+ let rec = p_def.recursive
3169
+ if ( rec ) {
3170
+ let dst_pars = rec.params_def
3171
+ if ( dst_pars ) {
3172
+ for ( let par in dst_pars ) {
3173
+ if ( !(p_def.tree[par].tree) && (p_def.tree[par].recursive === undefined) ) {
3174
+ dst_pars[par] = p_def.tree[par].data
3175
+ } else {
3176
+ if ( typeof p_def.tree[par].recursive === "object" ) {
3177
+ let pdescr = p_def.tree[par]
3178
+ this.executable_condition_processing(pdescr)
3179
+ this.tree_conditional_reduction(pdescr)
3180
+ pdescr.data = this.join_executables(pdescr)
3181
+ dst_pars[par] = pdescr.data
3182
+ }
3183
+ }
3184
+ }
3185
+ }
3186
+ if ( rec.executables ) {
3187
+ let pdescr = p_def
3188
+ this.executable_condition_processing(pdescr)
3189
+ this.tree_conditional_reduction(pdescr)
3190
+ pdescr.data = this.join_executables(pdescr)
3191
+ }
3192
+ will_return_data = p_def.data
3193
+ }
3194
+ } else {
3195
+ will_return_data = data
3196
+ }
3197
+ }
3198
+ }
3199
+ if ( parse_util.has_parameter_block(will_return_data) ) {
3200
+ will_return_data = parse_util.remove_parameter_block(will_return_data)
3201
+ }
3202
+ return will_return_data
3203
+ }
3204
+
3205
+
3206
+
3207
+ /**
3208
+ *
3209
+ * Updates the skeleton map, sk map, by figuring the data of an entry.
3210
+ * The data field will be placed into a file in the final steps.
3211
+ *
3212
+ * The skeleton map is obtained from a skeleton data structure belonging
3213
+ * to an operation version (e.g. default, shops, boxy, mixed... )
3214
+ *
3215
+ * This method considers the possibity that the sk map has not been constructed for some entries.
3216
+ *
3217
+ * If the sk map has been constructed, and if an entry has a 'recursive' field,
3218
+ * the method will attempt to update the param def entry of the recursive structure stored in the field.
3219
+ * Also, it may carry out any executions if there is an executable object.
3220
+ *
3221
+ * The parameter def has fields of different types. A tree type is one that allows for the reduction of values
3222
+ * across different files. A const type should have values already figured and stored in the parameterized
3223
+ * section of a the skeleton data structure.
3224
+ *
3225
+ * In the case of the tree type, the update of the parameter def object (by parameter) is handed off
3226
+ * to `data_from_parameter`.
3227
+ *
3228
+ * In the case of the constant types, the `evaluations` parameter is developed until a call to `early_evaluations`
3229
+ * can be made.
3230
+ *
3231
+ *
3232
+ * @param {object} transformed
3233
+ */
3234
+ up_prop_data_to_vars(transformed) {
3235
+ for ( let [sk_name, op_map ] of Object.entries(transformed) ) {
3236
+ for ( let [op, skel ] of Object.entries(op_map) ) {
3237
+ let sk_map = skel.skeleton_map
3238
+ if ( typeof sk_map !== "object" ) continue
3239
+ for ( let [entry_ky,desciptor] of Object.entries(sk_map) ) {
3240
+ if ( desciptor?.recursive ) {
3241
+ let rec = desciptor.recursive
3242
+ desciptor.backup_data = "" + desciptor.data
3243
+ if ( rec.params_def ) {
3244
+ //
3245
+ for ( let [pname,ptype] of Object.entries(rec.params_def) ) {
3246
+ if ( this.is_tree_type(ptype) ) {
3247
+ let pdat = this.data_from_parameter(skel,entry_ky,pname)
3248
+ rec.params_def[pname] = pdat
3249
+ } else if ( this.is_const_type(ptype) ) {
3250
+ //
3251
+ if ( desciptor.evaluations === undefined ) {
3252
+ desciptor.evaluations = Object.assign({},rec.params_def)
3253
+ }
3254
+ let evals = desciptor.evaluations
3255
+ if ( skel.parameterized[entry_ky] ) {
3256
+ evals[pname] = skel.parameterized[entry_ky][pname].data
3257
+ }
3258
+ }
3259
+ }
3260
+ //
3261
+ if ( desciptor.evaluations ) {
3262
+ this.early_evaluations(desciptor)
3263
+ }
3264
+ //
3265
+ }
3266
+ if ( rec.executables ) {
3267
+ let pdescr = desciptor
3268
+ this.executable_condition_processing(pdescr)
3269
+ this.tree_conditional_reduction(pdescr)
3270
+ let ddat = this.join_executables(pdescr)
3271
+ if ( parse_util.has_parameter_block(ddat) ) {
3272
+ ddat = parse_util.remove_parameter_block(ddat)
3273
+ }
3274
+ desciptor.data = ddat
3275
+ }
3276
+ } else {
3277
+ if ( desciptor === undefined ) {
3278
+ console.log("up_prop_data_to_vars",sk_name,entry_ky)
3279
+ }
3280
+ }
3281
+ }
3282
+ }
3283
+ }
3284
+ }
3285
+
3286
+
3287
+
3288
+ /**
3289
+ *
3290
+ * @param {object} transformed
3291
+ */
3292
+ list_to_skeletal_variable_assignment(transformed) {
3293
+ for ( let [sk_key, op_map ] of Object.entries(transformed) ) {
3294
+ for ( let [op, skel ] of Object.entries(op_map) ) {
3295
+ let sk_map = skel.skeleton_map
3296
+ if ( typeof sk_map !== "object" ) continue
3297
+ if ( skel.parameterized ) {
3298
+ for ( let [entry_ky,desciptor] of Object.entries(skel.parameterized) ) {
3299
+ let keys = Object.keys(desciptor)
3300
+ let list_key = keys.find((ky) => {
3301
+ if ( ky.startsWith("_type<") ) {
3302
+ return true
3303
+ } else {
3304
+ return false
3305
+ }
3306
+ })
3307
+ //
3308
+ if ( list_key ) {
3309
+ let entry = sk_map[entry_ky]
3310
+ if ( entry?.recursive ) {
3311
+ //console.log("list_to_skeletal_variable_assignment",entry.recursive.params_def)
3312
+ } else {
3313
+ continue
3314
+ }
3315
+ let el_var = list_key.replace("_type<","").replace(">","").trim()
3316
+ if ( desciptor[el_var] === "list" ) {
3317
+ let alist = desciptor[list_key]
3318
+ if ( Array.isArray(alist) ) {
3319
+ let par_src = entry.recursive.params_def
3320
+ entry.subst_recursive = []
3321
+ for ( let var_vals of alist ) {
3322
+ // entry.subst_recursive
3323
+ let subst_vals = {}
3324
+ for ( let ky in par_src ) {
3325
+ subst_vals[ky] = var_vals[ky]
3326
+ }
3327
+ entry.subst_recursive.push({ "params_def" : subst_vals })
3328
+ entry.conds = this.find_conditionals(entry.recursive.executables.execs)
3329
+ }
3330
+ }
3331
+ }
3332
+ }
3333
+ //
3334
+ }
3335
+ }
3336
+ }
3337
+ }
3338
+ }
3339
+
3340
+
3341
+
3342
+ /**
3343
+ *
3344
+ * @param {object} transformed
3345
+ */
3346
+ incremental_evaluations(transformed) {
3347
+ //
3348
+ for ( let [sk_key, op_map ] of Object.entries(transformed) ) {
3349
+ for ( let [op, skel ] of Object.entries(op_map) ) {
3350
+ if ( typeof skel.incrementer_set === "object" ) {
3351
+ for ( let [ky_vname,incr_descr] of Object.entries(skel.incrementer_set) ) {
3352
+ let subst_var = `@{${incr_descr.apply_to}}`
3353
+ let i = parseInt(incr_descr.start_val)
3354
+ let n = incr_descr.list.length
3355
+ let stepper = incr_descr.starter
3356
+ //
3357
+ let data = stepper.data
3358
+ stepper.data = parse_util.subst(data,subst_var,i)
3359
+ let l = incr_descr.list
3360
+ for ( let k = 0; k < n; k++ ) {
3361
+ let next = l[k]
3362
+ if ( next !== stepper ) {
3363
+ i++
3364
+ let data = next.data
3365
+ next.data = parse_util.subst(data,subst_var,i)
3366
+ }
3367
+ }
3368
+ }
3369
+ }
3370
+ }
3371
+ }
3372
+ //
3373
+ }
3374
+
3375
+
3376
+ /**
3377
+ *
3378
+ * @param {object} transformed
3379
+ */
3380
+ finalize_markup(transformed) {
3381
+ for ( let [sk_name, op_map ] of Object.entries(transformed) ) {
3382
+ for ( let [op, skel ] of Object.entries(op_map) ) {
3383
+ let sk_map = skel.skeleton_map
3384
+ if ( typeof sk_map !== "object" ) continue
3385
+ let fdata = ""
3386
+ for ( let [entry_ky,desciptor] of Object.entries(sk_map) ) {
3387
+ if ( entry_ky.indexOf("script::") >= 0 ) {
3388
+ // the script should be loaded
3389
+ fdata += (desciptor.data ? desciptor.data : "") + "\n"
3390
+ if ( this.remove_block_comments ) {
3391
+ fdata = parse_util.clear_block_comments(fdata)
3392
+ }
3393
+ } else {
3394
+ if ( typeof desciptor === "string" ) {
3395
+ fdata += desciptor
3396
+ } else if ( typeof desciptor === "object" ) {
3397
+ fdata += desciptor.data ? desciptor.data : ""
3398
+ }
3399
+ }
3400
+ }
3401
+
3402
+ skel.final.markup = fdata
3403
+ }
3404
+ }
3405
+ }
3406
+
3407
+
3408
+ /**
3409
+ *
3410
+ * for the call tosection_parsing(all_skeletons)
3411
+ * The one parameter `all_skeletons` is a map from file names to skeleton ascii.
3412
+ *
3413
+ * This method parses the skeleton ascii, managing the document structure,
3414
+ * creating lists of leaf files specified in the text and extracting skeleton files
3415
+ * in order to add them to the map `all_skeletons`.
3416
+ *
3417
+ * Skeletons are parsed into sections. HTML structure is permitted in a skeleton.
3418
+ * Variables are allowed as $$<variable name> forms, where <variable name> is an identifier.
3419
+ *
3420
+ * Parsing includes restructuring files based on configuration variables.
3421
+ * Each concern may have different requirments (formulaic variations on layout, including number of sections,
3422
+ * grid mapping, topic structure, etc.)
3423
+ *
3424
+ * @param {object} all_skeletons
3425
+ * @param {object} skel_to_concerns -- each skeleton serves a set of concerns
3426
+ */
3427
+ async skeleton_parsing(all_skeletons) {
3428
+ // expand and parse all skeletons being parsed in the batch
3429
+ let transform_1 = await this.section_parsing(all_skeletons)
3430
+ //
3431
+ // // This script parsing may be obsolete after a short stay here.
3432
+ // // preprocessing the skeletons has worked better.
3433
+ // // this may still be good for shared scripts that may be better embedded. But, some handling of multiple uses may help
3434
+ // let script_stats = this.coalesce_scripts(transform_1) // keeping track of scripts that are parsed and may be requested by any number of skeletons
3435
+ // this.update_script_stats_usage(transform_1,script_stats)
3436
+
3437
+ // let occurence_partition = this.partition(script_stats)
3438
+ // console.dir(occurence_partition)
3439
+ // await this.prep_script_directories(occurence_partition)
3440
+
3441
+ //
3442
+
3443
+ // Now, get ready for operating on the html inclusions specified by a skeleton.
3444
+ // Note: some loops will have been expanded leaving behind other directive (e.g. calc)
3445
+ await this.leaf_html_directives(transform_1)
3446
+
3447
+ let name_to_data = await this.delay_file_loading()
3448
+
3449
+ this.evaluate_delayed_queue(name_to_data)
3450
+
3451
+ this.incremental_evaluations(transform_1)
3452
+
3453
+ this.list_to_skeletal_variable_assignment(transform_1)
3454
+
3455
+ this.up_prop_data_to_vars(transform_1)
3456
+
3457
+ this.conditional_evaluations(transform_1)
3458
+
3459
+ this.lift_remaining_imports(transform_1)
3460
+
3461
+ this.loop_finalization(transform_1)
3462
+
3463
+ this.finalize_markup(transform_1)
3464
+
3465
+ // just to keep a record of what has taken place in this method
3466
+ let str = JSON.stringify(transform_1,null,4)
3467
+ await fos.write_out_string(this.top_level_parsed,str)
3468
+
3469
+ return transform_1
3470
+ }
3471
+
3472
+
3473
+
3474
+
3475
+ // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
3476
+
3477
+ /**
3478
+ * This method needs the outputs stored in the configuration file.
3479
+ * Each concern gets a reverse map of the ogroup skeleton map, which mapped template file names to skeleton inputs
3480
+ *
3481
+ * @param {object} skel_matrix
3482
+ */
3483
+ collect_concerns(outputs_field_of_conf) {
3484
+ let concerns_map = {}
3485
+ for ( let ogroup of outputs_field_of_conf ) { // an ogroup
3486
+ let concerns = ogroup.targets.concerns // targets ... concerns array (peculiar to this ogroup)
3487
+ for ( let concern of concerns ) {
3488
+ let mapped_c = concerns_map[concern] // the concern may appear in more than one ogroup, but that may not be a good idea considering ops
3489
+ if ( !mapped_c ) {
3490
+ mapped_c = {}
3491
+ concerns_map[concern] = mapped_c
3492
+ }
3493
+ let skeletons = ogroup.skeletons // output file to skeleton
3494
+ let r_skeletons = parse_util.reverse_map(skeletons)
3495
+ for ( let skeleton in r_skeletons ) { // the skeletone is a "file" name
3496
+ if ( mapped_c[skeleton] === undefined ) mapped_c[skeleton] = {} // not one to one
3497
+ mapped_c[skeleton] = Object.assign(mapped_c[skeleton],r_skeletons[skeleton]) // r_skeletons[skeleton] is an object (mapping not one to one)
3498
+ }
3499
+ }
3500
+ }
3501
+
3502
+ return concerns_map
3503
+ }
3504
+
3505
+
3506
+ async load_file_or_leave_it_as_path(concern_replace) {
3507
+ console.log(concern_replace)
3508
+ let fname = concern_replace.replace("{{{","").replace("}}}","").trim()
3509
+ let data = await fos.load_data_at_path(fname)
3510
+ if ( data && (typeof data === "string") ) {
3511
+ return data
3512
+ }
3513
+ return concern_replace
3514
+ }
3515
+
3516
+
3517
+ /**
3518
+ *
3519
+ * @param {Array} concerns
3520
+ * @param {string} dir_form
3521
+ * @param {string} tfile
3522
+ * @param {string} template
3523
+ */
3524
+ async output_ogroup_template(concerns,dir_form,tfile,template,concern_rewrites) {
3525
+ let promises = []
3526
+ for ( let concern of concerns ) {
3527
+ let top_out_dir = dir_form.replace("@concern",concern)
3528
+ let t_output_path = `${top_out_dir}${tfile}`
3529
+ let t_path = this.paths.compile_one_path(t_output_path)
3530
+ if ( concern_rewrites ) {
3531
+ for ( let rewrite_rule of Object.values(concern_rewrites) ) {
3532
+ let concern_replace = rewrite_rule.replace.replace("@concern",concern)
3533
+ concern_replace = await this.load_file_or_leave_it_as_path(concern_replace)
3534
+ template = template.replaceAll(rewrite_rule.find,concern_replace)
3535
+ }
3536
+ }
3537
+ console.log("output_ogroup_template",t_path)
3538
+ promises.push(fos.write_out_string(t_path,template))
3539
+ }
3540
+ //
3541
+ await Promise.all(promises)
3542
+ }
3543
+
3544
+
3545
+ /**
3546
+ *
3547
+ * @param {object} parsed_skels
3548
+ */
3549
+ async generate_all_concerns_templates(parsed_skels) {
3550
+ //
3551
+ let outputs = this.outputs
3552
+ for ( let ogroup of outputs ) {
3553
+ for ( let [tfile,file] of Object.entries(ogroup.skeletons) ) {
3554
+ //
3555
+ let skel_compiled_path = ogroup.skels_processed[file].ablsolute_path
3556
+ let op_key = ogroup.skels_processed[file].skel_vars_key
3557
+ op_key = op_key ? op_key : "defaults"
3558
+ let skel_data = parsed_skels[skel_compiled_path][op_key] // This is the stuff for template generation
3559
+
3560
+ let template = skel_data.final.markup // set in `final_markup`
3561
+ //
3562
+ if ( skel_data.concern_rewrites ) {
3563
+ let okeys = Object.keys(skel_data.concern_rewrites)
3564
+ if ( okeys.length ) { //{ '/site_app.js': '{{{ @{[targets.dir]}/scripts/site_app.js }}}' }
3565
+ for ( let ky of okeys ) {
3566
+ console.log("<1>generate_all_concerns_templates","concern_rewrites.....")
3567
+ let rewrites = skel_data.concern_rewrites[ky]
3568
+ //
3569
+ //console.dir(rewrites)
3570
+ let form = rewrites.data
3571
+ let generic_dir_spec = this.pull_parent_dir_dotted_target_dir(form,ogroup)
3572
+ if ( generic_dir_spec ) {
3573
+ generic_dir_spec = generic_dir_spec.replace("@kernel",rewrites.kernel)
3574
+ // let extraneous_this_impl = "/@kernel"
3575
+ // generic_dir_spec = generic_dir_spec.replace(extraneous_this_impl,"")
3576
+ console.log("<2>generate_all_concerns_templates","concern_rewrites.....",generic_dir_spec, form)
3577
+ skel_data.concern_rewrites[ky] = {
3578
+ "find" : form,
3579
+ "replace" : generic_dir_spec
3580
+ }
3581
+ }
3582
+ }
3583
+ } else {
3584
+ skel_data.concern_rewrites = false
3585
+ }
3586
+ }
3587
+ //
3588
+ // now make sure each concern gets the template file to be generated buy ogroup
3589
+ let targets = ogroup.targets // targets
3590
+ let dir_form = targets.dir_form
3591
+ dir_form = dir_form.replace("@target",this.created_dir)
3592
+
3593
+ await this.output_ogroup_template(targets.concerns,dir_form,tfile,template,skel_data.concern_rewrites )
3594
+ }
3595
+ }
3596
+ //
3597
+ let concerns = this.collect_concerns(this.outputs)
3598
+ return concerns
3599
+ }
3600
+
3601
+
3602
+ /**
3603
+ * These entries appear in the name_drop.db type of file for a particular page template of a particular concern.
3604
+ *
3605
+ * So, for instance, [websites]/@concern/templates/index.tmplt will have a name_drop type of db stored with it, as such:
3606
+ * websites]/@concern/templates/index_calc.db
3607
+ *
3608
+ * The following would be the example of an entry:
3609
+ *
3610
+ * `"about_box" : {
3611
+ "name": "about",
3612
+ "content": {
3613
+ "type": "@<type>",
3614
+ "file": "./about.@<type>"
3615
+ }
3616
+ }`
3617
+ *
3618
+ * It is expected to be updated in the following sort of way:
3619
+ *
3620
+ * "name" : "about",
3621
+ "content" : {
3622
+ "svg" : "",
3623
+ "file" : "./about.svg"
3624
+ }
3625
+ *
3626
+ * The update is done prior to running the second stage of roll-right. (It could be html instead of svg.)
3627
+ *
3628
+ * @param {string} concern
3629
+ * @param {object} track_map
3630
+ */
3631
+ update_calc_tracking(concern,track_map) {
3632
+ for ( let tk in track_map ) {
3633
+ let namer = tk.replace("Box","").replace("_box","")
3634
+ track_map[tk] = {
3635
+ "name" : namer,
3636
+ "content" : {
3637
+ "type" : "@<type>",
3638
+ "file" : `./${namer}.@<type>`
3639
+ }
3640
+ }
3641
+ }
3642
+
3643
+ track_map["_track_control"] = {
3644
+ "concern" : concern,
3645
+ "edited" : false,
3646
+ "mod-date" : Date.now(),
3647
+ "create-date" : Date.now()
3648
+ }
3649
+ }
3650
+
3651
+
3652
+ /**
3653
+ * If a user has already created a `_calc.db` file,
3654
+ * this method will inform the caller if that is the case and also
3655
+ * if the `_track_control` structure has an `edited` field set to true.
3656
+ *
3657
+ * As such, this affects the user's work process. A user wishing to keep edits must
3658
+ * set the `edited` field to true. Otherwise, the file may be overwritten, which is usually
3659
+ * not desirable, except occasionally. As such, the user should be able to toggle this field
3660
+ * in any application that manages this sort of file.
3661
+ *
3662
+ * @param {string} calc_tracking_file -- a path to a concerns calc DB file.
3663
+ * @returns {boolean}
3664
+ */
3665
+ async existing_calc_track(calc_tracking_file) {
3666
+ let tracker = await fos.load_json_data_at_path(calc_tracking_file)
3667
+ if ( tracker ) {
3668
+ if ( tracker._track_control.edited ) {
3669
+ return tracker
3670
+ }
3671
+ }
3672
+ return false
3673
+ }
3674
+
3675
+
3676
+ /**
3677
+ * This method takes an existing tracking file contents for a `_calc.db` region
3678
+ * and includes it in, and overwrites, the generic `_calc.db` found in the alpha directories
3679
+ * provided the user (admin).
3680
+ *
3681
+ * @param {object} new_tracking
3682
+ * @param {object} saved_track_map
3683
+ * @returns {boolean}
3684
+ */
3685
+ update_tracker_with_new(new_tracking,saved_track_map) {
3686
+ for ( let tky in new_tracking ) {
3687
+ if ( saved_track_map[tky] ) continue
3688
+ saved_track_map[tky] = new_tracking[tky]
3689
+ }
3690
+ return saved_track_map
3691
+ }
3692
+
3693
+
3694
+ /**
3695
+ * For reentry after compilation.
3696
+ * Load the ogroups file which should have the template files already processed
3697
+ * ahead of applying updates to the sectional database.
3698
+ */
3699
+ async load_ogroups_intermediate_files() {
3700
+ //
3701
+ let ogroups_file = `[websites]/${this.project_dir}/ogroups_intermediate_files.json`
3702
+ ogroups_file = this.paths.compile_one_path(ogroups_file)
3703
+ this.outputs = await fos.load_json_data_at_path(ogroups_file)
3704
+ //
3705
+ // console.dir(this.outputs,{ depth : 6 })
3706
+ }
3707
+
3708
+ /**
3709
+ *
3710
+ * makes sure that directories receiving the generated assets exists
3711
+ * and are structure according to the configuration.
3712
+ *
3713
+ * Writes out template files each with a '.tmplt' suffix.
3714
+ * Writes out a manifest to these files, concerns_to_files.json.
3715
+ *
3716
+ * Writes out db files for calculated regions. Outputs a file for each template.
3717
+ * The DB files can be used later to override the generic database stored in the alpha.
3718
+ *
3719
+ * Creates one top level file to keep track of the individual, template associated, files.
3720
+ * That is `concerns_named.db`
3721
+ *
3722
+ * All of the individual template and DB files can be output to the 'template' directories of the concerns.
3723
+ * The top level file can be output to the directory commanding all concerns, typically "template-configs"
3724
+ * in `[websites]` directory.
3725
+ *
3726
+ * @param {object} concerns_to_files
3727
+ */
3728
+
3729
+ async write_templates_op_data() {
3730
+ //
3731
+ let concerns_to_files = this.collect_concerns(this.outputs)
3732
+ //
3733
+ let concerns_file = `[websites]/${this.project_dir}/concerns_to_files.json`
3734
+ concerns_file = this.paths.compile_one_path(concerns_file)
3735
+ await fos.write_out_pretty_json(concerns_file,concerns_to_files,4)
3736
+ //
3737
+ let ogroups_file = concerns_file.replace("concerns_to_files.json","ogroups_intermediate_files.json")
3738
+ ogroups_file = this.paths.compile_one_path(ogroups_file)
3739
+ await fos.write_out_pretty_json(ogroups_file,this.outputs,8)
3740
+ //
3741
+ // get the name of the type level file
3742
+ let db_locations = concerns_file.replace("concerns_to_files.json","concerns_named.db")
3743
+ let concerns_db_files = {}
3744
+ parse_util.copy_keys(concerns_db_files,concerns_to_files,"object")
3745
+ //
3746
+ //
3747
+ let outputs = this.outputs
3748
+ for ( let ogroup of outputs ) {
3749
+ let targets = ogroup.targets // targets
3750
+ let dir_form = targets.dir_form
3751
+ //
3752
+ dir_form = dir_form.replace("@target",this.created_dir)
3753
+ for ( let concern of targets.concerns ) {
3754
+ //
3755
+ let db_output = concerns_db_files[concern]
3756
+ //
3757
+ let top_out_dir = dir_form.replace("@concern",concern)
3758
+ //
3759
+ let sk_maps = concerns_to_files[concern]
3760
+ let promises = []
3761
+ // console.log(">> >> >>")
3762
+ // console.log(concern)
3763
+ // console.dir(Object.keys(sk_maps))
3764
+ for ( let sk in sk_maps ) {
3765
+ let opairs = sk_maps[sk]
3766
+ for ( let tfile in opairs ) {
3767
+ //
3768
+ let t_output_path = `${top_out_dir}${tfile}`
3769
+ let t_path = this.paths.compile_one_path(t_output_path)
3770
+ //
3771
+ // console.log("write_templates",dir_form,">>",t_path) // outputs the template here.
3772
+ // promises.push(fos.write_out_string(t_path,template))
3773
+ //
3774
+ if ( this.tracking_skel_calc_usage[sk] ) {
3775
+ let track_map = this.tracking_skel_calc_usage[sk]
3776
+ let calc_tracking_file = t_path
3777
+ this.update_calc_tracking(concern,track_map)
3778
+ calc_tracking_file = calc_tracking_file.replace('.tmplt',`_calc.db`)
3779
+ let tracker = false
3780
+ // Checks to see if there are custom definitions set by the user.
3781
+ if ( tracker = await this.existing_calc_track(calc_tracking_file) ) {
3782
+ // Given there are such definition, use them.
3783
+ track_map = this.update_tracker_with_new(track_map,tracker)
3784
+ }
3785
+ // output the cacluation sections DB for each top level file.
3786
+ promises.push(fos.write_out_pretty_json(calc_tracking_file,track_map,4))
3787
+ db_output[calc_tracking_file] = 1 // add to the high level manifest pointing to these files
3788
+ }
3789
+ }
3790
+ }
3791
+ //
3792
+ await Promise.all(promises)
3793
+ }
3794
+ }
3795
+ //
3796
+ // output the top level file used to find the DBs
3797
+ // this is a map of concerns to db files
3798
+ if ( Object.keys(this.tracking_skel_calc_usage).length ) {
3799
+ await fos.write_out_pretty_json(db_locations,concerns_db_files,4)
3800
+ }
3801
+ //
3802
+ return concerns_to_files
3803
+ }
3804
+
3805
+
3806
+
3807
+ /**
3808
+ *
3809
+ * @param {object} el_var_map - var name and types
3810
+ * @param {object} el - var name and values
3811
+ * @param {string} target_data
3812
+ * @return {string}
3813
+ *
3814
+ */
3815
+ var_substitution(el_var_map,el,target_data) {
3816
+ //
3817
+ let td_update = "" + target_data
3818
+ for ( let [ky,vtype] of el_var_map ) {
3819
+ let val = el[ky]
3820
+ if ( typeof val === vtype ) {
3821
+ td_update = parse_util.subst(td_update,`@{${ky}}`,val)
3822
+ delete el[ky] // may test to see if something is not sets
3823
+ }
3824
+ }
3825
+ //
3826
+ return td_update
3827
+ }
3828
+
3829
+
3830
+ /**
3831
+ *
3832
+ * @param {object} resolver
3833
+ * @returns {string}
3834
+ */
3835
+ assemble_subfile(resolver) {
3836
+ // return ""
3837
+ let {file,tree,data,recursive} = resolver
3838
+ //
3839
+ let tranformed_data = "" + data
3840
+ let var_assigns = this.parameterized_block_unification(file,recursive,tree)
3841
+ if ( var_assigns && (typeof var_assigns === "object") ) {
3842
+ //
3843
+ let executables = recursive.executables
3844
+ let required_values = recursive.params_def
3845
+ if ( executables && required_values ) {
3846
+ for ( let execu of executables ) {
3847
+ let replace_form = execu.replace
3848
+ let cond = execu.condition
3849
+ let vname = cond.variable
3850
+ let case_to_use = ""
3851
+ if ( required_values[vname] !== undefined ) {
3852
+ case_to_use = execu.positive_exec.pos
3853
+ } else {
3854
+ case_to_use = execu.negative_exec.neg
3855
+ }
3856
+ tranformed_data = tranformed_data.replace(replace_form,case_to_use)
3857
+ }
3858
+ }
3859
+ //
3860
+ tranformed_data = this.var_substitution(resolver,var_assigns,tranformed_data)
3861
+ }
3862
+ //
3863
+ return tranformed_data
3864
+ }
3865
+
3866
+
3867
+ /**
3868
+ * This is a test...
3869
+ *
3870
+ * @param {object} vset
3871
+ * @param {string} data
3872
+ */
3873
+ resolve_with_configuration(vset,data) {
3874
+ let vsource = Object.assign({},this.global_variable_values)
3875
+ let t_data = this.var_substitution(vset,vsource,data)
3876
+ //
3877
+ this.vars_unset_in_run = Object.assign({},this.vars_unset_in_run,vsource)
3878
+ //
3879
+ return t_data
3880
+ }
3881
+
3882
+
3883
+ /**
3884
+ * This is supposed to be the starting point for calling variable instantiation
3885
+ * on the blocks at the skeleton level. The level_1_parameterized_file parameter
3886
+ * should be the expansion that is the value keyed by the file name in the skeleton map.
3887
+ *
3888
+ * @param {string} file_key
3889
+ * @param {object} level_1_parameterized_file
3890
+ * @param {object} parameterization_map
3891
+ */
3892
+ parameterized_block_unification(file_key,level_1_parameterized_file,parameterization_map) {
3893
+ let file_inputs = parameterization_map[file_key] // skeletal level parameterizations from loaded subfiles (e.g. key is file at the skeleton level)
3894
+ let l1pf = level_1_parameterized_file // already got it when getting file key (a description of what is in the file at the skeleton level)
3895
+ try {
3896
+ let required_values = l1pf.recursive.params_def // variable to types (sibling to executable if present)
3897
+ let executables = l1pf.recursive.executables
3898
+ for ( let [vname,vtype] of Object.entries(required_values) ) {
3899
+ if ( vname[0] === '_' ) continue
3900
+ let resolver = file_inputs[vname] //
3901
+ if ( resolver[vtype] ) {
3902
+ let resolved_data = ""
3903
+ switch( vtype ) {
3904
+ case "file" : {
3905
+ if ( resolver.tree ) {
3906
+ resolved_data = this.assemble_subfile(resolver)
3907
+ } else {
3908
+ resolved_data = resolver.data
3909
+ }
3910
+ break;
3911
+ }
3912
+ case "list" : {
3913
+ let vtype_def = `_type<${vname}>`
3914
+ let el_var_map = required_values[vtype_def]
3915
+ //
3916
+ let data_tmplt = l1pf.data
3917
+ let start_loop = `<${vname}>`
3918
+ let end_loop = `</${vname}>`
3919
+ let loop_part = data_tmplt.substring(data_tmplt.indexOf(start_loop),data_tmplt.indexOf(end_loop))
3920
+ let dlist = resolver[vtype]
3921
+ for ( let el of dlist ) {
3922
+ let el_subst = this.var_substitution(el_var_map,el,loop_part)
3923
+ if ( el_subst ) {
3924
+ resolved_data += el_subst
3925
+ }
3926
+ }
3927
+ //
3928
+ break;
3929
+ }
3930
+ }
3931
+
3932
+ required_values[vname] = resolved_data
3933
+ }
3934
+ }
3935
+
3936
+ let data = l1pf.data
3937
+ if ( executables ) {
3938
+ for ( let execu of executables ) {
3939
+ let replace_form = execu.replace
3940
+ let cond = execu.condition
3941
+ let vname = cond.variable
3942
+ let case_to_use = ""
3943
+ if ( required_values[vname] !== undefined ) {
3944
+ case_to_use = execu.positive_exec.pos
3945
+ } else {
3946
+ case_to_use = execu.negative_exec.neg
3947
+ }
3948
+ data = data.replace(replace_form,case_to_use)
3949
+ }
3950
+ l1pf.data = data
3951
+ }
3952
+ l1pf.data = this.var_substitution(required_values,required_values,data)
3953
+
3954
+ data = l1pf.data
3955
+ let vset = {}
3956
+ vset = this.errant_variable_extraction(vset,data)
3957
+ if ( vset && Object.keys(vset).length ) {
3958
+ l1pf.data = this.resolve_with_configuration(vset,data)
3959
+ }
3960
+
3961
+ } catch (e) {}
3962
+ }
3963
+
3964
+
3965
+
3966
+ /**
3967
+ *
3968
+ * load the skeletons identified in the top level configuration.
3969
+ * Use `load_skeletons` to get the files and a list of output keys (file names)
3970
+ * which will be the templates stored with the concerns.
3971
+ *
3972
+ * The `out_name_list` will be a list whose index elements correspond to the index
3973
+ * of output objects in the configure `output` object. a list of concerns can be found
3974
+ * in the targets field of one of the output objects. For each conern the skeletons
3975
+ * they require will be gathered.
3976
+ *
3977
+ * During the process of gathering, skeletons per concern, the keleton unification process
3978
+ * will look recursively for skeleton components of higher level files and add them to the list
3979
+ * of skeletons per concern.
3980
+ *
3981
+ * The `skeleton_unification` method may coallese imports of JavaScript into directories meant to be
3982
+ * targeted by bundlers. (Some source code is destined to the web page.)
3983
+ *
3984
+ */
3985
+ async skeleton_unification() {
3986
+ //
3987
+ // load relevant files (identified by the configuration) // skel_to_concerns is also global member
3988
+ let all_skeletons = await this.load_skeletons() // get the files and create `skel_to_concerns` map
3989
+ await this.load_name_drops_db() // loads this db only
3990
+ //
3991
+ // skel_to_concerns is added because each concern will have different structural demands
3992
+ // where page structure can be derived from cannonical descriptions (later maybe AI normalization)
3993
+ let parsed_skels = await this.skeleton_parsing(all_skeletons) // injest structure of all skeletons in this map
3994
+ await this.name_parameters_output() // after parsing deal with the database entries for each skeleton and make
3995
+ // a version of the DB which may be customized before taking the next step (phase 2)
3996
+
3997
+ return parsed_skels
3998
+ }
3999
+
4000
+
4001
+ }
4002
+
4003
+
4004
+
4005
+ module.exports = SkelToTemplate