2013-07-04 22:38:59 +02:00
|
|
|
#!/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/moy/Git-Mediawiki/
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Getopt::Long;
|
2013-07-04 22:39:00 +02:00
|
|
|
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);
|
2013-07-04 22:38:59 +02:00
|
|
|
|
|
|
|
# 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;
|
|
|
|
}
|
|
|
|
|
2013-07-04 22:39:00 +02:00
|
|
|
# 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;
|
|
|
|
}
|
|
|
|
|
2013-07-04 22:38:59 +02:00
|
|
|
my %commands = (
|
|
|
|
'help' =>
|
2013-07-04 22:39:00 +02:00
|
|
|
[\&help, {}, \&help],
|
|
|
|
'preview' =>
|
|
|
|
[\&preview, {
|
|
|
|
'<>' => \&file,
|
|
|
|
'output|o=s' => \$preview_file_name,
|
|
|
|
'remote|r=s' => \$remote_name,
|
|
|
|
'autoload|a' => \$autoload
|
|
|
|
}, \&preview_help]
|
2013-07-04 22:38:59 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
# 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]};
|
|
|
|
|
2013-07-04 22:39:00 +02:00
|
|
|
############################# 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;
|
|
|
|
}
|
|
|
|
|
2013-07-04 22:38:59 +02:00
|
|
|
############################## Help Functions ##################################
|
|
|
|
|
|
|
|
sub help {
|
|
|
|
print {*STDOUT} <<'END';
|
|
|
|
usage: git mw <command> <args>
|
|
|
|
|
|
|
|
git mw commands are:
|
|
|
|
help Display help information about git mw
|
2013-07-04 22:39:00 +02:00
|
|
|
preview Parse and render local file into HTML
|
2013-07-04 22:38:59 +02:00
|
|
|
END
|
|
|
|
exit;
|
|
|
|
}
|