#!/usr/local/bin/perl -w # -*- perl -*- ###################################################################### # gffmodify.pl -- Simple program to modify BioWare Gff files # Copyright (c) 2004 Tero Kivinen # All Rights Reserved. ###################################################################### # Program: gffmodify.pl # $Source: /u/samba/nwn/bin/RCS/gffmodify.pl,v $ # Author : $Author: kivinen $ # # (C) Tero Kivinen 2004 # # Creation : 14:53 Nov 21 2004 kivinen # Last Modification : 01:26 May 24 2007 kivinen # Last check in : $Date: 2007/05/23 22:26:58 $ # Revision number : $Revision: 1.7 $ # State : $State: Exp $ # Version : 1.462 # Edit time : 283 min # # Description : Simple program to modify BioWare Gff files # # $Log: gffmodify.pl,v $ # Revision 1.7 2007/05/23 22:26:58 kivinen # Fixed path splitting to accept windows paths. # # Revision 1.6 2005/07/06 11:13:14 kivinen # Added support for non-matching match. Added ability to use # parameters in compare, added new functions. # # Revision 1.5 2005/02/05 17:50:37 kivinen # Added documentation. # # Revision 1.4 2005/02/05 14:36:14 kivinen # Added support of parameters, and default value for ask (can # come from parameters too) # # Revision 1.3 2004/12/06 09:31:23 kivinen # Fixed bug. # # Revision 1.2 2004/12/05 16:52:23 kivinen # Added interactive query. Added @ask@ support. Added @areaname@ # and @areatag@ support. Added print_fields support. Added # variable add support. # # Revision 1.1 2004/11/21 14:28:49 kivinen # Created. # # $EndLog$ # # # # ###################################################################### # initialization require 5.6.0; package GffModify; use strict; use Getopt::Long; use File::Glob ':glob'; use Gff; use GffRead; use GffWrite; use Time::HiRes qw(time); use Pod::Usage; $Opt::verbose = 0; $Opt::find_struct = undef; @Opt::find_labels = (); @Opt::find_values = (); @Opt::find_operations = (); @Opt::or_labels = (); @Opt::or_values = (); @Opt::or_operations = (); $Opt::no_write = 0; $Opt::interactive = 0; $Opt::backup = 0; @Opt::modify_label = (); @Opt::modify_type = (); @Opt::modify_value = (); $Opt::print_fields = undef; @Opt::variable_label = (); @Opt::variable_type = (); @Opt::variable_value = (); @Opt::parameters = (); @Opt::parameter_names = (); @Opt::global_parameters = (); @Opt::global_parameter_names = (); @Opt::area_parameters = (); @Opt::area_parameter_names = (); @Opt::variable_parameters = (); @Opt::variable_parameter_names = (); ###################################################################### # Get version information open(PROGRAM, "<$0") || die "Cannot open myself from $0 : $!"; undef $/; $Prog::program = ; $/ = "\n"; close(PROGRAM); if ($Prog::program =~ /\$revision:\s*([\d.]*)\s*\$/i) { $Prog::revision = $1; } else { $Prog::revision = "?.?"; } if ($Prog::program =~ /version\s*:\s*([\d.]*\.)*([\d]*)\s/mi) { $Prog::save_version = $2; } else { $Prog::save_version = "??"; } if ($Prog::program =~ /edit\s*time\s*:\s*([\d]*)\s*min\s*$/mi) { $Prog::edit_time = $1; } else { $Prog::edit_time = "??"; } $Prog::version = "$Prog::revision.$Prog::save_version.$Prog::edit_time"; $Prog::progname = $0; $Prog::progname =~ s/^.*[\/\\]//g; $| = 1; ###################################################################### # Read rc-file if (defined($ENV{'HOME'})) { read_rc_file("$ENV{'HOME'}/.gffmodifyrc"); } ###################################################################### # Option handling Getopt::Long::Configure("no_ignore_case"); if (!GetOptions("config=s" => \$Opt::config, "verbose|v+" => \$Opt::verbose, "help|h" => \$Opt::help, "no-write|n" => \$Opt::no_write, "backup|b" => \$Opt::backup, "interactive|i" => \$Opt::interactive, "find|f=s" => sub { my($name, $value) = @_; if ($value !~ /^(.*):=:(.*)$/) { if ($value !~ /^(.*):!:(.*)$/) { die "--find needs label-regexp:=:value-regexp or label-regexp:!:value-regexp"; } else { push(@Opt::find_labels, $1); push(@Opt::find_values, $2); push(@Opt::find_operations, 1); } } else { push(@Opt::find_labels, $1); push(@Opt::find_values, $2); push(@Opt::find_operations, 0); } }, "or|o" => sub { if ($#Opt::find_labels == -1) { die "There must be at least one --find option " . "before --or"; } push(@Opt::or_labels, [ @Opt::find_labels ]); push(@Opt::or_values, [ @Opt::find_values ]); push(@Opt::or_operations, [ @Opt::find_operations ]); @Opt::find_labels = (); @Opt::find_values = (); @Opt::find_operations = (); }, "find-struct|s=s" => \$Opt::find_struct, "print-fields|p=s" => \$Opt::print_fields, "modify|m=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=\#]*)\#(\d+)=(.*)$/) { push(@Opt::modify_label, $1); push(@Opt::modify_type, $2); push(@Opt::modify_value, $2); } elsif ($value =~ /^([^=\#]*)=(.*)$/) { push(@Opt::modify_label, $1); push(@Opt::modify_type, undef); push(@Opt::modify_value, $2); } else { die "--modfify needs label[#type]=value"; } }, "variable=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=\#]*)\#(\d+|int|float|string)=(.*)$/) { push(@Opt::variable_label, $1); push(@Opt::variable_type, $2); push(@Opt::variable_value, $3); } elsif ($value =~ /^([^=\#]*)=(.*)$/) { push(@Opt::variable_label, $1); push(@Opt::variable_type, undef); push(@Opt::variable_value, $2); } else { die "--variable needs label[#type]=value"; } }, "global-parameter|g=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=]*)=(.*)$/) { push(@Opt::global_parameter_names, $1); push(@Opt::global_parameters, $2); } else { die "--global-parameter needs name=absolute-path"; } }, "parameter|P=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=]*)=(.*)$/) { push(@Opt::parameter_names, $1); push(@Opt::parameters, $2); } else { die "--parameter needs name=relative-path"; } }, "area-parameter|a=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=]*)=(.*)$/) { push(@Opt::area_parameter_names, $1); push(@Opt::area_parameters, $2); } else { die "--area-parameter needs name=absolute-path"; } }, "variable-parameter=s" => sub { my($name, $value) = @_; if ($value =~ /^([^=]*)=(.*)$/) { push(@Opt::variable_parameter_names, $1); push(@Opt::variable_parameters, $2); } else { die "--variable-parameter needs name=varname"; } }, "version|V" => \$Opt::version) || defined($Opt::help)) { usage(); } if (defined($Opt::version)) { print("\u$Prog::progname version " . "$Prog::version by Tero Kivinen.\n"); exit(0); } while (defined($Opt::config)) { my($tmp); $tmp = $Opt::config; undef $Opt::config; if (-f $tmp) { read_rc_file($tmp); } else { die "Config file $Opt::config not found: $!"; } } ###################################################################### # Main loop $| = 1; my($i, $t0, %args, %search); %args = (); if ($#Opt::modify_label == -1 && $#Opt::variable_label == -1) { warn "No --modify or --variable options given"; } push(@Opt::or_labels, \@Opt::find_labels); push(@Opt::or_values, \@Opt::find_values); push(@Opt::or_operations, \@Opt::find_operations); if (defined($Opt::find_struct)) { $search{find_label} = $Opt::find_struct; } else { $search{find_label} = '^/$'; } $search{proc} = \&find_proc; if (join(";", @ARGV) =~ /[*?]/) { my(@argv); foreach $i (@ARGV) { push(@argv, bsd_glob($i)); } @ARGV = @argv; } foreach $i (@ARGV) { my($gff); $args{'filename'} = $i; $main::file = $i; $t0 = time(); if ($Opt::verbose > 1) { print("Reading file $i...\n"); } $gff = GffRead::read(%args); if ($Opt::verbose > 2) { printf("Read done, %g seconds\n", time() - $t0); } $main::modified = 0; $gff->find(%search); if (!$Opt::no_write) { if ($main::modified) { if ($Opt::backup) { if ($Opt::verbose > 1) { print("Renaming $i -> $i.bak...\n"); } rename($i, $i . ".bak"); } if ($Opt::verbose) { print("Writing file $i...\n"); } &GffWrite::write($gff, filename => $i); } else { if ($Opt::verbose) { print("Skipped unmodified file $i...\n"); } } } } exit 0; ###################################################################### # Find proc sub find_proc { my($gff, $full_label, $label, $value, $parent_gffs) = @_; my($i, $j, $match, $item, $parameters); if ($Opt::verbose > 4) { print("Found structure $full_label\n"); } # Match all or'ed items togther, if any of them match set the # $match and exit loop. or_loop: for($i = 0; $i <= $#Opt::or_labels; $i++) { # Loop through all and items for the given or. If any of those # does not match, then go to the next round of the ors for($j = 0; $j <= $#{$Opt::or_labels[$i]}; $j++) { # Loop through all labels in the gff, and match them # agains the given label and value regexps. if ($Opt::or_labels[$i][$j] =~ /@/) { my($cmp); # There is params there. $parameters = find_params($gff, $full_label, $label, $value, $parent_gffs) if (!defined($parameters)); $cmp = replace_params($Opt::or_labels[$i][$j], $parameters); if (($Opt::or_operations[$i][$j] && ($cmp !~ /$Opt::or_values[$i][$j]/)) || (!($Opt::or_operations[$i][$j]) && ($cmp =~ /$Opt::or_values[$i][$j]/))) { if ($Opt::verbose > 5) { print("Found match for " . $Opt::or_labels[$i][$j] . "(==" . $cmp . "):" . ($Opt::or_operations[$i][$j] ? "!" : "=") . ":" . $Opt::or_values[$i][$j] . "\n"); } next; } else { if ($Opt::verbose > 5) { print("Didn't find match for " . $Opt::or_labels[$i][$j] . "(==" . $cmp . "):" . ($Opt::or_operations[$i][$j] ? "!" : "=") . ":" . $Opt::or_values[$i][$j] . "\n"); } next or_loop; } } $match = 0; foreach $item (keys %$gff) { if ($Opt::verbose > 6) { print("Trying to match " . $full_label . $item . ":=:" . $$gff{$item} . " with " . $Opt::or_labels[$i][$j] . ":" . ($Opt::or_operations[$i][$j] ? "!" : "=") . ":" . $Opt::or_values[$i][$j] . "\n"); } if ((($full_label . $item) =~ /$Opt::or_labels[$i][$j]/) && (($Opt::or_operations[$i][$j] && ($$gff{$item} !~ /$Opt::or_values[$i][$j]/)) || (!($Opt::or_operations[$i][$j]) && ($$gff{$item} =~ /$Opt::or_values[$i][$j]/)))) { $match = 1; if ($Opt::verbose > 5) { print("Found match for " . $Opt::or_labels[$i][$j] . ":" . ($Opt::or_operations[$i][$j] ? "!" : "=") . ":" . $Opt::or_values[$i][$j] . "\n"); } last; } } # Check if this item matched if (!$match) { # Didn't match, go to next or if ($Opt::verbose > 5) { print("Didn't find match for " . $Opt::or_labels[$i][$j] . ":" . ($Opt::or_operations[$i][$j] ? "!" : "=") . ":" . $Opt::or_values[$i][$j] . "\n"); } next or_loop; } } # All items in the and loop matched, this means we are done # call the proc and return. modify_struct($gff, $full_label, $label, $value, $parent_gffs); return; } # Didn't match, do nothing return; } ###################################################################### # Modify structure sub modify_struct { my($gff, $full_label, $label, $value, $parent_gffs) = @_; my($i, $parameters); $parameters = find_params($gff, $full_label, $label, $value, $parent_gffs); if (defined($Opt::print_fields)) { print_struct($gff, $full_label, $label, $value, $parent_gffs, $parameters); } if ($Opt::interactive) { my($reply); print("Modify '$full_label'? "); $reply = ; chomp $reply; if ($reply eq "q") { print("Exiting\n"); exit(0); } elsif ($reply ne "y") { return; } } for($i = 0; $i <= $#Opt::modify_label; $i++) { my($old, $new, $default); $old = $gff->value($Opt::modify_label[$i]); $new = $Opt::modify_value[$i]; $old = '' if (!defined($old)); if ($new =~ /^\@ask(\(.*\))?\@$/) { if (defined($1)) { $default = $1; $default =~ s/^\(//; $default =~ s/\)$//; } else { $default = $old; } $default = replace_params($default, $parameters); print("Give new value for '" . $full_label . $Opt::modify_label[$i] . "' (default '$default', old value '$old')? "); $new = ; chomp $new; if ($new eq '') { $new = $default ; } } $new = replace_params($new, $parameters); if ($Opt::verbose > 3) { if ($old ne '') { print("Modifying $Opt::modify_label[$i] in $full_label " . "from $old to $new\n"); } else { print("Adding $Opt::modify_label[$i] in $full_label " . "to have value $new\n"); } } if ($old ne $new) { $main::modified = 1; $gff->value($Opt::modify_label[$i], $new, $Opt::modify_type[$i]); } } for($i = 0; $i <= $#Opt::variable_label; $i++) { my($old, $new, $default); $old = $gff->variable($Opt::variable_label[$i]); if (defined($old)) { $old = $old->varvalue; } $old = '' if (!defined($old)); $new = $Opt::variable_value[$i]; if ($new =~ /^\@ask(\(.*\))?\@$/) { if (defined($1)) { $default = $1; $default =~ s/^\(//; $default =~ s/\)$//; } else { $default = $old; } $default = replace_params($default, $parameters); print("Give new value for '" . $full_label . $Opt::variable_label[$i] . "' variable (default '$default', old value '$old')? "); $new = ; chomp $new; if ($new eq '') { $new = $old; } } $new = replace_params($new, $parameters); if ($Opt::verbose > 3) { if ($old ne '') { print("Modifying variable $Opt::variable_label[$i] " . "in $full_label from $old to $new\n"); } else { print("Adding variable $Opt::variable_label[$i] " . "in $full_label to have value $new\n"); } } if ($old ne $new || $new eq '@remove@') { $main::modified = 1; if ($new eq '@remove@') { $new = undef; } $gff->variable($Opt::variable_label[$i], $new, $Opt::variable_type[$i]); } } } ###################################################################### # Print field from struct sub print_struct { my($gff, $full_label, $label, $value, $parent_gffs, $parameters) = @_; my($i, $prefix); foreach $i (sort keys %{$parameters}) { if ($Opt::print_fields =~ /\@$i\@/i) { print("$main::file: \@$i\@: $$parameters{$i}\n"); } } foreach $i (sort keys %$gff) { next if ($i =~ /____((struct_|file_|)type|string_ref|file_version)$/); next if ($i eq ''); if (($full_label . $i) =~ /$Opt::print_fields/) { if ($$gff{$i . ". ____type"} == 12) { if (defined($$gff{$i}{0})) { print_entry($gff, $full_label . $i . '/0', $i . '/0', $$gff{$i}{0}); } } elsif (UNIVERSAL::isa($$gff{$i}, 'HASH')) { &Gff::print(Gff->new($$gff{$i}), prefix => $main::file . ": " . $full_label . $i); } elsif (UNIVERSAL::isa($$gff{$i}, 'ARRAY')) { my($j); for($j = 0; $j <= $#{$$gff{$i}}; $j++) { &Gff::print(Gff->new($$gff{$i}[$j]), prefix => $main::file . ": " . $full_label . $i . "[" . $j . "]"); } } else { print_entry($gff, $full_label . $i, $i, $$gff{$i}); } } } } ###################################################################### # Print full entry sub print_entry { my($gff, $full_label, $label, $value) = @_; print("$main::file: $full_label: $value\n"); } ###################################################################### # Replace parameters # $newstr = replace_params($str, \%parameters) sub replace_params { my($str, $parameters) = @_; my($i, $changes); while (1) { $changes = 0; foreach $i (keys %{$parameters}) { $changes += ($str =~ s/\@$i\@/$$parameters{$i}/g); } last if ($changes == 0); } $str =~ s/\@random\((\d+)\)\@/int(rand($1) + 1)/eg; $str =~ s/\@frandom\((\d+)\)\@/rand($1) + 1/eg; $str =~ s/\@random0\((\d+)\)\@/int(rand($1))/eg; $str =~ s/\@frandom0\((\d+)\)\@/rand($1)/eg; $str =~ s/\@random\@/rand(1000000)/eg; $str =~ s/\@counter\((\d+),(\d+)\)\@/$1 + ($main::counter++ % $2)/eg; $str =~ s/\@counter\((\d+)\)\@/$1 + $main::counter++/eg; $str =~ s/\@counter\@/$main::counter++/eg; while (1) { $changes = 0; $changes += ($str =~ s/\@substr\(\s*([^@]*)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*\)@/substr($1, $2, $3)/eg); $changes += ($str =~ s/\@substr\(\s*([^@]*)\s*,\s*(-?\d+)\s*\)@/substr($1, $2)/eg); last if ($changes == 0); } return $str; } ###################################################################### # Find parameters # \%parameters = find_params($gff, $full_label, $label, $value, $parent_gffs); sub find_params { my($gff, $full_label, $label, $value, $parent_gffs) = @_; my(%parameters, $i, $v); if (defined($main::params_file) && $main::file eq $main::params_file) { %parameters = %main::params_parameters; } else { if ($main::file =~ /\.(git|gic|are)$/) { my($name, $tag); if ($main::file =~ /\.(git|gic)$/) { my($areagff, $file); $file = $main::file; $file =~ s/\.(git|gic)/.are/; $areagff = GffRead::read(filename => $file, return_errors => 1); if (!defined($areagff)) { die "Cannot open $file"; } else { $name = $$areagff{Name}{0}; $tag = $$areagff{Tag}; for($i = 0; $i <= $#Opt::area_parameters; $i++) { $v = $areagff->value($Opt::area_parameters[$i]); $v = "" if (!defined($v)); $parameters{$Opt::area_parameter_names[$i]} = $v; } } } else { $name = $$parent_gffs[0]{Name}{0}; $tag = $$parent_gffs[0]{Tag}; } $name = "" if (!defined($name)); $tag = "" if (!defined($tag)); $parameters{areaname} = $name; $parameters{areatag} = $tag; } for($i = 0; $i <= $#Opt::global_parameters; $i++) { $v = $$parent_gffs[0]->value($Opt::global_parameters[$i]); $v = "" if (!defined($v)); $parameters{$Opt::global_parameter_names[$i]} = $v; } for($i = 0; $i <= $#Opt::variable_parameters; $i++) { if ($Opt::variable_parameters[$i] =~ /^\/(.*)$/) { $v = $$parent_gffs[0]->variable($1); if (defined($v)) { $v = $v->varvalue; $v = "" if (!defined($v)); $parameters{$Opt::variable_parameter_names[$i]} = $v; } } } %main::params_parameters = %parameters; $main::params_file = $main::file; } $full_label = "" if (!defined($full_label)); $label = "" if (!defined($label)); $parameters{path} = $full_label; $parameters{label} = $label; for($i = 0; $i <= $#Opt::parameters; $i++) { $v = $gff->value($Opt::parameters[$i]); $v = "" if (!defined($v)); $parameters{$Opt::parameter_names[$i]} = $v; } for($i = 0; $i <= $#Opt::variable_parameters; $i++) { if ($Opt::variable_parameters[$i] =~ /^\/(.*)$/) { next; } elsif ($Opt::variable_parameters[$i] =~ /^\.\.(.*)$/) { my($var, $j); $var = $1; for($j = $#$parent_gffs; $j >= 0; $j--) { $v = $$parent_gffs[0]->variable($1); last if (defined($v)); } } else { $v = $gff->variable($Opt::variable_parameters[$i]); } if (defined($v)) { $v = $v->varvalue; $v = "" if (!defined($v)); $parameters{$Opt::variable_parameter_names[$i]} = $v; } } return \%parameters; } ###################################################################### # Read rc file sub read_rc_file { my($file) = @_; my($next, $space); if (open(RCFILE, "<$file")) { while () { chomp; while (/\\$/) { $space = 0; if (/\s+\\$/) { $space = 1; } s/\s*\\$//g; $next = ; chomp $next; if ($next =~ s/^\s+//g) { $space = 1; } if ($space) { $_ .= " " . $next; } else { $_ .= $next; } } if (/^\s*([a-zA-Z0-9_]+)\s*$/) { eval('$Opt::' . lc($1) . ' = 1;'); } elsif (/^\s*([a-zA-Z0-9_]+)\s*=\s*\"([^\"]*)\"\s*$/) { my($key, $value) = ($1, $2); $value =~ s/\\n/\n/g; $value =~ s/\\t/\t/g; eval('$Opt::' . lc($key) . ' = $value;'); } elsif (/^\s*([a-zA-Z0-9_]+)\s*=\s*(.*)\s*$/) { my($key, $value) = ($1, $2); $value =~ s/\\n/\n/g; $value =~ s/\\t/\t/g; eval('$Opt::' . lc($key) . ' = $value;'); } } close(RCFILE); } } ###################################################################### # Usage sub usage { Pod::Usage::pod2usage(0); } =head1 NAME gffmodify - Find matching items and modify them from the gff structures =head1 SYNOPSIS gffmodify [B<--help>|B<-h>] [B<--version>|B<-V>] [B<--verbose>|B<-v>] [B<--config> I] [B<--no-write>|B<-n>] [B<--interactive>|B<-i>] [B<--backup>|B<-b>] [B<--find-struct>|B<-s> I] [B<--print-fields>|B<-p> I] B<--find>|B<-f> IC<:=:>I [B<--find>|B<-f> IC<:=:>I ...] [B<--find>|B<-f> IC<:!:>I ...] [B<--or>|B<-o> B<--find>|B<-f> IC<:=:>I ...] [B<--modify>|B<-m> I