Skip to content

Commit 0078a7f

Browse files
Benoit Persongitster
authored andcommitted
git-remote-mediawiki: add preview subcommand into git mw
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 <[email protected]> Signed-off-by: Matthieu Moy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 07a263b commit 0078a7f

File tree

2 files changed

+311
-2
lines changed

2 files changed

+311
-2
lines changed

contrib/mw-to-git/Git/Mediawiki.pm

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ require Exporter;
1919

2020
# Methods which can be called as standalone functions as well:
2121
@EXPORT_OK = qw(clean_filename smudge_filename connect_maybe
22-
EMPTY HTTP_CODE_OK);
22+
EMPTY HTTP_CODE_OK HTTP_CODE_PAGE_NOT_FOUND);
2323
}
2424

2525
# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
@@ -30,6 +30,7 @@ use constant EMPTY => q{};
3030

3131
# HTTP codes
3232
use constant HTTP_CODE_OK => 200;
33+
use constant HTTP_CODE_PAGE_NOT_FOUND => 404;
3334

3435
sub clean_filename {
3536
my $filename = shift;

contrib/mw-to-git/git-mw.perl

Lines changed: 309 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
use warnings;
1313

1414
use Getopt::Long;
15+
use URI::URL qw(url);
16+
use LWP::UserAgent;
17+
use HTML::TreeBuilder;
18+
19+
use Git;
20+
use MediaWiki::API;
21+
use Git::Mediawiki qw(clean_filename connect_maybe
22+
EMPTY HTTP_CODE_PAGE_NOT_FOUND);
1523

1624
# By default, use UTF-8 to communicate with Git and the user
1725
binmode STDERR, ':encoding(UTF-8)';
@@ -26,9 +34,26 @@ sub v_print {
2634
return;
2735
}
2836

37+
# Preview parameters
38+
my $file_name = EMPTY;
39+
my $remote_name = EMPTY;
40+
my $preview_file_name = EMPTY;
41+
my $autoload = 0;
42+
sub file {
43+
$file_name = shift;
44+
return $file_name;
45+
}
46+
2947
my %commands = (
3048
'help' =>
31-
[\&help, {}, \&help]
49+
[\&help, {}, \&help],
50+
'preview' =>
51+
[\&preview, {
52+
'<>' => \&file,
53+
'output|o=s' => \$preview_file_name,
54+
'remote|r=s' => \$remote_name,
55+
'autoload|a' => \$autoload
56+
}, \&preview_help]
3257
);
3358

3459
# Search for sub-command
@@ -47,6 +72,288 @@ sub v_print {
4772
# Launch command
4873
&{$cmd->[0]};
4974

75+
############################# Preview Functions ################################
76+
77+
sub preview_help {
78+
print {*STDOUT} <<'END';
79+
USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a]
80+
[--output|-o <output filename>] [--verbose|-v]
81+
<blob> | <filename>
82+
83+
DESCRIPTION:
84+
Preview is an utiliy to preview local content of a mediawiki repo as if it was
85+
pushed on the remote.
86+
87+
For that, preview searches for the remote name of the current branch's
88+
upstream if --remote is not set. If that remote is not found or if it
89+
is not a mediawiki, it lists all mediawiki remotes configured and asks
90+
you to replay your command with the --remote option set properly.
91+
92+
Then, it searches for a file named 'filename'. If it's not found in
93+
the current dir, it will assume it's a blob.
94+
95+
The content retrieved in the file (or in the blob) will then be parsed
96+
by the remote mediawiki and combined with a template retrieved from
97+
the mediawiki.
98+
99+
Finally, preview will save the HTML result in a file. and autoload it
100+
in your default web browser if the option --autoload is present.
101+
102+
OPTIONS:
103+
-r <remote name>, --remote <remote name>
104+
If the remote is a mediawiki, the template and the parse engine
105+
used for the preview will be those of that remote.
106+
If not, a list of valid remotes will be shown.
107+
108+
-a, --autoload
109+
Try to load the HTML output in a new tab (or new window) of your
110+
default web browser.
111+
112+
-o <output filename>, --output <output filename>
113+
Change the HTML output filename. Default filename is based on the
114+
input filename with its extension replaced by '.html'.
115+
116+
-v, --verbose
117+
Show more information on what's going on under the hood.
118+
END
119+
exit;
120+
}
121+
122+
sub preview {
123+
my $wiki;
124+
my ($remote_url, $wiki_page_name);
125+
my ($new_content, $template);
126+
my $file_content;
127+
128+
if ($file_name eq EMPTY) {
129+
die "Missing file argument, see `git mw help`\n";
130+
}
131+
132+
v_print("### Selecting remote\n");
133+
if ($remote_name eq EMPTY) {
134+
$remote_name = find_upstream_remote_name();
135+
if ($remote_name) {
136+
$remote_url = mediawiki_remote_url_maybe($remote_name);
137+
}
138+
139+
if (! $remote_url) {
140+
my @valid_remotes = find_mediawiki_remotes();
141+
142+
if ($#valid_remotes == 0) {
143+
print {*STDERR} "No mediawiki remote in this repo. \n";
144+
exit 1;
145+
} else {
146+
my $remotes_list = join("\n\t", @valid_remotes);
147+
print {*STDERR} <<"MESSAGE";
148+
There are multiple mediawiki remotes, which of:
149+
${remotes_list}
150+
do you want ? Use the -r option to specify the remote.
151+
MESSAGE
152+
}
153+
154+
exit 1;
155+
}
156+
} else {
157+
if (!is_valid_remote($remote_name)) {
158+
die "${remote_name} is not a remote\n";
159+
}
160+
161+
$remote_url = mediawiki_remote_url_maybe($remote_name);
162+
if (! $remote_url) {
163+
die "${remote_name} is not a mediawiki remote\n";
164+
}
165+
}
166+
v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n");
167+
168+
$wiki = connect_maybe($wiki, $remote_name, $remote_url);
169+
170+
# Read file content
171+
if (! -e $file_name) {
172+
$file_content = git_cmd_try {
173+
Git::command('cat-file', 'blob', $file_name); }
174+
"%s failed w/ code %d";
175+
176+
if ($file_name =~ /(.+):(.+)/) {
177+
$file_name = $2;
178+
}
179+
} else {
180+
open my $read_fh, "<", $file_name
181+
or die "could not open ${file_name}: $!\n";
182+
$file_content = do { local $/ = undef; <$read_fh> };
183+
close $read_fh
184+
or die "unable to close: $!\n";
185+
}
186+
187+
v_print("### Retrieving template\n");
188+
($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//;
189+
$template = get_template($remote_url, $wiki_page_name);
190+
191+
v_print("### Parsing local content\n");
192+
$new_content = $wiki->api({
193+
action => 'parse',
194+
text => $file_content,
195+
title => $wiki_page_name
196+
}, {
197+
skip_encoding => 1
198+
}) or die "No response from remote mediawiki\n";
199+
$new_content = $new_content->{'parse'}->{'text'}->{'*'};
200+
201+
v_print("### Merging contents\n");
202+
if ($preview_file_name eq EMPTY) {
203+
($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/;
204+
}
205+
open(my $save_fh, '>:encoding(UTF-8)', $preview_file_name)
206+
or die "Could not open: $!\n";
207+
print {$save_fh} merge_contents($template, $new_content, $remote_url);
208+
close($save_fh)
209+
or die "Could not close: $!\n";
210+
211+
v_print("### Results\n");
212+
if ($autoload) {
213+
v_print("Launching browser w/ file: ${preview_file_name}");
214+
system('git', 'web--browse', $preview_file_name);
215+
} else {
216+
print {*STDERR} "Preview file saved as: ${preview_file_name}\n";
217+
}
218+
219+
exit;
220+
}
221+
222+
# uses global scope variable: $remote_name
223+
sub merge_contents {
224+
my $template = shift;
225+
my $content = shift;
226+
my $remote_url = shift;
227+
my ($content_tree, $html_tree, $mw_content_text);
228+
my $template_content_id = 'bodyContent';
229+
230+
$html_tree = HTML::TreeBuilder->new;
231+
$html_tree->parse($template);
232+
233+
$content_tree = HTML::TreeBuilder->new;
234+
$content_tree->parse($content);
235+
236+
$template_content_id = Git::config("remote.${remote_name}.mwIDcontent")
237+
|| $template_content_id;
238+
v_print("Using '${template_content_id}' as the content ID\n");
239+
240+
$mw_content_text = $html_tree->look_down('id', $template_content_id);
241+
if (!defined $mw_content_text) {
242+
print {*STDERR} <<"CONFIG";
243+
Could not combine the new content with the template. You might want to
244+
configure `mediawiki.IDContent` in your config:
245+
git config --add remote.${remote_name}.mwIDcontent <id>
246+
and re-run the command afterward.
247+
CONFIG
248+
exit 1;
249+
}
250+
$mw_content_text->delete_content();
251+
$mw_content_text->push_content($content_tree);
252+
253+
make_links_absolute($html_tree, $remote_url);
254+
255+
return $html_tree->as_HTML;
256+
}
257+
258+
sub make_links_absolute {
259+
my $html_tree = shift;
260+
my $remote_url = shift;
261+
for (@{ $html_tree->extract_links() }) {
262+
my ($link, $element, $attr) = @{ $_ };
263+
my $url = url($link)->canonical;
264+
if ($url !~ /#/) {
265+
$element->attr($attr, URI->new_abs($url, $remote_url));
266+
}
267+
}
268+
return $html_tree;
269+
}
270+
271+
sub is_valid_remote {
272+
my $remote = shift;
273+
my @remotes = git_cmd_try {
274+
Git::command('remote') }
275+
"%s failed w/ code %d";
276+
my $found_remote = 0;
277+
foreach my $remote (@remotes) {
278+
if ($remote eq $remote) {
279+
$found_remote = 1;
280+
last;
281+
}
282+
}
283+
return $found_remote;
284+
}
285+
286+
sub find_mediawiki_remotes {
287+
my @remotes = git_cmd_try {
288+
Git::command('remote'); }
289+
"%s failed w/ code %d";
290+
my $remote_url;
291+
my @valid_remotes = ();
292+
foreach my $remote (@remotes) {
293+
$remote_url = mediawiki_remote_url_maybe($remote);
294+
if ($remote_url) {
295+
push(@valid_remotes, $remote);
296+
}
297+
}
298+
return @valid_remotes;
299+
}
300+
301+
sub find_upstream_remote_name {
302+
my $current_branch = git_cmd_try {
303+
Git::command_oneline('symbolic-ref', '--short', 'HEAD') }
304+
"%s failed w/ code %d";
305+
return Git::config("branch.${current_branch}.remote");
306+
}
307+
308+
sub mediawiki_remote_url_maybe {
309+
my $remote = shift;
310+
311+
# Find remote url
312+
my $remote_url = Git::config("remote.${remote}.url");
313+
if ($remote_url =~ s/mediawiki::(.*)/$1/) {
314+
return url($remote_url)->canonical;
315+
}
316+
317+
return;
318+
}
319+
320+
sub get_template {
321+
my $url = shift;
322+
my $page_name = shift;
323+
my ($req, $res, $code, $url_after);
324+
325+
$req = LWP::UserAgent->new;
326+
if ($verbose) {
327+
$req->show_progress(1);
328+
}
329+
330+
$res = $req->get("${url}/index.php?title=${page_name}");
331+
if (!$res->is_success) {
332+
$code = $res->code;
333+
$url_after = $res->request()->uri(); # resolve all redirections
334+
if ($code == HTTP_CODE_PAGE_NOT_FOUND) {
335+
if ($verbose) {
336+
print {*STDERR} <<"WARNING";
337+
Warning: Failed to retrieve '$page_name'. Create it on the mediawiki if you want
338+
all the links to work properly.
339+
Trying to use the mediawiki homepage as a fallback template ...
340+
WARNING
341+
}
342+
343+
# LWP automatically redirects GET request
344+
$res = $req->get("${url}/index.php");
345+
if (!$res->is_success) {
346+
$url_after = $res->request()->uri(); # resolve all redirections
347+
die "Failed to get homepage @ ${url_after} w/ code ${code}\n";
348+
}
349+
} else {
350+
die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n";
351+
}
352+
}
353+
354+
return $res->decoded_content;
355+
}
356+
50357
############################## Help Functions ##################################
51358

52359
sub help {
@@ -55,6 +362,7 @@ sub help {
55362
56363
git mw commands are:
57364
help Display help information about git mw
365+
preview Parse and render local file into HTML
58366
END
59367
exit;
60368
}

0 commit comments

Comments
 (0)