1use warnings; 2use strict; 3 4package Jifty::Config; 5 6=head1 NAME 7 8Jifty::Config - the configuration handler for Jifty 9 10=head1 SYNOPSIS 11 12 # in your application 13 my $app_name = Jifty->config->framework('ApplicationName'); 14 my $frobber = Jifty->config->app('PreferredFrobnicator'); 15 16 # sub classing 17 package MyApp::Config; 18 use base 'Jifty::Config'; 19 20 sub post_load { 21 my $self = shift; 22 my $stash = $self->stash; # full config in a hash 23 24 ... do something with options ... 25 26 $self->stash( $stash ); # save config 27 } 28 29 1; 30 31=head1 DESCRIPTION 32 33This class is automatically loaded during Jifty startup. It contains the configuration information loaded from the F<config.yml> file (generally stored in the F<etc> directory of your application, but see L</load> for the details). This configuration file is stored in L<YAML> format. 34 35This configuration file contains two major sections named "framework" and "application". The framework section contains Jifty-specific configuration options and the application section contains whatever configuration options you want to use with your application. (I.e., if there's any configuration information your application needs to know at startup, this is a good place to put it.) 36 37Usually you don't need to know anything about this class except 38L<app|/"app VARIABLE"> and L<framework|/"framework VARIABLE"> methods and 39about various config files and order in which they are loaded what 40described in L</load>. 41 42=cut 43 44use Jifty::Util; 45use Jifty::YAML; 46 47use Hash::Merge; 48Hash::Merge::set_behavior('RIGHT_PRECEDENT'); 49 50use base qw/Class::Accessor::Fast/; 51__PACKAGE__->mk_accessors(qw/stash/); 52 53use vars qw/$CONFIG/; 54 55=head1 ACCESSING CONFIG 56 57=head2 framework VARIABLE 58 59Get the framework configuration variable C<VARIABLE>. 60 61 Jifty->config->framework('ApplicationName') 62 63=cut 64 65sub framework { return shift->_get( framework => @_ ) } 66 67=head2 app VARIABLE 68 69Get the application configuration variable C<VARIABLE>. 70 71 Jifty->config->framework('MyOption'); 72 73=cut 74 75sub app { return shift->_get( application => @_ ) } 76 77# A teeny helper for framework and app 78sub _get { return $_[0]->stash->{ $_[1] }{ $_[2] } } 79 80=head2 contextual_get CONTEXT VARIABLE 81 82Gets the configuration variable in the context C<CONTEXT>. The C<CONTEXT> is a 83slash-separated list of hash keys. For example, the following might return 84C<SQLite>: 85 86 contextual_get('/framework/Database', 'Driver') 87 88=cut 89 90sub contextual_get { 91 my $self = shift; 92 my $context = shift; 93 my $field = shift; 94 95 my $pointer = $self->stash; 96 97 my @fragments = grep { length } split '/', $context; 98 for my $fragment (@fragments) { 99 $pointer = $pointer->{$fragment} || return; 100 } 101 102 return $pointer->{$field}; 103} 104 105=head1 LOADING 106 107=head2 new PARAMHASH 108 109In general, you never need to call this, just use: 110 111 Jifty->config 112 113in your application. 114 115This class method instantiates a new C<Jifty::Config> object. 116 117PARAMHASH currently takes a single option 118 119=over 120 121=item load_config 122 123This boolean defaults to true. If true, L</load> will be called upon 124initialization. Using this object without loading prevents sub-classing 125and only makes sense if you want to generate default config for 126a new jifty application or something like that. 127 128=back 129 130=cut 131 132sub new { 133 my $proto = shift; 134 my %args = ( load_config => 1, 135 @_ 136 ); 137 my $self = {}; 138 bless $self, $proto; 139 140 # Setup the initially empty stash 141 $self->stash( {} ); 142 143 # Load from file unless they tell us not to 144 $self->load() if ($args{'load_config'}); 145 return $self; 146} 147 148=head2 load 149 150Loads all config files for your application and initializes application 151level sub-class. 152 153Called from L<new|/"new PARAMHASH">, takes no arguments, 154returns nothing interesting, but do the following: 155 156=head3 Application config 157 158Jifty first loads the main configuration file for the application, looking for 159the C<JIFTY_CONFIG> environment variable or C<etc/config.yml> under the 160application's base directory. 161 162=head3 Vendor config 163 164It uses the main configuration file to find a vendor configuration 165file -- if it doesn't find a framework variable named 'VendorConfig', 166it will use the C<JIFTY_VENDOR_CONFIG> environment variable. 167 168=head3 Site config 169 170After loading the vendor configuration file (if it exists), the 171framework will look for a site configuration file, specified in either 172the framework's C<SiteConfig> or the C<JIFTY_SITE_CONFIG> environment 173variable. (Usually in C<etc/site_config.yml>.) 174 175=head3 Test config(s) 176 177After loading the site configuration file (if it exists), the 178framework will look for a test configuration file, specified in either 179the framework's C<TestConfig> or the C<JIFTY_TEST_CONFIG> environment 180variable. 181 182Note that the test config may be drawn from several files if you use 183L<Jifty::Test>. See the documentation of L<Jifty::Test::load_test_configs>. 184 185=head3 Options clobbering 186 187Values in the test configuration will clobber the site configuration. 188Values in the site configuration file clobber those in the vendor 189configuration file. Values in the vendor configuration file clobber 190those in the application configuration file. 191(See L</WHY SO MANY FILES> for a deeper search for truth on this matter.) 192 193=head3 Guess defaults 194 195Once we're all done loading from files, several defaults are 196assumed based on the name of the application -- see L</guess>. 197 198=head3 Reblessing into application's sub-class 199 200OK, config is ready. Rebless this object into C<YourApp::Config> class 201and call L</post_load> hook, so you can do some tricks detailed in 202L</SUB-CLASSING>. 203 204=head3 Another hook 205 206After we have the config file, we call the coderef in C<$Jifty::Config::postload>, 207if it exists. This last bit is generally used by the test harness to do 208a little extra work. 209 210=head3 B<SPECIAL PER-VALUE PROCESSING> 211 212If a value begins and ends with "%" (e.g., "%bin/foo%"), converts it with 213C<Jifty::Util/absolute_path> to an absolute path. This is typically 214unnecessary, but helpful for configuration variables such as C<MailerArgs> 215that only sometimes specify files. 216 217=cut 218 219sub load { 220 my $self = shift; 221 222 # Add the default configuration file locations to the stash 223 $self->merge( $self->_default_config_files ); 224 225 # Calculate the location of the application etc/config.yml 226 my $file = $ENV{'JIFTY_CONFIG'} || Jifty::Util->app_root . '/etc/config.yml'; 227 228 my $app; 229 230 # Start by loading application configuration file 231 if ( -f $file and -r $file ) { 232 # Load the $app so we know where to find the vendor config file 233 $self->merge( $self->load_file($file) ); 234 } 235 236 # Load the vendor configuration file 237 my $vendor = $self->load_file( 238 Jifty::Util->absolute_path( 239 $self->framework('VendorConfig') || $ENV{'JIFTY_VENDOR_CONFIG'} 240 ) 241 ); 242 243 # Merge the app config with vendor config, vendor taking precedent 244 $self->merge( $vendor ); 245 246 # Load the site configuration file 247 my $site = $self->load_file( 248 Jifty::Util->absolute_path( 249 # Note: $ENV{'JIFTY_SITE_CONFIG'} is already considered 250 # in ->_default_config_files(), but we || here again 251 # in case someone overrided _default_config_files(). 252 $self->framework('SiteConfig') || $ENV{'JIFTY_SITE_CONFIG'} 253 ) 254 ); 255 256 # Merge the app, vendor, and site config, site taking precedent 257 $self->merge( $site ); 258 259 # Load the test configuration file 260 my $test = $self->load_file( 261 Jifty::Util->absolute_path( 262 $self->framework('TestConfig') || $ENV{'JIFTY_TEST_CONFIG'} 263 ) 264 ); 265 266 # Merge the app, vendor, site and test config, test taking precedent 267 $self->merge( $test ); 268 269 # Merge guessed values in for anything we didn't explicitly define 270 # Whatever's in the stash overrides anything we guess 271 $self->merge( $self->stash, $self->guess ); 272 273 # There are a couple things we want to guess that we don't want 274 # getting stuck in a default config file for an app 275 $self->merge( $self->stash, $self->defaults ); 276 277 # Bring old configurations up to current expectations 278 $self->stash($self->update_config($self->stash)); 279 280 # check for YourApp::Config 281 my $app_class = $self->framework('ApplicationClass') . '::Config'; 282 # we have no class loader at this moment :( 283 my $found = Jifty::Util->try_to_require( $app_class ); 284 if ( $found && $app_class->isa('Jifty::Config') ) { 285 bless $self, $app_class; 286 } elsif ( $found ) { 287# XXX this warning is not always useful, sometimes annoying, 288# e.g. RT has its own config mechanism, we don't want to sub-class 289# Jifty::Config at all. 290# warn "You have $app_class, however it's not an sub-class of Jifty::Config." 291# ." Read `perldoc Jifty::Config` about subclassing. Skipping."; 292 } 293 294 # post load hook for sub-classes 295 $self->post_load; 296 297 # Finally, check for global postload hooks (these are used by the 298 # test harness) 299 $self->$Jifty::Config::postload() 300 if $Jifty::Config::postload; 301} 302 303=head2 merge NEW, [FALLBACK] 304 305Merges the given C<NEW> hashref into the stash, with values taking 306precedence over pre-existing ones from C<FALLBACK>, which defaults to 307L</stash>. This also deals with special cases (MailerArgs, 308Handlers.View) where array reference contents should be replaced, not 309concatenated. 310 311=cut 312 313sub merge { 314 my $self = shift; 315 my ($new, $fallback) = @_; 316 $fallback ||= $self->stash; 317 318 # These are now more correctly done with the ! syntax, below, rather 319 # than these special-cases. 320 delete $fallback->{framework}{MailerArgs} if exists $new->{framework}{MailerArgs}; 321 delete $fallback->{framework}{View}{Handlers} if exists $new->{framework}{View}{Handlers}; 322 323 # Plugins _need_ some special-case magic to get merged, as they're 324 # not just a hashref of classname to config, but an arrayref of 325 # one-key hashrefs to config, where the key is the classname. This 326 # is so there is an ordering between plugins. 327 # 328 # Grab all of the existant plugin config refs, and key them by classname. 329 my %plugins; 330 for my $p (@{$fallback->{framework}{Plugins} || []}) { 331 my ($class) = keys %{$p}; 332 # It's _possible_ that a single config source defined a plugin 333 # multiple times; the || sets us up to merge into only the first 334 # one. 335 $plugins{$class} ||= $p->{$class}; 336 } 337 # Now iterate through all of the new ones, peelling off and merging 338 # new data into the old ref, or pushing the new ref into the old 339 # plugin list. 340 for my $p (@{delete $new->{framework}{Plugins} || []}) { 341 my ($class) = keys %{$p}; 342 if ($plugins{$class}) { 343 %{$plugins{$class}} = (%{$plugins{$class}}, %{$p->{$class}}); 344 } else { 345 push @{$fallback->{framework}{Plugins}}, $p; 346 } 347 } 348 349 my $unbang; 350 $unbang = sub { 351 my $ref = shift; 352 if (ref $ref eq "HASH") { 353 $ref->{$_} = delete $ref->{$_ . "!"} 354 for map {s/!$//; $_} grep {/!$/} keys %{$ref}; 355 $ref->{$_} = $unbang->( $ref->{$_} ) 356 for keys %{$ref}; 357 } elsif (ref $ref eq "ARRAY") { 358 $ref = [ map { $unbang->($_) } @{$ref} ]; 359 } 360 return $ref; 361 }; 362 363 $self->stash( $unbang->( Hash::Merge::merge( $fallback, $new ) ) ); 364} 365 366# Sets up the initial location of the site configuration file 367sub _default_config_files { 368 my $self = shift; 369 my $config = { 370 framework => { 371 SiteConfig => ( 372 $ENV{JIFTY_SITE_CONFIG} || 'etc/site_config.yml' 373 ) 374 } 375 }; 376 return $self->_expand_relative_paths($config); 377} 378 379=head2 post_load 380 381Helper hook for L</SUB-CLASSING> and post processing config. At this 382point does nothing by default. That may be changed so do something like: 383 384 sub post_load { 385 my $self = shift; 386 $self->post_load( @_ ); 387 ... 388 } 389 390=cut 391 392sub post_load {} 393 394=head2 load_file PATH 395 396Loads a YAML configuration file and returns a hashref to that file's 397data. 398 399=cut 400 401sub load_file { 402 my $self = shift; 403 my $file = shift; 404 405 # only try to load files that exist 406 return {} unless ( $file && -f $file ); 407 my $hashref = Jifty::YAML::LoadFile($file) 408 or die "I couldn't load config file $file: $!"; 409 410 # Make sure %path% values are made absolute 411 $hashref = $self->_expand_relative_paths($hashref); 412 return $hashref; 413} 414 415# Does a DFS, turning all leaves that look like C<%paths%> into absolute paths. 416sub _expand_relative_paths { 417 my $self = shift; 418 my $datum = shift; 419 420 # Recurse through each value in an array 421 if ( ref $datum eq 'ARRAY' ) { 422 return [ map { $self->_expand_relative_paths($_) } @$datum ]; 423 } 424 425 # Recurse through each value in a hash 426 elsif ( ref $datum eq 'HASH' ) { 427 for my $key ( keys %$datum ) { 428 my $new_val = $self->_expand_relative_paths( $datum->{$key} ); 429 $datum->{$key} = $new_val; 430 } 431 return $datum; 432 } 433 434 # Do nothing with other kinds of references 435 elsif ( ref $datum ) { 436 return $datum; 437 } 438 439 # Check scalars for %path% and convert the enclosed value to an abspath 440 else { 441 if ( defined $datum and $datum =~ /^%(.+)%$/ ) { 442 $datum = Jifty::Util->absolute_path($1); 443 } 444 return $datum; 445 } 446} 447 448=head1 OTHER METHODS 449 450=head2 stash 451 452It's documented only for L</SUB-CLASSING>. 453 454Returns the current config as a hash reference (see below). Plenty of code 455considers Jifty's config as a static thing, so B<don't mess> with it in 456run-time. 457 458 { 459 framework => { 460 ... 461 }, 462 application => { 463 ... 464 }, 465 } 466 467This method as well can be used to set a new config: 468 469 $config->stash( $new_stash ); 470 471=head2 guess 472 473Attempts to guess (and return) a configuration hash based solely 474on what we already know. (Often, in the complete absence of 475a configuration file). It uses the name of the directory containing 476the Jifty binary as a default for C<ApplicationName> if it can't find one. 477 478=cut 479 480sub guess { 481 my $self = shift; 482 483 # First try at guessing the app name... 484 my $app_name; 485 486 # Was it passed to this method? 487 if (@_) { 488 $app_name = shift; 489 } 490 491 # Is it already in the stash? 492 elsif ( $self->stash->{framework}->{ApplicationName} ) { 493 $app_name = $self->stash->{framework}->{ApplicationName}; 494 } 495 496 # Finally, just guess from the application root 497 else { 498 $app_name = Jifty::Util->default_app_name; 499 } 500 501 # Setup the application class name based on the application name 502 my $app_class = $self->stash->{framework}->{ApplicationClass} 503 || $app_name; 504 $app_class =~ s/-/::/g; 505 my $db_name = lc $app_name; 506 $db_name =~ s/-/_/g; 507 my $app_uuid = Jifty::Util->generate_uuid; 508 509 # Build up the guessed configuration 510 my $guess = { 511 framework => { 512 AdminMode => 1, 513 DevelMode => 1, 514 SkipAccessControl => 0, 515 ApplicationClass => $app_class, 516 TemplateClass => $app_class . "::View", 517 ApplicationName => $app_name, 518 ApplicationUUID => $app_uuid, 519 LogLevel => 'INFO', 520 Database => { 521 AutoUpgrade => 1, 522 Database => $db_name, 523 Driver => "SQLite", 524 Host => "localhost", 525 Password => "", 526 User => "", 527 Version => "0.0.1", 528 RecordBaseClass => 'Jifty::DBI::Record::Cachable', 529 CheckSchema => '1' 530 }, 531 Mailer => 'Sendmail', 532 MailerArgs => [], 533 L10N => { PoDir => "share/po", }, 534 535 View => { 536 Handlers => [ 537 'Jifty::View::Static::Handler', 538 'Jifty::View::Declare::Handler', 539 'Jifty::View::Mason::Handler' 540 ] 541 }, 542 Web => { 543 Port => '8888', 544 BaseURL => 'http://localhost', 545 DataDir => "var/mason", 546 StaticRoot => "share/web/static", 547 TemplateRoot => "share/web/templates", 548 ServeStaticFiles => 1, 549 MasonConfig => { 550 autoflush => 0, 551 error_mode => 'fatal', 552 error_format => 'text', 553 default_escape_flags => 'h', 554 }, 555 Globals => [], 556 PSGIStatic => 1, 557 }, 558 }, 559 }; 560 561 # Make sure to handle any %path% values we may have guessed 562 return $self->_expand_relative_paths($guess); 563} 564 565=head2 initial_config 566 567Returns a default guessed config for a new application. 568 569See L<Jifty::Script::App>. 570 571=cut 572 573sub initial_config { 574 my $self = shift; 575 my $guess = $self->guess(@_); 576 $guess->{'framework'}->{'ConfigFileVersion'} = 6; 577 578 # These are the plugins which new apps will get by default 579 $guess->{'framework'}->{'Plugins'} = [ 580 { AdminUI => {}, }, 581 { CompressedCSSandJS => {}, }, 582 { ErrorTemplates => {}, }, 583 { Halo => {}, }, 584 { LetMe => {}, }, 585 { OnlineDocs => {}, }, 586 { REST => {}, }, 587 { SkeletonApp => {}, }, 588 ]; 589 590 return $guess; 591} 592 593=head2 update_config $CONFIG 594 595Takes an application's configuration as a hashref. Right now, it just sets up 596plugins that match an older jifty version's defaults 597 598=cut 599 600sub update_config { 601 my $self = shift; 602 my $config = shift; 603 604 my $version = $config->{'framework'}->{'ConfigFileVersion'}; 605 my $plugins = ($config->{'framework'}->{'Plugins'} ||= []); 606 607 # This app configuration predates the plugin refactor 608 if ($version < 2) { 609 610 # These are the plugins which old apps expect because their 611 # features used to be in the core. 612 unshift (@{$plugins}, 613 { AdminUI => {}, }, 614 { CompressedCSSandJS => {}, }, 615 { ErrorTemplates => {}, }, 616 { Halo => {}, }, 617 { OnlineDocs => {}, }, 618 { REST => {}, }, 619 { SkeletonApp => {}, }, 620 ); 621 } 622 623 if ($version < 3) { 624 unshift (@{$plugins}, 625 { CSSQuery => {}, } 626 ); 627 } 628 629 if ($version < 4) { 630 unshift (@{$plugins}, 631 { Prototypism => {}, } 632 ); 633 } 634 635 if ($version < 5) { 636 unshift (@{$plugins}, 637 { Compat => {}, } 638 ); 639 640 push (@{$plugins}, 641 { Deflater => {}, } 642 ); 643 } 644 645 return $config; 646} 647 648=head2 defaults 649 650We have a couple default values that shouldn't be included in the 651"guessed" config, as that routine is used when initializing a new 652application. Generally, these are platform-specific file locations. 653 654=cut 655 656sub defaults { 657 my $self = shift; 658 return { 659 framework => { 660 ConfigFileVersion => '1', 661 L10N => { 662 DefaultPoDir => Jifty::Util->share_root . '/po', 663 }, 664 Web => { 665 DefaultStaticRoot => Jifty::Util->share_root . '/web/static', 666 DefaultTemplateRoot => Jifty::Util->share_root . '/web/templates', 667 SessionCookieName => 'JIFTY_SID_$PORT', 668 }, 669 } 670 }; 671 672} 673 674=head1 SUB-CLASSING 675 676Template for sub-classing you can find in L</SYNOPSIS>. 677 678Application config may have ApplicationClass or ApplicationName options, 679so it's B<important> to understand that your class goes into game later. 680Read </load> to understand when C<YourApp::Config> class is loaded. 681 682Use L</stash> method to get and/or change config. 683 684L</post_load> hook usually is all you want to (can :) ) sub class. Other 685methods most probably called before your class can operate. 686 687Sub-classing may be useful for: 688 689=over 4 690 691=item * validation 692 693For example check if file or module exists. 694 695=item * canonicalization 696 697For example turn relative paths into absolute or translate all possible 698variants of an option into a canonical structure 699 700=item * generation 701 702For example generate often used constructions based on other options, 703user of your app can even don't know about them 704 705=item * config upgrades 706 707Jifty has ConfigVersion option you may want to implement something like 708that in your apps 709 710=back 711 712Sub-classing is definitely not for: 713 714=over 4 715 716=item * default values 717 718You have L<so many files|/"WHY SO MANY FILES"> to allow users of your 719app and you to override defaults. 720 721=item * anything else but configuration 722 723=back 724 725=head1 WHY SO MANY FILES 726 727The Jifty configuration can be loaded from many locations. This breakdown allows for configuration files to be layered on top of each other for advanced deployments. 728 729This section hopes to explain the intended purpose of each configuration file. 730 731=head2 APPLICATION 732 733The first configuration file loaded is the application configuration. This file provides the basis for the rest of the configuration loaded. The purpose of this file is for storing the primary application-specific configuration and defaults. 734 735This can be used as the sole configuration file on a simple deployment. In a complex environment, however, this file may be considered read-only to be overridden by other files, allowing the later files to customize the configuration at each level. 736 737=head2 VENDOR 738 739The vendor configuration file is loaded and overrides settings in the application configuration. This is an intermediate level in the configuration. It overrides any defaults specified in the application configuration, but is itself overridden by the site configuration. 740 741This provides an additional layer of abstraction for truly complicated deployments. A developer may provide a particular Jifty application (such as the Wifty wiki available from Best Practical Solutions) for download. A system administrator may have a standard set of configuration overrides to use on several different deployments that can be set using the vendor configuration, which can then be further overridden by each deployment using a site configuration. Several installations of the application might even share the vendor configuration file. 742 743=head2 SITE 744 745The site configuration allows for specific overrides of the application and vendor configuration. For example, a particular Jifty application might define all the application defaults in the application configuration file. Then, each administrator that has downloaded that application and is installing it locally might customize the configuration for a particular deployment using this configuration file, while leaving the application defaults intact (and, thus, still available for later reference). This can even override the vendor file containing a standard set of overrides. 746 747=head1 MERGING RULES 748 749Values from files loaded later take precedence; that is, Jifty's 750defaults are overridden by the application configuration file, then the 751vendor configuration file, then the site configuration file. At each 752step, the new values are merged into the old values using 753L<Hash::Merge>. Specifically, arrays which exist in both old and new 754data structures are appended, and hashes are merged. 755 756Some special rules apply, however: 757 758=over 759 760=item * 761 762If a key in a hash ends in C<!>, the normal merging rules do not apply; 763it simply overrides the equivalent non-C<!> key's value. 764 765=item * 766 767Plugins from one file are merged into the plugin configuration from 768previous files if their plugin classes match. That is, if both 769F<config.yml> and F<site_config.yml> define a 770L<Jifty::Plugin::CompressedCSSandJS>, rather than causing _two_ such 771plugins to be instantiated, the F<site_config.yml>'s plugin 772configuration keys will override those of F<config.yml>. 773 774This rule is only special because the C<Plugins> key in Jifty's config 775is an arrayref, not a hashref on plugin class name, to allow for both 776template and dispatcher ordering, as well as the possibility of repeated 777plugins. 778 779=back 780 781=head1 SEE ALSO 782 783L<Jifty> 784 785=head1 AUTHOR 786 787Various folks at BestPractical Solutions, LLC. 788 789=head1 LICENSE 790 791Jifty is Copyright 2005-2010 Best Practical Solutions, LLC. 792Jifty is distributed under the same terms as Perl itself. 793 794=cut 795 7961; 797