#!/usr/local/bin/perl ###################################################################### # # This is a "pretty printer" for code # it takes various types of code as input, tries to identify it, # and outputs html with highlighted keywords, comments, etc. # # Copyright 1999 Greg Spencer (greg_spencer@acm.org) # ###################################################################### # Here is where we add the various configurations. Each instantiation of # these classes contains the same API for accessing things. They each have # a method for determining if a particular file is of a particular type, # called "IsA". package TxtType; ###################################################################### # text file filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { # it's always at least a text file -- we check this one *last* return 1; } sub GetHighlightList { my @highlight = (); return \@highlight; } sub GetKeywords { my %keywords = (); return \%keywords; } sub GetColors { my %color = (); return \%color; } sub GetStyles { my %style = (); return \%style; } package HtmlType; ###################################################################### # Html source. ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $suffix = $filename; $suffix =~ s/.*?([^\/\\]+)$/$1/; # get basename $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($suffix =~ m/\.(htm|html)$/i) { # print STDERR "It's an HTML file!\n"; return 1; } if ($sample=~m/<\s*(html|body).*>/i) { # use html or body tag # print STDERR "It's an HTML file!\n"; return 1; } # print STDERR "It's not an HTML file.\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "variable"); return \@highlight; } sub GetKeywords { my %keywords = ( "keyword" => '(<[/!?]?[-_=A-Za-z0-9\s]+(?:.*?>)?|>)', "variable" => '((?:[%]|&)[#a-zA-Z0-9]+;?)', "comment start" => '<!--', "comment end" => '-->', "string start" => '\'"', "string end" => '\'"' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "#589a00", "comment" => "#00582e", "variable" => "#ff5d00", "string" => "#ae222f", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", "variable" => "B", "string" => "I", ); return \%style; } package CxxType; ###################################################################### # C/C++/Microsoft Resource filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $suffix = $filename; $suffix =~ s/.*?([^\/\\]+)$/$1/; # get basename $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($suffix =~ m/\.(c|cpp|cxx|c\+\+|cc|h|hcc|h\+\+|incl?|rc)$/i) { # print STDERR "It's a C++ file!\n"; return 1; } if ($sample=~m/-\*-\s*([-\w+.]+)\s*-\*-/) { # use emacs hints, if present if ($1 =~ m/^(c\+\+|cc|c)$/i ) { # print STDERR "It's a C++ file!\n"; return 1; } } # print STDERR "It's not a C++ file.\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "preprocessor", "type", "protection", "members"); return \@highlight; } sub GetKeywords { my $operators = '[-[<>+*%(^!=&|]+[]=)]?'; my $token = '[a-zA-Z_]\w*'; my %keywords = ( "type" => '\b(auto|bool|char|class|const|double|enum|extern|float|friend|inline|int|long|register|short|signed|static|struct|template|typedef|union|unsigned|virtual|void|volatile)\b', "protection" => '\b(public|private|protected)\b', "preprocessor" => '(^#\s*\w+)', "members" => "((?:$token\\s*::\\s*)+(?:~?$token|operator\\s*(?:\\s+$token|$operators)))", "keyword" => '((?:\b(break|case|catch|continue|default|delete|do|false|for|goto|new|return|switch|then|this|throw|true|try|while)\b)|(?:(?!\#).\b(?:else|if)))', "comment start" => '/\*', "comment end" => '\*/', "string start" => '\'"', "string end" => '\'"', "comment inline" => '//' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "#589a00", "comment" => "#00582e", "type" => "#00acac", "preprocessor" => "#ff5d00", "protection" => "#1f0058", "members" => "#000080", "string" => "#ae222f", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", "string" => "I", "type" => "B", "preprocessor" => "B", "protection" => "I", "members" => "B", ); return \%style; } package VrmlType; ###################################################################### # VRML file class # There's an awful lot of non-vrml stuff here, but that's # because it highlights javascript too. ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $suffix = $filename; $suffix =~ s/.*?([^\/\\]+)$/$1/; # get basename $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($suffix =~ m/\.(wrl|pfx)$/i) { # print STDERR "It's a VRML file!\n"; return 1; } if ($sample=~m/#\s*(vrml|inventor)\s+v[0-9.]+/i) { # print STDERR "It's a VRML file!\n"; return 1; } # print STDERR "It's not a VRML file.\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "type", "members", "vrml keyword"); return \@highlight; } sub GetKeywords { my %keywords = ( "type" => '\b(bool|class|float|int|unsigned|var|DEF|PROTO|EXTERNPROTO|USE)\b', "keyword" => '((?:\b(break|case|catch|continue|default|delete|do|false|for|goto|new|return|switch|then|this|true|while|nul)\b)|(?:(?!\#).\b(?:else|if)))', "members" => '(function\s+\w+\s*\(.*?\))', "vrml keyword" => '\b(AsciiText|Cone|Cube|Cylinder|IndexedFaceSet|IndexedLineSet|PointSet|Sphere|Coordinate3|FontStyle|Info|LOD|Material|MaterialBinding|Normal|NormalBinding|Texture2|Texture2Transform|TextureCoordinate2|ShapeHints|MatrixTransform|Rotation|Scale|Transform|Translation|OrthographicCamera|PerspectiveCamera|DirectionalLight|PointLight|SpotLight|Group|Separator|Switch|TransformSeparator|WWWAnchor|WWWInline|Anchor|Appearance|AudioClip|Background|Billboard|Box|Collision|Color|ColorInterpolator|Coordinate|CoordinateInterpolator|CylinderSensor|DiskSensor|ElevationGrid|Extrusion|Fog|FontStyle|ImageTexture|Inline|MovieTexture|NavigationInfo|NormalInterpolator|OrientationInterpolator|PixelTexture|PlaneSensor|PositionInterpolator|ProximitySensor|ScalarInterpolator|Script|Shape|Sound|SphereSensor|Text|TextureTransform|TextureCoordinate|TimeSensor|TouchSensor|Viewpoint|VisibilitySensor|WorldInfo|eventIn|eventOut|field|exposedField|ROUTE|TO|IS|TRUE|FALSE|NULL)\b', "string start" => '\'"', "string end" => '\'"', "comment inline" => '//|#' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "#589a00", "comment" => "#00582e", "type" => "#00acac", "vrml keyword" => "#1f0058", "members" => "#000080", "string" => "#ae222f", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", "string" => "I", "type" => "B", "vrml keyword" => "B", "members" => "B", ); return \%style; } package IDLType; ###################################################################### # Microsoft ODL/IDL filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $suffix = $filename; $suffix =~ s/.*?([^\/\\]+)$/$1/; # get basename $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($suffix =~ m/\.(odl|idl)$/i) { # print STDERR "It's an ODL/IDL file!\n"; return 1; } if ($sample=~m/-\*-\s*([-\w+.]+)\s*-\*-/) { # use emacs hints, if present (yea, right!) if ($1 =~ m/^(odl|idl)$/i ) { # print STDERR "It's an ODL/IDL file!\n"; return 1; } } # print STDERR "It's not an ODL/IDL file.\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "directive", "interface", "typekind", "attribute"); return \@highlight; } sub GetKeywords { my $operators = '[-[<>+*%(^!=&|]+[]=)]?'; my $token = '[a-zA-Z_]\w*'; my %keywords = ( "keyword" => '\b(library|typedef|module|coclass|cpp_quote)\b', "directive" => '\b(importlib|import)\b', "interface" => "\\b((?:disp)?interface\\s+$token\\s*:\\s*$token)\\b", "typekind" => '\b(enum|struct|union|base_type|far|auto|bool|char|class|const|double|enum|extern|float|friend|inline|int|long|register|short|signed|static|struct|template|union|unsigned|virtual|void|volatile|public|private|protected)\b', "attribute" => '\b(auto_handle|callback|context_handle|endpoint|first_is|handle|helpcontext|helpstring|hidden|ignore|implicit_handle|in|last_is|length_is|local|max_is|object|out|pointer_default|ptr|ref|size_is|string|switch_type|transmit_as|unique|uuid|version)\b', "comment start" => '/\*', "comment end" => '\*/', "string start" => '\'"', "string end" => '\'"', "comment inline" => '//' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "#00acac", "comment" => "#00582e", "string" => "#ae222f", "directive" => "#ff5d00", "typekind" => "#580000", "attribute" => "#589a00", "interface" => "#000080", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", "string" => "I", "typekind" => "B", "attribute" => "B", "directive" => "B", "interface" => "B", ); return \%style; } package MakeType; ###################################################################### # Makefile filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $basename = $filename; $basename =~ s/.*?([^\/\\]+)$/$1/; # get basename my $suffix = $basename; $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($basename =~ m/makefile/i) { # print STDERR "It's a Makefile!\n"; return 1; } # Cosmo-specific -- this gets leafdefs and ismdefs files. if ($basename =~ m/.*(defs|rules)$/i) { # print STDERR "It's a Makefile!\n"; return 1; } if ($suffix =~ m/\.mak/i) { # print STDERR "It's a Makefile!\n"; return 1; } if ($sample=~m/-\*-\s*([-\w+.]+)\s*-\*-/) { # use emacs hints, if present if ($1 =~ m/make/i) { # print STDERR "It's a Makefile!\n"; return 1; } } # print STDERR "It's not a Makefile.\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "macroassign", "dependency", "variables"); return \@highlight; } sub GetKeywords { my %keywords = ( "keyword" => "^(include|ifndef|ifdef|if|else|endif|define|endef)", # Regex used to find macro assignment lines in a makefile. "macroassign" => "^(\\w*\\s*[*:+]?:?=)", # Variable references even in targets/strings/comments: "variables" => "(\\\$[({](?:[A-Za-z_][\\w:.=]*[})]))", # Regex used to find dependency lines in a makefile. "dependency" => "^([^\\s#:]+(?:\\s+[^\\s#:]+)*\\s*:(?:\\s*|[^=\\n].*))\$", # comment regexp "comment inline" => '#' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "#584c00", "comment" => "#00582e", "macroassign" => "#580000", "dependency" => "#003358", "variables" => "#1f0058", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", "macroassign" => "B", "dependency" => "B", "variables" => "I", ); return \%style; } package CollectType; ###################################################################### # InstallBuilder collection filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $basename = $filename; $basename =~ s/.*?([^\/\\]+)$/$1/; # get basename my $suffix = $basename; $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($basename =~ m/collect\..+/i) { # print STDERR "It's an InstallBuilder collection file!\n"; return 1; } # print STDERR "It's not an InstallBuilder collection file .\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword", "tag"); return \@highlight; } sub GetKeywords { my %keywords = ( "keyword" => '^(\s*(file|dir))', # IDB tag "tag" => '(\s+\w+)$', # comment regexp "comment inline" => '#' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "green", "tag" => "maroon", "comment" => "purple", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "tag" => "B", "comment" => "I", ); return \%style; } package SpecType; ###################################################################### # InstallBuilder specification filetype class ###################################################################### sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; # $self->init(@args); return $self; } sub IsA { my $self = shift; my $filename = shift; my $sample = shift; my $basename = $filename; $basename =~ s/.*?([^\/\\]+)$/$1/; # get basename my $suffix = $basename; $suffix =~ s/.*(\.[^.\/\\]+)$/$1/; # get suffix of basename, if any if ($basename =~ m/spec\.dat/i) { # print STDERR "It's an InstallBuilder spec file!\n"; return 1; } # print STDERR "It's not an InstallBuilder spec file .\n"; return 0; } sub GetHighlightList { my @highlight = ("keyword"); return \@highlight; } sub GetKeywords { my %keywords = ( "keyword" => '^(\s*(product|uiName|defaultInstallDir|endproduct|subsys|export|endsubsys))', # comment regexp "comment inline" => '#' ); return \%keywords; } sub GetColors { my %color = ( "keyword" => "green", "comment" => "purple", ); return \%color; } sub GetStyles { my %style = ( "keyword" => "B", "comment" => "I", ); return \%style; } package SourceType; # always check TxtType last, since it always returns true. @types = qw(CxxType MakeType CollectType SpecType IDLType HtmlType VrmlType TxtType) unless @types; sub FindSourceType { my $filename = shift; my $sample = shift; # print STDERR "There are ".($#types+1)." types.\n"; foreach (@types) { # print STDERR "Testing $_...\n"; my $tester = eval("new $_"); return $tester if $tester->IsA($filename,$sample); } return undef; } package SourceToHtml; ###################################################################### # # Variable Init # ###################################################################### # constructor sub new { my $type = shift; my $self = {}; my @args = @_; bless $self; $self->init(@args); return $self; } sub init { # constructor takes: # (file, sample, numbering, confidential, standalone, timestamp, title) my $self = shift; my $filename = shift; my $sample = shift; $self->{"number lines"}=shift; $self->{"confidential"}=shift; $self->{"standalone"}=shift; $self->{"extra cols"}=[]; $self->{"extra colwidths"}=[]; my $timestamp = shift; my $title = shift; my $label = shift; $self->{"timestamp"}=""; $self->{"stamp label"} = "Listing Date:"; if ("$timestamp" eq "1") { $self->{"timestamp"}=&Timestamp(time); } elsif ($timestamp) { $self->{"timestamp"}= $timestamp; $self->{"stamp label"} = $label if $label; } # $self->{"timestamp"}=&Timestamp(time) if ($timestamp == 1); $self->{"title"} = $title; $self->{"head"} = $self->{"title"}; $self->{"title"} =~ s|[^\s]*[/\\](.+)$|$1|; $self->{"user keys"} = ""; $self->{"user color"} = "#000080"; $self->{"user style"} = "B"; $self->{"output"} = ""; # print STDERR "File: $filename\n"; # print STDERR "Sample: $sample\n"; my $typeinfo = SourceType::FindSourceType($filename,$sample); # print STDERR "Found File Type.\n"; if ($typeinfo == undef) { # print STDERR "Unable to determine source type -- no highlighting performed.\n"; $self->{"highlight"} = []; $self->{"keywords"} = ""; $self->{"color"} = ""; $self->{"style"} = ""; } else { $self->{"highlight"} = $typeinfo->GetHighlightList(); $self->{"keywords"} = $typeinfo->GetKeywords(); $self->{"color"} = $typeinfo->GetColors(); $self->{"style"} = $typeinfo->GetStyles(); } } ###################################################################### # # Member functions # ###################################################################### sub SetUserKeywords { my $self = shift; if (@_) { push (@{$self->{"highlight"}},"user"); @{$self->{"user keys"}} = '\b('."@_".')\b'; } else { #remove "user" from the list. @{$self->{"user keys"}} = ""; my @newlist = (); while ($_ = shift @{$self->{"highlight"}}) { push(@newlist,$_) if $_ ne "user"; } @{$self->{"highlight"}} = @newlist; } } # args are the index of the column to set, and a reference # to the array to set it to. sub SetExtraColumn { my $self = shift; my $idx = shift; if ($idx > scalar(@{$self->{"extra cols"}})) { $idx = scalar(@{$self->{"extra cols"}}); } ${$self->{"extra colwidths"}}[$idx] = shift; ${$self->{"extra cols"}}[$idx] = shift; } sub Print { my $self = shift; $self->{"output"} .= "@_"; } sub PrintStandaloneHeader { my $self = shift; $self->Print(""); if ($self->{"head"}) { my $tmp = " (".$self->{"timestamp"}.")" if $self->{"timestamp"}; $self->Print("
$lefthead". " | $middlehead". " | $righthead |
\n");
$self->Print("$linecnt\n");
$self->Print("\n");
for ($j=0;$j\n");
$self->Print("");
for ($i=0;$i<10;$i++) {
$self->Print($self->{"extra cols"}[$j][$linecnt+$i]." | \n"); } $self->Print(""); } $self->Print("
|
");
while ($_ = shift(@input)) {
# quote html stuff
&metachar;
# fix multi-line comments (if any)
if ($self->{"keywords"}{"comment start"}) {
my $start = $self->{"keywords"}{"comment start"};
my $end = $self->{"keywords"}{"comment end"};
lather:
if ($incomment && m@(.*)($end)(.*)@) {
$self->Print($1.$2."".$self->{"style"}{"comment"}."> ");
$incomment = 0;
chomp $3;
$_ = $3."\n";
}
if ($incomment) { goto rinse; }
elsif (m@(.*)($start)(.*)@) {
$self->Print($self->Words($1).
"<".$self->{"style"}{"comment"}.">".$2);
chomp $3;
$_=$3."\n";
$incomment = 1;
goto lather;
}
}
$_=$self->Words($_);
rinse:
$self->Print($_);
}
$self->Print("
");
# now zero the output
my $retval = $self->{"output"};
$self->{"output"} = "";
return $retval;
}