1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
package VCP::Filter::labelmap;
=head1 NAME
VCP::Filter::labelmap - Alter or remove labels from each revision
=head1 SYNOPSIS
## From the command line:
vcp <source> labelmap: "rev_$rev_id" "change_$change_id" -- <dest>
## In a .vcp file:
LabelMap:
foo-... <<delete>> # remove all labels beginning with foo-
F...R <<delete>> # remove all labels F
v-(...) V-$1 # use uppercase v prefixes
=head1 DESCRIPTION
Allows labels to be altered or removed using a syntax similar to
VCP::Filter::map. This is being written for development use so more
documentation is needed. See L<VCP::Filter::map|VCP::Filter::map> for
more examples of pattern matching (though VCP::Filter::labelmap does
not use <branch_id> syntax).
=for test_script t/61labelmap.t
=cut
$VERSION = 1 ;
@ISA = qw( VCP::Filter );
use strict ;
use VCP::Debug qw( :debug );
use VCP::Filter;
use VCP::Logger qw( lg );
use Regexp::Shellish qw( compile_shellish );
#use base qw( VCP::Filter );
#use fields (
# 'MAP_SUB', ## The rules to apply, compiled in to an anon sub
#);
## NOTE: this code is simpler than, but similar to, the same-named
## helper routines in VCP::Filter::map. That module uses multifield
## patterns and actions, this one uses single field (ie just the
## label) patterns and actions.
sub _parse_expr {
my ( $type, $v ) = @_;
my %expr;
return () unless defined $v;
if ( $type eq "result" ) {
return ( delete => 1, %expr ) if $v eq "<<delete>>";
return ( keep => 1, %expr ) if $v eq "<<keep>>";
}
$expr{label} = $v;
die "unable to parse labelmap $type '$v'\n"
unless defined $expr{label};
for ( "label" ) { ## loop is just to mimic code in VCP::Filter::map
die "newline in '$expr{$_}' of labelmap $type '$v'\n"
if $expr{$_} =~ tr/\n//;
die "unescaped '$1' in '$expr{$_}' of labelmap $type '$v'\n"
if $expr{$_} =~
( $type eq "pattern"
? qr{(?<!\\)(?:\\\\)*([\@#<>\[\]{}\$])}
: qr{(?<!\\)(?:\\\\)*([\@#<>\[\]*?()]|\.\.\.)|(?<!\$)\{}
);
## We reserve a lot of metacharacters so we can do more later.
die "illegal escape sequence '$1' in '$expr{$_}' of labelmap $type '$v'\n"
if $expr{$_} =~ qr{(?<!\\)(?:\\\\)*(\\(?!=\.\.\.)[^\@#<>\[\]{}*?()])};
}
return %expr;
}
sub _compile_rule {
my $self = shift;
my ( $name, $pattern, $result ) = @_;
my %pattern_expr = _parse_expr pattern => $pattern;
my %result_expr = _parse_expr result => $result;
## The test expression is a single regexp that matches a string
## built up from some pieces of the rev metadata. Right now, only
## the name and the branch_id are tested, by someday the labels,
## change_id, rev_id, and comment could be tested. If so, the
## comment field would need to come last due to newline issues.
my $test_expr =
! keys %pattern_expr
? 1 ## This happens iff the pattern was undef (which
## should only happen for the default rule).
: join(
"",
"m'", ## Note the single-quotish context
do {
my $re = compile_shellish( $pattern_expr{label} );
$re =~ s{(')}{\\`}g;
$re =~ s{\A\(\?[\w-]*: (.*) \)}{$1}gx; # for readability
# of dumped code
$re;
},
"'",
);
$pattern = defined $pattern ? qq{"$pattern"} : "match all";
my $result_statement = join(
"",
debugging
? qq{lg( ' matched $name ($pattern)' );\n}
: (),
$result_expr{keep}
? (
debugging
? qq{lg( " <<keep>>ing" );\n}
: (),
"push \@l, \$_; ## Keep!\n"
)
: $result_expr{delete}
? (
debugging
? qq{lg( " <<delete>>ing" );\n}
: (),
"++\$changed; ## Delete!\n",
)
: do {
my $expr = $result_expr{label};
$expr =~ s{([\\"])}{\\$1}g;
$expr =~ s{\n}{\\n}g;
(
debugging
? qq{lg( " rewriting \$_ to '$expr'" );\n}
: (),
qq{push \@l, "$expr";\n},
qq{++\$changed;\n},
);
}
);
$result_statement =~ s/^/ /gm;
$result_statement = "elsif ( $test_expr ) {\n$result_statement}\n";
$result_statement =~ s/^/ /gm;
$result_statement;
}
sub _compile_rules {
my $self = shift;
my ( $rules ) = @_;
## NOTE: making this a closure causes spurious warnings at exit so
## we pass $self explicitly.
my $preamble = <<END_PREAMBLE;
my ( \$self, \$rev ) = \@_;
END_PREAMBLE
my $rule_number;
my $code = join( "",
$preamble,
"my \@l;\n",
"my \$changed;\n",
"for ( \$rev->labels ) {\n",
debugging
? qq{ my \$s = \$_; \$s =~ s/\\n/\\\\n/g; lg( "labelmap testing '\$s' (", \$rev->as_string, ")" );\n\n}
: (),
" if (0) {}\n",
map( $self->_compile_rule( @$_ ),
map( [ "Rule " . ++$rule_number, @$_ ], @$rules ),
[ "Default Rule", undef, "<<keep>>" ]
),
"}\n",
"\$rev->set_labels( \\\@l ) if \$changed;\n",
"\$self->dest->handle_rev( \$rev );\n",
);
$code =~ s/^/ /mg;
$code = "#line 1 VCP::Filter::labelmap::labelmap_function\n$code";
$code = "sub {\n$code}";
debug "labelmap code:\n$code" if debugging;
return( eval $code
or die "$@ compiling Map: code:\n",
do {
my $w = length( $code =~ tr/\n// + 1 ) ;
my $ln;
1 while chomp $code;
$code =~ s{^}[sprintf "%${w}d|",++$ln]gme;
"$code\n";
},
);
}
sub new {
my $self = shift->SUPER::new;
## Parse the options
my ( $spec, $options ) = @_ ;
$self->{MAP_SUB} = $self->_compile_rules(
$self->parse_rules_list( $options, "Pattern", "Replacement" )
);
return $self ;
}
sub filter_name { return "LabelMap" }
sub handle_rev {
my $self = shift;
$self->{MAP_SUB}->( $self, @_ );
}
=head1 AUTHOR
Barrie Slaymaker <barries@slaysys.com>
=head1 COPYRIGHT
Copyright (c) 2000, 2001, 2002 Perforce Software, Inc.
All rights reserved.
See L<VCP::License|VCP::License> (C<vcp help license>) for the terms of use.
=cut
1
|