git: do not mix in_git_dir with eval{}
[ikiwiki] / 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           if (! $data_dir ) {
64                 $config{filetypes_conf}= "/etc/highlight/filetypes.conf";
65               } elsif ( $data_dir -> can('searchFile') ) {
66                 # 3.18 +
67                 $config{filetypes_conf}=
68                   $data_dir -> searchFile("filetypes.conf");
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/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
109 sub 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
120 my %ext2lang;
121 my $filetypes_read=0;
122 my %highlighters;
123
124 # Parse highlight's config file to get extension => language mappings.
125 sub 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
158 sub 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.
170 sub 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.
191 sub 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
227 1