File Coverage

blib/lib/Hostfile/Manager.pm
Criterion Covered Total %
statement 70 73 95.8
branch 18 20 90.0
condition 4 9 44.4
subroutine 19 19 100.0
pod 7 9 77.7
total 118 130 90.7


line stmt bran cond sub pod time code
1             package Hostfile::Manager;
2              
3 1     1   5254 use strict;
  1         2  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         25  
5 1     1   572 use Moose;
  1         458070  
  1         6  
6 1     1   7732 use File::Find;
  1         2  
  1         76  
7 1     1   6 use File::Slurp;
  1         3  
  1         59  
8 1     1   6 use File::Basename qw/dirname/;
  1         2  
  1         1091  
9              
10             our $VERSION = '0.09';
11              
12             =head1 NAME
13              
14             Hostfile::Manager - Manage a hostfile by composing multiple fragments into a whole.
15              
16             =head1 SYNOPSIS
17              
18             use Hostfile::Manager;
19              
20             $manager = Hostfile::Manager->new;
21             $manager->enable_fragment($fragment_name);
22             $manager->write_hostfile;
23              
24             =head1 ACCESSORS
25              
26             =over 6
27              
28             =item B<< Str path_prefix( [Str $prefix] ) >>
29              
30             Defines the prefix that will be searched for hostfile fragments. Defaults to '/etc/hostfiles/'.
31              
32             =cut
33              
34             has path_prefix => (
35             is => 'rw',
36             isa => 'Str',
37             default => '/etc/hostfiles/',
38             );
39              
40             =item B<< Str hostfile_path( [Str $path] ) >>
41              
42             Defines the path to the hostfile to manage. Defaults to '/etc/hosts'.
43              
44             =cut
45              
46             has hostfile_path => (
47             is => 'rw',
48             isa => 'Str',
49             default => '/etc/hosts',
50             );
51              
52             =item B<< Str hostfile >>
53              
54             The contents of the hostfile under management.
55              
56             =cut
57              
58             has hostfile => (
59             is => 'ro',
60             isa => 'Str',
61             writer => '_set_hostfile',
62             lazy => 1,
63             builder => 'load_hostfile',
64             init_arg => undef,
65             );
66              
67             has blocks => (
68             is => 'ro',
69             isa => 'HashRef',
70             default => sub { {} },
71             init_arg => undef,
72             );
73              
74             =item B<< HashRef fragments >>
75              
76             The available hostfile fragments.
77              
78             =item B<< Array fragment_list >>
79              
80             A list of the names of available fragments.
81              
82             =cut
83              
84             sub fragment_list {
85 1     1 1 372 my ($self) = @_;
86 1         3 return sort { $a cmp $b } keys %{$self->fragments};
  1         8  
  1         35  
87             }
88              
89             =item B<< Str get_fragment( Str $fragment_name ) >>
90              
91             The contents of an individual hostfile fragment.
92              
93             =back
94              
95             =cut
96              
97             has fragments => (
98             is => 'ro',
99             isa => 'HashRef[Str]',
100             traits => ['Hash'],
101             lazy => 1,
102             builder => '_load_fragments',
103             handles => {
104             get_fragment => 'get',
105             },
106             init_arg => undef,
107             );
108              
109             =head1 METHODS
110              
111             =over 6
112              
113             =item B<< Hostfile::Manager->new( [\%options] ) >>
114              
115             Create a new manager instance. Available options are B<path_prefix> and B<hostfile_path>, listed in the L<ACCESSORS|/"ACCESSORS"> section.
116              
117             =cut
118              
119             sub load_hostfile {
120 14     14 0 1573 my ( $self, $filename ) = @_;
121              
122 14 100       308 $filename = $self->hostfile_path unless defined $filename;
123              
124 14 100       255 unless ( -e $filename ) {
125 1         29 Carp::croak("Hostfile must exist. File not found at $filename");
126             }
127              
128 13         54 my $file = read_file($filename);
129 13         1874 $self->_set_hostfile($file);
130             }
131              
132             =item B<< Bool write_hostfile >>
133              
134             Write the contents of the hostfile to disk.
135              
136             =cut
137              
138             sub write_hostfile {
139 1     1 1 5 my $self = shift;
140              
141 1         30 my $filename = $self->hostfile_path;
142              
143 1 50 33     81 unless ( ( !-e $filename && -w dirname($filename) ) || -w $filename ) {
      33        
144 0         0 Carp::croak("Unable to write hostfile to $filename");
145             }
146              
147 1         37 write_file( $filename, $self->hostfile );
148             }
149              
150             =item B<< Bool fragment_enabled( Str $fragment_name ) >>
151              
152             Test whether a named fragment is enabled in the hostfile under management.
153              
154             =cut
155              
156             sub fragment_enabled {
157 14     14 1 1146 my ( $self, $fragment_name ) = @_;
158              
159 14         453 $self->hostfile =~ $self->block($fragment_name);
160             }
161              
162             =item B<< enable_fragment( Str $fragment_name ) >>
163              
164             Enable a named fragment. If the fragment is currently enabled, it will be disabled first, removing any modifications that may have been made out-of-band.
165              
166             =cut
167              
168             sub enable_fragment {
169 4     4 1 772 my ( $self, $fragment_name ) = @_;
170              
171 4 100       164 my $fragment = $self->get_fragment($fragment_name) or return;
172              
173 3 100       12 $self->disable_fragment($fragment_name)
174             if $self->fragment_enabled($fragment_name);
175 3         80 $self->_set_hostfile( $self->hostfile
176             . "# BEGIN: $fragment_name\n$fragment# END: $fragment_name\n" );
177             }
178              
179             =item B<< disable_fragment( Str $fragment_name ) >>
180              
181             Disable a named fragment.
182              
183             =cut
184              
185             sub disable_fragment {
186 3     3 1 9 my ( $self, $fragment_name ) = @_;
187              
188 3         87 my $hostfile = $self->hostfile;
189 3         7 $hostfile =~ s/@{[$self->block($fragment_name)]}//g;
  3         8  
190              
191 3         95 $self->_set_hostfile($hostfile);
192             }
193              
194             =item B<< toggle_fragment( Str $fragment_name ) >>
195              
196             Enable a fragment if it is disabled, disable it otherwise.
197              
198             =cut
199              
200             sub toggle_fragment {
201 2     2 1 7 my ( $self, $fragment_name ) = @_;
202              
203 2 100       6 if ( $self->fragment_enabled($fragment_name) ) {
204 1         6 $self->disable_fragment($fragment_name);
205             }
206             else {
207 1         4 $self->enable_fragment($fragment_name);
208             }
209             }
210              
211             sub block {
212 21     21 0 575 my ( $self, $block_name ) = @_;
213              
214 21   66     573 $self->blocks->{$block_name} ||=
215             qr/#+\s*BEGIN: $block_name[\r\n](.*)#+\s*END: $block_name[\r\n]/ms;
216 21         615 return $self->blocks->{$block_name};
217             }
218              
219             sub _load_fragments {
220 8     8   14 my $self = shift;
221 8         15 my $fragments = {};
222 8         231 my $prefix = $self->path_prefix;
223              
224             find(
225             {
226             wanted => sub {
227 24 100   24   1895 return if -d $_;
228 16         141 $_ =~ s{^$prefix}{};
229 16         54 $fragments->{$_} = $self->_load_fragment($_);
230             },
231 8         700 no_chdir => 1
232             },
233             $prefix
234             );
235              
236 8         1271 $fragments;
237             }
238              
239             sub _load_fragment {
240 16     16   65 my ( $self, $fragment_name ) = @_;
241              
242 16         542 my $filename = $self->path_prefix . $fragment_name;
243              
244 16 50       229 unless ( -e $filename ) {
245 0         0 Carp::carp("Fragment not found at $filename");
246 0         0 return;
247             }
248              
249 16         66 read_file($filename);
250             }
251              
252             =item B<< Str fragment_status_flag( Str $fragment_name ) >>
253              
254             Returns a string indicating the current status of a named fragment.
255              
256             =over 2
257              
258             =item B<"+">
259              
260             The named fragment is enabled.
261              
262             =item B<"*">
263              
264             The named fragment is enabled and has been modified in the sourced hostfile.
265              
266             =item B<" ">
267              
268             The named fragment is not enabled.
269              
270             =back
271              
272             =back
273              
274             =cut
275              
276             sub fragment_status_flag {
277 3     3 1 392 my ( $self, $fragment_name ) = @_;
278 3         129 my $fragment_contents = $self->get_fragment($fragment_name);
279              
280 3         86 my ($found) = $self->hostfile =~ /@{[$self->block($fragment_name)]}/g;
  3         11  
281 3 100       26 return $found ? ( $found eq $fragment_contents ? "+" : "*" ) : " ";
    100          
282             }
283              
284 1     1   8 no Moose;
  1         2  
  1         10  
285             __PACKAGE__->meta->make_immutable;
286              
287             __END__
288              
289             =head1 LICENSE
290              
291             Copyright (c) 2010-11,2018 Anthony J. Mirabella. All rights reserved.
292             This program is free software; you can redistribute it and/or
293             modify it under the same terms as Perl itself.
294              
295             =head1 AUTHOR
296              
297             Anthony J. Mirabella <mirabeaj AT gmail DOT com>