source: create missing cache subdirectories
[sheet.git] / source.plp
1 <(common.inc.plp)><:
2
3 my $source = $Request;
4 my $incname = qr{ [a-z][/a-z0-9_.-]* \.(?:plp?|css|js|txt) }x;
5
6 if ($source =~ s{(?<=\Q.inc.pl\E)/jsonp?$}{} and -r $source) {
7         # convert perl include to json construct
8         checkmodified($source);
9         eval {
10                 my $data = do $source or die $@ || $! || 'read error';
11                 require JSON;
12                 my $converter = JSON->new;
13                 $converter->indent->space_after->canonical;
14
15                 $header{content_type} = 'application/json';
16                 $header{'Access-Control-Allow-Origin'} = '*';
17                 $header{content_type} = 'text/plain' if exists $get{debug};
18                 print $_, '(' for $get{callback} // ();
19                 print $converter->encode($data);
20                 print     ')' for $get{callback} // ();
21                 return 1;
22         } or do {
23                 $header{status} = '500 File unavailable';
24                 $header{content_type} = 'text/plain';
25                 print "Conversion failed: $@";
26         };
27         exit;
28 }
29
30 Html({
31         title => "$source source code",
32         version => '1.2',
33         description => !$source ? 'Index of source files for this site.' : [
34                 "Source code of the $source file at this site,",
35                 "with syntax highlighted and references linked."
36         ],
37         keywords => [qw'
38                 sheet cheat source code perl plp html agpl
39         '],
40         stylesheet => [qw'light dark mono red'],
41         data => [$source =~ m{\A($incname)\z}],
42 });
43
44 say '';
45
46 if (not $source or -d $source) {
47         PLP_START {
48                 print "<h1>Source files</h1>";
49         };
50
51         if ($source and $source ne 'tools') {
52                 Abort("Directory index not permitted", '403 source not allowed');
53         }
54
55         print "<p>Project code distributed under the AGPL. Please contribute back.</p>";
56         say '<ul>';
57         for (glob($source ? "$source/*" : '*.plp')) {
58                 say '<li>', showlink($_, "/source/$_");
59         }
60         say "</ul>\n";
61 }
62 else {
63         my $href = showlink($source, $source =~ m{\A (\w+) \.plp \z}x && "/$1");
64         PLP_START {
65                 say "<h1>Source of $href</h1>";
66         };
67
68         if ($source =~ m{(?:/|^)\.}) {
69                 Abort("File request not permitted", '403 source not allowed');
70         }
71         elsif ($source =~ s{::}{/}g or !-e $source) {
72                 $source .= '.pm';
73                 for (0 .. $#INC) {
74                         -e ($_ = "$INC[$_]/$source") or next;
75                         $source = $_;
76                         last;
77                 }
78         }
79         -r $source or Abort("Requested file not found", '404 source not found');
80         my $size = (stat $source)->[7];
81
82         my $cachefile = "source/$source.html";
83         if (-e $cachefile and (stat $cachefile)->[9] >= (stat $source)->[9]) {
84                 say '<pre>';
85                 print ReadFile($cachefile);
86                 say '</pre>';
87                 exit;
88         }
89         -e or mkdir for $cachefile =~ s{[^/]+\z}{}r; # dirname
90         open my $cache, '>', $cachefile
91                 or Alert("Could not save cache", "Opening $cachefile failed: $!");;
92
93         if (my $hl = eval {
94                 $size < 32_768 or die 'large files take too long to parse';
95                 require Text::VimColor;
96                 Text::VimColor->VERSION(0.12)
97                         or die 'early versions are buggy under FastCGI';
98                 delete $Text::VimColor::SYNTAX_TYPE{Underlined};
99                 return Text::VimColor->new(
100                         file => $source,
101                         vim_options => [@Text::VimColor::VIM_OPTIONS, '+:set enc=utf-8'],
102                 )->marked;
103         }) {
104                 my %TYPETAG = (
105                         Statement => 'strong',
106                         Error     => 'em',
107                         Todo      => 'em',
108                         PreProc   => 'strong',
109                 );
110
111                 say '<pre>';
112                 foreach (@{$hl}) {
113                         my ($type, $contents) = @{$_};
114                         $contents = decode_utf8($contents);
115                         my $tag = $type && ($TYPETAG{$type} || 'span');
116                         my $line = Text::VimColor::_xml_escape($contents);
117
118                         # link other page sources, stylesheets, and javascript
119                         $line =~ s{ ^(['"]?) \K ($incname) (?=\1$) }{ showlink($2, "/source/$2") }xe
120                                 if !$type || $type eq 'Constant';
121                         # link perl module names (Xx::Xx...)
122                         $line =~ s{ ^\s* \K ([A-Z]\w+(?:::\w+)+) (?![^;\s]) }{ showlink($1, "/source/$1") }xe
123                                 if !$type;
124                         # link generator scripts (by tools/...)
125                         $line =~ s{ ^.*? by\  \K (tools/\S+) }{ showlink($1, "/source/$1") }xe
126                                 if $type && $type eq 'Comment';
127
128                         $line = qq(<$tag class="sy-\l$type">$line</$tag>) if $tag;
129                         print $line;
130                         print {$cache} $line if $cache;
131                 }
132                 say '</pre>';
133         }
134         else {
135                 say '<pre>';
136                 print EscapeHTML(decode_utf8(ReadFile($source)));
137                 say '</pre>';
138         }
139
140         say '';
141 }
142