File Coverage

blib/lib/Brackup/Config.pm
Criterion Covered Total %
statement 81 106 76.4
branch 27 52 51.9
condition 1 3 33.3
subroutine 12 17 70.5
pod 0 11 0.0
total 121 189 64.0


line stmt bran cond sub pod time code
1             package Brackup::Config;
2              
3 16     16   47583 use strict;
  16         34  
  16         581  
4 14     14   8183 use Brackup::ConfigSection;
  14         38  
  14         489  
5 14     14   218 use warnings;
  14         28  
  14         409  
6 14     14   73 use Carp qw(croak);
  14         24  
  14         1121  
7 14     14   73 use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
  14         25  
  14         36399  
8              
9             sub new {
10 8     8 0 37 my ($class) = @_;
11 8         54 return bless {}, $class;
12             }
13              
14             sub add_section {
15 15     15 0 272 my ($self, $sec) = @_;
16 15         137 $self->{$sec->name} = $sec;
17             }
18              
19             sub get_section {
20 0     0 0 0 my ($self, $name) = @_;
21 0         0 return $self->{$name};
22             }
23              
24             sub load {
25 1     1 0 33 my ($class, $file) = @_;
26 1   33     4 $file ||= Brackup::Config->default_config_file_name;
27              
28 1         3 my $self = bless {}, $class;
29              
30 1 50       69 open (my $fh, $file) or do {
31 0 0       0 if (write_dummy_config($file)) {
32 0         0 die "Your config file needs tweaking. I put a commented-out template at: $file\n";
33             } else {
34 0         0 die "No config file at: $file\n";
35             }
36             };
37 1         3 my $sec = undef;
38 1         3 my %inherit = ();
39 1         35 while (my $line = <$fh>) {
40 34         39 $line =~ s/^\#.*//; # kill comments starting at beginning of line
41 34         29 $line =~ s/\s\#.*//; # kill comments with whitespace before the # (motivation: let # be in regexps)
42 34         57 $line =~ s/^\s+//;
43 34         68 $line =~ s/\s$//;
44 34 100       80 next unless $line ne "";
45              
46 26 100       94 if ($line =~ /^\[(.+)\]$/) {
    50          
47 7         16 my $name = $1;
48 7         23 $sec = Brackup::ConfigSection->new($name);
49 7 50       18 die "Duplicate config section '$name'" if $self->{$name};
50 7         26 $self->{$name} = $sec;
51             } elsif ($line =~ /^(\w+)\s*=\s*(.+)/) {
52 19 50       32 die "Declaration of '$1' outside of a section." unless $sec;
53 19 100       31 if ($1 eq 'inherit') {
54 5         13 $inherit{$sec->name} = $2;
55             } else {
56 14         35 $sec->add($1, $2);
57             }
58             } else {
59 0         0 die "Bogus config line: $line";
60             }
61             }
62              
63 1 50       4 unless ($sec) {
64 0         0 die "Your config file needs tweaking. There's a starting template at: $file\n";
65             }
66              
67             # Config section inheritance
68 1         2 my $loop_count = 0;
69 1         4 while (keys %inherit) {
70 2         5 for my $child_sec (keys %inherit) {
71             # If this parent_sec itself inherits from something else, defer this time around
72 6 100       13 next if exists $inherit{ $inherit{$child_sec} };
73              
74 5         8 my $parent_sec = delete $inherit{$child_sec};
75             # If missing, derive prefix ([SOURCE|TARGET]:) from section name
76 5 100       26 $parent_sec = (split /:/, $child_sec, 2)[0] . ':' . $parent_sec
77             if $parent_sec !~ m/:/;
78 5 50       28 die "Cannot inherit from unknown section '$parent_sec'." unless $self->{$parent_sec};
79 5         14 $self->inherit_from($self->{$parent_sec}, $self->{$child_sec});
80             }
81 2 50       7 die "Inheritance chain too long - looping?" if ++$loop_count > 20;
82             }
83              
84 1         18 return $self;
85             }
86              
87             sub default_config_file_name {
88 0     0 0 0 my ($class) = @_;
89              
90 0 0       0 if ($ENV{HOME}) {
    0          
91             # Default for UNIX folk
92 0         0 return "$ENV{HOME}/.brackup.conf";
93             }
94             elsif ($ENV{APPDATA}) {
95             # For Windows users
96 0         0 return "$ENV{APPDATA}/brackup.conf";
97             }
98             else {
99             # Fall back on the current directory
100 0         0 return "brackup.conf";
101             }
102              
103             }
104              
105             sub write_dummy_config {
106 0     0 0 0 my $file = shift;
107 0 0       0 sysopen (my $fh, $file, O_WRONLY | O_CREAT | O_EXCL, 0600) or return;
108 0         0 print $fh <
109             # This is an example config
110              
111             #[TARGET:raidbackups]
112             #type = Filesystem
113             #path = /raid/backup/brackup
114             #keep_backups = 10
115              
116             #[TARGET:amazon]
117             #type = Amazon
118             #aws_access_key_id = XXXXXXXXXX
119             #aws_secret_access_key = XXXXXXXXXXXX
120             #keep_backups = 10
121              
122             #[SOURCE:proj]
123             #path = /raid/bradfitz/proj/
124             #chunk_size = 5m
125             #gpg_recipient = 5E1B3EC5
126              
127             #[SOURCE:bradhome]
128             #path = /raid/bradfitz/
129             #noatime = 1
130             #chunk_size = 64MB
131             #ignore = ^\.thumbnails/
132             #ignore = ^\.kde/share/thumbnails/
133             #ignore = ^\.ee/minis/
134             #ignore = ^build/
135             #ignore = ^(gqview|nautilus)/thumbnails/
136              
137             ENDCONF
138             }
139              
140             sub load_root {
141 8     8 0 28 my ($self, $name, $cache) = @_;
142 8 50       256 my $conf = $self->{"SOURCE:$name"} or
143             die "Unknown source '$name'\n";
144              
145 8         150 my $root = Brackup::Root->new($conf, $cache);
146              
147             # iterate over config's ignore, and add those
148 8         54 foreach my $pat ($conf->values("ignore")) {
149 1         6 $root->ignore($pat);
150             }
151              
152             # common things to ignore
153 8         906 $root->ignore(qr!~$!);
154 8         66 $root->ignore(qr!^\.thumbnails/!);
155 8         53 $root->ignore(qr!^\.kde/share/thumbnails/!);
156 8         52 $root->ignore(qr!^\.ee/minis/!);
157 8         54 $root->ignore(qr!^\.(gqview|nautilus)/thumbnails/!);
158              
159             # abort if the user had any configuration we didn't understand
160 8 50       51 if (my @keys = $conf->unused_config) {
161 0         0 die "Aborting, unknown configuration keys in SOURCE:$name: @keys\n";
162             }
163              
164 8         99 return $root;
165             }
166              
167             sub list_sources {
168 0     0 0 0 my ($self) = @_;
169 0         0 return sort map { s/^SOURCE://; $_ } grep(/^SOURCE:/, keys %$self);
  0         0  
  0         0  
170             }
171              
172             sub list_targets {
173 0     0 0 0 my ($self) = @_;
174 0         0 return sort map { s/^TARGET://; $_ } grep(/^TARGET:/, keys %$self);
  0         0  
  0         0  
175             }
176              
177             sub load_target {
178 7     7 0 32 my ($self, $name, %opts) = @_;
179 7         26 my $testmode = delete $opts{testmode};
180 7 50       31 croak("Unknown options: " . join(', ', keys %opts)) if %opts;
181              
182 7 50       42 my $confsec = $self->{"TARGET:$name"} or
183             die "Unknown target '$name'\n";
184              
185 7 50       38 my $type = $confsec->value("type") or
186             die "Target '$name' has no 'type'";
187 7 50       86 die "Invalid characters in ${name}'s 'type'"
188             unless $type =~ /^\w+$/;
189              
190 7         25 my $class = "Brackup::Target::$type";
191 7 50   5   1557 eval "use $class; 1;" or die
  5         6818  
  5         13  
  5         131  
192             "Failed to load ${name}'s driver: $@\n";
193 7         51 my $target = $class->new($confsec);
194              
195 7 50       42 if (my @unk_config = $confsec->unused_config) {
196 0 0       0 die "Unknown config params in TARGET:$name: @unk_config\n"
197             unless $testmode;
198             }
199 7         116 return $target;
200             }
201              
202             # Copy all keys in $parent_sec that don't exist in $child_sec
203             sub inherit_from {
204 5     5 0 5 my ($self, $parent_sec, $child_sec) = @_;
205              
206 5         11 for my $key ($parent_sec->keys) {
207 20 100       46 next if $child_sec->values($key);
208 17         42 $child_sec->add($key, $_) foreach $parent_sec->values($key);
209             }
210             }
211              
212             1;
213              
214             __END__