File Coverage

blib/lib/Circle/Configurable.pm
Criterion Covered Total %
statement 70 160 43.7
branch 8 56 14.2
condition 3 34 8.8
subroutine 20 27 74.0
pod 0 10 0.0
total 101 287 35.1


line stmt bran cond sub pod time code
1             # You may distribute under the terms of the GNU General Public License
2             #
3             # (C) Paul Evans, 2008-2014 -- leonerd@leonerd.org.uk
4              
5             package Circle::Configurable;
6              
7 4     4   35 use strict;
  4         7  
  4         109  
8 4     4   16 use warnings;
  4         8  
  4         103  
9              
10 4     4   18 use base qw( Circle::Commandable );
  4         6  
  4         364  
11              
12             our $VERSION = '0.173320';
13              
14 4     4   28 use Carp;
  4         7  
  4         233  
15              
16 4     4   20 use Attribute::Storage qw( get_subattr get_subattrs apply_subattrs_for_pkg find_subs_with_attr );
  4         8  
  4         22  
17 4     4   2094 use Data::Dump qw( pp );
  4         17730  
  4         727  
18             require mro;
19              
20             #############################################
21             ### Attribute handlers for setting_* subs ###
22             #############################################
23              
24             my %setting_types = (
25             str => {},
26              
27             int => {
28             check => sub { m/^\d+$/ },
29             },
30              
31             bool => {
32             parse => sub {
33             return 1 if lc $_ eq "true" or lc $_ eq "on" or $_ eq "1";
34             return 0 if lc $_ eq "false" or lc $_ eq "off" or $_ eq "0";
35             die;
36             },
37             print => sub { $_ ? "true" : "false" },
38             },
39             );
40              
41             sub Setting_description :ATTR(CODE)
42             {
43 32     32 0 4657 my $class = shift;
44 32         60 my ( $text ) = @_;
45              
46 32         68 return $text;
47 4     4   29 }
  4         7  
  4         29  
48              
49             sub Setting_type :ATTR(CODE)
50             {
51 32     32 0 5675 my $class = shift;
52 32         61 my ( $typename ) = @_;
53              
54 32 50       84 exists $setting_types{$typename} or croak "Not a recognised type name '$typename'";
55              
56 32         78 return $setting_types{$typename};
57 4     4   998 }
  4         10  
  4         23  
58              
59             sub Setting_default :ATTR(CODE)
60             {
61 4     4 0 601 my $class = shift;
62 4         9 my ( $value ) = @_;
63              
64 4         22 return $value;
65 4     4   811 }
  4         8  
  4         15  
66              
67             sub Setting_inheritable :ATTR(CODE)
68             {
69 8     8 0 1231 return 1;
70 4     4   635 }
  4         9  
  4         13  
