1#!/usr/bin/perl 2package IkiWiki::Plugin::highlight; 3 4# This has been tested with highlight 2.16 and highlight 3.2+svn19. 5# In particular version 3.2 won't work. It detects the different 6# versions by the presence of the the highlight::DataDir class. 7 8use warnings; 9use strict; 10use IkiWiki 3.00; 11use Encode; 12 13my $data_dir; 14 15sub import { 16 hook(type => "getsetup", id => "highlight", call => \&getsetup); 17 hook(type => "checkconfig", id => "highlight", call => \&checkconfig); 18 # this hook is used by the format plugin 19 hook(type => "htmlizeformat", id => "highlight", 20 call => \&htmlizeformat, last => 1); 21} 22 23sub getsetup () { 24 return 25 plugin => { 26 safe => 1, 27 rebuild => 1, # format plugin 28 section => "format", 29 }, 30 tohighlight => { 31 type => "string", 32 example => ".c .h .cpp .pl .py Makefile:make", 33 description => "types of source files to syntax highlight", 34 safe => 1, 35 rebuild => 1, 36 }, 37 filetypes_conf => { 38 type => "string", 39 example => "/usr/local/etc/highlight/filetypes.conf", 40 description => "location of highlight's filetypes.conf", 41 safe => 0, 42 rebuild => undef, 43 }, 44 langdefdir => { 45 type => "string", 46 example => "/usr/local/share/highlight/langDefs", 47 description => "location of highlight's langDefs directory", 48 safe => 0, 49 rebuild => undef, 50 }, 51} 52 53sub checkconfig () { 54 eval q{use highlight}; 55 if (highlight::DataDir->can('new')) { 56 $data_dir=new highlight::DataDir(); 57 $data_dir->searchDataDir(""); 58 } else { 59 $data_dir=undef; 60 } 61 62 if (! exists $config{filetypes_conf}) { 63 if (! $data_dir ) { 64 $config{filetypes_conf}= "/usr/local/etc/highlight/filetypes.conf"; 65 } elsif ( $data_dir -> can('getFiletypesConfPath') ) { 66 # 3.14 + 67 $config{filetypes_conf}= 68 $data_dir -> getFiletypesConfPath("filetypes"); 69 } else { 70 # 3.9 + 71 $config{filetypes_conf}= 72 $data_dir -> getConfDir() . "/filetypes.conf"; 73 } 74 } 75 # note that this is only used for old versions of highlight 76 # where $data_dir will not be defined. 77 if (! exists $config{langdefdir}) { 78 $config{langdefdir}= "/usr/local/share/highlight/langDefs"; 79 80 } 81 if (exists $config{tohighlight} && read_filetypes()) { 82 foreach my $file (split ' ', $config{tohighlight}) { 83 my @opts = $file=~s/^\.// ? 84 (keepextension => 1) : 85 (noextension => 1); 86 my $ext = $file=~s/:(.*)// ? $1 : $file; 87 88 my $langfile=ext2langfile($ext); 89 if (! defined $langfile) { 90 error(sprintf(gettext( 91 "tohighlight contains unknown file type '%s'"), 92 $ext)); 93 } 94 95 hook( 96 type => "htmlize", 97 id => $file, 98 call => sub { 99 my %params=@_; 100 highlight($langfile, $file, $params{content}); 101 }, 102 longname => sprintf(gettext("Source code: %s"), $file), 103 @opts, 104 ); 105 } 106 } 107} 108 109sub htmlizeformat { 110 my $format=lc shift; 111 my $langfile=ext2langfile($format); 112 113 if (! defined $langfile) { 114 return; 115 } 116 117 return Encode::decode_utf8(highlight($langfile, $format, shift)); 118} 119 120my %ext2lang; 121my $filetypes_read=0; 122my %highlighters; 123 124# Parse highlight's config file to get extension => language mappings. 125sub read_filetypes () { 126 my $f; 127 if (!open($f, $config{filetypes_conf})) { 128 warn($config{filetypes_conf}.": ".$!); 129 return 0; 130 }; 131 132 local $/=undef; 133 my $config=<$f>; 134 close $f; 135 136 # highlight >= 3.2 format (bind-style) 137 while ($config=~m/Lang\s*=\s*\"([^"]+)\"[,\s]+Extensions\s*=\s*{([^}]+)}/sg) { 138 my $lang=$1; 139 foreach my $bit (split ',', $2) { 140 $bit=~s/.*"(.*)".*/$1/s; 141 $ext2lang{$bit}=$lang; 142 } 143 } 144 145 # highlight < 3.2 format 146 if (! keys %ext2lang) { 147 foreach (split("\n", $config)) { 148 if (/^\$ext\((.*)\)=(.*)$/) { 149 $ext2lang{$_}=$1 foreach $1, split ' ', $2; 150 } 151 } 152 } 153 154 return $filetypes_read=1; 155} 156 157 158sub searchlangdef { 159 my $lang=shift; 160 161 if ($data_dir) { 162 return $data_dir->getLangPath($lang . ".lang"); 163 } else { 164 return "$config{langdefdir}/$lang.lang"; 165 } 166 167} 168# Given a filename extension, determines the language definition to 169# use to highlight it. 170sub ext2langfile ($) { 171 my $ext=shift; 172 173 my $langfile=searchlangdef($ext); 174 return $langfile if exists $highlighters{$langfile}; 175 176 read_filetypes() unless $filetypes_read; 177 if (exists $ext2lang{$ext}) { 178 return searchlangdef($ext2lang{$ext}); 179 } 180 # If a language only has one common extension, it will not 181 # be listed in filetypes, so check the langfile. 182 elsif (-e $langfile) { 183 return $langfile; 184 } 185 else { 186 return undef; 187 } 188} 189 190# Interface to the highlight C library. 191sub highlight ($$) { 192 my $langfile=shift; 193 my $extorfile=shift; 194 my $input=shift; 195 196 eval q{use highlight}; 197 if ($@) { 198 print STDERR gettext("warning: highlight perl module not available; falling back to pass through"); 199 return $input; 200 } 201 202 my $gen; 203 if (! exists $highlighters{$langfile}) { 204 no warnings 'once'; 205 $gen = highlight::CodeGenerator::getInstance($highlight::XHTML); 206 use warnings; 207 $gen->setFragmentCode(1); # generate html fragment 208 $gen->setHTMLEnclosePreTag(1); # include stylish <pre> 209 if ($data_dir){ 210 # new style, requires a real theme, but has no effect 211 $gen->initTheme($data_dir->getThemePath("seashell.theme")); 212 } else { 213 # old style, anything works. 214 $gen->initTheme("/dev/null"); 215 } 216 $gen->loadLanguage($langfile); # must come after initTheme 217 $gen->setEncoding("utf-8"); 218 $highlighters{$langfile}=$gen; 219 } 220 else { 221 $gen=$highlighters{$langfile}; 222 } 223 224 return "<div class=\"highlight-$extorfile\">".$gen->generateString($input)."</div>"; 225} 226 2271 228