001:<#
002:.SYNOPSIS
003:  Create an HTML fragment file that when displayed, displays the source code
004:  with the various parts highlighted in different colors
005:.DESCRIPTION
006:  This programs takes as input a Powershell file and pretty prints it via
007:  <span style=''> codes to show different parts in different colors.
008:  This even prints the indents correctly and even allows for wrapping.
009:  Version 1.1
010:.PARAMETER InFile
011:  Powershell file to be listed. Required
012:.PARAMETER OutFile
013:  HTML fragment output file. If not supplied, will default to InFile with
014:  '.html appended and will be in the same directory as the input file.
015:.PARAMETER OptFile
016:  Not used currently
017:.PARAMETER TabEx
018:  Tab expansion.  The default is set to 2
019:.PARAMETER LineNums
020:  Whether we want line numbers or not
021:.NOTES
022:  Name: Pretty-Print.PS1
023:  Author: Bryan Price
024:  DateCreated: Nov2011
025:.LINK
026:  http://bytehead.wikidot.com/pretty-print-ps1
027:.EXAMPLE
028:  .\pretty-print.ps1 c:\User\Generic\PS\Print-Report.ps1 -TabEx 8
029:
030:# requires 2.0
031:#>

032:param ($infile=$(Throw 'Paramater infile is required!'), $outfile, $optfile, [int] $tabex=2, [switch] $linenums)
033:
034:# Declare our globals here.
035:$Global:oline = ''
036:$Global:curcol = 1
037:$Global:curlin = 1
038:
039:<#
040:
041:Thoughts
042:
043:1.  Configuration file fun!
044:2.  Need to think about other options, full web page, or just the code.
045:
046:#>