71              
72             sub APPLY_Setting
73             {
74 24     24 0 42 my $class = shift;
75 24         67 my ( $name, %args ) = @_;
76              
77 24   66     91 my $storage = $args{storage} || $name;
78              
79 4     4   761 no strict 'refs';
  4         7  
  4         804  
80 24         1429 *{"${class}::setting_$name"} = apply_subattrs_for_pkg $class,
81             Setting_description => qq("\Q$args{description}\E"),
82             Setting_type => qq("\Q$args{type}\E"),
83             ( exists $args{default} ?
84             ( Setting_default => pp($args{default}) ) : () ),
85             sub {
86 0     0   0 my $self = shift;
87 0         0 my ( $newvalue ) = @_;
88              
89 0 0       0 $self->{$storage} = $newvalue if @_;
90 0         0 return $self->{$storage};
91 24 100       189 };
92             }
93              
94             sub APPLY_Inheritable_Setting
95             {
96 8     8 0 13 my $class = shift;
97 8         25 my ( $name, %args ) = @_;
98              
99 8   33     40 my $storage = $args{storage} || $name;
100              
101 8         19 my $setting = "setting_$name";
102              
103 4     4   25 no strict 'refs';
  4         65  
  4         3726  
104 8         430 *{"${class}::setting_$name"} = apply_subattrs_for_pkg $class,
105             Setting_description => qq("\Q$args{description}\E"),
106             Setting_type => qq("\Q$args{type}\E"),
107             Setting_inheritable => qq(),
108             ( exists $args{default} ?
109             ( Setting_default => pp($args{default}) ) : () ),
110             sub {
111 12     12   21 my $self = shift;
112 12         22 my ( $newvalue ) = @_;
113              
114 12 50       31 $self->{$storage} = $newvalue if @_;
115 12 50       36 return $self->{$storage} if defined $self->{$storage};
116 12 100       73 if( my $parent = $self->parent ) {
117 4         45 return $parent->$setting;
118             }
119             else {
120 8         48 return undef;
121             }
122 8 50       61 };
123 8         41 *{"${class}::_setting_${name}_inherits"} = sub {
124 0     0     my $self = shift;
125 0   0       return $self->parent && !defined $self->{$storage};
126 8         28 };
127             }
128              
129             sub _get_settings
130             {
131 0     0     my $self = shift;
132              
133 0   0       my $class = ref $self || $self;
134              
135 0           my %subs = find_subs_with_attr( mro::get_linear_isa( $class ), "Setting_description",
136             matching => qr/^setting_/
137             );
138              
139 0           my %settings;
140 0           foreach my $name ( keys %subs ) {
141 0           ( my $settingname = $name ) =~ s/^setting_//;
142 0           my $cv = $subs{$name};
143              
144 0           my $attrs = $settings{$settingname} = get_subattrs( $cv );
145 0   0       m/^Setting_(.*)$/ and $attrs->{$1} = delete $attrs->{$_} for keys %$attrs;
146             }
147              
148 0           return \%settings;
149             }
150              
151             sub command_set
152             : Command_description("Display or manipulate configuration settings")
153             : Command_arg('setting?')
154             : Command_arg('value?')
155             : Command_opt('inherit=+', desc => "Inherit value from parent")
156             : Command_opt('help=+', desc => "Display help on setting(s)")
157             : Command_opt('values=+', desc => "Display value of each setting")
158             {
159 0     0 0 0 my $self = shift;
160 0         0 my ( $setting, $newvalue, $opts, $cinv ) = @_;
161              
162 0         0 my $opt_inherit = $opts->{inherit};
163 0         0 my $opt_help = $opts->{help};
164 0         0 my $opt_values = $opts->{values};
165              
166 0 0       0 if( !defined $setting ) {
167 0         0 my $settings = $self->_get_settings;
168              
169 0 0       0 keys %$settings or $cinv->respond( "No settings exist" ), return;
170              
171 0 0       0 if( $opt_values ) {
172 0         0 my @table;
173 0         0 foreach my $settingname ( sort keys %$settings ) {
174 0         0 $setting = $settings->{$settingname};
175              
176 0         0 my $curvalue = $self->can( "setting_$settingname" )->( $self );
177 0 0       0 if( $setting->{type}->{print} ) {
178 0         0 $curvalue = $setting->{type}->{print}->( local $_ = $curvalue );
179             }
180              
181 0 0 0     0 if( $setting->{inheritable} && $self->can( "_setting_${settingname}_inherits" )->( $self ) ) {
182 0         0 $settingname .= " [I]";
183             }
184              
185 0 0       0 push @table, [
186             $settingname,
187             ( defined $curvalue ? $curvalue : "" ),
188             ];
189             }
190              
191 0         0 $self->respond_table( \@table, colsep => ": ", headings => [ "Setting", "Value" ] );
192             }
193             else {
194 0         0 my @table;
195 0         0 foreach my $settingname ( sort keys %$settings ) {
196 0         0 my $setting = $settings->{$settingname};
197              
198 0   0     0 push @table, [ $settingname, ( $setting->{Setting_description} || "[no description]" ) ];
199             }
200              
201 0         0 $cinv->respond_table( \@table, colsep => " - ", headings => [ "Setting", "Description" ] );
202             }
203              
204 0         0 return;
205             }
206              
207 0         0 my $cv = $self->can( "setting_$setting" );
208 0 0       0 if( !defined $cv ) {
209 0         0 $cinv->responderr( "No such setting $setting" );
210 0         0 return;
211             }
212              
213 0 0       0 if( $opt_help ) {
214 0   0     0 my $description = get_subattr( $cv, 'Setting_description' ) || "[no description]";
215 0         0 $cinv->respond( "$setting - $description" );
216 0         0 return;
217             }
218              
219 0         0 my $type = get_subattr( $cv, 'Setting_type' );
220              
221 0         0 my $curvalue;
222 0 0 0     0 if( defined $newvalue or $opt_inherit ) {
223 0 0 0     0 if( !$opt_inherit and $type->{check} ) {
224 0         0 local $_ = $newvalue;
225 0 0       0 $type->{check}->( $newvalue ) or
226             $cinv->responderr( "'$newvalue' is not a valid value for $setting" ), return;
227             }
228              
229 0 0 0     0 if( !$opt_inherit and $type->{parse} ) {
230 0         0 local $_ = $newvalue;
231 0 0       0 eval { $newvalue = $type->{parse}->( $newvalue ); 1 } or
  0         0  
  0         0  
232             $cinv->responderr( "'$newvalue' is not a valid value for $setting" ), return;
233             }
234              
235 0 0       0 undef $newvalue if $opt_inherit;
236 0         0 $curvalue = $cv->( $self, $newvalue );
237             }
238             else {
239 0         0 $curvalue = $cv->( $self );
240             }
241              
242 0 0       0 if( $type->{print} ) {
243 0         0 local $_ = $curvalue;
244 0         0 $curvalue = $type->{print}->( local $_ = $curvalue );
245             }
246              
247 0 0       0 if( defined $curvalue ) {
248 0         0 $cinv->respond( "$setting: $curvalue" );
249             }
250             else {
251 0         0 $cinv->respond( "$setting is not set" );
252             }
253              
254 0         0 return;
255 4     4   29 }
  4         8  
  4         16  
256              
257             sub get_configuration
258             {
259 0     0 0   my $self = shift;
260              
261 0           my $ynode = YAML::Node->new({});
262 0           $self->store_configuration( $ynode );
263              
264 0           return $ynode;
265             }
266              
267             sub load_configuration
268             {
269 0     0 0   my $self = shift;
270 0           my ( $ynode ) = @_;
271              
272 0           foreach my $setting ( keys %{ $self->_get_settings } ) {
  0            
273 0 0         my $cv = $self->can( "setting_$setting" ) or croak "$self has no setting $setting";
274 0           my $value = $ynode->{$setting};
275 0 0 0       if( !defined $value and
276             defined( my $default = get_subattr( $cv, "Setting_default" ) ) ) {
277 0           $value = $default;
278             }
279 0 0         $cv->( $self, $value ) if defined $value;
280             }
281             }
282              
283             sub store_configuration
284             {
285 0     0 0   my $self = shift;
286 0           my ( $ynode ) = @_;
287              
288 0           foreach my $setting ( keys %{ $self->_get_settings } ) {
  0            
289 0 0         my $cv = $self->can( "setting_$setting" ) or croak "$self has no setting $setting";
290 0           my $value = $cv->( $self );
291 0 0         $ynode->{$setting} = $value if defined $value;
292             }
293             }
294              
295             0x55AA;