The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package DBIx::Class::EasyConf::YAML;

use 5.008008;
use strict;

use YAML;
our $VERSION = '0.11';
our $VERBOSE = undef;

sub configure {
  my ($package, $fh) = @_;

  my %args = map { lc $_ => 1 } @ARGV;
  $VERBOSE    = $args{verbose} ? 1 : undef;
  my $config  = $args{configuration} ? 1 : undef;

  {
    no strict 'refs';
    $fh ||=  *{"${package}::DATA"};
  }

  my $yaml = do { local $/; <$fh> };
  my $data = YAML::Load($yaml);

  _set_table         ($package, $data);
  _set_columns       ($package, $data);
  _set_pk            ($package, $data);
  _set_unique        ($package, $data);
  _set_relationships ($package, $data);

  print_configuration($package) if $config;
}


sub _set_table {
  my ($package, $data) = @_;
  my $table = $data->{table}
    or die "Cannot configure $package: table not specified.\n";
  $package->table($table);
}

sub _set_pk {
  my ($package, $data) = @_;
  my $pk = $data->{primary_key}
    or die "Cannot configure $package: primary key not specified.\n";
  $package->set_primary_key(ref $pk eq 'ARRAY' ? @$pk : $pk);
}

sub _set_columns {
  my ($package, $data) = @_;
  die "Cannot configure $package: columns not properly specified.\n"
    unless ref $data->{columns} eq 'HASH';
  my $columns = $data->{columns};
  $package->add_columns(%{ $columns });
}

sub _set_unique {
  my ($package, $data) = @_;
  return unless ref $data->{unique} eq 'HASH';
  for my $name (keys %{ $data->{unique} }) {
    my $spec = $data->{unique}->{$name};
    my $cols = ref $spec eq 'ARRAY' ? $spec : [ $spec ];
    $package->add_unique_constraint($name => $cols);
  }
}

sub _set_relationships {
  my ($package, $data) = @_;
  return unless ref $data->{relationships} eq 'ARRAY';
  for my $relation (@{ $data->{relationships} }) {
    my ($relation, $value) = each %{ $relation };
    my ($type, $class, $condition, $attrs) = @{ $value };
    $class = resolve_relative_class($class, $package) 
      unless $type eq 'many_to_many';
    $package->$type($relation, $class, $condition, $attrs);

    if ($VERBOSE) {
      my %relationships = map { $_ => 1 } $package->relationships;
      my $ok = exists $relationships{$relation} ? 1 : undef;
      my $space_one = " " x (50 - length $package);
      my $space_two = " " x (20 - length $relation);
      print STDERR 
	($ok ? "RELATION OK:" : "RELATION FAIL: "),
	  "$package $space_one $relation $space_two $type\n",
	    "\t$package->$type($relation, $class, $condition)\n\n";
    }
  }
}
  
sub resolve_relative_class {
  my ($class, $package) = @_;
  return $class if $class =~ /::/; # fully qualified already.
  (my $new_package = $package) =~ s/::[^:]+$/::$class/;
  return $new_package;
}

sub print_configuration {
  my $package = shift;
  local $\ = "\n";
  print "Table: ", $package->table;
  print "Primary Key: ", join ", " => $package->primary_columns;
  
  print "Columns: ";
  for my $col ($package->columns) {
    my %info = %{ $package->column_info($col) };
    print "\t$col:";
    print "\t\t$_ => $info{$_}" for sort keys %info;
  }
  
  print "Relationships:";
  for my $rel ($package->relationships) {
    my %info = %{ $package->relationship_info($rel) };
    print "\t$rel:";
    for my $key (sort keys %info) {
      if (ref $info{$key}) {
	print "\t\t$key:";
	print "\t\t\t$_ => $info{$key}->{$_}" for sort keys %{ $info{$key} };
      }
      else {
	print "\t\t$key => $info{$key}";
      }
    }
  }

  print "Constraints: ";
  my %uniq = $package->unique_constraints;
  map { print "\t$_ => ", join ", " => @{ $uniq{$_} } } keys %uniq;
}


1;
__END__

=head1 NAME

DBIx::Class::EasyConf::YAML - DBIx::Class Component for text based
schema configuration

=head1 SYNOPSIS

  package MyApp::Schema::Result::SomeTable;
  use parent qw[ DBIx::Class::Core ];
  __PACKAGE__->load_components(qw[ EasyConf::YAML ]);
  our $DDL ||= __PACKAGE__->configure();

  1;

  __DATA__
  --->
  =head1 NAME
  
  MyAPP::Schema::Result::SomeTable - Random Schema File
  
  =head1 DESCRIPTION
  ---
    table: some_table
    primary_key: id
    columns:
      id:
        type: int
        nullable: 0
        is_auto_increment: 1
      name:
        type: VARCHAR
        size: 16
        is_nullable: 0
      description:
        type: VARCHAR
        size: 128
        is_nullable: 1
    relationships:
      - other_relation:
          - belongs_to
          - MyApp::Schema::Result::SomeOtherTable
          - id
    unique:
      name_uniq: id
      desc_uniq: 
        - name
        - description
  # EndOfYAML


=head1 DESCRIPTION

Generates a DBIx::Class::ResultSource from a YAML description.  If the
YAML is presented as shown in the SYNOPSIS the ResultSource class will
be self POD documenting.  If the class is executed with
'configuration' in @ARGV, a summary of the ResultSource is printed to
standard out.

=head1 GOTCHA

Note that relationships sometimes need to be created in a particular
order (such is the case when defining many_to_many relationships).
Given that, the relationships key takes an array of hashes; watch the
indentation carefully (it's correct above).  It'd be possible to
optionally allow a hash here, but I think that might lead to hard to
find errors.  Drop me a line if you have a strong opinion.

=head1 RATIONALE

The "self-documenting" bit mentioned above.  Also, there's a boatload
of punctuation and quoting that is required to do this the usual way;
it's less error prone, in my opinion, to use YAML as good text editors
will do the right thing by it.

=head1 AUTHOR

kevin montuori <montuori@gmail.com>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009 by Kevin Montuori & mconsultancy

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.

=cut