]>
Commit | Line | Data |
---|---|---|
07a263b9 BP |
1 | #!/usr/bin/perl |
2 | ||
3 | # Copyright (C) 2013 | |
4 | # Benoit Person <benoit.person@ensimag.imag.fr> | |
5 | # Celestin Matte <celestin.matte@ensimag.imag.fr> | |
6 | # License: GPL v2 or later | |
7 | ||
8 | # Set of tools for git repo with a mediawiki remote. | |
9 | # Documentation & bugtracker: https://github.com/moy/Git-Mediawiki/ | |
10 | ||
11 | use strict; | |
12 | use warnings; | |
13 | ||
14 | use Getopt::Long; | |
0078a7fa BP |
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); | |
07a263b9 BP |
23 | |
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)'; | |
27 | ||
28 | # Global parameters | |
29 | my $verbose = 0; | |
30 | sub v_print { | |
31 | if ($verbose) { | |
32 | return print {*STDERR} @_; | |
33 | } | |
34 | return; | |
35 | } | |
36 | ||
0078a7fa BP |
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 | ||
07a263b9 BP |
47 | my %commands = ( |
48 | 'help' => | |
0078a7fa BP |
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] | |
07a263b9 BP |
57 | ); |
58 | ||
59 | # Search for sub-command | |
60 | my $cmd = $commands{'help'}; | |
61 | for (0..@ARGV-1) { | |
62 | if (defined $commands{$ARGV[$_]}) { | |
63 | $cmd = $commands{$ARGV[$_]}; | |
64 | splice @ARGV, $_, 1; | |
65 | last; | |
66 | } | |
67 | }; | |
68 | GetOptions( %{$cmd->[1]}, | |
69 | 'help|h' => \&{$cmd->[2]}, | |
70 | 'verbose|v' => \$verbose); | |
71 | ||
72 | # Launch command | |
73 | &{$cmd->[0]}; | |
74 | ||
0078a7fa BP |
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 | ||
07a263b9 BP |
357 | ############################## Help Functions ################################## |
358 | ||
359 | sub help { | |
360 | print {*STDOUT} <<'END'; | |
361 | usage: git mw <command> <args> | |
362 | ||
363 | git mw commands are: | |
364 | help Display help information about git mw | |
0078a7fa | 365 | preview Parse and render local file into HTML |
07a263b9 BP |
366 | END |
367 | exit; | |
368 | } |