| #!/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; | 
 | 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; | 
 | } |