← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:09 2015

Filename/var/www/foswiki11/lib/Foswiki/Plugins.pm
StatementsExecuted 2067 statements in 4.99ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
681341.96ms40.5msFoswiki::Plugins::::dispatchFoswiki::Plugins::dispatch (recurses: max depth 1, inclusive time 594µs)
1111.81ms3.67msFoswiki::Plugins::::BEGIN@21Foswiki::Plugins::BEGIN@21
111542µs97.9msFoswiki::Plugins::::loadFoswiki::Plugins::load
111287µs1.30sFoswiki::Plugins::::settingsFoswiki::Plugins::settings
7911260µs260µsFoswiki::Plugins::::addListenerFoswiki::Plugins::addListener
111255µs49.9msFoswiki::Plugins::::enableFoswiki::Plugins::enable
111186µs668µsFoswiki::Plugins::::finishFoswiki::Plugins::finish
102132µs32µsFoswiki::Plugins::::haveHandlerForFoswiki::Plugins::haveHandlerFor
11121µs29µsFoswiki::Plugins::::newFoswiki::Plugins::new
11117µs56µsFoswiki::Plugins::::BEGIN@32Foswiki::Plugins::BEGIN@32
11114µs27µsFoswiki::Plugins::::BEGIN@17Foswiki::Plugins::BEGIN@17
11112µs18µsFoswiki::Plugins::::BEGIN@18Foswiki::Plugins::BEGIN@18
1119µs21µsFoswiki::Plugins::::BEGIN@340Foswiki::Plugins::BEGIN@340
1118µs21µsFoswiki::Plugins::::BEGIN@19Foswiki::Plugins::BEGIN@19
1118µs17µsFoswiki::Plugins::::BEGIN@342Foswiki::Plugins::BEGIN@342
0000s0sFoswiki::Plugins::::__ANON__[:147]Foswiki::Plugins::__ANON__[:147]
0000s0sFoswiki::Plugins::::_handleACTIVATEDPLUGINSFoswiki::Plugins::_handleACTIVATEDPLUGINS
0000s0sFoswiki::Plugins::::_handleFAILEDPLUGINSFoswiki::Plugins::_handleFAILEDPLUGINS
0000s0sFoswiki::Plugins::::_handlePLUGINDESCRIPTIONSFoswiki::Plugins::_handlePLUGINDESCRIPTIONS
0000s0sFoswiki::Plugins::::getPluginVersionFoswiki::Plugins::getPluginVersion
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Plugins
6
7This module defines the singleton object that handles Plugins
8loading, initialization and execution.
9
10This class uses Chain of Responsibility (GOF) pattern to dispatch
11handler calls to registered plugins.
12
13=cut
14
15package Foswiki::Plugins;
16
17228µs241µs
# spent 27µs (14+14) within Foswiki::Plugins::BEGIN@17 which was called: # once (14µs+14µs) by Foswiki::BEGIN@634 at line 17
use strict;
# spent 27µs making 1 call to Foswiki::Plugins::BEGIN@17 # spent 14µs making 1 call to strict::import
18225µs224µs
# spent 18µs (12+6) within Foswiki::Plugins::BEGIN@18 which was called: # once (12µs+6µs) by Foswiki::BEGIN@634 at line 18
use warnings;
# spent 18µs making 1 call to Foswiki::Plugins::BEGIN@18 # spent 6µs making 1 call to warnings::import
19223µs234µs
# spent 21µs (8+13) within Foswiki::Plugins::BEGIN@19 which was called: # once (8µs+13µs) by Foswiki::BEGIN@634 at line 19
use Assert;
# spent 21µs making 1 call to Foswiki::Plugins::BEGIN@19 # spent 13µs making 1 call to Assert::import
20
212105µs13.67ms
# spent 3.67ms (1.81+1.86) within Foswiki::Plugins::BEGIN@21 which was called: # once (1.81ms+1.86ms) by Foswiki::BEGIN@634 at line 21
use Foswiki::Plugin ();
# spent 3.67ms making 1 call to Foswiki::Plugins::BEGIN@21
22
23=begin TML
24
25---++ PUBLIC constant $VERSION
26
27This is the version number of the plugins package. Use it for checking
28if you have a recent enough version.
29
30=cut
31
3241.02ms4103µs
# spent 56µs (17+39) within Foswiki::Plugins::BEGIN@32 which was called: # once (17µs+39µs) by Foswiki::BEGIN@634 at line 32
use version 0.77; our $VERSION = version->parse("2.2");
# spent 56µs making 1 call to Foswiki::Plugins::BEGIN@32 # spent 27µs making 1 call to version::import # spent 12µs making 1 call to version::vxs::_VERSION # spent 8µs making 1 call to version::vxs::parse
33
341200nsour $inited = 0;
35
3613µsmy %onlyOnceHandlers = (
37 registrationHandler => 1,
38 writeHeaderHandler => 1,
39 redirectCgiQueryHandler => 1,
40 renderFormFieldForEditHandler => 1,
41 renderWikiWordHandler => 1,
42);
43
44=begin TML
45
46---++ PUBLIC $SESSION
47
48This is a reference to the Foswiki session object. It can be used in
49plugins to get at the methods of the Foswiki kernel.
50
51You are _highly_ recommended to only use the methods in the
52=Foswiki::Func= interface, unless you have no other choice,
53as kernel methods may change between Foswiki releases.
54
55=cut
56
571100nsour $SESSION;
58
59=begin TML
60
61---++ ClassMethod new( $session )
62
63Construct new singleton plugins collection object. The object is a
64container for a list of plugins and the handlers registered by the plugins.
65The plugins and the handlers are carefully ordered.
66
67=cut
68
69
# spent 29µs (21+8) within Foswiki::Plugins::new which was called: # once (21µs+8µs) by Foswiki::new at line 1754 of /var/www/foswiki11/lib/Foswiki.pm
sub new {
701900ns my ( $class, $session ) = @_;
7116µs my $this = bless( { session => $session }, $class );
72
731500ns unless ($inited) {
7412µs13µs Foswiki::registerTagHandler( 'PLUGINDESCRIPTIONS',
# spent 3µs making 1 call to Foswiki::registerTagHandler
75 \&_handlePLUGINDESCRIPTIONS );
7612µs12µs Foswiki::registerTagHandler( 'ACTIVATEDPLUGINS',
# spent 2µs making 1 call to Foswiki::registerTagHandler
77 \&_handleACTIVATEDPLUGINS );
7811µs12µs Foswiki::registerTagHandler( 'FAILEDPLUGINS', \&_handleFAILEDPLUGINS );
# spent 2µs making 1 call to Foswiki::registerTagHandler
791600ns $inited = 1;
80 }
81
8215µs return $this;
83}
84
85=begin TML
86
87---++ ObjectMethod finish()
88Break circular references.
89
90=cut
91
92# Note to developers; please undef *all* fields in the object explicitly,
93# whether they are references or not. That way this method is "golden
94# documentation" of the live fields in the object.
95
# spent 668µs (186+483) within Foswiki::Plugins::finish which was called: # once (186µs+483µs) by Foswiki::finish at line 2163 of /var/www/foswiki11/lib/Foswiki.pm
sub finish {
961600ns my $this = shift;
97
9812µs1166µs $this->dispatch('finishPlugin');
# spent 166µs making 1 call to Foswiki::Plugins::dispatch
99
100140µs undef $this->{registeredHandlers};
10112µs foreach ( @{ $this->{plugins} } ) {
1023766µs37316µs $_->finish();
# spent 316µs making 37 calls to Foswiki::Plugin::finish, avg 9µs/call
103 }
104134µs undef $this->{plugins};
10516µs undef $this->{session};
106}
107
108=begin TML
109
110---++ ObjectMethod load($allDisabled) -> $loginName
111
112Find all active plugins, and invoke the early initialisation.
113Has to be done _after_ prefs are read.
114
115Returns the user returned by the last =initializeUserHandler= to be
116called.
117
118If allDisabled is set, no plugin handlers will be called.
119
120=cut
121
122
# spent 97.9ms (542µs+97.4) within Foswiki::Plugins::load which was called: # once (542µs+97.4ms) by Foswiki::Users::initialiseUser at line 266 of /var/www/foswiki11/lib/Foswiki/Users.pm
sub load {
12312µs my ( $this, $allDisabled ) = @_;
124
1251500ns my %lookup;
126
1271800ns my $session = $this->{session};
12812µs my $query = $session->{request};
129
1301700ns my @pluginList = ();
1311200ns my %already;
132
13312µs unless ($allDisabled) {
13416µs142µs if ( $query && defined( $query->param('debugenableplugins') ) ) {
# spent 42µs making 1 call to Foswiki::Request::param
135 foreach
136 my $pn ( split( /[,\s]+/, $query->param('debugenableplugins') ) )
137 {
138 push(
139 @pluginList,
140 Foswiki::Sandbox::untaint(
141 $pn,
142 sub {
143 my $pn = shift;
144 throw Error::Simple('Bad debugenableplugins')
145 unless $pn =~ /^\w+$/;
146 return $pn;
147 }
148 )
149 );
150 }
151 }
152 else {
15312µs if ( $Foswiki::cfg{PluginsOrder} ) {
15415µs foreach
155 my $plugin ( split( /[,\s]+/, $Foswiki::cfg{PluginsOrder} ) )
156 {
157
158 # Note this allows the same plugin to be listed
159 # multiple times! Thus their handlers can be called
160 # more than once. This is *desireable*.
16136µs if ( $Foswiki::cfg{Plugins}{$plugin}{Enabled} ) {
16232µs push( @pluginList, $plugin );
16334µs $already{$plugin} = 1;
164 }
165 }
166 }
167131µs foreach my $plugin ( sort keys %{ $Foswiki::cfg{Plugins} } ) {
1684118µs next unless ref( $Foswiki::cfg{Plugins}{$plugin} ) eq 'HASH';
1694038µs if ( $Foswiki::cfg{Plugins}{$plugin}{Enabled}
170 && !$already{$plugin} )
171 {
172348µs push( @pluginList, $plugin );
1733416µs $already{$plugin} = 1;
174 }
175 }
176 }
177 }
178
179 # Uncomment this to monitor plugin load times
180 #Monitor::MARK('About to initPlugins');
181
1821100ns my $user; # the user login name
1831300ns my $userDefiner; # the plugin that is defining the user
18411µs foreach my $pn (@pluginList) {
185374µs my $p;
18637128µs37386µs unless ( $p = $lookup{$pn} ) {
# spent 386µs making 37 calls to Foswiki::Plugin::new, avg 10µs/call
187 $p = new Foswiki::Plugin( $session, $pn );
188 }
1893730µs push @{ $this->{plugins} }, $p;
1903758µs3792.2ms my $anotherUser = $p->load();
# spent 92.2ms making 37 calls to Foswiki::Plugin::load, avg 2.49ms/call
191379µs if ($anotherUser) {
1921300ns if ($userDefiner) {
193 die 'Two plugins - '
194 . $userDefiner->{name} . ' and '
195 . $p->{name}
196 . ' are both trying to define the user login name.';
197 }
198 else {
1991400ns $userDefiner = $p;
2001200ns $user = $anotherUser;
201 }
202 }
203
204 # Report initialisation errors
2053727µs24.79ms if ( $p->{errors} ) {
# spent 2.95ms making 1 call to Foswiki::logger # spent 1.85ms making 1 call to Foswiki::Logger::PlainFile::log
206 $this->{session}
207 ->logger->log( 'warning', join( "\n", @{ $p->{errors} } ) );
208 }
2093754µs $lookup{$pn} = $p;
210
211 # Uncomment this to monitor plugin load times
212 #Monitor::MARK($pn);
213 }
214
215180µs return $user;
216}
217
218=begin TML
219
220---++ ObjectMethod settings()
221
222Push plugin settings onto preference stack
223
224=cut
225
226
# spent 1.30s (287µs+1.30) within Foswiki::Plugins::settings which was called: # once (287µs+1.30s) by Foswiki::new at line 1955 of /var/www/foswiki11/lib/Foswiki.pm
sub settings {
2271600ns my $this = shift;
228
229 # Set the session for this call stack
2301900ns local $Foswiki::Plugins::SESSION = $this->{session};
23111µs11µs ASSERT( $Foswiki::Plugins::SESSION->isa('Foswiki') ) if DEBUG;
# spent 1µs making 1 call to Assert::ASSERTS_OFF
232
233134µs foreach my $plugin ( @{ $this->{plugins} } ) {
23437193µs371.30s $plugin->registerSettings($this);
# spent 1.30s making 37 calls to Foswiki::Plugin::registerSettings, avg 35.1ms/call
235 }
236}
237
238=begin TML
239
240---++ ObjectMethod enable()
241
242Initialisation that is done after the user is known.
243
244=cut
245
246
# spent 49.9ms (255µs+49.6) within Foswiki::Plugins::enable which was called: # once (255µs+49.6ms) by Foswiki::new at line 1976 of /var/www/foswiki11/lib/Foswiki.pm
sub enable {
2471600ns my $this = shift;
24812µs my $prefs = $this->{session}->{prefs};
24915µs156µs my $dissed = $prefs->getPreference('DISABLEDPLUGINS') || '';
# spent 56µs making 1 call to Foswiki::Prefs::getPreference
25013µs my %disabled = map { s/^\s+//; s/\s+$//; $_ => 1 } split( /,/, $dissed );
251
252 # Set the session for this call stack
25311µs local $Foswiki::Plugins::SESSION = $this->{session};
25412µs12µs ASSERT( $Foswiki::Plugins::SESSION->isa('Foswiki') ) if DEBUG;
# spent 2µs making 1 call to Assert::ASSERTS_OFF
255
25619µs foreach my $plugin ( @{ $this->{plugins} } ) {
2573755µs if ( $disabled{ $plugin->{name} } ) {
258 $plugin->{disabled} = 1;
259 $plugin->{reason} =
260 $this->{session}
261 ->i18n->maketext('See the DISABLEDPLUGINS preference setting.');
262 push(
263 @{ $plugin->{errors} },
264 $plugin->{name} . ' has been disabled'
265 );
266 }
267 else {
2683765µs3749.3ms $plugin->registerHandlers($this);
# spent 49.3ms making 37 calls to Foswiki::Plugin::registerHandlers, avg 1.33ms/call
269 }
270
271 # Report initialisation errors
2723769µs2321µs if ( $plugin->{errors} ) {
# spent 312µs making 1 call to Foswiki::Logger::PlainFile::log # spent 10µs making 1 call to Foswiki::logger
273 $this->{session}
274 ->logger->log( 'warning', join( "\n", @{ $plugin->{errors} } ) );
275 }
276 }
277}
278
279=begin TML
280
281---++ ObjectMethod getPluginVersion() -> $number
282
283Returns the $Foswiki::Plugins::VERSION number if no parameter is specified,
284else returns the version number of a named Plugin. If the Plugin cannot
285be found or is not active, 0 is returned.
286
287=cut
288
289sub getPluginVersion {
290 my ( $this, $thePlugin ) = @_;
291
292 return $VERSION unless $thePlugin;
293
294 foreach my $plugin ( @{ $this->{plugins} } ) {
295 if ( $plugin->{name} eq $thePlugin ) {
296 return $plugin->getVersion();
297 }
298 }
299 return 0;
300}
301
302=begin TML
303
304---++ ObjectMethod addListener( $command, $handler )
305
306 * =$command= - name of the event
307 * =$handler= - the handler object.
308
309Add a listener to the end of the list of registered listeners for this event.
310The listener must implement =invoke($command,...)=, which will be triggered
311when the event is to be processed.
312
313=cut
314
315
# spent 260µs within Foswiki::Plugins::addListener which was called 79 times, avg 3µs/call: # 79 times (260µs+0s) by Foswiki::Plugin::registerHandlers at line 282 of /var/www/foswiki11/lib/Foswiki/Plugin.pm, avg 3µs/call
sub addListener {
3167951µs my ( $this, $c, $h ) = @_;
317
31879335µs push( @{ $this->{registeredHandlers}{$c} }, $h );
319}
320
321=begin TML
322
323---++ ObjectMethod dispatch( $handlerName, ...)
324Dispatch the given handler, passing on ... in the parameter vector
325
326=cut
327
328
# spent 40.5ms (1.96+38.5) within Foswiki::Plugins::dispatch which was called 68 times, avg 595µs/call: # 17 times (73µs+0s) by Foswiki::Render::internalLink at line 585 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 4µs/call # 7 times (548µs+6.88ms) by Foswiki::expandMacros at line 3347 of /var/www/foswiki11/lib/Foswiki.pm, avg 1.06ms/call # 7 times (241µs+457µs) by Foswiki::expandMacros at line 3317 of /var/www/foswiki11/lib/Foswiki.pm, avg 100µs/call # 7 times (72µs+88µs) by Foswiki::expandMacros at line 3380 of /var/www/foswiki11/lib/Foswiki.pm, avg 23µs/call # 6 times (589µs+2.10ms) by Foswiki::__ANON__[/var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm:331] at line 303 of /var/www/foswiki11/lib/Foswiki/Macros/INCLUDE.pm, avg 449µs/call # 5 times (162µs+18.4ms) by Foswiki::Render::getRenderedVersion at line 1166 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 3.71ms/call # 5 times (74µs+9.67ms) by Foswiki::Render::getRenderedVersion at line 1158 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 1.95ms/call # 5 times (146µs+717µs) by Foswiki::Render::getRenderedVersion at line 1466 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 173µs/call # 5 times (16µs+0s) by Foswiki::Render::getRenderedVersion at line 1448 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 3µs/call # once (12µs+154µs) by Foswiki::Plugins::finish at line 98 # once (12µs+70µs) by Foswiki::generateHTTPHeaders at line 951 of /var/www/foswiki11/lib/Foswiki.pm # once (6µs+0s) by Foswiki::writeCompletePage at line 832 of /var/www/foswiki11/lib/Foswiki.pm # once (6µs+0s) by Foswiki::generateHTTPHeaders at line 927 of /var/www/foswiki11/lib/Foswiki.pm
sub dispatch {
329
330 # must be shifted to clear parameter vector
3316826µs my $this = shift;
3326819µs my $handlerName = shift;
33368151µs foreach my $plugin ( @{ $this->{registeredHandlers}{$handlerName} } ) {
334
335 # Set the value of $SESSION for this call stack
336238152µs local $SESSION = $this->{session};
337238329µs238286µs ASSERT( $Foswiki::Plugins::SESSION->isa('Foswiki') ) if DEBUG;
# spent 286µs making 238 calls to Assert::ASSERTS_OFF, avg 1µs/call
338
339 # apply handler on the remaining list of args
340236µs233µs
# spent 21µs (9+12) within Foswiki::Plugins::BEGIN@340 which was called: # once (9µs+12µs) by Foswiki::BEGIN@634 at line 340
no strict 'refs';
# spent 21µs making 1 call to Foswiki::Plugins::BEGIN@340 # spent 12µs making 1 call to strict::unimport
341238451µs23838.5ms my $status = $plugin->invoke( $handlerName, @_ );
# spent 38.8ms making 238 calls to Foswiki::Plugin::invoke, avg 163µs/call, recursion: max depth 1, sum of overlapping time 381µs
3422584µs226µs
# spent 17µs (8+9) within Foswiki::Plugins::BEGIN@342 which was called: # once (8µs+9µs) by Foswiki::BEGIN@634 at line 342
use strict 'refs';
# spent 17µs making 1 call to Foswiki::Plugins::BEGIN@342 # spent 9µs making 1 call to strict::import
343238248µs if ( $status && $onlyOnceHandlers{$handlerName} ) {
344 return $status;
345 }
346 }
34768208µs return;
348}
349
350=begin TML
351
352---++ ObjectMethod haveHandlerFor( $handlerName ) -> $boolean
353
354 * =$handlerName= - name of the handler e.g. preRenderingHandler
355Return: true if at least one plugin has registered a handler of
356this type.
357
358=cut
359
360
# spent 32µs within Foswiki::Plugins::haveHandlerFor which was called 10 times, avg 3µs/call: # 5 times (22µs+0s) by Foswiki::Render::getRenderedVersion at line 1168 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 4µs/call # 5 times (10µs+0s) by Foswiki::Render::getRenderedVersion at line 1186 of /var/www/foswiki11/lib/Foswiki/Render.pm, avg 2µs/call
sub haveHandlerFor {
361107µs my ( $this, $handlerName ) = @_;
362
3631038µs return 0 unless defined( $this->{registeredHandlers}{$handlerName} );
364 return scalar( @{ $this->{registeredHandlers}{$handlerName} } );
365}
366
367# %FAILEDPLUGINS reports reasons why plugins failed to load
368# note this is invoked with the session as the first parameter
369sub _handleFAILEDPLUGINS {
370 my $this = shift->{plugins};
371
372 my $text = CGI::start_table(
373 {
374 border => 1,
375 class => 'foswikiTable',
376 summary => $this->{session}->i18n->maketext("Failed plugins")
377 }
378 ) . CGI::Tr( {}, CGI::th( {}, 'Plugin' ) . CGI::th( {}, 'Errors' ) );
379
380 foreach my $plugin ( @{ $this->{plugins} } ) {
381 my $td;
382 if ( $plugin->{errors} ) {
383 $td = CGI::td(
384 { class => 'foswikiAlert' },
385 "\n<verbatim>\n"
386 . join( "\n", @{ $plugin->{errors} } )
387 . "\n</verbatim>\n"
388 );
389 }
390 else {
391 $td = CGI::td( {}, 'none' );
392 }
393 my $web = $plugin->topicWeb();
394 $text .= CGI::Tr(
395 { valign => 'top' },
396 CGI::td(
397 {},
398 ' '
399 . ( $web ? "$web." : '!' )
400 . $plugin->{name} . ' '
401 . CGI::br()
402 . (
403 $SESSION->{users}->isAdmin( $SESSION->{user} )
404 ? $Foswiki::cfg{Plugins}{ $plugin->{name} }{Module} . ' '
405 : ''
406 )
407 )
408 . $td
409 );
410 }
411
412 $text .= CGI::end_table()
413 . CGI::start_table(
414 {
415 border => 1,
416 class => 'foswikiTable',
417 summary => $this->{session}->i18n->maketext("Plugin handlers")
418 }
419 ) . CGI::Tr( {}, CGI::th( {}, 'Handler' ) . CGI::th( {}, 'Plugins' ) );
420
421 foreach my $handler (@Foswiki::Plugin::registrableHandlers) {
422 my $h = '';
423 if ( defined( $this->{registeredHandlers}{$handler} ) ) {
424 $h = join(
425 CGI::br(),
426 map { $_->{name} } @{ $this->{registeredHandlers}{$handler} }
427 );
428 }
429 if ($h) {
430 if ( defined( $Foswiki::Plugin::deprecated{$handler} ) ) {
431 $h .= CGI::br()
432 . CGI::span(
433 { class => 'foswikiAlert' },
434" __This handler is deprecated__ - please check for updated versions of the plugins that use it!"
435 );
436 }
437 $text .= CGI::Tr( { valign => 'top' },
438 CGI::td( {}, $handler ) . CGI::td( {}, $h ) );
439 }
440 }
441
442 return
443 $text
444 . CGI::end_table() . "\n*"
445 . scalar( @{ $this->{plugins} } )
446 . " plugins*\n\n";
447}
448
449# note this is invoked with the session as the first parameter
450sub _handlePLUGINDESCRIPTIONS {
451 my $this = shift->{plugins};
452 my $text = '';
453 foreach my $plugin ( @{ $this->{plugins} } ) {
454 $text .= CGI::li( {}, $plugin->getDescription() . ' ' );
455 }
456
457 return CGI::ul( {}, $text );
458}
459
460# note this is invoked with the session as the first parameter
461sub _handleACTIVATEDPLUGINS {
462 my $this = shift->{plugins};
463 my $text = '';
464 foreach my $plugin ( @{ $this->{plugins} } ) {
465 unless ( $plugin->{disabled} ) {
466 my $web = $plugin->topicWeb();
467 $text .= ( $web ? "$web." : '!' ) . "$plugin->{name}, ";
468 }
469 }
470 $text =~ s/\,\s*$//o;
471 return $text;
472}
473
47415µs1;
475__END__