1212use warnings;
1313
1414use 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
1725binmode 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+
2947my %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\t name: ${remote_name} \n\t url: ${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
52359sub help {
@@ -55,6 +362,7 @@ sub help {
55362
56363git mw commands are:
57364 help Display help information about git mw
365+ preview Parse and render local file into HTML
58366END
59367 exit ;
60368}
0 commit comments