File Coverage

blib/lib/App/RoboBot/Config.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 21 23 91.3


line stmt bran cond sub pod time code
1             package App::RoboBot::Config;
2             $App::RoboBot::Config::VERSION = '4.003';
3 5     5   44 use v5.20;
  5         16  
4              
5 5     5   21 use namespace::autoclean;
  5         6  
  5         42  
6              
7 5     5   351 use Moose;
  5         8  
  5         36  
8 5     5   24172 use MooseX::SetOnce;
  5         9  
  5         93  
9              
10 5     5   2476 use Config::Any::Merge;
  5         19414  
  5         117  
11 5     5   868 use DBD::Pg;
  0            
  0            
12             use DBIx::DataStore;
13             use File::HomeDir;
14             use Log::Log4perl;
15             use Log::Log4perl::Appender::Screen;
16             use Try::Tiny;
17              
18             use App::RoboBot::NetworkFactory;
19             use App::RoboBot::Channel;
20             use App::RoboBot::Nick;
21              
22             has 'bot' => (
23             is => 'ro',
24             isa => 'App::RoboBot',
25             required => 1,
26             );
27              
28             has 'config_paths' => (
29             is => 'rw',
30             isa => 'ArrayRef[Str]',
31             traits => [qw( SetOnce )],
32             predicate => 'has_config_paths',
33             );
34              
35             has 'config' => (
36             is => 'rw',
37             isa => 'HashRef',
38             predicate => 'has_config',
39             );
40              
41             has 'networks' => (
42             is => 'rw',
43             isa => 'HashRef',
44             );
45              
46             has 'channels' => (
47             is => 'rw',
48             isa => 'ArrayRef[App::RoboBot::Channel]',
49             );
50              
51             has 'plugins' => (
52             is => 'rw',
53             isa => 'HashRef',
54             default => sub { {} },
55             );
56              
57             has 'db' => (
58             is => 'rw',
59             isa => 'DBIx::DataStore',
60             traits => [qw( SetOnce )],
61             );
62              
63             sub load_config {
64             my ($self) = @_;
65              
66             my ($logger);
67              
68             try {
69             unless ($self->has_config) {
70             $self->locate_config unless $self->has_config_paths;
71              
72             if (my $cfg = Config::Any::Merge->load_files({ files => $self->config_paths, use_ext => 1, override => 1 })) {
73             $self->config($cfg);
74             } else {
75             die "Could not load configuration files: " . join(', ', @{$self->config_paths});
76             }
77             }
78              
79             $self->init_logging;
80              
81             $logger = $self->bot->logger('core.config');
82              
83             $self->validate_database;
84             $logger->debug('Database configuration initialized.');
85              
86             $self->validate_globals;
87             $logger->debug('Global settings initialized.');
88              
89             $self->validate_networks;
90             $logger->debug('Network configurations initialized.');
91              
92             $self->validate_plugins;
93             $logger->debug('Plugin configurations initialized.');
94              
95             $self->bot->networks([ values %{$self->networks} ]);
96             } catch {
97             die "Could not load and validate configuration: $_";
98             };
99              
100             $logger->debug('All configuration data loaded.');
101             }
102              
103             sub locate_config {
104             my ($self) = @_;
105              
106             my $home = File::HomeDir->my_home();
107             my @exts = qw( conf yml yaml json xml ini );
108             my @bases = ("$home/.lispy/lispy.", "$home/.lispy.", "/etc/lispy.");
109              
110             my @configs;
111              
112             foreach my $base (@bases) {
113             foreach my $ext (@exts) {
114             push(@configs, $base . $ext);
115             }
116             }
117              
118             my @found;
119              
120             CONFIG_FILE:
121             foreach my $path (@configs) {
122             if (-f $path && -r _) {
123             push(@found, $path);
124             }
125             }
126              
127             $self->config_paths([reverse @found]);
128              
129             die "Unable to locate a configuration file!" unless $self->has_config_paths;
130             }
131              
132             sub init_logging {
133             my ($self) = @_;
134              
135             my $log_cfg = $self->config->{'logging'} // {
136             'log4j.rootLogger' => 'INFO, stdout',
137             'log4j.appender.stdout' => 'org.apache.log4j.ConsoleAppender',
138             'log4j.appender.stdout.layout' => 'org.apache.log4j.PatternLayout',
139             'log4j.appender.stdout.layout.ConversionPattern' => '%d %5p [%c] %m%n',
140             };
141              
142             my $config_str = join("\n", map { sprintf('%s=%s', $_, $log_cfg->{$_}) } sort keys %{$log_cfg});
143              
144             Log::Log4perl::init( \$config_str );
145             }
146              
147             sub validate_globals {
148             my ($self) = @_;
149              
150             my $logger = $self->bot->logger('core.config.globals');
151              
152             my %global = (
153             nick => 'lispy',
154             );
155              
156             $self->config->{'global'} = \%global unless exists $self->config->{'global'};
157              
158             foreach my $k (keys %global) {
159             $self->config->{'global'}{$k} = $global{$k} unless exists $self->config->{'global'}{$k};
160             }
161              
162             $logger->debug(sprintf('Global setting %s = %s.', $_, $self->config->{'global'}{$_}))
163             for sort keys %{$self->config->{'global'}};
164              
165             $self->config->{'global'}{'nick'} = App::RoboBot::Nick->new(
166             config => $self,
167             name => $self->config->{'global'}{'nick'}
168             );
169             }
170              
171             sub validate_database {
172             my ($self) = @_;
173              
174             my $logger = $self->bot->logger('core.config.database');
175              
176             my %database = (
177             name => 'robobot',
178             );
179              
180             $self->config->{'database'} = \%database unless exists $self->config->{'database'};
181              
182             foreach my $k (keys %database) {
183             $self->config->{'database'}{$k} = $database{$k} unless exists $self->config->{'database'}{$k};
184             }
185              
186             if (exists $self->config->{'database'}{'primary'} && ref($self->config->{'database'}{'primary'}) eq 'HASH') {
187             $logger->debug('Establishing database connection using explicit configuration hash.');
188             $self->db(DBIx::DataStore->new({ config => $self->config->{'database'} })) or die "Could not validate explicit database connection!";
189             } else {
190             $logger->debug('Establishing database connection using named DataStore definition.');
191             $self->db(DBIx::DataStore->new($self->config->{'database'}{'name'})) or die "Could not validate named database connection!";
192             }
193              
194             $self->bot->migrate_database;
195             }
196              
197             sub validate_networks {
198             my ($self) = @_;
199              
200             my $logger = $self->bot->logger('core.config.networks');
201              
202             my @networks;
203             my @channels;
204              
205             $logger->debug('Creating network factory.');
206              
207             my $nfactory = App::RoboBot::NetworkFactory->new(
208             bot => $self->bot,
209             config => $self,
210             nick => $self->config->{'global'}{'nick'},
211             );
212              
213             foreach my $network_name (keys %{$self->config->{'network'}}) {
214             $logger->debug(sprintf('Getting configuration data for network %s.', $network_name));
215             my $net_cfg = $self->config->{'network'}{$network_name};
216              
217             # Do not load (and eventually connect to) the network if the 'enabled'
218             # property exists and is set to a falsey value.
219             next if exists $self->config->{'network'}{$network_name}{'enabled'}
220             && !$self->config->{'network'}{$network_name}{'enabled'};
221              
222             $logger->debug(sprintf('Using factory to create network entry for %s.', $network_name));
223             push(@networks, $nfactory->create($network_name, $net_cfg));
224              
225             my @network_channels;
226              
227             # Coerce channel list into an arrayref if only a single channel is
228             # listed for this network.
229             $net_cfg->{'channel'} = [] unless exists $net_cfg->{'channel'};
230             $net_cfg->{'channel'} = [$net_cfg->{'channel'}] if ref($net_cfg->{'channel'}) ne 'ARRAY';
231              
232             foreach my $chan_name (@{$net_cfg->{'channel'}}) {
233             $logger->debug(sprintf('Adding %s to channel list for network %s.', $chan_name, $network_name));
234             push(@network_channels, App::RoboBot::Channel->new( config => $self, network => $networks[-1], name => $chan_name));
235             push(@channels, $network_channels[-1]);
236             }
237              
238             $networks[-1]->channels([@network_channels]);
239             }
240              
241             $logger->debug('Assigning networks list to bot.');
242             $self->networks({ map { $_->name => $_ } @networks });
243             $self->channels(\@channels);
244             }
245              
246             sub validate_plugins {
247             my ($self) = @_;
248              
249             my $logger = $self->bot->logger('core.config.plugins');
250              
251             foreach my $plugin_name (keys %{$self->config->{'plugin'}}) {
252             $logger->debug(sprintf('Collecting configuration data for %s plugin.', $plugin_name));
253             $self->plugins->{lc($plugin_name)} = $self->config->{'plugin'}{$plugin_name};
254             }
255             }
256              
257             __PACKAGE__->meta->make_immutable;
258              
259             1;