# Copyright © 2010 Julian Blake Kongslie # Licensed under the BSD 3-clause license. use strict; use warnings; package Piny::Config; use Moose; use Moose::Util::TypeConstraints; use MooseX::StrictConstructor; use Carp; use Config::Simple qw( -lc ); use Piny::Environment; # Types subtype 'GitBool' => as 'Str' => where { $_ =~ /^(1|0|true|false)$/i } => message { 'Not correct format for a git-compatible boolean.' } ; subtype 'Path' => as 'Str' => where { $_ =~ /^\// and -e $_ } => message { 'Not an absolute path, or does not exist.' } ; subtype 'PathDir' => as 'Path' => where { -d $_ } => message { 'Not an absolute path, or not a directory.' } ; subtype 'HttpUrl' => as 'Str' => where { $_ =~ /^(http|https):\/\//i } => message { 'Not a http:// or https:// URL.' } ; subtype 'HttpsUrl' => as 'Str' => where { $_ =~ /^https:\/\//i } => message { 'Not a https:// URL.' } ; subtype 'RepoPermission' => as 'Str' => where { $_ eq "0666" or $_ eq "0664" or $_ eq "0660" or $_ eq "0640" or $_ eq "group" or $_ eq "true" or $_ eq "false" or $_ eq "all" or $_ eq "everybody" or $_ eq "world" or $_ eq "1" or $_ eq "2" } => message { 'Must be one of 0666, 0664 (or all, everybody, world), 0660 (or true, 1, group), or 0640 (or false, 0).' } ; # Attributes has 'confpath' => ( is => 'ro' , isa => 'Str' , predicate => 'has_confpath' ); has '_conf' => ( is => 'ro' , isa => 'HashRef[Str]' , lazy_build => 1 , clearer => 'clear_conf' , init_arg => undef ); # Builder methods # If constructed with just one argument, then treat it as a config path. around BUILDARGS => sub { my ( $orig, $class ) = ( shift, shift ); if ( @_ == 1 && ! ref $_[0] ) { return $class->$orig( confpath => $_[0] ); } else { return $class->$orig( @_ ); }; }; sub _build__conf { my ( $s ) = @_; my $conf; if ( $s->has_confpath and -e $s->confpath ) { $conf = Config::Simple->new( $s->confpath ); if ( defined $conf ) { $conf = $conf->vars; } else { $conf = { }; }; } else { $conf = { }; }; my $home = Piny::Environment->instance->user->home . "/.gitconfig"; if ( -s $home ) { my $userconf = Config::Simple->new( $home ); if ( defined $userconf ) { $userconf = $userconf->vars; foreach my $key ( keys %$userconf ) { if ( not exists $conf->{$key} ) { $conf->{$key} = $userconf->{$key}; }; }; }; }; if ( -s "/etc/piny-default.conf" ) { my $default = Config::Simple->new( "/etc/piny-default.conf" ); if ( defined $default ) { $default = $default->vars; foreach my $key ( keys %$default ) { if ( not exists $conf->{$key} ) { $conf->{$key} = $default->{$key}; }; }; }; }; if ( -s "/etc/piny-override.conf" ) { my $override = Config::Simple->new( "/etc/piny-override.conf" ); if ( defined $override ) { $override = $override->vars; foreach my $key ( keys %$override ) { $conf->{$key} = $override->{$key}; }; }; }; foreach my $key ( keys %$conf ) { $conf->{$key} = "" unless defined $conf->{$key}; }; return $conf; }; # Save the config sub save { my ( $s ) = @_; if ( not $s->has_confpath ) { croak "Can't save a Piny::Config if the confpath is not set!"; }; my ( $override, $userconf, $default ) = ( {}, {}, {} ); if ( -s "/etc/piny-override.conf" ) { $override = Config::Simple->new( "/etc/piny-override.conf" )->vars; }; my $home = Piny::Environment->instance->user->home . "/.gitconfig"; if ( $s->confpath ne $home and -s $home ) { $userconf = Config::Simple->new( $home )->vars; }; if ( -s "/etc/piny-default.conf" ) { $default = Config::Simple->new( "/etc/piny-default.conf" )->vars; }; foreach my $key ( keys %{$s->_conf} ) { if ( exists $override->{$key} ) { $s->_conf->{$key} = $override->{$key}; delete $s->_conf->{$key} if $key =~ /^piny\./; } elsif ( exists $userconf->{$key} ) { delete $s->_conf->{$key} if $userconf->{$key} eq $s->_conf->{$key}; } elsif ( exists $default->{$key} ) { delete $s->_conf->{$key} if $key =~ /^piny\./ and $default->{$key} eq $s->_conf->{$key}; }; }; my $cs = Config::Simple->new( syntax => "ini" ); foreach my $key ( keys %{$s->_conf} ) { $cs->param( $key, $s->_conf->{$key} ); }; $cs->write( $s->confpath ); $s->clear_conf; }; # Dump all tweakables my @tweakables = ( ); sub all_tweakables { my ( $s ) = @_; my $r = { }; foreach my $t ( @tweakables ) { eval { $r->{$t} = $s->$t; }; }; return $r; }; # Tweakable helper sub tweakable { my ( $attr, $default, $isa, $inhibit ) = @_; $attr = lc $attr; my $attrname = $attr; $attrname =~ s/_/./; if ( $attrname =~ /_/ ) { croak "Illegal attribute name $attrname! (use only one underbar)"; }; push @tweakables, $attr unless defined $inhibit and $inhibit; has $attr => ( is => 'rw' , isa => $isa , lazy_build => 1 , trigger => sub { my ( $s, $new, $old ) = @_; $s->_conf->{$attrname} = $new; if ( $s->has_confpath ) { $s->save; } else { carp "Attribute $attrname modification ignored!"; }; $s->clear_conf; my $clearer = "clear_$attr"; $s->$clearer; } ); my $builder = sub { my ( $s ) = @_; if ( exists $s->_conf->{$attrname} ) { return $s->_conf->{$attrname}; } else { return $default; }; }; { no strict "refs"; *{"_build_" . $attr} = $builder; }; }; # The tweakables # Global tweakables, which only make sense in the global config file. tweakable "piny_adminemail" => "jrayhawk\@omgwallhack.org", 'Str', 'inhibit'; tweakable "piny_template" => undef, 'Maybe[PathDir]', 'inhibit'; # Repo-specific tweakables, in the repos' .git/config files. tweakable "piny_ikiwiki" => "true", 'GitBool'; tweakable "piny_ikiwikidestdir" => "/srv/www/piny.be/", 'PathDir'; tweakable "piny_ikiwikisrcdir" => "/srv/ikiwiki/", 'PathDir'; tweakable "piny_ikiwikiurl" => "http://piny.be/", 'HttpUrl'; tweakable "piny_ikiwikisecureurl" => "https://secure.piny.be/", 'HttpsUrl'; tweakable "piny_ikiwikisecurepath" => "/srv/www/secure.piny.be/", 'PathDir'; tweakable "core_sharedrepository" => '0664', 'RepoPermission'; tweakable "receive_denynonfastforwards" => "true", 'GitBool'; # User-specific tweakables, in the users' ~/.gitconfig files. tweakable "user_email" => undef, 'Maybe[Str]'; # Moose boilerplate __PACKAGE__->meta->make_immutable; 1;