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