switch to my pluginbundle, weaving pod and authors moved to contributors
[gitmo/MooseX-ConfigFromFile.git] / lib / MooseX / ConfigFromFile.pm
1 package MooseX::ConfigFromFile;
2 # ABSTRACT: An abstract Moose role for setting attributes from a configfile
3
4 use Moose::Role;
5 use MooseX::Types::Path::Tiny 0.005 'Path';
6 use MooseX::Types::Moose 'Undef';
7 use Try::Tiny;
8 use Carp qw(croak);
9 use namespace::autoclean;
10
11 requires 'get_config_from_file';
12
13 has configfile => (
14     is => 'ro',
15     isa => Path|Undef,
16     coerce => 1,
17     predicate => 'has_configfile',
18     do { try { require MooseX::Getopt; (traits => ['Getopt']) } },
19     lazy => 1,
20     # it sucks that we have to do this rather than using a builder, but some old code
21     # simply swaps in a new default sub into the attr definition
22     default => sub {
23         my $class = shift;
24         $class->_get_default_configfile if $class->can('_get_default_configfile');
25     },
26 );
27
28 sub new_with_config {
29     my ($class, %opts) = @_;
30
31     my $configfile;
32
33     if(defined $opts{configfile}) {
34         $configfile = $opts{configfile}
35     }
36     else {
37         # This would only succeed if the consumer had defined a new configfile
38         # sub to override the generated reader - as suggested in old
39         # documentation -- or if $class is an instance not a class name
40         $configfile = try { $class->configfile };
41
42         # this is gross, but since a lot of users have swapped in their own
43         # default subs, we have to keep calling it rather than calling a
44         # builder sub directly - and it might not even be a coderef either
45         my $cfmeta = $class->meta->find_attribute_by_name('configfile');
46         $configfile = $cfmeta->default if not defined $configfile and $cfmeta->has_default;
47
48         if (ref $configfile eq 'CODE') {
49             $configfile = $configfile->($class);
50         }
51
52         my $init_arg = $cfmeta->init_arg;
53         $opts{$init_arg} = $configfile if defined $configfile and defined $init_arg;
54     }
55
56     if (defined $configfile) {
57         my $hash = $class->get_config_from_file($configfile);
58
59         no warnings 'uninitialized';
60         croak "get_config_from_file($configfile) did not return a hash (got $hash)"
61             unless ref $hash eq 'HASH';
62
63         %opts = (%$hash, %opts);
64     }
65
66     $class->new(%opts);
67 }
68
69 no Moose::Role; 1;
70
71 __END__
72
73 =pod
74
75 =head1 SYNOPSIS
76
77   ########
78   ## A real role based on this abstract role:
79   ########
80
81   package MooseX::SomeSpecificConfigRole;
82   use Moose::Role;
83
84   with 'MooseX::ConfigFromFile';
85
86   use Some::ConfigFile::Loader ();
87
88   sub get_config_from_file {
89     my ($class, $file) = @_;
90
91     my $options_hashref = Some::ConfigFile::Loader->load($file);
92
93     return $options_hashref;
94   }
95
96
97   ########
98   ## A class that uses it:
99   ########
100   package Foo;
101   use Moose;
102   with 'MooseX::SomeSpecificConfigRole';
103
104   # optionally, default the configfile:
105   sub _get_default_configfile { '/tmp/foo.yaml' }
106
107   # ... insert your stuff here ...
108
109   ########
110   ## A script that uses the class with a configfile
111   ########
112
113   my $obj = Foo->new_with_config(configfile => '/etc/foo.yaml', other_opt => 'foo');
114
115 =head1 DESCRIPTION
116
117 This is an abstract role which provides an alternate constructor for creating
118 objects using parameters passed in from a configuration file.  The
119 actual implementation of reading the configuration file is left to
120 concrete sub-roles.
121
122 It declares an attribute C<configfile> and a class method C<new_with_config>,
123 and requires that concrete roles derived from it implement the class method
124 C<get_config_from_file>.
125
126 =for stopwords configfile
127
128 Attributes specified directly as arguments to C<new_with_config> supersede those
129 in the configfile.
130
131 L<MooseX::Getopt> knows about this abstract role, and will use it if available
132 to load attributes from the file specified by the command line flag C<--configfile>
133 during its normal C<new_with_options>.
134
135 =head1 Attributes
136
137 =head2 configfile
138
139 This is a L<Path::Tiny> object which can be coerced from a regular path
140 string or any object that supports stringification.
141 This is the file your attributes are loaded from.  You can add a default
142 configfile in the consuming class and it will be honored at the appropriate
143 time; see below at L</_get_default_configfile>.
144
145 If you have L<MooseX::Getopt> installed, this attribute will also have the
146 C<Getopt> trait supplied, so you can also set the configfile from the
147 command line.
148
149 =head1 Class Methods
150
151 =head2 new_with_config
152
153 This is an alternate constructor, which knows to look for the C<configfile> option
154 in its arguments and use that to set attributes.  It is much like L<MooseX::Getopts>'s
155 C<new_with_options>.  Example:
156
157   my $foo = SomeClass->new_with_config(configfile => '/etc/foo.yaml');
158
159 Explicit arguments will override anything set by the configfile.
160
161 =head2 get_config_from_file
162
163 This class method is not implemented in this role, but it is required of all
164 classes or roles that consume this role.
165 Its two arguments are the class name and the configfile, and it is expected to return
166 a hashref of arguments to pass to C<new()> which are sourced from the configfile.
167
168 =head2 _get_default_configfile
169
170 This class method is not implemented in this role, but can and should be defined
171 in a consuming class or role to return the default value of the configfile (if not
172 passed into the constructor explicitly).
173
174 =cut