ikiwiki (3.20130711) unstable; urgency=low
[ikiwiki.git] / IkiWiki / Plugin / highlight.pm
1 #!/usr/bin/perl
2 package 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
8 use warnings;
9 use strict;
10 use IkiWiki 3.00;
11 use Encode;
12
13 my $data_dir;
14
15 sub 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
23 sub 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 => "/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/share/highlight/langDefs",
47                         description => "location of highlight's langDefs directory",
48                         safe => 0,
49                         rebuild => undef,
50                 },
51 }
52
53 sub 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                 $config{filetypes_conf}= 
64                      ($data_dir ? $data_dir->getConfDir() : "/etc/highlight/")
65                           . "filetypes.conf";
66         }
67         if (! exists $config{langdefdir}) {
68                 $config{langdefdir}=
69                      ($data_dir ? $data_dir->getLangPath("")
70                       : "/usr/share/highlight/langDefs");
71
72         }
73         if (exists $config{tohighlight} && read_filetypes()) {
74                 foreach my $file (split ' ', $config{tohighlight}) {
75                         my @opts = $file=~s/^\.// ?
76                                 (keepextension => 1) :
77                                 (noextension => 1);
78                         my $ext = $file=~s/:(.*)// ? $1 : $file;
79                 
80                         my $langfile=ext2langfile($ext);
81                         if (! defined $langfile) {
82                                 error(sprintf(gettext(
83                                         "tohighlight contains unknown file type '%s'"),
84                                         $ext));
85                         }
86         
87                         hook(
88                                 type => "htmlize",
89                                 id => $file,
90                                 call => sub {
91                                         my %params=@_;
92                                         highlight($langfile, $file, $params{content});
93                                 },
94                                 longname => sprintf(gettext("Source code: %s"), $file),
95                                 @opts,
96                         );
97                 }
98         }
99 }
100
101 sub htmlizeformat {
102         my $format=lc shift;
103         my $langfile=ext2langfile($format);
104
105         if (! defined $langfile) {
106                 return;
107         }
108
109         return Encode::decode_utf8(highlight($langfile, $format, shift));
110 }
111
112 my %ext2lang;
113 my $filetypes_read=0;
114 my %highlighters;
115
116 # Parse highlight's config file to get extension => language mappings.
117 sub read_filetypes () {
118         my $f;
119         if (!open($f, $config{filetypes_conf})) {
120                 warn($config{filetypes_conf}.": ".$!);
121                 return 0;
122         };
123
124         local $/=undef;
125         my $config=<$f>;
126         close $f;
127
128         # highlight >= 3.2 format (bind-style)
129         while ($config=~m/Lang\s*=\s*\"([^"]+)\"[,\s]+Extensions\s*=\s*{([^}]+)}/sg) {
130                 my $lang=$1;
131                 foreach my $bit (split ',', $2) {
132                         $bit=~s/.*"(.*)".*/$1/s;
133                         $ext2lang{$bit}=$lang;
134                 }
135         }
136
137         # highlight < 3.2 format
138         if (! keys %ext2lang) {
139                 foreach (split("\n", $config)) {
140                         if (/^\$ext\((.*)\)=(.*)$/) {
141                                 $ext2lang{$_}=$1 foreach $1, split ' ', $2;
142                         }
143                 }
144         }
145
146         return $filetypes_read=1;
147 }
148
149
150 # Given a filename extension, determines the language definition to
151 # use to highlight it.
152 sub ext2langfile ($) {
153         my $ext=shift;
154
155         my $langfile="$config{langdefdir}/$ext.lang";
156         return $langfile if exists $highlighters{$langfile};
157
158         read_filetypes() unless $filetypes_read;
159         if (exists $ext2lang{$ext}) {
160                 return "$config{langdefdir}/$ext2lang{$ext}.lang";
161         }
162         # If a language only has one common extension, it will not
163         # be listed in filetypes, so check the langfile.
164         elsif (-e $langfile) {
165                 return $langfile;
166         }
167         else {
168                 return undef;
169         }
170 }
171
172 # Interface to the highlight C library.
173 sub highlight ($$) {
174         my $langfile=shift;
175         my $extorfile=shift;
176         my $input=shift;
177
178         eval q{use highlight};
179         if ($@) {
180                 print STDERR gettext("warning: highlight perl module not available; falling back to pass through");
181                 return $input;
182         }
183
184         my $gen;
185         if (! exists $highlighters{$langfile}) {
186                 $gen = highlight::CodeGenerator::getInstance($highlight::XHTML);
187                 $gen->setFragmentCode(1); # generate html fragment
188                 $gen->setHTMLEnclosePreTag(1); # include stylish <pre>
189                 if ($data_dir){
190                         # new style, requires a real theme, but has no effect
191                         $gen->initTheme($data_dir->getThemePath("seashell.theme"));
192                 } else {
193                         # old style, anything works.
194                         $gen->initTheme("/dev/null");
195                 }
196                 $gen->loadLanguage($langfile); # must come after initTheme
197                 $gen->setEncoding("utf-8");
198                 $highlighters{$langfile}=$gen;
199         }
200         else {          
201                 $gen=$highlighters{$langfile};
202         }
203
204         return "<div class=\"highlight-$extorfile\">".$gen->generateString($input)."</div>";
205 }
206
207 1