adds the ability to filter generated code
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Base.pm
index 16cde2b..2a67684 100644 (file)
@@ -17,8 +17,7 @@ use File::Temp qw//;
 use Class::Unload;
 use Class::Inspector ();
 use Scalar::Util 'looks_like_number';
-use File::Slurp 'read_file';
-use DBIx::Class::Schema::Loader::Utils qw/split_name dumper_squashed eval_package_without_redefine_warnings class_path/;
+use DBIx::Class::Schema::Loader::Utils qw/split_name dumper_squashed eval_package_without_redefine_warnings class_path slurp_file/;
 use DBIx::Class::Schema::Loader::Optional::Dependencies ();
 use Try::Tiny;
 use DBIx::Class ();
@@ -73,8 +72,13 @@ __PACKAGE__->mk_group_ro_accessors('simple', qw/
                                 loader_class
                                 qualify_objects
                                 tables
+                                table_comments_table
+                                column_comments_table
                                 class_to_table
                                 uniq_to_primary
+                                quiet
+
+                                filter_generated_text
 /);
 
 
@@ -102,6 +106,17 @@ __PACKAGE__->mk_group_accessors('simple', qw/
                                 naming_set
 /);
 
+my $CURRENT_V = 'v7';
+
+my @CLASS_ARGS = qw(
+    schema_components schema_base_class result_base_class
+    additional_base_classes left_base_classes additional_classes components
+    result_roles
+);
+
+my $LF   = "\x0a";
+my $CRLF = "\x0d\x0a";
+
 =head1 NAME
 
 DBIx::Class::Schema::Loader::Base - Base DBIx::Class::Schema::Loader Implementation.
@@ -240,13 +255,35 @@ next major version upgrade:
 
     __PACKAGE__->naming('v7');
 
+=head2 quiet
+
+If true, will not print the usual C<Dumping manual schema ... Schema dump
+completed.> messages. Does not affect warnings (except for warnings related to
+L</really_erase_my_files>.)
+
 =head2 generate_pod
 
 By default POD will be generated for columns and relationships, using database
 metadata for the text if available and supported.
 
-Reading database metadata (e.g. C<COMMENT ON TABLE some_table ...>) is only
-supported for Postgres right now.
+Comment metadata can be stored in two ways.
+
+The first is that you can create two tables named C<table_comments> and
+C<column_comments> respectively.  They both need to have columns named
+C<table_name> and C<comment_text>.  The second one needs to have a column
+named C<column_name>.  Then data stored in these tables will be used as a
+source of metadata about tables and comments.
+
+(If you wish you can change the name of these tables with the parameters
+L</table_comments_table> and L</column_comments_table>.)
+
+As a fallback you can use built-in commenting mechanisms.  Currently this is
+only supported for PostgreSQL, Oracle and MySQL.  To create comments in
+PostgreSQL you add statements of the form C<COMMENT ON TABLE some_table IS
+'...'>, the same syntax is used in Oracle. To create comments in MySQL you add
+C<COMMENT '...'> to the end of the column or table definition.  Note that MySQL
+restricts the length of comments, and also does not handle complex Unicode
+characters properly.
 
 Set this to C<0> to turn off all POD generation.
 
@@ -282,6 +319,16 @@ which it will be forced into a separate description section.
 
 The default is C<60>
 
+=head2 table_comments_table
+
+The table to look for comments about tables in.  By default C<table_comments>.
+See L</generate_pod> for details.
+
+=head2 column_comments_table
+
+The table to look for comments about columns in.  By default C<column_comments>.
+See L</generate_pod> for details.
+
 =head2 relationship_attrs
 
 Hashref of attributes to pass to each generated relationship, listed
@@ -637,6 +684,21 @@ Automatically promotes the largest unique constraints with non-nullable columns
 on tables to primary keys, assuming there is only one largest unique
 constraint.
 
+=head2 filter_generated_text
+
+An optional hook that lets you filter the generated text for various classes through
+a function that change it in any way that you want.  The function will receive the class
+and text, and returns the new text to use instead.  For instance you could add
+custom comment, run C<perltidy>, or do anything else that you want.
+
+If this exists but fails to return text matching C</package/>, no file will be generated.
+
+    filter_generated_base => sub {
+        my ($class, $text) = @_;
+       ...
+       return $new_text;
+    }
+
 =head1 METHODS
 
 None of these methods are intended for direct invocation by regular
@@ -645,14 +707,6 @@ L<DBIx::Class::Schema::Loader>.
 
 =cut
 
-my $CURRENT_V = 'v7';
-
-my @CLASS_ARGS = qw(
-    schema_components schema_base_class result_base_class
-    additional_base_classes left_base_classes additional_classes components
-    result_roles
-);
-
 # ensure that a peice of object data is a valid arrayref, creating
 # an empty one or encapsulating whatever's there.
 sub _ensure_arrayref {
@@ -774,6 +828,8 @@ sub new {
 
     $self->{schema_class} ||= ( ref $self->{schema} || $self->{schema} );
     $self->{schema} ||= $self->{schema_class};
+    $self->{table_comments_table} ||= 'table_comments';
+    $self->{column_comments_table} ||= 'column_comments';
 
     croak "dump_overwrite is deprecated.  Please read the"
         . " DBIx::Class::Schema::Loader::Base documentation"
@@ -1109,7 +1165,7 @@ sub _load_external {
         warn qq/# Loaded external class definition for '$class'\n/
             if $self->debug;
 
-        my $code = $self->_rewrite_old_classnames(scalar read_file($real_inc_path, binmode => ':encoding(UTF-8)'));
+        my $code = $self->_rewrite_old_classnames(slurp_file $real_inc_path);
 
         if ($self->dynamic) { # load the class too
             eval_package_without_redefine_warnings($class, $code);
@@ -1132,7 +1188,7 @@ sub _load_external {
     }
 
     if ($old_real_inc_path) {
-        my $code = read_file($old_real_inc_path, binmode => ':encoding(UTF-8)');
+        my $code = slurp_file $old_real_inc_path;
 
         $self->_ext_stmt($class, <<"EOF");
 
@@ -1283,11 +1339,10 @@ sub _load_tables {
 
     if(!$self->skip_relationships) {
         # The relationship loader needs a working schema
-        $self->{quiet} = 1;
+        local $self->{quiet} = 1;
         local $self->{dump_directory} = $self->{temp_directory};
         $self->_reload_classes(\@tables);
         $self->_load_relationships(\@tables);
-        $self->{quiet} = 0;
 
         # Remove that temp dir from INC so it doesn't get reloaded
         @INC = grep $_ ne $self->dump_directory, @INC;
@@ -1386,7 +1441,7 @@ sub _reload_class {
         eval_package_without_redefine_warnings ($class, "require $class");
     }
     catch {
-        my $source = read_file($self->_get_dump_filename($class), binmode => ':encoding(UTF-8)');
+        my $source = slurp_file $self->_get_dump_filename($class);
         die "Failed to reload class $class: $_.\n\nCLASS SOURCE:\n\n$source";
     };
 }
@@ -1440,7 +1495,7 @@ sub _dump_to_dir {
 
     my $target_dir = $self->dump_directory;
     warn "Dumping manual schema for $schema_class to directory $target_dir ...\n"
-        unless $self->{dynamic} or $self->{quiet};
+        unless $self->dynamic or $self->quiet;
 
     my $schema_text =
           qq|package $schema_class;\n\n|
@@ -1539,7 +1594,7 @@ sub _dump_to_dir {
         }
     }
 
-    warn "Schema dump completed.\n" unless $self->{dynamic} or $self->{quiet};
+    warn "Schema dump completed.\n" unless $self->dynamic or $self->quiet;
 
 }
 
@@ -1559,7 +1614,7 @@ sub _write_classfile {
 
     if (-f $filename && $self->really_erase_my_files) {
         warn "Deleting existing file '$filename' due to "
-            . "'really_erase_my_files' setting\n" unless $self->{quiet};
+            . "'really_erase_my_files' setting\n" unless $self->quiet;
         unlink($filename);
     }
 
@@ -1617,7 +1672,15 @@ sub _write_classfile {
     $text .= qq|$_\n|
         for @{$self->{_dump_storage}->{$class} || []};
 
-    # Check and see if the dump is infact differnt
+    if ($self->{filter_generated_text}) {
+        $text = $self->{filter_generated_text}->($class, $text);
+       if (not $text or not $text =~ /package/) {
+           warn("$class skipped due to filter") if $self->debug;
+           return;
+       }
+    }
+
+    # Check and see if the dump is in fact different
 
     my $compare_to;
     if ($old_md5) {
@@ -1679,7 +1742,7 @@ sub _parse_generated_file {
         or croak "Cannot open '$fn' for reading: $!";
 
     my $mark_re =
-        qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
+        qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\r?\n};
 
     my ($md5, $ts, $ver, $gen);
     while(<$fh>) {
@@ -1688,7 +1751,7 @@ sub _parse_generated_file {
             $md5 = $2;
 
             # Pull out the version and timestamp from the line above
-            ($ver, $ts) = $gen =~ m/^# Created by DBIx::Class::Schema::Loader v(.*?) @ (.*?)\Z/m;
+            ($ver, $ts) = $gen =~ m/^# Created by DBIx::Class::Schema::Loader v(.*?) @ (.*?)\r?\Z/m;
 
             $gen .= $pre_md5;
             croak "Checksum mismatch in '$fn', the auto-generated part of the file has been modified outside of this loader.  Aborting.\nIf you want to overwrite these modifications, set the 'overwrite_modifications' loader option.\n"
@@ -1704,7 +1767,10 @@ sub _parse_generated_file {
     my $custom = do { local $/; <$fh> }
         if $md5;
 
-    close ($fh);
+    $custom ||= '';
+    $custom =~ s/$CRLF|$LF/\n/g;
+
+    close $fh;
 
     return ($gen, $md5, $ver, $ts, $custom);
 }