| 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; |