git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
From: benoit.person@ensimag.fr
To: git@vger.kernel.org
Cc: Celestin Matte <celestin.matte@ensimag.fr>,
	Matthieu Moy <matthieu.moy@grenoble-inp.fr>,
	Junio C Hamano <gitster@pobox.com>,
	Benoit Person <benoit.person@ensimag.fr>
Subject: [PATCH v7 7/7] git-remote-mediawiki: Add preview subcommand into git mw
Date: Wed,  3 Jul 2013 00:39:49 +0200	[thread overview]
Message-ID: <1372804789-12732-8-git-send-email-benoit.person@ensimag.fr> (raw)
In-Reply-To: <1372804789-12732-1-git-send-email-benoit.person@ensimag.fr>

From: Benoit Person <benoit.person@ensimag.fr>

In the current state, a user of git-remote-mediawiki can edit the markup text
locally, but has to push to the remote wiki to see how the page is rendererd.
Add a new 'git mw preview' command that allows rendering the markup text on
the remote wiki without actually pushing any change on the wiki.

This uses Mediawiki's API to render the markup and inserts it in an actual
HTML page from the wiki so that CSS can be rendered properly. Most links
should work when the page exists on the remote.

Signed-off-by: Benoit Person <benoit.person@ensimag.fr>
Signed-off-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
---
 contrib/mw-to-git/Git/Mediawiki.pm |   3 +-
 contrib/mw-to-git/git-mw.perl      | 310 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 311 insertions(+), 2 deletions(-)

diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm
index 47fe4f4..d13c4df 100644
--- a/contrib/mw-to-git/Git/Mediawiki.pm
+++ b/contrib/mw-to-git/Git/Mediawiki.pm
@@ -19,7 +19,7 @@ require Exporter;
 
 # Methods which can be called as standalone functions as well:
 @EXPORT_OK = qw(clean_filename smudge_filename connect_maybe
-				EMPTY HTTP_CODE_OK);
+				EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND);
 }
 
 # Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
@@ -30,6 +30,7 @@ use constant EMPTY => q{};
 
 # HTTP codes
 use constant HTTP_CODE_OK => 200;
+use constant HTTP_CODE_PAGE_NOT_FOUND => 404;
 
 sub clean_filename {
 	my $filename = shift;
diff --git a/contrib/mw-to-git/git-mw.perl b/contrib/mw-to-git/git-mw.perl
index 4a3e4a9..28df3ee 100755
--- a/contrib/mw-to-git/git-mw.perl
+++ b/contrib/mw-to-git/git-mw.perl
@@ -12,6 +12,14 @@ 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)';
@@ -26,9 +34,26 @@ sub v_print {
 	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]
+		[\&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
@@ -47,6 +72,288 @@ GetOptions( %{$cmd->[1]},
 # 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 {
@@ -55,6 +362,7 @@ 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;
 }
-- 
1.8.3.1.590.gc07d91b

      parent reply	other threads:[~2013-07-02 22:51 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-07-02 22:39 [PATCH v7 0/7] git-remote-mediawiki: new tool to preview local changes without pushing benoit.person
2013-07-02 22:39 ` [PATCH v7 1/7] git-remote-mediawiki: Introduction of Git::Mediawiki.pm benoit.person
2013-07-02 22:59   ` Junio C Hamano
2013-07-02 22:39 ` [PATCH v7 2/7] wrap-for-bin: Make bin-wrappers chainable benoit.person
2013-07-02 23:01   ` Junio C Hamano
2013-07-02 23:19     ` Benoît Person
2013-07-03  8:54       ` Junio C Hamano
2013-07-03  9:46         ` Benoît Person
2013-07-03 10:30   ` Michael Haggerty
2013-07-03 10:57   ` Matthieu Moy
2013-07-03 20:24     ` Junio C Hamano
2013-07-03 20:38       ` Matthieu Moy
2013-07-03 21:50         ` Junio C Hamano
2013-07-02 22:39 ` [PATCH v7 3/7] git-remote-mediawiki: New git bin-wrapper for developement benoit.person
2013-07-02 23:04   ` Junio C Hamano
2013-07-02 22:39 ` [PATCH v7 4/7] git-remote-mediawiki: Update tests to run with the new bin-wrapper benoit.person
2013-07-03  9:18   ` Matthieu Moy
2013-07-02 22:39 ` [PATCH v7 5/7] git-remote-mediawiki: Factoring code between git-remote-mediawiki and Git::Mediawiki benoit.person
2013-07-02 22:39 ` [PATCH v7 6/7] git-remote-mediawiki: Adding git-mw command benoit.person
2013-07-02 22:39 ` benoit.person [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: http://vger.kernel.org/majordomo-info.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1372804789-12732-8-git-send-email-benoit.person@ensimag.fr \
    --to=benoit.person@ensimag.fr \
    --cc=celestin.matte@ensimag.fr \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=matthieu.moy@grenoble-inp.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).