-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path$$.Settings.jsxlib
More file actions
1049 lines (896 loc) · 37.2 KB
/
$$.Settings.jsxlib
File metadata and controls
1049 lines (896 loc) · 37.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*******************************************************************************
Name: Settings
Desc: Encapsulates a set of custom settings handled throughout a script.
Path: /etc/$$.Settings.jsxlib
Require: Dom/label ; $$.kill() $$.clone() $$.JSON()
Encoding: ÛȚF8
Core: NO
Kind: Module.
API: =access() DontClone declare()
hasScope() footprint() hasKey()
canDeclare() activate() reset() backup()
addLive() objKeyCount() appKeyCount()
DOM-access: extractLabel() insertLabel()
Todo: documentation.
Created: 150630 (YYMMDD)
Modified: 230403 (YYMMDD)
*******************************************************************************/
;$$.hasOwnProperty('Settings') || eval(__(MODULE, $$, 'Settings', 230403, 'access'))
//==========================================================================
// BACKGROUND
//==========================================================================
/*
PURPOSE
Managing settings and user preferences is an essential need in every solid
project. The present module takes this question seriously and offers a
consistent solution. It allows to declare the vital settings of your current
script, then it provides a complete save/restore/reset mechanism taking into
account the lifespan (here called the 'scope') of each setting.
You can deal with constant (read-only) parameters as well as with session-
persistent settings, or even long-term data stored in either the app or any
DOM object.
Settings are basically treated as key-value pairs. Your script can simply
declare a set `{ k1:v1, k2:v2, ... }`, each `k_i` being the KEY and each `v_i`
being the DEFAULT VALUE of that key. From then, these settings can be easily
shared, accessed, modified, throughout the script, using `$$.Settings` as a
unique provider in one place.
[REM] We choose to implement `$$.Settings` as a MODULE, not a CLASS, because
there is no point in having concurrent settings in the memory space of a
single script. (Only one script is running at a time!)
STAGES
The API of $$.Settings exposes four important methods that can be thought as
ordered 'stages' of interaction with the module. Below is just an overview of
these routines, whose syntax is detailed in the respective code blocks.
1. DECLARATION STAGE -> declare() [REQUIRED]
The client script must, of course, tell `$$.Settings` which set of
key-value pairs it is intended to manage. This is done through the
Declaration stage using `$$.Settings.declare(...)`. This step
must be achieved once-per-engine (typically in a `onEngine`
handler) before performing any other API stage. The `declare` method
expects an Unique ID and an Object that contains the whole set of
parameters with their associated scopes.
2. ACTIVATION STAGE -> activate() [REQUIRED]
Each time your script runs or re-runs, it wants to ACTIVATE the settings
with regard to their respective lifespan. Some have to be restored from
their previous states, others need to wake up at their default value.
This is the purpose of the ACTIVATION stage, which rawly corresponds to
an `onLoad` step. Before activation, all settings are considered undefined.
3. RESET STAGE -> reset() [OPTIONAL]
At any point of the client code, you may want to go back to the default
settings. This is a RESET stage. Some options are available for
fine-tuning this process. Resetting is technically very similar to
activating, with additional instructions. Unlike the Activation stage,
a Reset stage is typically the response to a user request.
4. BACKUP STAGE -> backup() [OPTIONAL]
It is not *always* required to save the current settings, but of course
this is very frequently what you expect. The Backup stage addresses this
job, which by definition focuses on persistent data (see the SCOPE
section below.) Make sure your script calls `$$.Settings.backup()` when
needed. (If you skip it, data will activate to their previous state on
the next execution of the script.)
SCOPES
You can assign a SCOPE to each setting, telling the system how, and how long,
a key is supposed to both remain in memory and be restored. When the user
re-runs the script, some settings need to be restored to their default value,
others need to recover their latest state (within the InDesign session), still
others have to fit a particular object that wasn't here before, and so on.
This module is based on my own practice with identifying recurring patterns
and requirements about script settings. Although it is probably not exhaustive,
it defines the following scopes and scenarios:
1. CONSTANTS. Pure read-only key-value associations, which the client code
cannot even change once declared. Useful for product name, version, user license
data, and the many fixed strings or numbers that the script must keep safe.
(Thanks to the `watch` method, the system prevents the key from being changed--
unless deeply hacked!)
2. LIVE KEYS. Purely 'volatile' parameters (never saved nor restored) just used
to share data across different places during a process. These are not 'settings'
in the usual sense; see them as on-the-fly params that belong to the Settings
space for convenience. Use case: the active document ID, or some current target,
must be known from separate modules to achieve some task. But you want to keep
them strongly decoupled, so passing the information as an argument bores you.
Instead, your functions will get the data, if available, from `$$.Settings`.
3. RESET KEYS. Keys that can freely evolve once activated (as the Live Keys),
but expected to *always* wake up at a specific value. Here, 'always' means,
"each time the script starts up." You can see Reset Keys as Live Keys coming
with an 'onLoad' handler that automatically resets them to a determined value.
This makes a difference in engine-persistent scripts, where Live Keys wake
up in an arbitrary state (depending of previous operations) while Reset Keys
are guaranteed to regain their default value.
4. ENGINE KEYS. These keys are aligned with the script engine. That is, they
will remember their latest value throughout the session in a persistent engine,
but they will reset to the default value if used in the `main` engine. Unlike
Live Keys, Engine Keys have a default value so they can be forcibly reset from
the client code. Unlike Reset Keys, Engine Keys do not automatically reset if
the engine is persistent. Engine Keys reflect the default scope, they typically
behave as data in your script engine, according to its particular lifecycle.
5. SESSION KEYS. Whatever the engine in use, these keys persist throughout
the whole InDesign session (unless forcibly reset, or unsaved.) Data are kept
as JSON strings in the system environment, so Session Keys use a very fast
backup/restore mechanism compared to DOM-based routines. Use cases: whenever
you need to keep track of 'what happened before' in the current InDesign
session, e.g, script runcount, tasks already performed, etc.
6. OBJECT-HOSTED KEYS (OBJ keys.) These special keys are intended to be saved
in an InDesign DOM object, if supplied. By default they activate to a default
value and behave as Engine Keys--so you can still manage default settings from
here when no DOM object is available. But, if $$.Settings is activated with
a DOM object passed in, then it becomes the actual data provider of Obj Keys.
The same object (or another one!) can then be used as the target during the
backup stage. Use case: your script build & manages highly data-driven docs and
needs to keep various metadata attached to each of them. When the user re-opens
such a document, the settings should reflect the specific data attached to it.
[CHG211226] $$.Settings now supports arbitrary Object as source or destination
host for OBJ-scoped keys. If you supply a non-DOM object `obj`, data are
read from (resp. written to) `obj.<SUID>`, where <SUID> represents the uid
string that identifies the settings.
7. APPLICATION-HOSTED KEYS (APP keys.) These are the most persistent keys:
InDesign will remember them even in later sessions (after quit/restart.)
Technically, an APP Key can be thought as an OBJ Key with OBJ==InDesign. Its
typical use is of course addressing long-term user preferences. The script
should then offer a 'Go Back to Defaults' option, which can be done through
`$$.Settings.reset(null)`.
8. HYBRID KEYS. Mix of an OBJ Key and an APP Key. An Hybrid Key strongly
imitates the global vs. local paradigm used in InDesign preferences. If a
DOM object is present (typically, a `Document`) then the value is taken
from it, otherwise the application key is considered. For example, if your
script provides Swatch- or Style-related settings, you may want to manage
them in a global/local fashion, as InDesign does.
See the NOTICE for further detail.
*/
//==========================================================================
// NOTICE
//==========================================================================
/*
KEY SCOPES (SUMMARY)
============================================================================
BRANCH TYPE DECL ACTIVATE RESET BCKP DESCRIPTION
============================================================================
CONST CNST= 1 VAL NOOP NOOP NOOP Read-only.
----------------------------------------------------------------------------
LIVE LIVE= 2 VAL NOOP NOOP NOOP Volatile (no default.)
----------------------------------------------------------------------------
RESET RSET= 4 DEF DEF DEF NOOP Restorable but non-persistent.
----------------------------------------------------------------------------
[ENGINE] ENGI= 8 DEF VAL?:DEF DEF NOOP Engine-persistent (if present.)
This is the default scope.
----------------------------------------------------------------------------
SESSION SESS=16 DEF VAL DEF NOOP Session-persistent
(even in main engine.)
----------------------------------------------------------------------------
OBJ DOBJ=32 DEF OBJ?: null?DEF: null? DOM-Object-persistent.
VAL?:DEF OBJ?:DEF NOOP:
OBJ?:
NOOP
----------------------------------------------------------------------------
APP INDD=64 DEF APP?: null?DEF: null? App-persistent.
VAL?:DEF APP?:DEF NOOP:
APP
----------------------------------------------------------------------------
HYB HYBR=96 DEF OBJ?: null?DEF: null? Hybrid.
APP?: OBJ?: NOOP:
VAL?:DEF APP?:DEF OBJ?:
APP
----------------------------------------------------------------------------
*/
[PRIVATE]
({
// Scopes
// ---
CNST : 1, // Constant key, can't be changed.
LIVE : 2, // Live key, free to change, no default, volatile.
RSET : 4, // Similar to live, but can be restored to its default.
ENGI : 8, // Engine-persistent (if a non-main engine is in use.)
SESS : 16, // Session-persistent (whatever the engine in use.)
DOBJ : 32, // Object-persistent (if a DOM object is provided.)
INDD : 64, // Application-persistent.
HYBR : 96, // Hybrid scope (DOBJ or INDD)
BRCH : function(/*key*/s)
//----------------------------------
// (Branch-Map.) Map a user-friendly branch name to a scope.
// => 0 [KO] | 1|2|4|8|... [OK]
{
return (s=callee.Q[s]) ? this[s] : 0;
}.setup({ Q:
{
'CONST': 'CNST', // The branch 'CONST' is not required, use `_MYCONSTANT` instead.
'LIVE': 'LIVE',
'RESET': 'RSET',
'ENGINE': 'ENGI', // The branch 'ENGINE' is not required, that's the default scope.
'SESSION': 'SESS',
'OBJ': 'DOBJ',
'APP': 'INDD',
'HYB': 'HYBR',
},
// [180308] Added for extra parsing.
// ---
RE: /^CNST|LIVE|RSET|ENGI|SESS|DOBJ|INDD|HYBR$/,
}),
FDEL : function(/*obj&*/o,/*key*/k)
//----------------------------------
// (Deleter.) Kill and delete `o[k]`.
// this :: ~
// => o
{
$.global[callee.µ.__root__].kill(o[k]);
delete o[k];
return o;
},
FSET : function(/*obj&*/o,/*key*/k,/*any*/v)
//----------------------------------
// (Setter.) Kill `o[k]` first, then clone `v` in it.
// [CHG190523] Manage DontClone.
// this :: ~
// => o[k]
{
if( callee.µ.DontClone ) return ( o[k]=v );
this.FDEL(o,k);
return ( o[k] = $.global[callee.µ.__root__].clone(v) );
},
})
[PRIVATE]
({
// Settings-Unique-ID.
// ---
SUID : '',
// The keys and their present value.
// ---
KEYS : {},
// Resettable mask. (Any scope that &-matches RSBL supports the reset feature.)
// ---
RSBL : (µ['~'].RSET)|(µ['~'].ENGI)|(µ['~'].SESS)|(µ['~'].DOBJ)|(µ['~'].INDD)|(µ['~'].HYBR),
KSCO : function(/*key*/k)
//----------------------------------
// (Key-Scope.) this :: ~
// => 0 [KO] | 1|2|4|8|... [OK]
{
return callee.Q[k] || 0;
}.setup({ Q:{} }),
KDEF : function(/*key*/k)
//----------------------------------
// (Key-Default.) this :: ~
// => undefined [KO] | <any> [OK]
{
return callee.Q.hasOwnProperty(k) ? callee.Q[k] : (void 0);
}.setup({ Q:{} }),
WTCH : function(/*key*/k,/*?any*/ov,/*?any|NULL*/nv, I)
//----------------------------------
// (Key-Watcher.) Use null to default.
// this :: ~.KEYS
{
return (I=callee.µ['~']),
(
( (I.CNST)&(I.KSCO(k)) ) ? ov :
(
null===nv ?
I.KDEF(k) :
[this.unwatch(k),I.FSET(this,k,nv),this.watch(k,callee)][1]
)
);
},
})
[PRIVATE]
({
GOBJ : function(/*DOM|obj*/src,/*any*/defRet, t)
//----------------------------------
// (Get-From-Obj.) Get/extract keys from src's SUID label or key.
// [ADD211226] If src is a simple, non-DOM object, get data from
// src[this.SUID].
// ---
// => new set [OK] | defRet [KO]
{
t = 'function'==typeof(src.toSpecifier) ?
$.global[callee.µ.__root__].Dom.fromLabel(src, this.SUID, false) :
( 'string' == typeof(t=src[this.SUID]) ? $.global[callee.µ.__root__].JSON.eval(t) : false );
// [FIX220118] Security fix: returns `defRet` whenever eval() produces a non-Object.
// ---
return ( false===t || t!==Object(t) ) ? defRet : t;
},
SOBJ : function(/*DOM|obj&*/dst,/*any{}*/o)
//----------------------------------
// (Set-to-Obj.) Set/insert o keys into dst's SUID label or key.
// [ADD211226] If dst is a simple, non-DOM object, save the
// stringified keys in `dst[this.SUID]`.
// => str (json)
{
return 'function'==typeof(dst.toSpecifier) ?
$.global[callee.µ.__root__].Dom.toLabel(dst, this.SUID, o) :
( dst[this.SUID] = $.global[callee.µ.__root__].JSON.lave(o) );
},
GAPP : function(/*any*/defRet)
//----------------------------------
// (Get-From-App.) Get keys from app's SUID label.
// => new set [OK] | defRet [KO]
{
return this.GOBJ(app,defRet);
},
SAPP : function(/*any{}*/o)
//----------------------------------
// (Set-to-App.) Set o keys into app SUID label.
// => str (json)
{
return this.SOBJ(app,o);
},
GENV : function(/*str*/k, s)
//----------------------------------
// (Get-From-Env.) Get (extract) this key from the env-var `SUID_<k>`.
// => any value [OK] | null [KO]
{
return (s=$.getenv(this.SUID+'_'+k)) ? $.global[callee.µ.__root__].JSON.eval(s) : null;
},
SENV : function(/*str*/k,/*any*/x, s)
//----------------------------------
// (Set-To-Env.) Set this key in the env-var `SUID_<k>`.
// => str (json)
{
$.setenv(this.SUID+'_'+k, s=$.global[callee.µ.__root__].JSON.lave(x));
return s;
},
})
[PRIVATE]
({
ONUM : 0,
ANUM : 0,
MASK : 0, // Mask of all added scopes. [ADD180308]
DELK : function( o,k)
//----------------------------------
// (Delete-Keys.) Unwatch and remove all existing keys.
// => undefined
{
o = this.KEYS;
for( k in o )
{
if( !o.hasOwnProperty(k) ) continue;
o.unwatch(k);
this.FDEL(o,k);
this.FDEL(this.KDEF.Q,k);
delete this.KSCO.Q[k];
}
},
ADDK : function(/*key*/k,/*uint*/scope,/*any*/data)
//----------------------------------
// (Add-Key.) Throws an error if the key `k` already exists.
// => undefined
{
if( this.KEYS.hasOwnProperty(k) )
{
$.global[callee.µ.__root__].error(__("The key '%1' is already declared.",k), callee);
}
this.KSCO.Q[k] = scope;
if( scope == this.LIVE || scope == this.CNST )
{
this.FSET(this.KEYS, k, data);
}
else
{
this.FSET(this.KDEF.Q, k, data);
this.KEYS[k] = void 0; // undefined -> must be activated!
}
this.MASK |= scope; // [ADD180308]
},
DECL : function(/*str*/suid,/*{...,LIVE:{},RESET:{},OBJ:{},APP:{},HYB:{}}*/o, k,z,oo)
//----------------------------------
// (Declare-All-Settings.)
// [REM] Branch names are case-sensitive.
// => undefined
{
// Kills existing keys (if any.)
// ---
this.SUID && this.DELK();
// Set the 'Settings Unique ID.'
// ---
this.SUID = suid;
// Reset the scope mask to zero.
// ---
this.MASK = 0;
// Parse input and create keys from scratch.
// ---
for( k in o )
{
if( !o.hasOwnProperty(k) ) continue;
if( !(z=this.BRCH(k)) )
{
// Implicit scope is determined as follows:
// I.CNST if key has the form '_xxx' ('_' is then removed),
// I.ENGI otherwise.
// ---
z = this['_' == k[0] ? 'CNST' : 'ENGI'];
this.ADDK(k.substr(1&z),z,o[k]);
continue;
}
// Here o[k] is a z-scoped branch, and must be an object.
// ---
if( Object(oo=o[k])!==oo )
{
$.global[callee.µ.__root__].error(
__("The declaration of the branch '%1' is not valid. (Not an object.)", k),
callee );
}
for( k in oo )
{
oo.hasOwnProperty(k) && this.ADDK(k, z, oo[k]);
}
}
},
ACRS : function(/*?DOM|obj|null*/src,/*bool*/DO_RESET, APP_SRC,o,k,z,v,indd,dobj,zo,za)
//----------------------------------
// (Activate-Or-Reset.) Activate or Reset the declared keys.
// [CHG220117] Optim: if src===app, do not read the label twice!
// [REM220117] If src is a non-DOM obj, src[SUID] should provide
// the JSON string associated to OBJ/HYB keys, as expected by ~.GOBJ.
// => undefined
{
APP_SRC = src ? app===src : false;
o = this.KEYS;
indd = dobj = false;
za = zo = 0;
for( k in o )
{
if( !o.hasOwnProperty(k) ) continue;
z = this.KSCO(k);
v = null;
// If the key is resettable, determine its activation value.
// [REM] LIVE and CNST keys are excluded from this block.
// ---
while( z&this.RSBL )
{
// [ADD180221] Manage session key thru $.getenv(<SUID>_<KEY>)
// SESS
// ---
if( (z&this.SESS) && null!==(v=this.GENV(k)) ) break;
// DOBJ | INDD | HYBR
// If DO_RESET && src===null, take the default and break.
// ---
if( (z&this.HYBR) && DO_RESET && null===src ){ v=this.KDEF(k); break; }
// DOBJ | HYBR
// ---
if( (z&this.DOBJ) )
{
// If not already done, unserialize data from the src object.
// ---
if( false===dobj && src )
{
dobj = this.GOBJ(src, void 0);
APP_SRC && (false===indd) && (indd=dobj);
}
// Look for the key.
// [REM] If not found in a hybrid key, do not break!
// ---
if( dobj && dobj.hasOwnProperty(k) ){ ++zo; v=dobj[k]; break; }
}
// INDD | HYBR
// ---
if( (z&this.INDD) )
{
// If not already done, unserialize data from the app.
// ---
(false===indd) && (indd=this.GAPP(void 0));
APP_SRC && (false===dobj) && (dobj=indd);
// Look for the key.
// ---
if( indd && indd.hasOwnProperty(k) ){ ++za; v=indd[k]; break; }
}
// RESET
// Take the default value in any of the following cases:
// (a) forced reset ; (b) RSET key ; (c) key undefined.
// ---
if( DO_RESET || (z&this.RSET) || ('undefined'==typeof this.KEYS[k]) )
{
v = this.KDEF(k);
}
break;
}
// Set or change the existing key into its activation value.
// ---
o.unwatch(k);
null===v || (this.FSET(o,k,v))
o.watch(k,this.WTCH);
}
// Indicate whether OBJ and/or APP labels have been found during activation,
// and how many properties have been restored from each.
// ---
if( APP_SRC )
{
// dobj===indd
this.ONUM = this.ANUM = zo+za;
indd && $.global[callee.µ.__root__].kill(indd);
}
else
{
this.ONUM = zo;
dobj && $.global[callee.µ.__root__].kill(dobj);
this.ANUM = za;
indd && $.global[callee.µ.__root__].kill(indd);
}
},
BCKP : function(/*?DOM|obj&|null*/dst, SKIP_HYB,APP_DST,indd,dobj,zo,za,o,k,z)
//----------------------------------
// (Backup.)
// [CHG220117] Optim: if dst===app, do not read/set the label twice!
// => undefined
{
SKIP_HYB = +(null===dst);
APP_DST = dst ? app===dst : false;
// Retrieve existing app/dst keys, or {}, in a new obj.
// [CHG220117] If dst===app, indd and dobj are the same ref.
// [REM220118] It's important to recover existing keys before
// applying those determined by the present settings, because
// skipped keys must be preserved.
// ---
indd = this.GAPP({});
dobj = dst && (APP_DST ? indd : this.GOBJ(dst,{}));
zo = za = 0;
o = this.KEYS;
for( k in o )
{
if( !o.hasOwnProperty(k) ) continue;
z = this.KSCO(k);
// SESS
// ---
if( z&this.SESS )
{
this.SENV(k,o[k]);
continue;
}
// OBJ | HYB
// ---
if( z&this.DOBJ )
{
if( SKIP_HYB ) continue;
if( dst ){ ++zo; dobj[k]=o[k]; continue; }
}
// APP | HYB
// ---
if( z&this.INDD )
{
++za;
indd[k]=o[k];
}
}
if( APP_DST )
{
// dobj===indd
// ---
(zo||za) && this.SOBJ(dst,indd);
$.global[callee.µ.__root__].kill(indd);
}
else
{
zo && this.SOBJ(dst,dobj);
$.global[callee.µ.__root__].kill(dobj);
za && this.SAPP(indd);
$.global[callee.µ.__root__].kill(indd);
}
},
})
//==========================================================================
// API
//==========================================================================
[PUBLIC]
({
// [ADD190523] Usually it's safer to let `$$.Settings` perform a JSON clone
// while setting a key to an object reference (`ss.myKey=myObj`), because
// that reference may be lost and ss.myKey is supposed to work anyway.
// But in specific cases you may want to explicitly prevent the module from
// cloning objects during script execution, either for performance purposes
// or for bypassing cloning limitations on complex objects (augmented arrays,
// etc) Then you can set `$$.Settings.DontClone` to 1.
// ---
DontClone: 0,
declare : function declare_S_Õ_(/*str*/suid,/*{...,LIVE:{},RESET:{},SESSION:{},OBJ:{},APP:{},HYB:{}}*/oInput, $$,I)
//----------------------------------
// Create a set of keys from scratch and register their default values. This function must be
// called *once* per engine, and before `activate()`. It does not restore nor enable any key.
// Recommended usage: $$.Settings.canDeclare() && $$.Settings.declare(...);
// ---
// suid :: Settings unique ID (string.)
// oInput :: Set of keys with their default values, e.g { myString:"foobar", myNumber:123, ... }
// By default, keys are ENGINE-scoped, meaning they keep their latest value as long as
// the engine is active. Keys declared with underscore prefix (`_`) are made constant,
// so they cannot be changed even using `ss.MYKEY = newValue`. (Note: the `_` prefix
// is automatically removed from the key name.) To declare additional scopes, use any
// of the following branch: LIVE, RESET, SESSION, OBJ, APP, HYB. For example,
// { mySimpleKey:"foobar", _MYCONST:3.14, SESSION: { mySessionKey:321 } }
// ---
// [REM] You can explicitly use the branches CONST and ENGINE.
// [REM] An error is thrown if a key name is re-used or anything else
// goes wrong during the declaration.
// ---
// Full oInput example:
// {
// _PROGNAME: "MyProgram", // Contant key named PROGNAME; its value cannot be changed.
// indexMode: "auto", // Engine-persistent. The latest value remains as long as
// // this engine is alive--assuming you don't call reset().
// // REM: Will work as RESET if no persistent engine is active.
// LIVE: { tempValue: "test" }, // Non-persistent and no reset. Can be changed at wish; useful
// // to pass dynamic data among the settings while perfoming a task.
// RESET: { jobDone: false }, // Non-persistent with auto-reset. This key automatically recovers
// // its init value *each time you activate* the settings (including
// // in the context of a persistent engine.)
// SESSION: { runCount: 0 }, // Session-persistent (whatever the engine in use.) Useful to keep
// // data across multiple runs in basic scripts avoiding #targetengine.
// // REM: Will work as ENGINE if a persistent engine is active.
// OBJ: { modified: 180221 }, // Stored in a DOM-Object (Document, PageItem, etc.). This key can
// // be stored in (and restored from) a DOM object, if supplied.
// // (See activate, backup and reset for more detail.)
// APP: { version: 2.321 }, // Application-persistent. This key is stored/restored at the
// // application level, which makes it persistent as long as the
// // user do not trash InDesign preferences.
// // (See activate, backup and reset for more detail.)
// HYB: { prefSize: [15,10] }, // Object or Application-persistent. This (hybrid) key is stored
// // and restored either at object or application level, depending
// // on which argument is supplied to the API. If a DOM object is
// // provided, it works as OBJ; otherwise it works as APP. Useful
// // to manage preferences that can be superseded by some object.
// // (See activate, backup and reset for more detail.)
// }
// ---
// => undefined
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( (I=callee.µ['~']).SUID )
{
$$.error( __("Settings already declared. Use %1.canDeclare() to check.",callee.µ), callee )
}
if( ('string' != typeof suid) || suid.length < 1 )
{
$$.error( __("Empty UID is not allowed."), callee )
}
if( oInput !== Object(oInput) )
{
$$.error( __("Invalid object in settings declaration."), callee )
}
(+$$.trace) && $$.trace(__("%1 > Declaring settings for uid: %2.",callee.µ,suid.toSource()));
I.DECL(String(suid), oInput);
},
canDeclare : function canDeclare_B( I)
//----------------------------------
// Whether declare() can be invoked.
// => 0 [NO] | 1 [YES]
{
return (I=callee.µ['~']).SUID ? 0 : 1;
},
hasScope : function hasScope_IS_I(/*uint|str*/scope, I,t)
//----------------------------------
// [ADD180309] Whether the declared settings contain (at least)
// one key associated to the supplied scope(s).
// `scope` :: 'CNST'|'CONST'|1 ; 'LIVE'|2 ; 'RSET'|'RESET'|4 ;
// 'ENGI'|'ENGINE'|8 ; 'SESS'|'SESSION'|16 ;
// 'DOBJ'|'OBJ'|32 ; 'INDD'|'APP'|64 ; 'HYBR'|'HYB'|96
// As an uint, `scope` can be a OR of mixed values.
// [REM] The scope 96 (HYB) actually represents the three options
// OBJ or APP or HYB. To test whether a key is strictly hybrid,
// check whether retval===96.
// => >0 [OK] | 0 [KO] | ERR
{
if( !(I=callee.µ['~']).SUID )
{
$.global[callee.µ.__root__].error( __("Settings must be declared before calling hasScope."), callee );
}
if( 'number' != typeof (t=scope) )
{
scope = String(scope).toUpperCase();
// ---
// [TODO] Might be improved to support "s1|s2|s3..."
// ---
t = I.BRCH(scope) || (I.hasOwnProperty(scope) && I.BRCH.RE.test(scope) && I[scope]);
}
if( !t )
{
$.global[callee.µ.__root__].error( __("The scope %1 is not recognized.",scope), callee );
}
return t&I.MASK;
},
footprint : function footprint_is_b_S(/*uint|str=120*/scope,/*bool=0*/STRICT, $$,I,t,o,r,k)
//----------------------------------
// [ADD220504] Return a 'footprint' (hash string) representing
// unambiguously the *current* settings in the supplied scope(s).
// `scope` :: 'CNST'|'CONST'|1 ; 'LIVE'|2 ; 'RSET'|'RESET'|4 ;
// 'ENGI'|'ENGINE'|8 ; 'SESS'|'SESSION'|16 ;
// 'DOBJ'|'OBJ'|32 ; 'INDD'|'APP'|64 ; 'HYBR'|'HYB'|96
// As an uint, `scope` can be a OR of mixed values.
// The default scope is assumed 120, which represents all keys that
// might be restored (ENGI|SESS|OBJ|APP|HYB).
// `STRICT` :: bool [reserved] Whether the keys must *exactly* have
// the scope specified by `scope` (disregarding mixed cases.)
// Only makes sense for extracting strict OBJ (vs. APP,
// vs. HYB) keys.
// [REM] The scope 96 (HYB) actually represents the three options
// OBJ or APP or HYB. To extract OBJ(vs. APP vs. HYB keys) only, use
// STRICT=1. And to get e.g a footprint of OBJ and APP keys *excluding
// HYB*, merge the strings µ.footprint('OBJ',1)+µ.footprint('APP',1).
// ---
// => str [OK] | '' [KO] | ERR
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( !(I=callee.µ['~']).SUID )
{
$$.error( __("Settings must be declared before calling footprint."), callee );
}
switch( typeof(t=scope) )
{
case 'undefined':
t=120;
break;
case 'number':
t|=0;
break;
default:
scope = String(scope).toUpperCase();
// ---
// [TODO] Might be improved to support "s1|s2|s3..."
// ---
t = I.BRCH(scope) || (I.hasOwnProperty(scope) && I.BRCH.RE.test(scope) && I[scope]);
}
if( !t )
{
$$.error( __("The scope %1 is not recognized.",scope), callee );
}
o = I.KEYS;
r = [];
if( STRICT )
for( k in o ) o.hasOwnProperty(k) && (t===I.KSCO(k)) && (r.push(k+':'+$$.JSON(o[k])));
else
for( k in o ) o.hasOwnProperty(k) && (t&I.KSCO(k)) && (r.push(k+':'+$$.JSON(o[k])));
return r.length ? ('{'+r.sort().join(',')+'}') : '';
},
hasKey : function hasKey_K_I(/*key*/k, I,t)
//----------------------------------
// [ADD180318] Whether the declared settings contain a key `k`.
// If so, returns the associated scope (>0.)
// Retval :: 1 (CNST) ; 2 (LIVE) ; 4 (RSET) ; 8 (ENGI)
// 16 (SESS) ; 32 (OBJ) ; 64 (INDD) ; 96 (HYBR)
// => >0 [OK] | 0 [KO]
{
if( !(I=callee.µ['~']).SUID )
{
$.global[callee.µ.__root__].error( __("Settings must be declared before calling hasScope."), callee );
}
return I.KEYS.hasOwnProperty(k) ? I.KSCO(k) : 0;
},
activate : function activate_dõs_Õ(/*?DOM|obj|str*/src, $$,I,s,t)
//----------------------------------
// Restore each key to its most relevant value based on its scope.
// If `src` is a non-DOM object, `src[SUID]` is expected to provide
// the JSON string associated to OBJ/HYB keys. If `src` is supplied as
// a string, the corresponding { SUID:src } structure is implied.
// (a) If src is a DOM or non-DOM object, use it as a source for
// any OBJ or HYB key. Rem: If src===app, OBJ keys are then
// extracted for the app label (as well as HYB and APP keys.)
// (b) If src is not provided or 'falsy,' OBJ keys will activate
// in default scope and HYB keys will activate in APP scope.
// [ADD211226] Supports non-DOM `src`.
// [REM220117] Supports string argument -> {SUID:src} then implied
// ---
// => keys-accessor
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( !(s=(I=callee.µ['~']).SUID) )
{
$$.error( __("Settings must be declared before activation."), callee );
}
'string'==typeof src && ( (t={})[s]=src, src=t ); // [FIX230403] Typo: str -> src
(+$$.trace) && $$.trace(__("%1 > Activating keys (uid: %2.) Host: %3."
, callee.µ
, s.toSource()
, src ? String(src) : "app (excl. OBJ-scoped keys.)"
));
I.ACRS(src, false);
return I.KEYS;
},
reset : function reset_dõs$null$_Õ(/*?DOM|obj|str|null*/src, $$,I,s,t)
//----------------------------------
// Reset each key to its default or stored value, based on its scope.
// If `src` is a non-DOM object, `src[SUID]` is expected to provide
// the JSON string associated to OBJ/HYB keys. If `src` is supplied as
// a string, the corresponding { SUID:src } structure is implied.
// (a) If src is a DOM, non-DOM object or undefined, every OBJ,
// APP and HYB key is activated the usual way (cf µ.activate)
// while all other keys are reset to their default value.
// (b) If src===NULL, all keys are reset to their default, including
// OBJ, APP and HYB keys.
// [ADD211226] Supports non-DOM `src`.
// [REM220117] Supports string argument -> {SUID:src} then implied
// ---
// => keys-accessor
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( !(s=(I=callee.µ['~']).SUID) )
{
$$.error( __("Settings must be declared before calling reset."), callee )
}
'string'==typeof src && ( (t={})[s]=src, src=t ); // [FIX230403] Typo: str -> src
(+$$.trace) && $$.trace(__("%1 > Resetting %2 keys to their defaults (uid: %3.) Host: %4."
, callee.µ
, null===src ? "all" : "non-hosted"
, s.toSource()
, src ? String(src) : (null===src ? "none" : "app")
));
I.ACRS(src, true);
return I.KEYS;
},
backup : function backup_dõ$null$_(/*?DOM|obj&|null*/dst, $$,I,s)
//----------------------------------
// Backup the current values of label-stored keys. If `dst` is
// a non-DOM object, `dst[SUID]` will receive the JSON string
// associated to final OBJ/HYB keys so it can be further processed
// by the client code. (This trick can be used to bypass or delay
// actual DOM backup.)
// (a) If dst is a DOM or non-DOM object, use it as a target for
// any OBJ or HYB key. In particular, if dst===app, it will
// receive all OBJ/HYB/APP keys.
// (b) If dst===NULL, only backup *strict* APP keys (ignoring HYB.)
// (c) Otherwise, backup HYB and APP keys in app.
// ---
// [ADD211226] Supports non-DOM `dst`.
// [CHG180605] Prevents undo.
// [CHG190206] JsxBlind safe.
// => undefined
{
$$ = $.global[callee.µ.__root__]; // agnostic reference
if( !(s=(I=callee.µ['~']).SUID) )
{
$$.error( __("Settings must be declared before calling backup."), callee )
}
(+$$.trace) && $$.trace(__("%1 > Saving keys %2(uid: %3.) Host: %4."
, callee.µ
, null===dst ? "excluding hybrid " : ""
, s.toSource()
, dst ? String(dst) : "app"
));
// [CHG180605] Calling doScript/autoUndo prevents the backup stage from being undone.
// [CHG190206] JsxBlind safe.
// [CHG230403] JsxBlind safer.
// ---
callee.ARG1 = I;
callee.ARG2 = dst;
app.doScript("callee.ARG1.BCKP(callee.ARG2)", +ScriptLanguage.javascript, void 0, +UndoModes.autoUndo);
delete callee.ARG1;
delete callee.ARG2;
},
addLive : function addLive_Õ_Õ(/*any{}*/oLiveKeys, I,k)
//----------------------------------
// Add LIVE keys to the existing keys.
// [REM] CANNOT BE USED WHILE NO ACTIVATION IS DONE
// => keys-accessor
{