SPUG: multi-line substition, all at once.
Jacinta Richardson
jarich at perltraining.com.au
Thu Jun 25 04:25:30 PDT 2009
Ryan Corder wrote:
> my $code=<<__CODE__;
> : sub hello {
> : say "Hello World!";
> : }
> __CODE__
>
> I would like to get back:
>
> [code] sub hello {
> say "Hello World!";
> }
> [/code]
and
> my $code=<<__CODE__;
> foo bar
> baz
>
> : sub hello {
> : say "Hello World!";
> : }
> asdf
> snap
> crackle
> pop
> __CODE__
>
> [code]
> sub hello {
> say "Hello World!";
> }
> [/code]
right?
It's a good idea to be explicit about your HEREDOC quoting. By default it's
double quotes, but it doesn't hurt to put them in. I also don't recommend using
@ as your delimiters in substitutions, but that's because they hurt my eyes (and
my syntax highlighting doesn't understand them). You had this:
$code =~ s@(?:(?<=\s\:).*)+@"[code]$&[/code]"@esg;
which could be more nicely written as:
$code =~ s{
(?: # Non-capturing match
(?<=\s:) # follows a space and a :
.*
)+ # one or more
}
{[code]$&\[/code]}sgx;
or, to get rid of the assertion:
$code =~ s{
(
^\s: # the line starts with space:
.*
)+ # one or more
}
{[code]$&\[/code]}msgx;
Note that I do need to escape the second [ on the right handside (as your code
should have too), but I don't need quotes or /e (neither did you). The right
hand side of a substitute already acts as a double quoted string.
As you point out all of these patterns return:
:[code] sub hello {
: say "Hello World!";
: }
[/code]
Which isn't quite right. Worse they behave completely wrong when given more
complex data:
foo bar
baz
[code] : sub hello {
: say "Hello World!";
: }
asdf
snap
crackle
pop
The expression below works better:
my $code = '
foo bar
baz
: sub hello {
: say "Hello World!";
: }
asdf
snap
crackle
pop
';
$code =~ s{
\A.*? # Get rid of any excess at the top
( # start $1
(
^ # start of a line
\s: # space then colon
[^\n]*\n # just 1 line (including newline)
)+ # one or more times
) # end of $1
.*
}{[code]$1\[/code]\n}sxm;
print $code;
__END__
[code] : sub hello {
: say "Hello World!";
: }
[/code]
The problem is that you want to both capture the lines which are identified by
starting with the space: and then you want to remove the space:. I don't
believe that this can be done (at least in an efficient and readable fashion)
without cheating. Here's an example of cheating:
my $code = '
foo bar
baz
: sub hello {
: say "Hello World!";
: }
asdf
snap
crackle
pop
';
$code =~ s{
\A.*? # Get rid of any excess at the top
( # start $1
(
^ # start of a line
\s: # space then colon
[^\n]*\n # just 1 line (including newline)
)+ # one or more times
) # end of $1
.*
}{"[code]\n".no_colon($1)."[/code]\n"}sexm;
print $code;
sub no_colon {
my $line = shift;
$line =~ s/^ ://gm;
return $line;
}
__END__
[code]
sub hello {
say "Hello World!";
}
[/code]
This still uses two regular expressions, we just hide one of them away in a
subroutine. I don't really like using /e, so I'd still suggest doing this in
two expressions, such as the example previous plus $code =~ s/^ ://gm;
You *might* be able to achieve this in one regular expression using a re-entrant
expression, but I haven't been able to, and I don't think it would be easily
maintainable.
All the best,
J
--
("`-''-/").___..--''"`-._ | Jacinta Richardson |
`6_ 6 ) `-. ( ).`-.__.`) | Perl Training Australia |
(_Y_.)' ._ ) `._ `. ``-..-' | +61 3 9354 6001 |
_..`--'_..-_/ /--'_.' ,' | contact at perltraining.com.au |
(il),-'' (li),' ((!.-' | www.perltraining.com.au |
More information about the spug-list
mailing list