-
Notifications
You must be signed in to change notification settings - Fork 94
/
TODO
724 lines (542 loc) · 25.7 KB
/
TODO
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
#========================================================================
#
# TODO
#
# DESCRIPTION
# TODO list for the Template Toolkit version 2.13, containing
# known bugs, limitations, planned enhancements, long term visions
# and a few whacky ideas.
#
# AUTHOR
# Andy Wardley <abw@wardley.org>
#
#------------------------------------------------------------------------
# $Id$
#========================================================================
#------------------------------------------------------------------------
# Miscellaneous
#------------------------------------------------------------------------
* The 'eval' filter leaks memory, as reported by Colin Johnson. The
filter subroutine created contains a reference to the context and then
gets cached in the FILTER_CACHE item of the context. Hey presto -
circular references. The reset() method should probably clear the
FILTER_CACHE. Also need to check the plugins cache for similar
problems.
* The handling of the DELIMITER parameter could be improved. At the
moments it's hardcoded and hacked to Do The Right Thing for Win32
but I'd prefer it to Do The Good Thing.
* If you use 'ttree' with a COMPILE_EXT or COMPILE_DIR option then
templates in the 'lib' directories will be compiled, but those in
the src directories will not. This is because ttree does a chdir()
to the src directory and processes files as './myfile'. TT doesn't
compile RELATIVE files by default.
* No recursion checking is performed for BLOCKs, only
Template::Document instances. This is probably the way it will stay
(unless anyone shouts loudly enough) but it should be documented
anyway. STOP PRESS: I had an idea that bare BLOCK subs should be
blessed into Template::Document class to allow $template->process()
to be called regardless. Template::Document methods would need to
test $self for CODE/HASH and Do The Right Thing. This would then
allow recursion testing for BLOCKs as well as Template::Document
objects.
* It would be nice if there was an option so that the files generated
under the COMPILE_DIR are relative to the INCLUDE_PATH and not absolute.
This could cause potential conflicts (e.g. if INCLUDE_PATH changes
between sessions and the same files in different INCLUDE_PATH dirs
maps to the samed compiled version) but is convenient for those times
when you know that's not going to be a problem.
* Richard Tietjen's patch for stash replace. Allows back references
(e.g. $1) but it would be nice to find a rock-solid way to implement
it without relying on unusual ^A delimiter character.
* Further to the above, Craig Barratt has this solution which will be
going into the next verion (2.05b) unless anyone has any further
suggestions to make before then.
It would be great if replace handled backreferences. I don't like
the ^A solution since the string could contain ^A, plus it is a
security hole. The attempt I posted only works for up to 9
backreferences and doesn't handle an escaped '\$' and uses nested
evals:
$str =~ s{$search}{
my $r = $replace;
my @d = (0, $1, $2, $3, $4, $5, $6, $7, $8, $9);
$r =~ s/\$(\d+)/$d[$1]/eg;
$r;
}eg;
I wish there was a perl predefined variable array containing all
the backreferences (is there one?). You can avoid the hard-coded
limit of 9 with extra evals, and a bit of work on the re could
handle the escaped '\$' case, so maybe that would be good enough.
* Craig also notes, in fixing the problem with NEXT not working
inside SWITCH (see Changes v2.04):
By the way, I came across another arcane bug:
NEXT FOREACH k = [1];
is legal syntax but is an infinite loop, since $_[0]->{ INFOR } in
Parser.yp is not set when the NEXT is parsed, so it generates a
plain "next;" rather than calling $factor->next(). I don't see an
easy, clean fix.
#------------------------------------------------------------------------
# Documentation
#------------------------------------------------------------------------
* Extend the FAQ.
* Document the Splash! library properly, once the interface is a little
more settled.
* Examples for libraries (HTML, Splash & PostScript) should be integrated
into the documentation.
#------------------------------------------------------------------------
# Directives
#------------------------------------------------------------------------
* A 'FOR', like 'FOREACH' but without using an iterator. You wouldn't get
the 'loop' reference to test 'first', 'last', etc., against, but it would
be faster for those cases when you didn't need it. This will likely
be implemented as a facility feature (see later).
* PRINT should be defined as a new directive, doing what the print()
method of Template::View currently does (the Right Thing).
[% PRINT node %] === [% tt.view.print(node) %]
NOTE TO SELF: this is a Very Good Idea [tm]. PRINT becomes the way to
display a data structure (e.g. hash, list, XML element, MyThingy, database
record, etc.) in an "intelligent" fashion. Implemented underneath via
the current default VIEW.
* ARGS. There may be a requirement for reusable template components
to define what variables they plan to use. This would allow some
optimisation and also possibly help to avoid global variable clashes.
Would also be a useful "comment" directive for human readers and maybe
also help in debugging (WARNING: expected 'title' argument).
[% ARGS title # no default
bgcol='#ffffff' # default value
%]
#------------------------------------------------------------------------
# Stash
#------------------------------------------------------------------------
* The XS Stash does not work with tied hashes (e.g. DBI.tie). Also note
that enabling the XS Stash cause all the Template::* modules to be
installed in an architecture-dependant directory. For info, see
http://www.tt2.org/pipermail/templates/2001-September/001568.html
* Jonas Liljegren reports a segfault when using the XS Stash under
Apache/mod_perl with certain undefined variables. At the time of
writing detail is scarce. Check the mailing list for further
details.
* Stas Bekman raised the issue of the Stash not being able to correctly
differentiate between scalar/list context and in particular, the
cgi.param examples not working as expected. This is fixed in v3 and
in Craig's Stash/Context.pm which does the right lookahead to allow
'scalar' and 'list' postfix operators. e.g. cgi.param.list
* Have stash, etc., add current template name/line when reporting errors.
(may be tricky under the current implementation)
#------------------------------------------------------------------------
# Parser
#------------------------------------------------------------------------
* Lists don't accept arbitrary expressions as elements. In other words
you can't do this: [% foo(bar + 1) %]. This has been fixed in the v3
parser. See http://www.tt2.org/v3/
* The parser isn't as intelligent as it could be about blocks of template
code commented out en masse. The pre-scanner find the first terminating
END_TAG after an opening tag, regardless of it being on a
commented line or not.
e.g.
[%#
#
# [% INCLUDE blah %] <- directive ends here
# foo <- this gets printed
%]
* Allow { and } as block delimiters, replacing for the ugly ';' and
big, chunky [% END %] approach.
e.g. something like:
[% FOREACH a = [ 1 2 3 ] %]
[% IF b == a %]
[% INCLUDE foo %]
[% ELSE %]
[% INCLUDE bar %]
[% END %]
[% END %]
could be written as:
[% FOREACH a = [ 1 2 3 ] {
IF b == a {
INCLUDE foo
}
ELSE {
INCLUDE bar
}
}
%]
* Ability to set different parser options for BLOCK definitions, etc.
[% BLOCK header
eval_perl = 0
pre_chomp = 1
%]
...
[% END %]
Anonymous BLOCK can then be used to set a parser scope
[% BLOCK trim=1 %]
...
[% END %]
[% BLOCK trim=0 %]
...
[% END %]
And/or set different tag styles, etc.
[% BLOCK tags='star' %]
[* INCLUDE this_is_a_directive *]
[% INCLUDE this_is_not %]
[* END *]
[% INCLUDE back_to_normal %]
This is likely to be a TT3 feature and I've already got the basic
parser for this up and running. It might get back-pactched into
version 2, otherwise you might have to wait for the first alpha
release of verion 3.
* Craig Barratt reports the following:
I looked at Parse.yp to see how hard it would be to push FILTER
evaluation down into the expr rule, so that you could put filters
inside expressions (eg: using repeat() just like "x" in
perl). More about that later.
In browsing through Parser.yp I noticed several issues:
- The operator precedence is very different to perl, C etc.
For example, these expressions evaluate differently in
TT2 versus perl, C etc:
+ "1 || 0 && 0" evaluates to 0 in TT2 and 1 in perl or C.
TT2 parses it as (1||0) && 0; in perl and C && is higher
precedence than ||.
+ "1 + !0 + 1" evaluates to 1 in TT2 and 3 in perl or C.
TT2 parses it as 1 + !(0 + 1); in perl and C ! is higher
precedence than +.
+ Many other expressions parse incorrectly, but the effect
is benign since most rules return flat text that perl
correctly re-parses. Eg, 2 * 3 + 4 is incorrectly parsed
as (2 * (3 + 4)), but happily just the string "2 * 3 + 4"
is compiled by perl, which correctly evaluates it as
(2 * 3) + 4.
- There is no unary minus and the NUMBER token is signed. So you can
write "x = -2;" but not "x = -y;". Moreover, "x = 1 -1;" is a syntax
error (since "1 -1" returns just two tokens NUMBER, NUMBER). (As a
workaround you can rewrite these as "x = 0-y;" and "x = 1 - 1".)
- You cannot have expressions in lists ([..]) and function arguments.
I have modified the Parser.pm (to make NUMBER unsigned) and modified
Grammar.pm.skel and Parser.yp to fix most of these issues (improved
operator precedence, unary minus and plus), and also to allow
expressions in a few more places (eg: range). But the last item
has me stuck.
The parse rules for lists and function arguments make COMMA optional,
so you can equivalently write [1 2 3 4] or [1,,,,,2 3 4] or [1,2,3,4].
This makes it very difficult to make each term an expression, because
the resulting grammar has many ambiguities. For example, is [1 -1]
two elements [1, -1] or a single element [0]? One partial solution is
to move the bracketed expression rule '(' expr ')' to the term rule,
allowing expressions to be included via parens. But there are also
ambiguities, eg: does [foo (1+1)] have 2 elements or is it a function
call to foo?
Without allowing expressions in lists or function arguments, the unary
minus change I've made means that the NUMBER token is unsigned, so with
my changes you cannot write [-1, 2, 3]. Not a good thing.
One solution is to change the grammar so that COMMAs are required in
lists and arguments, but that would break several test cases and
probably break lots of old templates. But this might be the only
way to produce a grammar that is a lot more similar to perl.
Another solution is to ignore these issues altogether and use temporary
variables to precompute expressions that you need in lists or function
arguments, or use explicit lvalue assignments, eg:
foo(x + 2); becomes temp = x + 2;
foo(temp);
or
List = [x+1,x+2,x+4]; becomes List = [];
List.0 = x+1;
List.1 = x+2;
List.2 = x+4;
Both of these look ugly to me.
Back to the FILTER issues. Ultimately I'd like to be able to embed filters
as low precedence operators in expressions, and write:
List = [
"foo" | repeat(10),
"bar" | repeat(10)
];
but I doubt there is a non-ambiguous upward compatible grammar that
supports this.
Comments?
#------------------------------------------------------------------------
# Plugins
#------------------------------------------------------------------------
* We need a way to easily enable/disable certain plugins. This should
be addressed by facility provision. Probably something for v3.
* The Text::Autoformat module has some problems with versions of Perl
prior to 5.6.0 when using a locale which has a decimal separator
other than '.' (e.g. Swedish, which uses ','). Damian has been made
aware of the problem (and I note he now has a new version out which
I need to check). For now, the Makefile.PL issues a warning but
continues regardless.
* The Template::Plugin DBI iterator first/last() methods don't behave
the same as list first/last(). Randal also reports that get_all()
doesn't work as it should - may be a conflict in code/docs?
* PLUGINS could accept a reference to an object which is used as a
singleton factory for a plugin. (NOTE: 2.01 includes PLUGIN_FACTORY
to implement this, but currently undocumented because it's likely to
change).
* Add Leo & Leon's Page plugin, or try to find some way of implementing
it in terms of the Table plugin. I think the jury is still out on the
matter of whether it counts as duplicated functionality.
* A more general solution for XML (e.g. DOM, XPath, etc) would be for
TT to support a PerlSAX handler which generates the appropriate
callbacks to the view. This should make it possible to easily
display XML content from XML::DOM, XML::XPath, or any other SAX
compliant source.
Something like this:
# define a view
[% VIEW my_view
prefix="my/xml/dom/path/" ;
END
%]
# get some XML
[% USE dom = XML.DOM %]
[% doc = dom.parser(my.files.xmldata) %]
# ask the view to print the data
[% my_view.print(doc) %]
The view print() method will call the relevant 2SAX method on the
XML node, passing a SAX2TTView handler to make the relevant calls
back to the view to display parts of the XML data model as SAX events
are received.
#------------------------------------------------------------------------
# Views
#------------------------------------------------------------------------
The current implementation is there to get me (and anybody else who's
interested) using it and trying to identify the problems, requirements
and general issues involved. I've got a better idea now about what a
VIEW should be in notional terms, but I'm still not quite sure about
the syntax and API.
General thoughts:
* A view defines a set of templates. Things like prefix, suffix,
default, etc., can be specified to customise template selection.
In this sense, it is like a custom provider of those templates.
It implements the template() method to fetch a template according
to those rules.
* It is also a custom processor of those templates. It implements the
process() method. In this sense, it is like a custom context.
* It also implements dispatch logic to apply the right template to the
right kind of data. It does this via the print() method. It may
have all kinds of custom dispatch logic.
* A view takes responsiblity for things template related as opposed
to anything data related (stash) or application logic related
(plugins, runtime code, etc). It is the user interface facility
within the engine.
#------------------------------------------------------------------------
# Splash!
#------------------------------------------------------------------------
The current implementation is a fairly ugly hack to get something up
and running that's good enough to use. It's mainly template driven
and doesn't scale very well, particularly with global variables
clashing all over the place. My plan is that this will become a
view-based system and will no doubt be a test-bed for the
implementation of the view facility.
To include:
* Variable management for storing metadata relating to an
interface/view, protected from the rest of template space.
* Also, style management for defining different styles (e.g. plain,
fancy, icecold, whitehot) for different interface elements (e.g.
bars, borders, menu text) in different modes (e.g. selected, unselected),
or under different, possibly custom conditions (e.g. user preferences,
guest or logged in, etc.).
* Clearly define API for different elements, allowing people to write
apps to the API which run across different conformant widget sets.
* support themes which define a set of styles
* May be wise to move Splash out to a separate distribution.
Randal Schwartz highlighted some problems with non-compliant HTML
being generated. These include:
* no DOCTYPE declaration (added to html/header)
* ALT attribute missing from many <img> tags
* <font ...><table>...</table></font> is illegal (not sure where this
gets done)
* <tr> shouldn't have HEIGHT attribute
* <H3> block element inside inline element
* <A> not allowed here (not sure)
#------------------------------------------------------------------------
# Test Suite
#------------------------------------------------------------------------
* t/file.t and t/directry.t are currently disable on Win32 until I get
a chance to fix a couple of minor bugs relating to '/' vs '\' file
separators.
#------------------------------------------------------------------------
# Facilities
#------------------------------------------------------------------------
Core facilities currently implemented by Template::Context should be
moved out into separate facilities. These currently fall into the
categories of things like stash, view, plugins, filters, parser and
maybe some others. (NOTE: this might tie in very closely with Camelot
and the resource/presenter/actor breakdown, aka model/view/controller).
* 'view' would handle template fetching and processing. It is the view
that talks to a provider, possibly adding special prefixes, suffixes,
doing default templates, special dispatch logic, etc.
* 'stash' is responsible for storing variables, as it currently is.
* 'plugins' is responsible for fetching plugins.
* 'filters' is responsible for fetching filters.
* maybe both the above would get bundled into something like 'logic'?
* 'parser' would make parts of the parser accessible
* 'output' could be used to generate output
There would be some facility to install, customise and remove facilities
to modify TT behaviour as required. This would allow us to disable
certain plugins, or remove the plugin facility altogether, for example.
Or we could install a new stash facility which generated a different kind
of code (e.g. less magic, more speed). Or install a new custom facility
to do some application or domain specific task.
Facilities should be accessible via the context:
my $stash = $context->stash(); # currently works
my $view = $context->view(); # not yet
General form:
my $xyz = $context->facility('xyz');
Multiple form:
my ($a, $b, $c) = $context->facility(qw[ a b c ]);
This can then be written into generated code pretty much as the stash
currently is. The facilities would define the code generators that
currently clutter up Template::Directives. They would ensure that the
facility is scheduled to be requested from the context at the start of
the sub:
sub {
my $context = shift;
my ($stash, $view) = $context->faciity(qw( stash view ));
and then transform the various directives into appropriate callbacks
into the facility:
$output .= $stash->get('x');
$output .= $view->process('header');
The context becomes a switching centre for the Template Toolkit, with
most, if not all of its existing functionality moved out to
facilities. The context should acquire all facilities at the start of
a process lifecycle, run the template, and then release them all
again. This should all be done according to the process contract
which specifies which facilities should be installed, which can be
modified, what can and can't be be loaded, and so on. The
contract would also define things like PRE_PROCESS templates, error
handling, etc.
Thus the role of Template::Service is to undertake a contract with the
client and attempt to fulfill it. A Template::Contract defines the
terms of the contract and the Template::Context becomes an embodiment
(runtime instance) of a contract.
The current context would be available as the 'tt2' template variable,
with facilities available as object methods. Thus, the following become
possible:
[% tt2.stash.get('foo') %] # [% foo %]
[% tt2.view.process('header') %] # [% PROCESS header %]
[% tt2.parser.start_tag %] # can't do this currently
Directives would be re-written into code like that above. You can
use the 'tt2.facility.whatever' form when directive syntax would
otherwise get in the way:
[% mycode( header = tt2.view.process('my/fancy/header'),
footer = tt2.view.process('your/dull/footer') ) %]
Or to explicitly disambiguate:
[% tt2.stash.get('foo.size') %] # object method
[% tt2.stash.list.size(foo) %] # virtual method
[% foo.size %] # lucky dip :-)
Facility management would itself be a facility. Thus, to disable runtime
facility loading, you would simply unload (or not load) the facility
management facility.
[% tt2.facility.install(module='my_facility', name='foo') %]
[% tt2.foo.bar(123) %]
Note that there are some issues relating to cross-cutting facilities,
otherwise known as "aspects" (see Aspect Oriented Programming). For
example, variable localisation cross cuts views and data management
(i.e. tt2.view.include('header', title='my title') must first localise
the stash and delocalise it again afterwards). It may be appropriate
to install aspects as separate entities (e.g. 'localisation') which
can be invoked to apply cross cutting concerns in a generic way.
Perhaps aspects are implemented as their own facility?
[% tt2.aspect.localise %] ... [% tt2.aspect.delocalise %]
or
[% aspect = tt2.aspect.localise %] # cloned & specialised context
[% aspect.view.process('header', title='my title') %]
or
...
Another example would be debugging:
[% tt2dbg = tt2.aspect.debug(...params...) %]
[% tt2dbg.view.process('header') %]
or:
[% tt2 = tt2.aspect.debug(...) %]
...
[% tt2 = tt2.release %]
#------------------------------------------------------------------------
# Output
#------------------------------------------------------------------------
It should be possible to stack output buffers. In other words, you
stop writing to the current output buffer and open a new buffer and
start writing to that. The final output is simply the concatenation
of all output buffers.
The clever part of all this is that is should be possible to keep a
handle on an earlier buffer and go back and append to it at some point
in the future. A typical use is for generating tables of contents at
the start of a document when you don't know in advance what the document
contains. Simply push a new output buffer at the point of the TOC,
generate the rest of the document (keeping track of all the section
titles, etc) and at the end of the document, go back and generate the
TOC onto the end of the first buffer.
Haven't decided on any syntax yet, but it will almost certainly be
implemented as a facility. At the lowest level, something like
this perhaps:
[% INCLUDE header %] # write content to first buffer
[% toc = tt2.output.push %] # save current buffer as toc
Blah blah blah # write content to second buffer
[% INCLUDE footer %]
[% tt2.output.buffer = toc %] # re-instate first buffer
This is the TOC # append to first buffer
The output stack would look something like this:
+-----------------+
| <header> |
| This is the TOC |
+-----------------+
|
V
+-----------------+
| Blah blah blah |
| <footer> |
+-----------------+
Which to the end user, would silently be concatenated into:
<header>
This is the TOC
Blah blah blah
<footer>
Another use is to ensure dependencies on other templates get resolved.
For example, many of the PostScript library templates rely on other
templates. At present, each template sets a global variable to
say "I've been loaded" while also inspecting these variables for any
templates that it relies on, loading them via INCLUDE/PROCESS if not
set. e.g. ps/cross:
[% # this works, but it's a bit clunky
DEFAULT radius = '5 mm';
PROCESS ps/mm UNLESS global.ps.loaded.mm;
global.ps.loaded.cross = 1;
-%]
/cross {
...
}
Better would be to have each template simply activate the flags for
those templates that it relies on. The header file pushes a new output
buffer and the footer file goes back to the header buffer and INCLUDEs
the dependant templates.
[% PROCESS ps/header + ps/complex %]
10 mm 10 mm complex
[% PROCESS ps/footer %]
ps/complex:
[% global.ps.require.simple = 1 %]
/complex {
...
simple
...
}
ps/simple:
[% global.ps.require.mm = 1 %]
/simple {
...
31 mm 41 mm moveto
...
}
ps/header:
%%!PS-Adobe-3.0
...
%%EndComments
[% global.ps.header = tt2.output.push %]
ps/footer:
[% FOREACH file = ['mm','simple',...];
INCLUDE $file IF global.ps.require.$file;
END;
%]
Ultimately, the whole ps library would become a facility. The
acquire() method (called by the Template::Service to acquire the
facility at the start of processing a template) would do the 'header'
part (i.e. push the output buffer), and the release() method (called
at the tail end to release the facility) would do the dependency
checks. The dependency registrations would be facility features,
e.g.
gs/complex:
[% tt2.ps.require.mm = 1 %] # either
[% tt2.ps.require('mm', 'simple') %] # or