9d05e9fcf49126c7d1f97d815e6c3435a82cd83a
[ikiwiki.git] / IkiWiki / Plugin / highlight.pm
1 #!/usr/bin/perl
2 package IkiWiki::Plugin::highlight;
3
4 use warnings;
5 use strict;
6 use IkiWiki 3.00;
7 use Encode;
8
9 sub import {
10         hook(type => "getsetup", id => "highlight",  call => \&getsetup);
11         hook(type => "checkconfig", id => "highlight", call => \&checkconfig);
12         # this hook is used by the format plugin
13         hook(type => "htmlizeformat", id => "highlight", 
14                 call => \&htmlizeformat, last => 1);
15 }
16
17 sub getsetup () {
18         return
19                 plugin => {
20                         safe => 1,
21                         rebuild => 1, # format plugin
22                         section => "format",
23                 },
24                 tohighlight => {
25                         type => "string",
26                         example => ".c .h .cpp .pl .py Makefile:make",
27                         description => "types of source files to syntax highlight",
28                         safe => 1,
29                         rebuild => 1,
30                 },
31                 filetypes_conf => {
32                         type => "string",
33                         example => "/etc/highlight/filetypes.conf",
34                         description => "location of highlight's filetypes.conf",
35                         safe => 0,
36                         rebuild => undef,
37                 },
38                 langdefdir => {
39                         type => "string",
40                         example => "/usr/share/highlight/langDefs",
41                         description => "location of highlight's langDefs directory",
42                         safe => 0,
43                         rebuild => undef,
44                 },
45 }
46
47 sub checkconfig () {
48         if (! exists $config{filetypes_conf}) {
49                 $config{filetypes_conf}="/etc/highlight/filetypes.conf";
50         }
51         if (! exists $config{langdefdir}) {
52                 $config{langdefdir}="/usr/share/highlight/langDefs";
53         }
54         if (exists $config{tohighlight}) {
55                 foreach my $file (split ' ', $config{tohighlight}) {
56                         my @opts = $file=~s/^\.// ?
57                                 (keepextension => 1) :
58                                 (noextension => 1);
59                         my $ext = $file=~s/:(.*)// ? $1 : $file;
60                 
61                         my $langfile=ext2langfile($ext);
62                         if (! defined $langfile) {
63                                 error(sprintf(gettext(
64                                         "tohighlight contains unknown file type '%s'"),
65                                         $ext));
66                         }
67         
68                         hook(
69                                 type => "htmlize",
70                                 id => $file,
71                                 call => sub {
72                                         my %params=@_;
73                                         highlight($langfile, $params{content});
74                                 },
75                                 longname => sprintf(gettext("Source code: %s"), $file),
76                                 @opts,
77                         );
78                 }
79         }
80 }
81
82 sub htmlizeformat {
83         my $format=lc shift;
84         my $langfile=ext2langfile($format);
85
86         if (! defined $langfile) {
87                 return;
88         }
89
90         return Encode::decode_utf8(highlight($langfile, shift));
91 }
92
93 my %ext2lang;
94 my $filetypes_read=0;
95 my %highlighters;
96
97 # Parse highlight's config file to get extension => language mappings.
98 sub read_filetypes () {
99         open (my $f, $config{filetypes_conf}) || error("$config{filetypes_conf}: $!");
100         local $/=undef;
101         my $config=<$f>;
102         close $f;
103
104         # highlight >= 3.2 format (bind-style)
105         while ($config=~m/Lang\s*=\s*\"([^"]+)\"[,\s]+Extensions\s*=\s*{([^}]+)}/sg) {
106                 my $lang=$1;
107                 foreach my $bit (split ',', $2) {
108                         $bit=~s/.*"(.*)".*/$1/s;
109                         $ext2lang{$bit}=$lang;
110                 }
111         }
112
113         # highlight < 3.2 format
114         if (! keys %ext2lang) {
115                 foreach (split("\n", $config)) {
116                         if (/^\$ext\((.*)\)=(.*)$/) {
117                                 $ext2lang{$_}=$1 foreach $1, split ' ', $2;
118                         }
119                 }
120         }
121
122         $filetypes_read=1;
123 }
124
125
126 # Given a filename extension, determines the language definition to
127 # use to highlight it.
128 sub ext2langfile ($) {
129         my $ext=shift;
130
131         my $langfile="$config{langdefdir}/$ext.lang";
132         return $langfile if exists $highlighters{$langfile};
133
134         read_filetypes() unless $filetypes_read;
135         if (exists $ext2lang{$ext}) {
136                 return "$config{langdefdir}/$ext2lang{$ext}.lang";
137         }
138         # If a language only has one common extension, it will not
139         # be listed in filetypes, so check the langfile.
140         elsif (-e $langfile) {
141                 return $langfile;
142         }
143         else {
144                 return undef;
145         }
146 }
147
148 # Interface to the highlight C library.
149 sub highlight ($$) {
150         my $langfile=shift;
151         my $input=shift;
152
153         eval q{use highlight};
154         if ($@) {
155                 print STDERR gettext("warning: highlight perl module not available; falling back to pass through");
156                 return $input;
157         }
158
159         my $gen;
160         if (! exists $highlighters{$langfile}) {
161                 $gen = highlightc::CodeGenerator_getInstance($highlightc::XHTML);
162                 $gen->setFragmentCode(1); # generate html fragment
163                 $gen->setHTMLEnclosePreTag(1); # include stylish <pre>
164                 $gen->initTheme("/dev/null"); # theme is not needed because CSS is not emitted
165                 $gen->initLanguage($langfile); # must come after initTheme
166                 $gen->setEncoding("utf-8");
167                 $highlighters{$langfile}=$gen;
168         }
169         else {          
170                 $gen=$highlighters{$langfile};
171         }
172
173         return $gen->generateString($input);
174 }
175
176 1