| #!/usr/bin/perl |
| |
| # Copyright (C) 2013 |
| # Benoit Person <benoit.person@ensimag.imag.fr> |
| # Celestin Matte <celestin.matte@ensimag.imag.fr> |
| # License: GPL v2 or later |
| |
| # Set of tools for git repo with a mediawiki remote. |
| # Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki |
| |
| use strict; |
| use warnings; |
| |
| use Getopt::Long; |
| use URI::URL qw(url); |
| use LWP::UserAgent; |
| use HTML::TreeBuilder; |
| |
| use Git; |
| use MediaWiki::API; |
| use Git::Mediawiki qw(clean_filename connect_maybe |
| EMPTY HTTP_CODE_PAGE_NOT_FOUND); |
| |
| # By default, use UTF-8 to communicate with Git and the user |
| binmode STDERR, ':encoding(UTF-8)'; |
| binmode STDOUT, ':encoding(UTF-8)'; |
| |
| # Global parameters |
| my $verbose = 0; |
| sub v_print { |
| if ($verbose) { |
| return print {*STDERR} @_; |
| } |
| return; |
| } |
| |
| # Preview parameters |
| my $file_name = EMPTY; |
| my $remote_name = EMPTY; |
| my $preview_file_name = EMPTY; |
| my $autoload = 0; |
| sub file { |
| $file_name = shift; |
| return $file_name; |
| } |
| |
| my %commands = ( |
| 'help' => |
| [\&help, {}, \&help], |
| 'preview' => |
| [\&preview, { |
| '<>' => \&file, |
| 'output|o=s' => \$preview_file_name, |
| 'remote|r=s' => \$remote_name, |
| 'autoload|a' => \$autoload |
| }, \&preview_help] |
| ); |
| |
| # Search for sub-command |
| my $cmd = $commands{'help'}; |
| for (0..@ARGV-1) { |
| if (defined $commands{$ARGV[$_]}) { |
| $cmd = $commands{$ARGV[$_]}; |
| splice @ARGV, $_, 1; |
| last; |
| } |
| }; |
| GetOptions( %{$cmd->[1]}, |
| 'help|h' => \&{$cmd->[2]}, |
| 'verbose|v' => \$verbose); |
| |
| # Launch command |
| &{$cmd->[0]}; |
| |
| ############################# Preview Functions ################################ |
| |
| sub preview_help { |
| print {*STDOUT} <<'END'; |
| USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a] |
| [--output|-o <output filename>] [--verbose|-v] |
| <blob> | <filename> |
| |
| DESCRIPTION: |
| Preview is an utiliy to preview local content of a mediawiki repo as if it was |
| pushed on the remote. |
| |
| For that, preview searches for the remote name of the current branch's |
| upstream if --remote is not set. If that remote is not found or if it |
| is not a mediawiki, it lists all mediawiki remotes configured and asks |
| you to replay your command with the --remote option set properly. |
| |
| Then, it searches for a file named 'filename'. If it's not found in |
| the current dir, it will assume it's a blob. |
| |
| The content retrieved in the file (or in the blob) will then be parsed |
| by the remote mediawiki and combined with a template retrieved from |
| the mediawiki. |
| |
| Finally, preview will save the HTML result in a file. and autoload it |
| in your default web browser if the option --autoload is present. |
| |
| OPTIONS: |
| -r <remote name>, --remote <remote name> |
| If the remote is a mediawiki, the template and the parse engine |
| used for the preview will be those of that remote. |
| If not, a list of valid remotes will be shown. |
| |
| -a, --autoload |
| Try to load the HTML output in a new tab (or new window) of your |
| default web browser. |
| |
| -o <output filename>, --output <output filename> |
| Change the HTML output filename. Default filename is based on the |
| input filename with its extension replaced by '.html'. |
| |
| -v, --verbose |
| Show more information on what's going on under the hood. |
| END |
| exit; |
| } |
| |
| sub preview { |
| my $wiki; |
| my ($remote_url, $wiki_page_name); |
| my ($new_content, $template); |
| my $file_content; |
| |
| if ($file_name eq EMPTY) { |
| die "Missing file argument, see `git mw help`\n"; |
| } |
| |
| v_print("### Selecting remote\n"); |
| if ($remote_name eq EMPTY) { |
| $remote_name = find_upstream_remote_name(); |
| if ($remote_name) { |
| $remote_url = mediawiki_remote_url_maybe($remote_name); |
| } |
| |
| if (! $remote_url) { |
| my @valid_remotes = find_mediawiki_remotes(); |
| |
| if ($#valid_remotes == 0) { |
| print {*STDERR} "No mediawiki remote in this repo. \n"; |
| exit 1; |
| } else { |
| my $remotes_list = join("\n\t", @valid_remotes); |
| print {*STDERR} <<"MESSAGE"; |
| There are multiple mediawiki remotes, which of: |
| ${remotes_list} |
| do you want ? Use the -r option to specify the remote. |
| MESSAGE |
| } |
| |
| exit 1; |
| } |
| } else { |
| if (!is_valid_remote($remote_name)) { |
| die "${remote_name} is not a remote\n"; |
| } |
| |
| $remote_url = mediawiki_remote_url_maybe($remote_name); |
| if (! $remote_url) { |
| die "${remote_name} is not a mediawiki remote\n"; |
| } |
| } |
| v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n"); |
| |
| $wiki = connect_maybe($wiki, $remote_name, $remote_url); |
| |
| # Read file content |
| if (! -e $file_name) { |
| $file_content = git_cmd_try { |
| Git::command('cat-file', 'blob', $file_name); } |
| "%s failed w/ code %d"; |
| |
| if ($file_name =~ /(.+):(.+)/) { |
| $file_name = $2; |
| } |
| } else { |
| open my $read_fh, "<", $file_name |
| or die "could not open ${file_name}: $!\n"; |
| $file_content = do { local $/ = undef; <$read_fh> }; |
| close $read_fh |
| or die "unable to close: $!\n"; |
| } |
| |
| v_print("### Retrieving template\n"); |
| ($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//; |
| $template = get_template($remote_url, $wiki_page_name); |
| |
| v_print("### Parsing local content\n"); |
| $new_content = $wiki->api({ |
| action => 'parse', |
| text => $file_content, |
| title => $wiki_page_name |
| }, { |
| skip_encoding => 1 |
| }) or die "No response from remote mediawiki\n"; |
| $new_content = $new_content->{'parse'}->{'text'}->{'*'}; |
| |
| v_print("### Merging contents\n"); |
| if ($preview_file_name eq EMPTY) { |
| ($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/; |
| } |
| open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name) |
| or die "Could not open: $!\n"; |
| print {$save_fh} merge_contents($template, $new_content, $remote_url); |
| close($save_fh) |
| or die "Could not close: $!\n"; |
| |
| v_print("### Results\n"); |
| if ($autoload) { |
| v_print("Launching browser w/ file: ${preview_file_name}"); |
| system('git', 'web--browse', $preview_file_name); |
| } else { |
| print {*STDERR} "Preview file saved as: ${preview_file_name}\n"; |
| } |
| |
| exit; |
| } |
| |
| # uses global scope variable: $remote_name |
| sub merge_contents { |
| my $template = shift; |
| my $content = shift; |
| my $remote_url = shift; |
| my ($content_tree, $html_tree, $mw_content_text); |
| my $template_content_id = 'bodyContent'; |
| |
| $html_tree = HTML::TreeBuilder->new; |
| $html_tree->parse($template); |
| |
| $content_tree = HTML::TreeBuilder->new; |
| $content_tree->parse($content); |
| |
| $template_content_id = Git::config("remote.${remote_name}.mwIDcontent") |
| || $template_content_id; |
| v_print("Using '${template_content_id}' as the content ID\n"); |
| |
| $mw_content_text = $html_tree->look_down('id', $template_content_id); |
| if (!defined $mw_content_text) { |
| print {*STDERR} <<"CONFIG"; |
| Could not combine the new content with the template. You might want to |
| configure `mediawiki.IDContent` in your config: |
| git config --add remote.${remote_name}.mwIDcontent <id> |
| and re-run the command afterward. |
| CONFIG |
| exit 1; |
| } |
| $mw_content_text->delete_content(); |
| $mw_content_text->push_content($content_tree); |
| |
| make_links_absolute($html_tree, $remote_url); |
| |
| return $html_tree->as_HTML; |
| } |
| |
| sub make_links_absolute { |
| my $html_tree = shift; |
| my $remote_url = shift; |
| for (@{ $html_tree->extract_links() }) { |
| my ($link, $element, $attr) = @{ $_ }; |
| my $url = url($link)->canonical; |
| if ($url !~ /#/) { |
| $element->attr($attr, URI->new_abs($url, $remote_url)); |
| } |
| } |
| return $html_tree; |
| } |
| |
| sub is_valid_remote { |
| my $remote = shift; |
| my @remotes = git_cmd_try { |
| Git::command('remote') } |
| "%s failed w/ code %d"; |
| my $found_remote = 0; |
| foreach my $remote (@remotes) { |
| if ($remote eq $remote) { |
| $found_remote = 1; |
| last; |
| } |
| } |
| return $found_remote; |
| } |
| |
| sub find_mediawiki_remotes { |
| my @remotes = git_cmd_try { |
| Git::command('remote'); } |
| "%s failed w/ code %d"; |
| my $remote_url; |
| my @valid_remotes = (); |
| foreach my $remote (@remotes) { |
| $remote_url = mediawiki_remote_url_maybe($remote); |
| if ($remote_url) { |
| push(@valid_remotes, $remote); |
| } |
| } |
| return @valid_remotes; |
| } |
| |
| sub find_upstream_remote_name { |
| my $current_branch = git_cmd_try { |
| Git::command_oneline('symbolic-ref', '--short', 'HEAD') } |
| "%s failed w/ code %d"; |
| return Git::config("branch.${current_branch}.remote"); |
| } |
| |
| sub mediawiki_remote_url_maybe { |
| my $remote = shift; |
| |
| # Find remote url |
| my $remote_url = Git::config("remote.${remote}.url"); |
| if ($remote_url =~ s/mediawiki::(.*)/$1/) { |
| return url($remote_url)->canonical; |
| } |
| |
| return; |
| } |
| |
| sub get_template { |
| my $url = shift; |
| my $page_name = shift; |
| my ($req, $res, $code, $url_after); |
| |
| $req = LWP::UserAgent->new; |
| if ($verbose) { |
| $req->show_progress(1); |
| } |
| |
| $res = $req->get("${url}/index.php?title=${page_name}"); |
| if (!$res->is_success) { |
| $code = $res->code; |
| $url_after = $res->request()->uri(); # resolve all redirections |
| if ($code == HTTP_CODE_PAGE_NOT_FOUND) { |
| if ($verbose) { |
| print {*STDERR} <<"WARNING"; |
| Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want |
| all the links to work properly. |
| Trying to use the mediawiki homepage as a fallback template ... |
| WARNING |
| } |
| |
| # LWP automatically redirects GET request |
| $res = $req->get("${url}/index.php"); |
| if (!$res->is_success) { |
| $url_after = $res->request()->uri(); # resolve all redirections |
| die "Failed to get homepage @ ${url_after} w/ code ${code}\n"; |
| } |
| } else { |
| die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n"; |
| } |
| } |
| |
| return $res->decoded_content; |
| } |
| |
| ############################## Help Functions ################################## |
| |
| sub help { |
| print {*STDOUT} <<'END'; |
| usage: git mw <command> <args> |
| |
| git mw commands are: |
| help Display help information about git mw |
| preview Parse and render local file into HTML |
| END |
| exit; |
| } |