Wednesday, July 14, 2010

powershell: my 3 favorite functions (2 of 2)


you may have noticed that when you print a really, really long string in powershell, the text is wrapped at the edge of the console. if your screen is 80 characters wide, your string gets wrapped at 80 characters. you may also have noticed that this wrapping isn't very intelligent. your string will be wrapped at exactly 80 characters, even if that means that the line gets broken in the middle of a word. just look through the powershell help to see what i mean.

textwrap

python has a nifty little module called textwrap that wraps a string the smart way. i don't know a lot about it—i only use the parts i need. it is not my intent to re-write the python module for powershell, only to borrow the concept with my own textwrap function. the idea is to take a long string and a maximum width in characters and return a string formatted so that each line does not exceed the width. whole words should be preserved at the end of each line so that the text is readable. because this is a more complex function, we will build it incrementally and talk about it along the way.


C:\> function textwrap ($longstring, $width) {
>>> $word_list = -split $longstring
>>> $out_strings = @("")
>>> $i = 0
>>>

the simple way to begin is to take the long string and convert into an array of words using the string split() method or, if you're using powershell 2, the split operator as i did here. we're going to convert the string to an array of strings, each no longer than the desired width. we'll store this in $out_strings. notice we initialized the array with an empty string. that way we can write to $out_strings[0] without getting errors about non-existent array elements. we'll start indexing our array at zero with our good buddy $i.


>>> foreach ($word in $word_list) {
>>> if ($out_strings[$i].Length + $word.Length -gt $width) {
>>> $i++
>>> $out_strings += ""
>>> }
>>> $out_strings[$i] += $word
>>>

what we want to do is concatenate each word to the end of the string in our array as long as that wouldn't make the string longer than our maximum width. we use the foreach loop to fetch each word from the list. in the loop we test to see if we can safely add the word to the end of the string in the first array element. once we've added as many words to the string as we can, we increment the array index and append a new empty string to the array. then we add the next word to the end of the new string.


>>> if ($out_strings[$i].Length + 1 -le $width) {
>>> $out_strings[$i] += " "
>>> }
>>> }
>>> return ($out_strings -join "`n")
>>> }
>>>

before we leave the foreach loop we need to add a space to the end of the word we just concatenated onto our string. that is, as long as it doesn't put us above our width. the if statement above adds a space if there is room to do so. finally, we return our array of strings as a single string with the join operator. we separate them with a newline character so that the new string prints on multiple lines. given a long string stored in $ls a sample run of this function might look like this:


C:\> "-" * 25
-------------------------
C:\> textwrap $ls 25
This is a really long
string. It is wrapped in
a way that doesn't chop
up words.
C:\>

the finished function appears below. i've added code so that it can even deal with hyphenated words and do the right thing. i'll let you examine that to see how it works. until next time.


33
34 # textwrap:
35 # expects a string and an int
36 # returns a string
37 # Intelligently wraps a long string to a maximum width.
38
39 function textwrap ($long_string, $width) {
40 $word_list = -split $long_string
41 $out_strings = @("")
42 $i = 0
43
44 foreach ($word in $word_list) {
45 # If adding the word would make the final string too long, start a
46 # new string:
47 if ($out_strings[$i].Length + $word.Length -gt $width) {
48 # If the word is hyphenated, try just the first part:
49 if (([regex] "\w+\-\w+").IsMatch($word)) {
50 if ($out_strings[$i].Length + 1 +
51 $word.Split("-")[0].Length -le $width) {
52 $out_strings[$i] += $word.Split("-")[0]
53 $out_strings[$i] += "-"
54 $word = $word.SubString($word.IndexOf("-") + 1)
55 }
56 }
57 $i++
58 $out_strings += ""
59 }
60 $out_strings[$i] += $word
61
62 # Add a space if it doesn't make the string too long.
63 if ($out_strings[$i].Length + 1 -le $width) {
64 $out_strings[$i] += " "
65 }
66 }
67
68 return ($out_strings -join "`n")
69 } # end function textwrap
70

No comments:

Post a Comment