6   unshift @INC, "modules/override"; # Use our own versions of various modules (e.g. YAML).
 
   7   push    @INC, "modules/fallback"; # Only use our own versions of modules if there's no system version.
 
  10 use CGI qw( -no_xhtml);
 
  13 use Digest::MD5 qw(md5_hex);
 
  14 use English qw( -no_match_vars );
 
  16 use List::MoreUtils qw(none);
 
  18 use Rose::DB::Object 0.809;
 
  28 use SL::DB::Helper::ALL;
 
  29 use SL::DB::Helper::Mappings;
 
  31 my %blacklist     = SL::DB::Helper::Mappings->get_blacklist;
 
  32 my %package_names = SL::DB::Helper::Mappings->get_package_names;
 
  38 our $script =  __FILE__;
 
  41 $OUTPUT_AUTOFLUSH       = 1;
 
  42 $Data::Dumper::Sortkeys = 1;
 
  44 our $meta_path    = "SL/DB/MetaSetup";
 
  45 our $manager_path = "SL/DB/Manager";
 
  49 our %foreign_key_name_map = (
 
  50   oe                   => { payment => 'payment_terms', },
 
  51   ar                   => { payment => 'payment_terms', },
 
  52   ap                   => { payment => 'payment_terms', },
 
  54   orderitems           => { parts => 'part', trans => 'order', },
 
  55   delivery_order_items => { parts => 'part' },
 
  56   invoice              => { parts => 'part' },
 
  57   follow_ups           => { 'employee_obj' => 'created_for' },
 
  59   periodic_invoices_configs => { oe => 'order' },
 
  64   SL::LxOfficeConf->read;
 
  66   my $client = $config{client} || $::lx_office_conf{devel}{client};
 
  69     error("No client found in config. Please provide a client:");
 
  73   $::lxdebug      = LXDebug->new();
 
  74   $::locale       = Locale->new("de");
 
  76   $form->{script} = 'rose_meta_data.pl';
 
  77   $::auth         = SL::Auth->new();
 
  79   if (!$::auth->set_client($client)) {
 
  80     error("No client with ID or name '$client' found in config. Please provide a client:");
 
  84   foreach (($meta_path, $manager_path)) {
 
  93   ($schema, $table) = split(m/\./, $table) if $table =~ m/\./;
 
  94   my $package    =  ucfirst($spec[1] || $spec[0]);
 
  95   $package       =~ s/_+(.)/uc($1)/ge;
 
  96   my $meta_file  =  "${meta_path}/${package}.pm";
 
  97   my $mngr_file  =  "${manager_path}/${package}.pm";
 
  98   my $file       =  "SL/DB/${package}.pm";
 
 100   my $schema_str = $schema ? <<CODE : '';
 
 101 __PACKAGE__->meta->schema('$schema');
 
 105     package SL::DB::AUTO::$package;
 
 107     use base qw(SL::DB::Object);
 
 109     __PACKAGE__->meta->table('$table');
 
 111     __PACKAGE__->meta->auto_initialize;
 
 116     error("Error in execution for table '$table'");
 
 117     error("'$EVAL_ERROR'") unless $config{quiet};
 
 121   my %args = (indent => 2, use_setup => 0);
 
 123   my $definition =  "SL::DB::AUTO::$package"->meta->perl_class_definition(%args);
 
 124   $definition =~ s/\n+__PACKAGE__->meta->initialize;\n+/\n\n/;
 
 125   $definition =~ s/::AUTO::/::/g;
 
 128   # Sort column definitions alphabetically
 
 129   if ($definition =~ m/__PACKAGE__->meta->columns\( \n (.+?) \n \);/msx) {
 
 130     my ($start, $end)  = ($-[1], $+[1]);
 
 131     my $sorted_columns = join "\n", sort split m/\n/, $1;
 
 132     substr $definition, $start, $end - $start, $sorted_columns;
 
 136   my $foreign_key_definition = "SL::DB::AUTO::$package"->meta->perl_foreign_keys_definition(%args);
 
 137   $foreign_key_definition =~ s/::AUTO::/::/g;
 
 139   if ($foreign_key_definition && ($definition =~ /\Q$foreign_key_definition\E/)) {
 
 140     my ($start, $end) = ($-[0], $+[0]);
 
 142     while (my ($auto_generated_name, $desired_name) = each %{ $foreign_key_name_map{$table} || {} }) {
 
 143       $foreign_key_definition =~ s/^ \s \s ${auto_generated_name} \b/  ${desired_name}/msx;
 
 146     # Sort foreign key definitions alphabetically
 
 147     if ($foreign_key_definition =~ m/\(\n(.+)\n\)/s) {
 
 148       my ($list_start, $list_end) = ($-[0], $+[0]);
 
 149       my @foreign_keys            = split m/\n\n/m, $1;
 
 150       my $sorted_foreign_keys     = "(\n" . join("\n\n", sort @foreign_keys) . "\n)";
 
 152       substr $foreign_key_definition, $list_start, $list_end - $list_start, $sorted_foreign_keys;;
 
 155     substr($definition, $start, $end - $start) = $foreign_key_definition;
 
 158   $definition =~ s/(meta->table.*)\n/$1\n$schema_str/m if $schema;
 
 160   my $full_definition = <<CODE;
 
 161 # This file has been auto-generated. Do not modify it; it will be overwritten
 
 162 # by $::script automatically.
 
 166   my $meta_definition = <<CODE;
 
 167 # This file has been auto-generated only because it didn't exist.
 
 168 # Feel free to modify it at will; it will not be overwritten automatically.
 
 170 package SL::DB::${package};
 
 174 use SL::DB::MetaSetup::${package};
 
 175 use SL::DB::Manager::${package};
 
 177 __PACKAGE__->meta->initialize;
 
 182   my $file_exists = -f $meta_file;
 
 184     my $old_size    = -s $meta_file;
 
 185     my $orig_file   = do { local(@ARGV, $/) = ($meta_file); <> };
 
 186     my $old_md5     = md5_hex($orig_file);
 
 187     my $new_size    = length $full_definition;
 
 188     my $new_md5     = md5_hex($full_definition);
 
 189     if ($old_size == $new_size && $old_md5 eq $new_md5) {
 
 190       notice("No changes in $meta_file, skipping.") unless $config{quiet};
 
 194     show_diff(\$orig_file, \$full_definition) if $config{show_diff};
 
 197   if (!$config{nocommit}) {
 
 198     open my $out, ">", $meta_file || die;
 
 199     print $out $full_definition;
 
 202   notice("File '$meta_file' " . ($file_exists ? 'updated' : 'created') . " for table '$table'");
 
 206   if (!$config{nocommit}) {
 
 207     open my $out, ">", $file || die;
 
 208     print $out $meta_definition;
 
 211   notice("File '$file' created as well.");
 
 213   return if -f $mngr_file;
 
 215   if (!$config{nocommit}) {
 
 216     open my $out, ">", $mngr_file || die;
 
 218 # This file has been auto-generated only because it didn't exist.
 
 219 # Feel free to modify it at will; it will not be overwritten automatically.
 
 221 package SL::DB::Manager::${package};
 
 225 use SL::DB::Helper::Manager;
 
 226 use base qw(SL::DB::Helper::Manager);
 
 228 sub object_class { 'SL::DB::${package}' }
 
 230 __PACKAGE__->make_manager_methods;
 
 236   notice("File '$mngr_file' created as well.");
 
 242     'client=s'          => \ my $client,
 
 244     'no-commit|dry-run' => \ my $nocommit,
 
 245     help                => sub { pod2usage(verbose => 99, sections => 'NAME|SYNOPSIS|OPTIONS') },
 
 246     quiet               => \ my $quiet,
 
 250   $options->{client}   = $client;
 
 251   $options->{all}      = $all;
 
 252   $options->{nocommit} = $nocommit;
 
 253   $options->{quiet}    = $quiet;
 
 254   $options->{color}    = -t STDOUT ? 1 : 0;
 
 257     if (eval { require Text::Diff; 1 }) {
 
 258       $options->{show_diff} = 1;
 
 260       error('Could not load Text::Diff. Sorry, no diffs for you.');
 
 266    my ($text_a, $text_b) = @_;
 
 273    Text::Diff::diff($text_a, $text_b, { OUTPUT => sub {
 
 274      for (split /\n/, $_[0]) {
 
 275        if ($config{color}) {
 
 276          print colored($_, $colors{substr($_, 0, 1)}), $/;
 
 285   pod2usage(verbose => 99, sections => 'SYNOPSIS');
 
 291     my $db  = SL::DB::create(undef, 'KIVITENDO');
 
 292     @tables = grep { my $table = $_; none { $_ eq $table } @{ $blacklist{KIVITENDO} } } $db->list_tables;
 
 297     error("You specified neither --all nor any specific tables.");
 
 305   print STDERR colored(shift, 'red'), $/;
 
 312 parse_args(\%config);
 
 314 my @tables = make_tables();
 
 316 my @unknown_tables = grep { !$package_names{KIVITENDO}->{$_} } @tables;
 
 317 if (@unknown_tables) {
 
 318   error("The following tables do not have entries in \%SL::DB::Helper::Mappings::kivitendo_package_names: " . join(' ', sort @unknown_tables));
 
 322 process_table($_, $package_names{KIVITENDO}->{$_}) for @tables;
 
 332 rose_auto_create_model - mana Rose::DB::Object classes for kivitendo
 
 336   scripts/rose_auto_create_model.pl --client name-or-id table1 [table2 ...]
 
 337   scripts/rose_auto_create_model.pl --client name-or-id [--all|-a]
 
 340   scripts/rose_auto_create_model.pl --client name-or-id --all
 
 342   # updates only customer table, login taken from config
 
 343   scripts/rose_auto_create_model.pl customer
 
 345   # updates only parts table, package will be Part
 
 346   scripts/rose_auto_create_model.pl parts=Part
 
 348   # try to update parts, but don't do it. tell what would happen in detail
 
 349   scripts/rose_auto_create_model.pl --no-commit parts
 
 353 Rose::DB::Object comes with a nice function named auto initialization with code
 
 354 generation. The documentation of Rose describes it like this:
 
 356 I<[...] auto-initializing metadata at runtime by querying the database has many
 
 357 caveats. An alternate approach is to query the database for metadata just once,
 
 358 and then generate the equivalent Perl code which can be pasted directly into
 
 359 the class definition in place of the call to auto_initialize.>
 
 361 I<Like the auto-initialization process itself, perl code generation has a
 
 362 convenient wrapper method as well as separate methods for the individual parts.
 
 363 All of the perl code generation methods begin with "perl_", and they support
 
 364 some rudimentary code formatting options to help the code conform to you
 
 365 preferred style. Examples can be found with the documentation for each perl_*
 
 368 I<This hybrid approach to metadata population strikes a good balance between
 
 369 upfront effort and ongoing maintenance. Auto-generating the Perl code for the
 
 370 initial class definition saves a lot of tedious typing. From that point on,
 
 371 manually correcting and maintaining the definition is a small price to pay for
 
 372 the decreased start-up cost, the ability to use the class in the absence of a
 
 373 database connection, and the piece of mind that comes from knowing that your
 
 374 class is stable, and won't change behind your back in response to an "action at
 
 375 a distance" (i.e., a database schema update).>
 
 377 Unfortunately this reads easier than it is, since classes need to go into the
 
 378 right package and directory, certain stuff needs to be adjusted and table names
 
 379 need to be translated into their class names. This script will wrap all that
 
 380 behind a few simple options.
 
 382 In the most basic version, just give it a login and a table name, and it will
 
 383 load the schema information for this table and create the appropriate class
 
 384 files, or update them if already present.
 
 386 Each table has three associated files. A C<SL::DB::MetaSetup::*>
 
 387 class, which is a perl version of the schema definition, a
 
 388 C<SL::DB::*> class file and a C<SL::DB::Manager::*> manager class
 
 389 file. The first one will be updated if the schema changes, the second
 
 390 and third ones will only be created if it they do not exist.
 
 396 =item C<--client, -c CLIENT>
 
 398 Provide a client whose database settings are used. If not present the
 
 399 client is loaded from the config key C<devel/client>. If that too is
 
 400 not found, an error is thrown.
 
 402 Note that C<CLIENT> can be either a database ID or a client's name.
 
 406 Process all tables from the database. Only those that are blacklistes in
 
 407 L<SL::DB::Helper::Mappings> are excluded.
 
 409 =item C<--no-commit, -n>
 
 413 Do not write back generated files. This will do everything as usual but not
 
 414 actually modify any file.
 
 418 Displays diff for selected file, if file is present and newer file is
 
 419 different. Beware, does not imply C<--no-commit>.
 
 427 Does not print extra information, such as skipped files that were not
 
 428 changed and errors where the auto initialization failed.
 
 438 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>,
 
 439 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>