Merge branch 'jn/gitweb-syntax-highlight'
authorJunio C Hamano <gitster@pobox.com>
Sun, 13 Jun 2010 18:21:37 +0000 (11:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 13 Jun 2010 18:21:37 +0000 (11:21 -0700)
* jn/gitweb-syntax-highlight:
gitweb: Refactor syntax highlighting support
gitweb: Syntax highlighting support

1  2 
gitweb/gitweb.perl
diff --combined gitweb/gitweb.perl
index e108bbc60ac9f02de5dc796377a251ede2bae6a4,7d9b66046353a1fb2fea105c66b03866bb6e8f6f..2365311d94e78b894b42526c3181e45f23ce14ac
@@@ -11,7 -11,7 +11,7 @@@ use strict
  use warnings;
  use CGI qw(:standard :escapeHTML -nosticky);
  use CGI::Util qw(unescape);
 -use CGI::Carp qw(fatalsToBrowser);
 +use CGI::Carp qw(fatalsToBrowser set_message);
  use Encode;
  use Fcntl ':mode';
  use File::Find qw();
@@@ -445,6 -445,19 +445,19 @@@ our %feature = 
        'javascript-actions' => {
                'override' => 0,
                'default' => [0]},
+       # Syntax highlighting support. This is based on Daniel Svensson's
+       # and Sham Chukoury's work in gitweb-xmms2.git.
+       # It requires the 'highlight' program present in $PATH,
+       # and therefore is disabled by default.
+       # To enable system wide have in $GITWEB_CONFIG
+       # $feature{'highlight'}{'default'} = [1];
+       'highlight' => {
+               'sub' => sub { feature_bool('highlight', @_) },
+               'override' => 0,
+               'default' => [0]},
  );
  
  sub gitweb_get_feature {
@@@ -952,21 -965,6 +965,21 @@@ if ($git_avatar eq 'gravatar') 
        $git_avatar = '';
  }
  
 +# custom error handler: 'die <message>' is Internal Server Error
 +sub handle_errors_html {
 +      my $msg = shift; # it is already HTML escaped
 +
 +      # to avoid infinite loop where error occurs in die_error,
 +      # change handler to default handler, disabling handle_errors_html
 +      set_message("Error occured when inside die_error:\n$msg");
 +
 +      # you cannot jump out of die_error when called as error handler;
 +      # the subroutine set via CGI::Carp::set_message is called _after_
 +      # HTTP headers are already written, so it cannot write them itself
 +      die_error(undef, undef, $msg, -error_handler => 1, -no_http_header => 1);
 +}
 +set_message(\&handle_errors_html);
 +
  # dispatch
  if (!defined $action) {
        if (defined $hash) {
@@@ -987,16 -985,11 +1000,16 @@@ if ($action !~ m/^(?:opml|project_list|
        die_error(400, "Project needed");
  }
  $actions{$action}->();
 -exit;
 +DONE_GITWEB:
 +1;
  
  ## ======================================================================
  ## action links
  
 +# possible values of extra options
 +# -full => 0|1      - use absolute/full URL ($my_uri/$my_url as base)
 +# -replay => 1      - start from a current view (replay with modifications)
 +# -path_info => 0|1 - don't use/use path_info URL (if possible)
  sub href {
        my %params = @_;
        # default is to use -absolute url() i.e. $my_uri
        }
  
        my $use_pathinfo = gitweb_check_feature('pathinfo');
 -      if ($use_pathinfo and defined $params{'project'}) {
 +      if (defined $params{'project'} &&
 +          (exists $params{-path_info} ? $params{-path_info} : $use_pathinfo)) {
                # try to put as many parameters as possible in PATH_INFO:
                #   - project name
                #   - action
@@@ -2441,9 -2433,6 +2454,9 @@@ sub git_get_projects_list 
                        follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
 +                              # global variables
 +                              our $project_maxdepth;
 +                              our $projectroot;
                                # skip project-list toplevel, if we get it.
                                return if (m!^[/.]$!);
                                # only directories can be git repositories
@@@ -3179,33 -3168,81 +3192,88 @@@ sub blob_contenttype 
        return $type;
  }
  
+ # guess file syntax for syntax highlighting; return undef if no highlighting
+ # the name of syntax can (in the future) depend on syntax highlighter used
+ sub guess_file_syntax {
+       my ($highlight, $mimetype, $file_name) = @_;
+       return undef unless ($highlight && defined $file_name);
+       # configuration for 'highlight' (http://www.andre-simon.de/)
+       # match by basename
+       my %highlight_basename = (
+               #'Program' => 'py',
+               #'Library' => 'py',
+               'SConstruct' => 'py', # SCons equivalent of Makefile
+               'Makefile' => 'make',
+       );
+       # match by extension
+       my %highlight_ext = (
+               # main extensions, defining name of syntax;
+               # see files in /usr/share/highlight/langDefs/ directory
+               map { $_ => $_ }
+                       qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl),
+               # alternate extensions, see /etc/highlight/filetypes.conf
+               'h' => 'c',
+               map { $_ => 'cpp' } qw(cxx c++ cc),
+               map { $_ => 'php' } qw(php3 php4),
+               map { $_ => 'pl'  } qw(perl pm), # perhaps also 'cgi'
+               'mak' => 'make',
+               map { $_ => 'xml' } qw(xhtml html htm),
+       );
+       my $basename = basename($file_name, '.in');
+       return $highlight_basename{$basename}
+               if exists $highlight_basename{$basename};
+       $basename =~ /\.([^.]*)$/;
+       my $ext = $1 or return undef;
+       return $highlight_ext{$ext}
+               if exists $highlight_ext{$ext};
+       return undef;
+ }
+ # run highlighter and return FD of its output,
+ # or return original FD if no highlighting
+ sub run_highlighter {
+       my ($fd, $highlight, $syntax) = @_;
+       return $fd unless ($highlight && defined $syntax);
+       close $fd
+               or die_error(404, "Reading blob failed");
+       open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+                 "highlight --xhtml --fragment --syntax $syntax |"
+               or die_error(500, "Couldn't open file or run syntax highlighter");
+       return $fd;
+ }
  ## ======================================================================
  ## functions printing HTML: header, footer, error page
  
 +sub get_page_title {
 +      my $title = to_utf8($site_name);
 +
 +      return $title unless (defined $project);
 +      $title .= " - " . to_utf8($project);
 +
 +      return $title unless (defined $action);
 +      $title .= "/$action"; # $action is US-ASCII (7bit ASCII)
 +
 +      return $title unless (defined $file_name);
 +      $title .= " - " . esc_path($file_name);
 +      if ($action eq "tree" && $file_name !~ m|/$|) {
 +              $title .= "/";
 +      }
 +
 +      return $title;
 +}
 +
  sub git_header_html {
        my $status = shift || "200 OK";
        my $expires = shift;
 +      my %opts = @_;
  
 -      my $title = "$site_name";
 -      if (defined $project) {
 -              $title .= " - " . to_utf8($project);
 -              if (defined $action) {
 -                      $title .= "/$action";
 -                      if (defined $file_name) {
 -                              $title .= " - " . esc_path($file_name);
 -                              if ($action eq "tree" && $file_name !~ m|/$|) {
 -                                      $title .= "/";
 -                              }
 -                      }
 -              }
 -      }
 +      my $title = get_page_title();
        my $content_type;
        # require explicit support from the UA if we are to send the page as
        # 'application/xhtml+xml', otherwise send it as plain old 'text/html'.
                $content_type = 'text/html';
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
 -                         -status=> $status, -expires => $expires);
 +                         -status=> $status, -expires => $expires)
 +              unless ($opts{'-no_http_header'});
        my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
  <?xml version="1.0" encoding="utf-8"?>
@@@ -3437,7 -3473,6 +3505,7 @@@ sub die_error 
        my $status = shift || 500;
        my $error = esc_html(shift) || "Internal Server Error";
        my $extra = shift;
 +      my %opts = @_;
  
        my %http_responses = (
                400 => '400 Bad Request',
                500 => '500 Internal Server Error',
                503 => '503 Service Unavailable',
        );
 -      git_header_html($http_responses{$status});
 +      git_header_html($http_responses{$status}, undef, %opts);
        print <<EOF;
  <div class="page_body">
  <br /><br />
@@@ -3460,8 -3495,7 +3528,8 @@@ EO
        print "</div>\n";
  
        git_footer_html();
 -      exit;
 +      goto DONE_GITWEB
 +              unless ($opts{'-error_handler'});
  }
  
  ## ----------------------------------------------------------------------
@@@ -5380,6 -5414,7 +5448,7 @@@ sub git_blob 
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(500, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
+       # use 'blob_plain' (aka 'raw') view for files that cannot be displayed
        if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)! && -B $fd) {
                close $fd;
                return git_blob_plain($mimetype);
        # we can have blame only for text/* mimetype
        $have_blame &&= ($mimetype =~ m!^text/!);
  
+       my $highlight = gitweb_check_feature('highlight');
+       my $syntax = guess_file_syntax($highlight, $mimetype, $file_name);
+       $fd = run_highlighter($fd, $highlight, $syntax)
+               if $syntax;
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
                        chomp $line;
                        $nr++;
                        $line = untabify($line);
-                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"" . href(-replay => 1)
-                               . "#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+                       printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
+                              $nr, href(-replay => 1), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
@@@ -6151,8 -6190,8 +6224,8 @@@ sub git_commitdiff 
                        }
                        push @commit_spec, '--root', $hash;
                }
 -              open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8',
 -                      '--stdout', @commit_spec
 +              open $fd, "-|", git_cmd(), "format-patch", @diff_opts,
 +                      '--encoding=utf8', '--stdout', @commit_spec
                        or die_error(500, "Open git-format-patch failed");
        } else {
                die_error(400, "Unknown commitdiff format");