Created
January 31, 2011 21:20
-
-
Save perlpilot/804840 to your computer and use it in GitHub Desktop.
Minor improvements to flussence's implementation of .indent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use v6; | |
use Test; | |
use MONKEY_TYPING; | |
my Int $?TABSTOP = 5; | |
my Str $?expanded-tab = ' ' x $?TABSTOP; | |
=begin pod | |
=for vim | |
{{{ | |
=head1 Str.indent() | |
(trying to write this down in words I can understand) | |
* A "line" in this context is everything up to and including the \n. | |
* Tabs are elastic; C</^ ' ' \t/> equals C</^ \t/> given C<< $?TABSTOP > 1 >>. | |
* A full tab is equivalent to C<' ' x ($?TABSTOP // 8)>. | |
=head2 Indenting (positive number) | |
For lines beginning with tabs: | |
* Collapse as many C<$steps> as possible into C<\t> | |
* Prepend the C<\t> and add the remaining C<$steps> after the leading whitespace of the line | |
For all other lines: | |
* Prepend C<' ' x $steps> to each line | |
=head2 Outdenting (negative number) | |
Remove space from the right. | |
For lines beginning with tabs: | |
* Unindent from the right of the leading whitespace | |
* Tabs expand to C<' ' x $?TABSTOP> | |
For lines not beginning with tabs: | |
* Unindent from the left if sufficient normal space | |
* If tabs exist after spaces, elastic-expand them | |
=for vim | |
}}} | |
=end pod | |
augment class Str { | |
# Zero indent does nothing | |
our Str multi method indent($steps as Int where { $_ == 0 }) is export { | |
self; | |
} | |
# Indent | |
our Str multi method indent($steps as Int where { $_ > 0 }) is export { | |
# We want to keep trailing \n so we have to .comb explicitly instead of .lines | |
return self.comb(/:r ^^ \N* \n?/).map({ | |
given $_ { | |
# On lines with leading tabs, keep them and add spaces after | |
when /^(\t \s*) (.*)/ { | |
"\t" x ($steps div $?TABSTOP) ~ $0 | |
~ ' ' x ($steps mod $?TABSTOP) ~ $1; | |
} | |
# Use the existing space character if they're all the same | |
when /^ (\s) ($0*) (.*)/ { | |
$0 x $steps ~ $1 ~ $2 | |
} | |
# Otherwise we just stick spaces at the beginning | |
default { ' ' x $steps ~ $_ } | |
} | |
}).join; | |
} | |
# Outdent | |
our Str multi method indent($steps as Int where { $_ < 0 }) is export { | |
my Int $prefix = self.common-indent; | |
warn sprintf('Asked to remove %d spaces, but the shortest indent is %d', -$steps, $prefix) | |
if -$steps > $prefix; | |
self.outdent(-$steps, $prefix) | |
} | |
# Auto-trim | |
our Str multi method indent(Whatever) is export { | |
self.outdent(self.common-indent); | |
} | |
# Various bits of the outdenting code below | |
my Str method outdent(Int $steps, Int $prefix = $steps) { | |
my @lines = self.comb(/:r ^^ \N* \n?/); | |
# TODO: this eval is a workaround because Rakudo doesn't grok | |
# variables in the range following a ** quantifier (nor | |
# in a range in a closure following a ** quantifier) | |
my $leading-ws = eval "token \{ ^ ' ' ** 0..$steps \}"; | |
return @lines.map({ | |
given $_ { | |
# Tab-indented line: remove spaces from right, with elastic tab expansion | |
when /^(\t+ \s*) (.*)/ { ... } | |
# Space-indented line | |
default { $_.subst($leading-ws, '') } | |
} | |
}).join; | |
} | |
my Int method common-indent() { | |
[min] self.comb(/^^\s+/)».expand-tabs()».chars; | |
} | |
my Str method expand-tabs() { | |
self.subst(/\t/, ' ' x $?TABSTOP, :g); | |
} | |
} | |
# S32/Str:586 - simple indent gets added at the beginning of the line {{{ | |
for 'quack', " \t quack" -> $initial { | |
my @spaced = ([\~] ' ' xx 4) X~ $initial; | |
for @spaced.kv -> $index, $result { | |
is @spaced[0].indent($index), | |
$result, | |
'simple space indentation'; | |
} | |
} | |
is " \t quack\n \t meow".indent(1).perl, | |
" \t quack\n \t meow".perl, | |
'Added space should be placed at the beginning of each line'; | |
# }}} | |
# S32/Str:590 {{{ | |
is ' quack'.indent(-2), | |
' quack', | |
'If $steps is negative, outdent that many spaces'; | |
is " quack\n meow".indent(-3).perl, | |
"quack\n meow".perl, | |
'If a line contains too few spaces, only those should be removed'; | |
given 'Warn when outdenting more than existing spaces would permit' -> $test { | |
' quack'.indent(-3); | |
flunk $test; | |
CATCH { pass $test; } | |
} | |
# }}} | |
# S32/Str:593 {{{ | |
is " quack\n meow\n fish noises".indent(*).perl, | |
" quack\nmeow\n fish noises".perl, | |
'If $steps is *, visually align the string with the left margin by removing common indent'; | |
# }}} | |
# S32/Str:596 {{{ | |
is "\tquack".indent(-1).perl, | |
(' ' x ($?TABSTOP - 1) ~ 'quack').perl, | |
'Tabs expand to $?TABSTOP spaces as needed'; | |
# }}} | |
# S32/Str:600 {{{ | |
is "\tquack\n\t meow".indent($?TABSTOP + 1).perl, | |
"\t\t quack\n\t\t meow".perl, | |
'Non-$?TABSTOP indenting on tabbed lines should work sensibly'; | |
# }}} | |
# S32/Str:601 {{{ | |
is "\t quack".indent(-1).perl, | |
"\tquack".perl, | |
'When leading tabs are being outdented, preserve them by removing space from the right'; | |
is "\t quack".indent(-2).perl, | |
(' ' x ($?TABSTOP - 1) ~ 'quack').perl, | |
'Expand tabs after normal space removal runs out'; | |
is " \t quack".indent(-1).perl, | |
"\t quack".perl, | |
'Leading whitespace before a tab gets removed'; | |
is " \t \t quack".indent(-2).perl, | |
(' ' x 7 ~ "\t quack").perl, | |
'Lines beginning with non-tab should outdent in left-to-right order, with tab expansion'; | |
is "\tquack\n\t meow".indent($?TABSTOP).perl, | |
"\t\tquack\n\t\t meow".perl, | |
'Recognise tab indentation and indent it consistently'; | |
is "\tquack\nmeow".indent($?TABSTOP).perl, | |
"\t\tquack\n{$?expanded-tab}meow".perl, | |
'Tab-indentation should work even when some lines have no leading whitespace'; | |
# }}} | |
# Sanity checks {{{ | |
is "\ta\n b".indent(1).perl, | |
"\ta\n b".lines».indent(1).join("\n").perl, | |
'Each line should be indented independently of others'; | |
is "\ta\n b".indent(0).perl, | |
"\ta\n b".perl, | |
'.indent(0) should be a no-op'; | |
is "\ta\n b".indent(1).indent(16).indent(0).indent(*).perl, | |
"\ta\n b".indent(True).indent('0x10').indent('blah').indent(*).perl, | |
'.indent accepts weird scalar input and coerces it to Int when necessary'; | |
is " \t a\n \t b\n".indent(1).perl, | |
" \t a\n \t b\n".perl, | |
'Indentation should not be appended after a trailing \n'; | |
# }}} | |
done; | |
# vim: set fdm=marker # |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment