4 # Benoit Person <benoit.person@ensimag.imag.fr>
5 # Celestin Matte <celestin.matte@ensimag.imag.fr>
6 # License: GPL v2 or later
8 # Set of tools for git repo with a mediawiki remote.
9 # Documentation & bugtracker: https://github.com/Git-Mediawiki/Git-Mediawiki
17 use HTML::TreeBuilder;
21 use Git::Mediawiki qw(clean_filename connect_maybe
22 EMPTY HTTP_CODE_PAGE_NOT_FOUND);
24 # By default, use UTF-8 to communicate with Git and the user
25 binmode STDERR, ':encoding(UTF-8)';
26 binmode STDOUT, ':encoding(UTF-8)';
32 return print {*STDERR} @_;
38 my $file_name = EMPTY;
39 my $remote_name = EMPTY;
40 my $preview_file_name = EMPTY;
53 'output|o=s' => \$preview_file_name,
54 'remote|r=s' => \$remote_name,
55 'autoload|a' => \$autoload
59 # Search for sub-command
60 my $cmd = $commands{'help'};
62 if (defined $commands{$ARGV[$_]}) {
63 $cmd = $commands{$ARGV[$_]};
68 GetOptions( %{$cmd->[1]},
69 'help|h' => \&{$cmd->[2]},
70 'verbose|v' => \$verbose);
75 ############################# Preview Functions ################################
78 print {*STDOUT} <<'END';
79 USAGE: git mw preview [--remote|-r <remote name>] [--autoload|-a]
80 [--output|-o <output filename>] [--verbose|-v]
84 Preview is an utiliy to preview local content of a mediawiki repo as if it was
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.
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.
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
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.
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.
109 Try to load the HTML output in a new tab (or new window) of your
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'.
117 Show more information on what's going on under the hood.
124 my ($remote_url, $wiki_page_name);
125 my ($new_content, $template);
128 if ($file_name eq EMPTY) {
129 die "Missing file argument, see `git mw help`\n";
132 v_print("### Selecting remote\n");
133 if ($remote_name eq EMPTY) {
134 $remote_name = find_upstream_remote_name();
136 $remote_url = mediawiki_remote_url_maybe($remote_name);
140 my @valid_remotes = find_mediawiki_remotes();
142 if ($#valid_remotes == 0) {
143 print {*STDERR} "No mediawiki remote in this repo. \n";
146 my $remotes_list = join("\n\t", @valid_remotes);
147 print {*STDERR} <<"MESSAGE";
148 There are multiple mediawiki remotes, which of:
150 do you want ? Use the -r option to specify the remote.
157 if (!is_valid_remote($remote_name)) {
158 die "${remote_name} is not a remote\n";
161 $remote_url = mediawiki_remote_url_maybe($remote_name);
163 die "${remote_name} is not a mediawiki remote\n";
166 v_print("selected remote:\n\tname: ${remote_name}\n\turl: ${remote_url}\n");
168 $wiki = connect_maybe($wiki, $remote_name, $remote_url);
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";
176 if ($file_name =~ /(.+):(.+)/) {
180 open my $read_fh, "<", $file_name
181 or die "could not open ${file_name}: $!\n";
182 $file_content = do { local $/ = undef; <$read_fh> };
184 or die "unable to close: $!\n";
187 v_print("### Retrieving template\n");
188 ($wiki_page_name = clean_filename($file_name)) =~ s/\.[^.]+$//;
189 $template = get_template($remote_url, $wiki_page_name);
191 v_print("### Parsing local content\n");
192 $new_content = $wiki->api({
194 text => $file_content,
195 title => $wiki_page_name
198 }) or die "No response from remote mediawiki\n";
199 $new_content = $new_content->{'parse'}->{'text'}->{'*'};
201 v_print("### Merging contents\n");
202 if ($preview_file_name eq EMPTY) {
203 ($preview_file_name = $file_name) =~ s/\.[^.]+$/.html/;
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);
209 or die "Could not close: $!\n";
211 v_print("### Results\n");
213 v_print("Launching browser w/ file: ${preview_file_name}");
214 system('git', 'web--browse', $preview_file_name);
216 print {*STDERR} "Preview file saved as: ${preview_file_name}\n";
222 # uses global scope variable: $remote_name
224 my $template = shift;
226 my $remote_url = shift;
227 my ($content_tree, $html_tree, $mw_content_text);
228 my $template_content_id = 'bodyContent';
230 $html_tree = HTML::TreeBuilder->new;
231 $html_tree->parse($template);
233 $content_tree = HTML::TreeBuilder->new;
234 $content_tree->parse($content);
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");
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.
250 $mw_content_text->delete_content();
251 $mw_content_text->push_content($content_tree);
253 make_links_absolute($html_tree, $remote_url);
255 return $html_tree->as_HTML;
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;
265 $element->attr($attr, URI->new_abs($url, $remote_url));
271 sub is_valid_remote {
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) {
283 return $found_remote;
286 sub find_mediawiki_remotes {
287 my @remotes = git_cmd_try {
288 Git::command('remote'); }
289 "%s failed w/ code %d";
291 my @valid_remotes = ();
292 foreach my $remote (@remotes) {
293 $remote_url = mediawiki_remote_url_maybe($remote);
295 push(@valid_remotes, $remote);
298 return @valid_remotes;
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");
308 sub mediawiki_remote_url_maybe {
312 my $remote_url = Git::config("remote.${remote}.url");
313 if ($remote_url =~ s/mediawiki::(.*)/$1/) {
314 return url($remote_url)->canonical;
322 my $page_name = shift;
323 my ($req, $res, $code, $url_after);
325 $req = LWP::UserAgent->new;
327 $req->show_progress(1);
330 $res = $req->get("${url}/index.php?title=${page_name}");
331 if (!$res->is_success) {
333 $url_after = $res->request()->uri(); # resolve all redirections
334 if ($code == HTTP_CODE_PAGE_NOT_FOUND) {
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 ...
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";
350 die "Failed to get '${page_name}' @ ${url_after} w/ code ${code}\n";
354 return $res->decoded_content;
357 ############################## Help Functions ##################################
360 print {*STDOUT} <<'END';
361 usage: git mw <command> <args>
364 help Display help information about git mw
365 preview Parse and render local file into HTML