File Coverage

blib/lib/Hostfile/Manager.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Hostfile::Manager;
2              
3 1     1   5159 use strict;
  1         4  
  1         44  
4 1     1   7 use warnings;
  1         2  
  1         31  
5 1     1   398 use Moose;
  0            
  0            
6             use File::Find;
7             use File::Slurp;
8             use File::Basename qw/dirname/;
9              
10             our $VERSION = '0.08';
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             =item B<< Str get_fragment( Str $fragment_name ) >>
83              
84             The contents of an individual hostfile fragment.
85              
86             =back
87              
88             =cut
89              
90             has fragments => (
91             is => 'ro',
92             isa => 'HashRef[Str]',
93             traits => ['Hash'],
94             lazy => 1,
95             builder => '_load_fragments',
96             handles => {
97             fragment_list => 'keys',
98             get_fragment => 'get',
99             },
100             init_arg => undef,
101             );
102              
103             =head1 METHODS
104              
105             =over 6
106              
107             =item B<< Hostfile::Manager->new( [\%options] ) >>
108              
109             Create a new manager instance. Available options are B<path_prefix> and B<hostfile_path>, listed in the L<ACCESSORS|/"ACCESSORS"> section.
110              
111             =cut
112              
113             sub load_hostfile {
114             my ( $self, $filename ) = @_;
115              
116             $filename = $self->hostfile_path unless defined $filename;
117              
118             unless ( -e $filename ) {
119             Carp::croak("Hostfile must exist. File not found at $filename");
120             }
121              
122             my $file = read_file($filename);
123             $self->_set_hostfile($file);
124             }
125              
126             =item B<< Bool write_hostfile >>
127              
128             Write the contents of the hostfile to disk.
129              
130             =cut
131              
132             sub write_hostfile {
133             my $self = shift;
134              
135             my $filename = $self->hostfile_path;
136              
137             unless ( ( !-e $filename && -w dirname($filename) ) || -w $filename ) {
138             Carp::croak("Unable to write hostfile to $filename");
139             }
140              
141             write_file( $filename, $self->hostfile );
142             }
143              
144             =item B<< Bool fragment_enabled( Str $fragment_name ) >>
145              
146             Test whether a named fragment is enabled in the hostfile under management.
147              
148             =cut
149              
150             sub fragment_enabled {
151             my ( $self, $fragment_name ) = @_;
152              
153             $self->hostfile =~ $self->block($fragment_name);
154             }
155              
156             =item B<< enable_fragment( Str $fragment_name ) >>
157              
158             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.
159              
160             =cut
161              
162             sub enable_fragment {
163             my ( $self, $fragment_name ) = @_;
164              
165             my $fragment = $self->get_fragment($fragment_name) or return;
166              
167             $self->disable_fragment($fragment_name)
168             if $self->fragment_enabled($fragment_name);
169             $self->_set_hostfile( $self->hostfile
170             . "# BEGIN: $fragment_name\n$fragment# END: $fragment_name\n" );
171             }
172              
173             =item B<< disable_fragment( Str $fragment_name ) >>
174              
175             Disable a named fragment.
176              
177             =cut
178              
179             sub disable_fragment {
180             my ( $self, $fragment_name ) = @_;
181              
182             my $hostfile = $self->hostfile;
183             $hostfile =~ s/@{[$self->block($fragment_name)]}//g;
184              
185             $self->_set_hostfile($hostfile);
186             }
187              
188             =item B<< toggle_fragment( Str $fragment_name ) >>
189              
190             Enable a fragment if it is disabled, disable it otherwise.
191              
192             =cut
193              
194             sub toggle_fragment {
195             my ( $self, $fragment_name ) = @_;
196              
197             if ( $self->fragment_enabled($fragment_name) ) {
198             $self->disable_fragment($fragment_name);
199             }
200             else {
201             $self->enable_fragment($fragment_name);
202             }
203             }
204              
205             sub block {
206             my ( $self, $block_name ) = @_;
207              
208             $self->blocks->{$block_name} ||=
209             qr/#+\s*BEGIN: $block_name[\r\n](.*)#+\s*END: $block_name[\r\n]/ms;
210             return $self->blocks->{$block_name};
211             }
212              
213             sub _load_fragments {
214             my $self = shift;
215             my $fragments = {};
216             my $prefix = $self->path_prefix;
217              
218             find(
219             {
220             wanted => sub {
221             return if -d $_;
222             $_ =~ s{^$prefix}{};
223             $fragments->{$_} = $self->_load_fragment($_);
224             },
225             no_chdir => 1
226             },
227             $prefix
228             );
229              
230             $fragments;
231             }
232              
233             sub _load_fragment {
234             my ( $self, $fragment_name ) = @_;
235              
236             my $filename = $self->path_prefix . $fragment_name;
237              
238             unless ( -e $filename ) {
239             Carp::carp("Fragment not found at $filename");
240             return;
241             }
242              
243             read_file($filename);
244             }
245              
246             =item B<< Str fragment_status_flag( Str $fragment_name ) >>
247              
248             Returns a string indicating the current status of a named fragment.
249              
250             =over 2
251              
252             =item B<"+">
253              
254             The named fragment is enabled.
255              
256             =item B<"*">
257              
258             The named fragment is enabled and has been modified in the sourced hostfile.
259              
260             =item B<" ">
261              
262             The named fragment is not enabled.
263              
264             =back
265              
266             =back
267              
268             =cut
269              
270             sub fragment_status_flag {
271             my ( $self, $fragment_name ) = @_;
272             my $fragment_contents = $self->get_fragment($fragment_name);
273              
274             my ($found) = $self->hostfile =~ /@{[$self->block($fragment_name)]}/g;
275             return $found ? ( $found eq $fragment_contents ? "+" : "*" ) : " ";
276             }
277              
278             no Moose;
279             __PACKAGE__->meta->make_immutable;
280              
281             __END__
282              
283             =head1 LICENSE
284              
285             Copyright (c) 2010-11 Anthony J. Mirabella. All rights reserved.
286             This program is free software; you can redistribute it and/or
287             modify it under the same terms as Perl itself.
288              
289             =head1 AUTHOR
290              
291             Anthony J. Mirabella <mirabeaj AT gmail DOT com>