047:
048:Function mung-htmlspace {
049:# This will preserve any indentation, on the left, or internal
050:# Repetitive spaces will be replaced by alternating non-break and regular spaces
051:  param($iline)
052:  if ($iline -eq $null) {
053:    return ''
054:  }
055:  $iline = $iline.TrimEnd(' ')              # Get rid of trailing spaces
056:  if ( $iline.indexof(' ') -eq -1 ) {
057:    return $iline                           # No spaces, ignore
058:  }
059:  if ( $iline[0] -eq ' ' ) {                # If the first char is a space, make non-breakable
060:    $tline = '&nbsp;' + $iline[1]           # Keep 2nd char as is no matter what, can't be space
061:  } else {
062:    $tline = $iline[0] + $iline[1]          # Keep the first 2 chars as is.
063:  }
064:  $iline = $iline.remove(0,2)
065:  # Replace two spaces with non-break and regular
066:  $iline = $iline -replace '  ', '&nbsp; '  # Alternate &nb and reg
067:  $iline = $iline -replace '  ', ' &nbsp;'  # Fix remaining double spaces
068:  $tline = $tline + $iline                  # Tack on the remain line
069:  return $tline
070:}
071:
072:Function start-new-line {
073:  begin {
074:    $numdigits = [int]([math]::log10($program.count) + 1)
075:    $lformat = "{0:D$numdigits}:"
076:  }
077:  process {
078:    if ($linenums.isPresent) {
079:      $pre = $lformat -f $Global:curlin               # Make the line number (in the default color) if needed
080:    } else {
081:      $pre = ""                                       # Don't worry about line number
082:    }
083:    $Global:oline = mung-htmlspace($Global:oline)     # take care of HTML space issue
084:    $Global:oline = $Global:oline + '<br />'          # add in break
085:    add-content ($pre + $Global:oline) -path $outfile # write out finished product
086:    $Global:oline = ''                                # Reset output line
087:    $Global:curcol = 1                                # Set column to beginning
088:    ++$Global:curlin                                  # Increment line tracking
089:  }
090:}
091:
092:Function Space-out-line {
093:  param($tok)
094:  $i = [int] $tok.StartColumn                       # Cheat
095:  while ($tok.StartLine -gt $Global:curlin) {       # Make sure we start on right line
096:    start-new-line
097:  }
098:  if ($i -gt $Global:curcol) {                      # Create spaces to where we need
099:    $Global:oline += (' ' * ($i - $Global:curcol))
100:    $Global:curcol = $i                             # Set our new column placeholder
101:  }
102:}
103:
104:Function add-nomove {                       # Add content without
105:  param($format)                            # modifying the current column count
106:  $Global:oline += $format
107:}
108:
109:Function add-token {
110:  param($token)
111:  [void] [System.Reflection.Assembly]::Loadwithpartialname("System.Web")
112:  $atcol = $token.StartColumn                     # Temp column
113:  $atlen = $token.Length                          # Temp length
114:  $atlin = $token.StartLine                       # Temp line
115:  if ($token.Type -eq 'LineContinuation') {
116:    $atlen = 1                                    # MS includes the `R`N as well.  Stupid
117:  }
118:  if ( ! ($token.StartLine -eq $token.EndLine) ) {  # Multiple lines, multiline comment
119:    while( $atlin -lt $token.EndLine ) {          # For each extra line
120:      $content = $program[$atlin-1].Substring($atcol - 1)
121:      $content = [System.Web.HttpUtility]::HtmlEncode($content) # Encode the string for HTML
122:      $Global:oline = $Global:oline + $content
123:      start-new-line
124:      $atcol = 1                                  # Reset column
125:      ++$atlin                                    # Bump to next line
126:      $atlen = $token.EndColumn - 1               # Length is now the last column
127:    }
128:  }
129:  # Process like a regular line
130:  $content = $program[$atlin-1].Substring($atcol - 1, $atlen)
131:  $content = [System.Web.HttpUtility]::HtmlEncode($content) # Encode the string for HTML
132:  $Global:oline = $Global:oline + $content
133:  $Global:curcol += $atlen
134:}
135:
136:Function detabify-array {
137:  for ($i = 0; $i -lt $program.count; ++$i ) {
138:    $line = $program[$i]                  # get line
139:    if ( ($x = ($line.indexof("`t")) ) -ne -1 ) {
140:      for ( ; $x -ne -1 ; $x=($line.indexof("`t")) ) {
141:        $line = $line.remove($x,1)
142:        for ( $j = (($x+1) % $tabex) + 1; $j -ne 0; --$j ) {
143:          $line = $line.insert($x,' ')    # pad out line to enough spaces.
144:        }
145:      }
146:      $program[$i] = $line
147:    }
148:  }
149:}
150:
151:# Actual program start
152:
153:$parser = [System.Management.Automation.PsParser]
154:
155:# No leading $, we're comparing to Content
156:
157:$autovars = @('$','?','^','_','Args','ConsoleFileName','Error','Event',
158: 'EventSubscriber','ExecutionContext','False','ForEach','Home','Host','Input',
159: 'LastExitCode','Matches','MyInvocation','NestedPromptLevel','NULL','PID',
160: 'Profile','PSBoundParameters','PsCmdlet','PsCulture','PSDebugContext','PsHome',
161: 'PSScriptRoot','PsUICulture','PsVersionTable','Pwd','Sender','ShellID',
162: 'SourceArgs','SourceEventArgs','This','True')
163:
164:$infile = (Resolve-path $infile).Path
165:if ($outfile -eq $null) {
166:  $outfile = $infile + '.html'
167:} else {
168:  $outfile = (Resolve-path $outfile).Path
169:}
170:'<code>' | Set-Content $outfile
171:$program = Get-Content $infile
172:detabify-array
173:
174:# Some programs sort on StartLine and StartColumn.  Don't see the need.
175:$pprogram = $parser::Tokenize($program, [ref] $null)
176:
177:# To keep from massively inflating the output, anything that we want to keep black, we may not mark at all
178:# Keeping this generic so that I can modify to use CSS instead
179:
180:$pret = `
181:@{'Attribute' = '<span style="color: black">';          # Black
182:  'Command' = '<span style="color: #A0522D">';          # Sienna
183:  'CommandArgument' = '<span style="color: #808080">';  # Gray
184:  'CommandParameter' = '<span style="color: #A719D6">'; # Blue Violet
185:  'Comment' = '<span style="color: #1C6C22">';          # Forest Green
186:  'GroupEnd' = '<span style="color: black">';           # Black
187:  'GroupStart' = '<span style="color: black">';         # Black
188:  'Keyword' = '<span style="color: #C75209">';          # Brown
189:  'LineContinuation' = '<span style="color: black">';   # Black
190:  'LoopLabel' = '<span style="color:#00FFFF">';         # Turquoise
191:  'Member' = '<span style="color: #2D61FB">';           # Light Blue
192:  'Number' = '<span style="color: #C71D18">';           # Fire Brick
193:  'Operator' = '<span style="color: #FF0000">';         # Red
194:  'Position' = '<span style="color: #FFFF00">';         # Yellow
195:  'StatementSeparator' = '<span style="color: black">'; # Black
196:  'String' = '<span style="color: #6802F6">';           # Dark Violet
197:  'Type' = '<span style="color: #FF8000">';             # Dark Orange
198:  'Variable' = '<span style="color: #0000FF">';         # Blue
199:  'AutoVar' = '<span style="color: #26FF00">'           # Bright Green
200:}
201:
202:$espan = '</span>'
203:
204:$post = @{'Attribute' = $espan;'Command' = $espan;
205:  'CommandArgument' = $espan; 'CommandParameter' = $espan;
206:  'Comment' = $espan; 'GroupEnd' = $espan;
207:  'GroupStart' = $espan; 'Keyword' = $espan;
208:  'LineContinuation' = $espan; 'LoopLabel' = $espan;
209:  'Member' = $espan; 'Number' = $espan;
210:  'Operator' = $espan; 'Position' = $espan;
211:  'StatementSeparator' = $espan; 'String' = $espan;
212:  'Type' = $espan; 'Variable' = $espan;
213:  'AutoVar' = $espan
214:}
215:
216:foreach ( $token in $pprogram ) {
217:  if ($token.Type -ne 'NewLine') {
218:    Space-out-line $token
219:    $isauto = $false
220:    if($token.Type -eq 'Variable') {
221::test for($i = 0; $i -lt $autovars.Count; ++$i ) {
222:        if ( ($token.content.ToLower()) -eq ($autovars[$i].ToLower()) ) {
223:          $isauto = $true
224:          break
225:        }
226:      }
227:    }
228:    if ($isauto) {
229:      add-nomove($pret['AutoVar'])                # Add in AutoVars formatting
230:      add-token($token)                           # Add token from $program array
231:      add-nomove($post['AutoVar'])                # Add in finsihing formatting
232:    }
233:    else {
234:      add-nomove($pret[$token.Type.toString()])   # Add in formatting before token
235:      add-token($token)                           # Add token from $program array
236:      add-nomove($post[$token.Type.toString()])   # Add in finishing formatting
237:    }
238:  }
239:  else {
240:    start-new-line
241:  }
242:}
243:if ($Global:oline.length -gt 1) {
244:  start-new-line                                  # Ensure we're at the end of the road
245:}
246:'</code>' | Add-Content $outfile