convert to Distar
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Utils.pm
CommitLineData
f05af9ba 1package Catalyst::Utils;
2
3use strict;
37a3ac5c 4use File::Spec;
d837e1a7 5use HTTP::Request;
812a28c9 6use Path::Class;
d837e1a7 7use URI;
5e5bd6df 8use Carp qw/croak/;
a8946dc8 9use Cwd;
e7399d8b 10use Class::Load 'is_class_loaded';
17b3d800 11use String::RewritePrefix;
3086ccde 12use Class::Load ();
0db6e9d5 13use namespace::clean;
ec4d7259 14use Devel::InnerPackage;
cbe627b9 15use Moose::Util;
0db6e9d5 16
f05af9ba 17=head1 NAME
18
19Catalyst::Utils - The Catalyst Utils
20
21=head1 SYNOPSIS
22
23See L<Catalyst>.
24
25=head1 DESCRIPTION
26
b0ad47c1 27Catalyst Utilities.
39fc2ce1 28
f05af9ba 29=head1 METHODS
30
b5ecfcf0 31=head2 appprefix($class)
41ca9ba7 32
85d9fce6 33 MyApp::Foo becomes myapp_foo
41ca9ba7 34
35=cut
36
37sub appprefix {
38 my $class = shift;
0ef447d8 39 $class =~ s/::/_/g;
41ca9ba7 40 $class = lc($class);
41 return $class;
42}
43
b5ecfcf0 44=head2 class2appclass($class);
84cf74e7 45
0ef447d8 46 MyApp::Controller::Foo::Bar becomes MyApp
47 My::App::Controller::Foo::Bar becomes My::App
2d90477f 48
84cf74e7 49=cut
50
51sub class2appclass {
52 my $class = shift || '';
53 my $appname = '';
0ef447d8 54 if ( $class =~ /^(.+?)::([MVC]|Model|View|Controller)::.+$/ ) {
84cf74e7 55 $appname = $1;
56 }
57 return $appname;
58}
59
b5ecfcf0 60=head2 class2classprefix($class);
2930d610 61
0ef447d8 62 MyApp::Controller::Foo::Bar becomes MyApp::Controller
63 My::App::Controller::Foo::Bar becomes My::App::Controller
2d90477f 64
2930d610 65=cut
66
67sub class2classprefix {
68 my $class = shift || '';
69 my $prefix;
0ef447d8 70 if ( $class =~ /^(.+?::([MVC]|Model|View|Controller))::.+$/ ) {
2930d610 71 $prefix = $1;
72 }
73 return $prefix;
74}
75
b5ecfcf0 76=head2 class2classsuffix($class);
84cf74e7 77
0ef447d8 78 MyApp::Controller::Foo::Bar becomes Controller::Foo::Bar
2d90477f 79
84cf74e7 80=cut
81
82sub class2classsuffix {
83 my $class = shift || '';
84 my $prefix = class2appclass($class) || '';
0ef447d8 85 $class =~ s/$prefix\:://;
84cf74e7 86 return $class;
87}
88
b5ecfcf0 89=head2 class2env($class);
3ad654e0 90
26e73131 91Returns the environment name for class.
3ad654e0 92
93 MyApp becomes MYAPP
94 My::App becomes MY_APP
95
96=cut
97
98sub class2env {
99 my $class = shift || '';
0ef447d8 100 $class =~ s/::/_/g;
3ad654e0 101 return uc($class);
102}
103
b5ecfcf0 104=head2 class2prefix( $class, $case );
f05af9ba 105
e2cc89a9 106Returns the uri prefix for a class. If case is false the prefix is converted to lowercase.
f05af9ba 107
0ef447d8 108 My::App::Controller::Foo::Bar becomes foo/bar
2d90477f 109
f05af9ba 110=cut
111
112sub class2prefix {
113 my $class = shift || '';
e494bd6b 114 my $case = shift || 0;
f05af9ba 115 my $prefix;
0ef447d8 116 if ( $class =~ /^.+?::([MVC]|Model|View|Controller)::(.+)$/ ) {
e494bd6b 117 $prefix = $case ? $2 : lc $2;
0ef447d8 118 $prefix =~ s{::}{/}g;
f05af9ba 119 }
120 return $prefix;
121}
122
b5ecfcf0 123=head2 class2tempdir( $class [, $create ] );
37a3ac5c 124
e2cc89a9 125Returns a tempdir for a class. If create is true it will try to create the path.
37a3ac5c 126
127 My::App becomes /tmp/my/app
7d7519a4 128 My::App::Controller::Foo::Bar becomes /tmp/my/app/c/foo/bar
37a3ac5c 129
130=cut
131
132sub class2tempdir {
133 my $class = shift || '';
134 my $create = shift || 0;
4be535b1 135 my @parts = split '::', lc $class;
37a3ac5c 136
137 my $tmpdir = dir( File::Spec->tmpdir, @parts )->cleanup;
138
4be535b1 139 if ( $create && !-e $tmpdir ) {
37a3ac5c 140
ab61f021 141 eval { $tmpdir->mkpath; 1 }
142 or do {
41a8bf1f 143 # don't load Catalyst::Exception as a BEGIN in Utils,
144 # because Utils often gets loaded before MyApp.pm, and if
145 # Catalyst::Exception is loaded before MyApp.pm, it does
146 # not honor setting
147 # $Catalyst::Exception::CATALYST_EXCEPTION_CLASS in
148 # MyApp.pm
149 require Catalyst::Exception;
37a3ac5c 150 Catalyst::Exception->throw(
4be535b1 151 message => qq/Couldn't create tmpdir '$tmpdir', "$@"/ );
37a3ac5c 152 }
153 }
154
155 return $tmpdir->stringify;
156}
157
a8946dc8 158=head2 home($class)
159
160Returns home directory for given class.
161
0f519d62 162=head2 dist_indicator_file_list
163
a8946dc8 164Returns a list of files which can be tested to check if you're inside
e01b6093 165a CPAN distribution which is not yet installed.
166
167These are:
168
169=over
170
171=item Makefile.PL
172
173=item Build.PL
174
175=item dist.ini
176
df221478 177=item L<cpanfile>
178
e01b6093 179=back
0f519d62 180
181=cut
182
183sub dist_indicator_file_list {
df221478 184 qw{Makefile.PL Build.PL dist.ini cpanfile};
0f519d62 185}
186
812a28c9 187sub home {
51f412bd 188 my $class = shift;
189
190 # make an $INC{ $key } style string from the class name
191 (my $file = "$class.pm") =~ s{::}{/}g;
192
193 if ( my $inc_entry = $INC{$file} ) {
51452916 194 {
51f412bd 195 # look for an uninstalled Catalyst app
196
197 # find the @INC entry in which $file was found
198 (my $path = $inc_entry) =~ s/$file$//;
a8946dc8 199 $path ||= cwd() if !defined $path || !length $path;
200 my $home = dir($path)->absolute->cleanup;
201
202 # pop off /lib and /blib if they're there
203 $home = $home->parent while $home =~ /b?lib$/;
204
205 # only return the dir if it has a Makefile.PL or Build.PL or dist.ini
206 if (grep { -f $home->file($_) } dist_indicator_file_list()) {
207 # clean up relative path:
208 # MyApp/script/.. -> MyApp
209
210 my $dir;
211 my @dir_list = $home->dir_list();
212 while (($dir = pop(@dir_list)) && $dir eq '..') {
213 $home = dir($home)->parent->parent;
214 }
215
216 return $home->stringify;
217 }
51452916 218 }
4be535b1 219
51f412bd 220 {
221 # look for an installed Catalyst app
222
223 # trim the .pm off the thing ( Foo/Bar.pm -> Foo/Bar/ )
224 ( my $path = $inc_entry) =~ s/\.pm$//;
225 my $home = dir($path)->absolute->cleanup;
226
bd85860b 227 # return if it's a valid directory
51f412bd 228 return $home->stringify if -d $home;
62459712 229 }
812a28c9 230 }
51f412bd 231
232 # we found nothing
a8946dc8 233 return 0;
03fb1bee 234}
235
b5ecfcf0 236=head2 prefix($class, $name);
812a28c9 237
238Returns a prefixed action.
239
0ef447d8 240 MyApp::Controller::Foo::Bar, yada becomes foo/bar/yada
812a28c9 241
242=cut
243
244sub prefix {
245 my ( $class, $name ) = @_;
246 my $prefix = &class2prefix($class);
247 $name = "$prefix/$name" if $prefix;
248 return $name;
249}
250
b5ecfcf0 251=head2 request($uri)
4d60aa90 252
e2cc89a9 253Returns an L<HTTP::Request> object for a uri.
4d60aa90 254
255=cut
256
257sub request {
258 my $request = shift;
259 unless ( ref $request ) {
a88c7ec8 260 if ( $request =~ m/^http/i ) {
f4c0f6f7 261 $request = URI->new($request);
4d60aa90 262 }
263 else {
f4c0f6f7 264 $request = URI->new( 'http://localhost' . $request );
4d60aa90 265 }
266 }
267 unless ( ref $request eq 'HTTP::Request' ) {
268 $request = HTTP::Request->new( 'GET', $request );
269 }
4d60aa90 270 return $request;
271}
272
dd91afb5 273=head2 ensure_class_loaded($class_name, \%opts)
d9183506 274
275Loads the class unless it already has been loaded.
276
dd91afb5 277If $opts{ignore_loaded} is true always tries the require whether the package
278already exists or not. Only pass this if you're either (a) sure you know the
279file exists on disk or (b) have code to catch the file not found exception
280that will result if it doesn't.
281
d9183506 282=cut
283
284sub ensure_class_loaded {
285 my $class = shift;
d06051f7 286 my $opts = shift;
d9183506 287
5e5bd6df 288 croak "Malformed class Name $class"
289 if $class =~ m/(?:\b\:\b|\:{3,})/;
290
59ede84e 291 croak "Malformed class Name $class"
292 if $class =~ m/[^\w:]/;
293
294 croak "ensure_class_loaded should be given a classname, not a filename ($class)"
295 if $class =~ m/\.pm$/;
296
f55d1491 297 # $opts->{ignore_loaded} can be set to true, and this causes the class to be required, even
298 # if it already has symbol table entries. This is to support things like Schema::Loader, which
299 # part-generate classes in memory, but then also load some of their contents from disk.
d06051f7 300 return if !$opts->{ ignore_loaded }
e7399d8b 301 && is_class_loaded($class); # if a symbol entry exists we don't load again
fbedfd6b 302
d9183506 303 # this hack is so we don't overwrite $@ if the load did not generate an error
304 my $error;
305 {
306 local $@;
7a1958eb 307 my $file = $class . '.pm';
308 $file =~ s{::}{/}g;
309 eval { CORE::require($file) };
d9183506 310 $error = $@;
311 }
6bfff75e 312
d9183506 313 die $error if $error;
fbedfd6b 314
f55d1491 315 warn "require $class was successful but the package is not defined."
e7399d8b 316 unless is_class_loaded($class);
6bfff75e 317
318 return 1;
d9183506 319}
320
358e1592 321=head2 merge_hashes($hashref, $hashref)
322
323Base code to recursively merge two hashes together with right-hand precedence.
324
325=cut
326
327sub merge_hashes {
328 my ( $lefthash, $righthash ) = @_;
329
330 return $lefthash unless defined $righthash;
b0ad47c1 331
358e1592 332 my %merged = %$lefthash;
0ef447d8 333 for my $key ( keys %$righthash ) {
334 my $right_ref = ( ref $righthash->{ $key } || '' ) eq 'HASH';
335 my $left_ref = ( ( exists $lefthash->{ $key } && ref $lefthash->{ $key } ) || '' ) eq 'HASH';
336 if( $right_ref and $left_ref ) {
358e1592 337 $merged{ $key } = merge_hashes(
338 $lefthash->{ $key }, $righthash->{ $key }
0ef447d8 339 );
358e1592 340 }
341 else {
342 $merged{ $key } = $righthash->{ $key };
0ef447d8 343 }
358e1592 344 }
b0ad47c1 345
358e1592 346 return \%merged;
347}
348
cb69249e 349=head2 env_value($class, $key)
350
351Checks for and returns an environment value. For instance, if $key is
352'home', then this method will check for and return the first value it finds,
353looking at $ENV{MYAPP_HOME} and $ENV{CATALYST_HOME}.
354
355=cut
356
357sub env_value {
358 my ( $class, $key ) = @_;
359
360 $key = uc($key);
361 my @prefixes = ( class2env($class), 'CATALYST' );
362
363 for my $prefix (@prefixes) {
364 if ( defined( my $value = $ENV{"${prefix}_${key}"} ) ) {
365 return $value;
366 }
367 }
368
369 return;
370}
d9183506 371
39fc2ce1 372=head2 term_width
373
374Try to guess terminal width to use with formatting of debug output
375
376All you need to get this work, is:
377
3781) Install Term::Size::Any, or
379
b0ad47c1 3802) Export $COLUMNS from your shell.
39fc2ce1 381
382(Warning to bash users: 'echo $COLUMNS' may be showing you the bash
b0ad47c1 383variable, not $ENV{COLUMNS}. 'export COLUMNS=$COLUMNS' and you should see
39fc2ce1 384that 'env' now lists COLUMNS.)
385
386As last resort, default value of 80 chars will be used.
387
ad9e8de9 388Calling C<term_width> with a true value will cause it to be recalculated; you
389can use this to cause it to get recalculated when your terminal is resized like
390this
391
392 $SIG{WINCH} = sub { Catalyst::Utils::term_width(1) };
393
39fc2ce1 394=cut
395
396my $_term_width;
397
398sub term_width {
ad9e8de9 399 my $force_reset = shift;
400
401 undef $_term_width if $force_reset;
402
39fc2ce1 403 return $_term_width if $_term_width;
404
ab61f021 405 my $width;
406 eval '
dfcb05ee 407 use Term::Size::Any;
408 ($width) = Term::Size::Any::chars;
409 1;
ab61f021 410 ' or do {
dfcb05ee 411 if($@ =~m[Can't locate Term/Size/Any.pm]) {
412 warn "Term::Size::Any is not installed, can't autodetect terminal column width\n";
413 } else {
414 warn "There was an error trying to detect your terminal size: $@\n";
415 }
b4ed26ff 416 };
417
418 unless ($width) {
dfcb05ee 419 warn 'Trouble trying to detect your terminal size, looking at $ENV{COLUMNS}'."\n";
39fc2ce1 420 $width = $ENV{COLUMNS}
421 if exists($ENV{COLUMNS})
422 && $ENV{COLUMNS} =~ m/^\d+$/;
b4ed26ff 423 }
39fc2ce1 424
dfcb05ee 425 do {
426 warn "Cannot determine desired terminal width, using default of 80 columns\n";
427 $width = 80 } unless ($width && $width >= 80);
39fc2ce1 428 return $_term_width = $width;
429}
430
17b3d800 431
432=head2 resolve_namespace
433
434Method which adds the namespace for plugins and actions.
435
436 __PACKAGE__->setup(qw(MyPlugin));
196932de 437
17b3d800 438 # will load Catalyst::Plugin::MyPlugin
439
440=cut
441
442
443sub resolve_namespace {
5d8129e9 444 my $appnamespace = shift;
17b3d800 445 my $namespace = shift;
446 my @classes = @_;
196932de 447 return String::RewritePrefix->rewrite({
448 q[] => qq[${namespace}::],
449 q[+] => q[],
450 (defined $appnamespace
451 ? (q[~] => qq[${appnamespace}::])
452 : ()
453 ),
454 }, @classes);
17b3d800 455}
456
3086ccde 457=head2 build_middleware (@args)
458
459Internal application that converts a single middleware definition (see
460L<Catalyst/psgi_middleware>) into an actual instance of middleware.
461
462=cut
463
464sub build_middleware {
465 my ($class, $namespace, @init_args) = @_;
466
467 if(
468 $namespace =~s/^\+// ||
469 $namespace =~/^Plack::Middleware/ ||
470 $namespace =~/^$class/
471 ) { ## the string is a full namespace
472 return Class::Load::try_load_class($namespace) ?
473 $namespace->new(@init_args) :
474 die "Can't load class $namespace";
475 } else { ## the string is a partial namespace
9b5bca00 476 if(Class::Load::try_load_class($class .'::Middleware::'. $namespace)) { ## Load Middleware from Project namespace
477 my $ns = $class .'::Middleware::'. $namespace;
478 return $ns->new(@init_args);
318213cd 479 } elsif(Class::Load::try_load_class("Plack::Middleware::$namespace")) { ## Act like Plack::Builder
480 return "Plack::Middleware::$namespace"->new(@init_args);
d9c6a83f 481 } else {
482 die "Can't load middleware via '$namespace'. It's not ".$class."::Middleware::".$namespace." or Plack::Middleware::$namespace";
3086ccde 483 }
484 }
485
486 return; ## be sure we can count on a proper return when valid
487}
488
489=head2 apply_registered_middleware ($psgi)
490
491Given a $psgi reference, wrap all the L<Catalyst/registered_middlewares>
492around it and return the wrapped version.
493
494This exists to deal with the fact Catalyst registered middleware can be
495either an object with a wrap method or a coderef.
496
497=cut
498
499sub apply_registered_middleware {
500 my ($class, $psgi) = @_;
501 my $new_psgi = $psgi;
502 foreach my $middleware ($class->registered_middlewares) {
503 $new_psgi = Scalar::Util::blessed $middleware ?
504 $middleware->wrap($new_psgi) :
505 $middleware->($new_psgi);
506 }
507 return $new_psgi;
508}
17b3d800 509
ec4d7259 510=head2 inject_component
6adc45cf 511
ec4d7259 512Used to add components at runtime:
513
514 into The Catalyst package to inject into (e.g. My::App)
515 component The component package to inject
cbe627b9 516 traits (Optional) ArrayRef of L<Moose::Role>s that the componet should consume.
ec4d7259 517 as An optional moniker to use as the package name for the derived component
518
519For example:
520
521 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple )
522
523 The above will create 'My::App::Controller::Other::App::Controller::Apple'
524
525 Catalyst::Utils::inject_component( into => My::App, component => Other::App::Controller::Apple, as => Apple )
526
527 The above will create 'My::App::Controller::Apple'
528
529 Catalyst::Utils::inject_component( into => $myapp, component => 'MyRootV', as => 'Controller::Root' );
530
531Will inject Controller, Model, and View components into your Catalyst application
532at setup (run)time. It does this by creating a new package on-the-fly, having that
533package extend the given component, and then having Catalyst setup the new component
534(via $app->setup_component).
535
536B<NOTE:> This is basically a core version of L<CatalystX::InjectComponent>. If you were using that
537you can now use this safely instead. Going forward changes required to make this work will be
538synchronized with the core method.
539
cbe627b9 540B<NOTE:> The 'traits' option is unique to the L<Catalyst::Utils> version of this feature.
541
542B<NOTE:> These injected components really need to be a L<Catalyst::Component> and a L<Moose>
543based class.
544
ec4d7259 545=cut
546
547sub inject_component {
548 my %given = @_;
549 my ($into, $component, $as) = @given{qw/into component as/};
550
551 croak "No Catalyst (package) given" unless $into;
552 croak "No component (package) given" unless $component;
553
554 Class::Load::load_class($component);
555
556 $as ||= $component;
557 unless ( $as =~ m/^(?:Controller|Model|View)::/ || $given{skip_mvc_renaming} ) {
558 my $category;
559 for (qw/ Controller Model View /) {
560 if ( $component->isa( "Catalyst::$_" ) ) {
561 $category = $_;
562 last;
563 }
564 }
565 croak "Don't know what kind of component \"$component\" is" unless $category;
566 $as = "${category}::$as";
567 }
568 my $component_package = join '::', $into, $as;
569
570 unless ( Class::Load::is_class_loaded $component_package ) {
571 eval "package $component_package; use base qw/$component/; 1;" or
572 croak "Unable to build component package for \"$component_package\": $@";
cbe627b9 573 Moose::Util::apply_all_roles($component_package, @{$given{traits}}) if $given{traits};
ec4d7259 574 (my $file = "$component_package.pm") =~ s{::}{/}g;
575 $INC{$file} ||= 1;
576 }
577
578 my $_setup_component = sub {
579 my $into = shift;
580 my $component_package = shift;
e39312ba 581 $into->components->{$component_package} = $into->delayed_setup_component( $component_package );
ec4d7259 582 };
583
584 $_setup_component->( $into, $component_package );
ec4d7259 585}
6adc45cf 586
9c7b6768 587=head1 PSGI Helpers
588
589Utility functions to make it easier to work with PSGI applications under Catalyst
590
591=head2 env_at_path_prefix
592
593Localize C<$env> under the current controller path prefix:
594
595 package MyApp::Controller::User;
596
597 use Catalyst::Utils;
598
599 use base 'Catalyst::Controller';
600
601 sub name :Local {
602 my ($self, $c) = @_;
603 my $env = $c->Catalyst::Utils::env_at_path_prefix;
604 }
605
efa8265f 606Assuming you have a request like GET /user/name:
9c7b6768 607
608In the example case C<$env> will have PATH_INFO of '/name' instead of
609'/user/name' and SCRIPT_NAME will now be '/user'.
610
611=cut
612
613sub env_at_path_prefix {
614 my $ctx = shift;
615 my $path_prefix = $ctx->controller->path_prefix;
616 my $env = $ctx->request->env;
617 my $path_info = $env->{PATH_INFO};
618 my $script_name = ($env->{SCRIPT_NAME} || '');
619
620 $path_info =~ s/(^\/\Q$path_prefix\E)//;
621 $script_name = "$script_name$1";
622
623 return +{
624 %$env,
625 PATH_INFO => $path_info,
626 SCRIPT_NAME => $script_name };
627}
628
629=head2 env_at_action
630
4477b313 631Localize C<$env> under the current action namespace.
9c7b6768 632
633 package MyApp::Controller::User;
634
635 use Catalyst::Utils;
636
637 use base 'Catalyst::Controller';
638
639 sub name :Local {
640 my ($self, $c) = @_;
641 my $env = $c->Catalyst::Utils::env_at_action;
642 }
643
4477b313 644Assuming you have a request like GET /user/name:
9c7b6768 645
646In the example case C<$env> will have PATH_INFO of '/' instead of
647'/user/name' and SCRIPT_NAME will now be '/user/name'.
648
efa8265f 649Alternatively, assuming you have a request like GET /user/name/foo:
4477b313 650
651In this example case C<$env> will have PATH_INFO of '/foo' instead of
652'/user/name/foo' and SCRIPT_NAME will now be '/user/name'.
653
9c7b6768 654This is probably a common case where you want to mount a PSGI application
655under an action but let the Args fall through to the PSGI app.
656
657=cut
658
659sub env_at_action {
660 my $ctx = shift;
661 my $argpath = join '/', @{$ctx->request->arguments};
662 my $path = '/' . $ctx->request->path;
663
664 $path =~ s/\/?\Q$argpath\E\/?$//;
665
666 my $env = $ctx->request->env;
667 my $path_info = $env->{PATH_INFO};
668 my $script_name = ($env->{SCRIPT_NAME} || '');
669
670 $path_info =~ s/(^\Q$path\E)//;
671 $script_name = "$script_name$1";
672
673 return +{
674 %$env,
675 PATH_INFO => $path_info,
676 SCRIPT_NAME => $script_name };
677}
678
679=head2 env_at_request_uri
680
4477b313 681Localize C<$env> under the current request URI:
9c7b6768 682
683 package MyApp::Controller::User;
684
685 use Catalyst::Utils;
686
687 use base 'Catalyst::Controller';
688
689 sub name :Local Args(1) {
690 my ($self, $c, $id) = @_;
691 my $env = $c->Catalyst::Utils::env_at_request_uri
692 }
693
efa8265f 694Assuming you have a request like GET /user/name/hello:
9c7b6768 695
696In the example case C<$env> will have PATH_INFO of '/' instead of
697'/user/name' and SCRIPT_NAME will now be '/user/name/hello'.
698
699=cut
700
701sub env_at_request_uri {
702 my $ctx = shift;
703 my $path = '/' . $ctx->request->path;
704 my $env = $ctx->request->env;
705 my $path_info = $env->{PATH_INFO};
706 my $script_name = ($env->{SCRIPT_NAME} || '');
707
708 $path_info =~ s/(^\Q$path\E)//;
709 $script_name = "$script_name$1";
710
711 return +{
712 %$env,
713 PATH_INFO => $path_info,
714 SCRIPT_NAME => $script_name };
715}
716
2f381252 717=head1 AUTHORS
f05af9ba 718
2f381252 719Catalyst Contributors, see Catalyst.pm
f05af9ba 720
721=head1 COPYRIGHT
722
536bee89 723This library is free software. You can redistribute it and/or modify it under
f05af9ba 724the same terms as Perl itself.
725
726=cut
727
7281;