fvwm-convert-2.6: Convert all function lines where possible.
[fvwm.git] / bin / fvwm-convert-2.6.in
blobe380addb4e9fdff9d8e64624090761a002af5551
1 #!@PERL@
2 # -*-perl-*-
4 # Convert .fvwm2rc from 2.4.x format to 2.6.x format.
6 # Original author:  Thomas Adam <thomas.adam22@gmail.com> Dec. 2009
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 use strict;
23 use Cwd;
24 use File::Basename;
25 use Getopt::Long;
27 # Global array for all our converted lines.
28 my @converted_lines = ();
30 # Global softref for addtofunc continuations.
31 my $last_func_ref;
32 my %converted_funcs = ();
34 # Global for additional files...
35 my @additional_files = ();
37 # GetOpts
38 my $follow_read = '';
39 my $process_read = 0;
41 # Convert conditional command syntax correctly.
42 sub __convert_conditionals
44     my( $cond ) = @_;
45     my( $line ) = $cond->[-1];
47     # Take the last component.  We no longer care for "[*]" as conditional
48     # command parameters.  But we shouldn't really put another conditional
49     # in its place, so we'll just remove it.
50     $line =~ s/\[\*\]//;
51     
52     # And convert over Next [$1] syntax.
53     $line =~ s/\[(.*?)\]/\($1\)/;
54     
55     $line = "$1 ". join( ', ', split( /\s+/, $2 ) ) . " $3" if $line =~
56     /(all|current|direction|next|none|prev|pick|thiswindow|windowid)\s*(\(.*?\))(.*)/i;
58     $cond->[-1] = $line;
61 # Process the files specified and output them to a destination file.
62 sub process_files
64     my( $files ) = @_;
66     no strict "refs";
67     foreach my $f ( @$files )
68     {
69         my( $s, $d ) = @$f;
70         my $cwd_path = getcwd();
72         warn "Following:  Read $s...\n" if $process_read;
74         if( !defined $d or $d eq '' ) 
75         {
76             my $fbasename = basename( $s );
77             $d = "$cwd_path/$fbasename.converted";
78         }
80         if( -e $d ) {
81             die "Destination file:  $d exists\n";
82         }
84         open( my $in_file, '<', $s ) or die
85             "Unable to open source file:  $!\n";
87         while( <$in_file> )
88         {  
89             chomp;
91             # We have to handle continuation lines here, such as:
92             #
93             # Style foo !Bar, !Baz, \
94             # NoSomethingElse
95             if( /\\\s*$/ )
96             {
97                 $_ .= <$in_file>;
98                 redo;
99             }
100             dispatch_line($_);
101         }
102         
103         write_out_file($d);
104         @converted_lines = ();
105         %converted_funcs = ();
106     }
109 # Convert style syntax over where applicable.
110 sub convert_styles
112     my( $line ) = @_;
113     my @converted;
115     # At the very least, we can cheat and just negate everything.  Whilst it
116     # isn't deprecated yet, it will be -- so it won't hurt to do it here.
118     # Split the line out first of all, between the "Style foo" part, and the
119     # actual styles being applied.
120     my( @style_parts ) = ($line =~ /^(style\s+\"??[\w+*?]\"??)(.*)$/i);
122     # Convert the second part over.
123     foreach( split( /\s*,\s*/, $style_parts[1] ) )
124     {
125         # There is no !PPosition style, but there is !UsePPosition
126         s/(?:No)(.*)/\!$1/ unless $_ =~ /nopposition/i;
127         s/nopposition/!UsePPosition/i;
128         push @converted, $_;
129     }
131     push @converted_lines, $style_parts[0] . join(', ',
132         @converted);
135 # Buckshot approach at turning fvwmthemes into colorsets.  Can't really do
136 # much more than this, but at least gives the user something to go on.
137 sub convert_fvwmtheme
139     my( $line ) = @_;
141     $line =~ s/^\*fvwmtheme\s*:?//i;
142     $line = undef if $line =~ /modulesynchronous.*?fvwmtheme/i;
144     push @converted_lines, $line;
147 # Comment out the modulepath line -- grr.
148 sub handle_modulepath
150     my( $line ) = @_;
151     $line = "# " . $line;
153     push( @converted_lines, $line );
156 # This should have happened in the fvwm-2.4 convert script, but handle it
157 # here anyway.
158 sub convert_windowshadesteps
160     my( $line ) = @_;
161     $line =~ /(\d+)p?/ ? 
162         $line = "Style * WindowShadeSteps $1" : 
163         $line = "Style * " . $line;
165     push( @converted_lines, $line );
168 sub convert_edge_resistance
170     my( $line ) = @_;
172     # This gets converted into two parts.  One is the EdgeResistance
173     # command, the other is a style line.
174     #
175     # We could only ever have had two numbers as arguments to
176     # EdgeResistance.
177     my( $edge_res_arg, $move_res_arg ) = 
178         ( $line =~ /edgeresistance\s*(\d+)\s*(\d+)/i );
180     push( @converted_lines,
181         qq|
182         EdgeResistance $edge_res_arg
183         Style * EdgeMoveResistance $move_res_arg| );
186 sub convert_snapattraction
188     my( $line ) = @_;
190     push( @converted_lines, "Style * " . $line );
193 sub convert_key_mouse_bindings
195     my( $line ) = @_;
196     my @components = split( /(\s+)/, $line, 5 );
198     # Also, conditional commands should now be separated with commas and not
199     # whitespace, so try and fix these up where we can.  It's not the
200     # intention we'll catch them all, but at least try and do so based on
201     # where they're likely to be used.
202     __convert_conditionals(\@components);
204     push( @converted_lines, join '', @components );
207 sub handle_continuation
209     no strict "refs"; # Yes, yes...
210     my( $line ) = @_;
212     if( !defined $last_func_ref || $last_func_ref eq '' )
213     {
214         my @func_parts = split( /(\+\s*\"?(?:i|c|d|h|m)\"?\s*)/i, $line, 2 );
216         __convert_conditionals(\@func_parts);
218         push( @converted_lines, join '', @func_parts );
219         return;
220     }
222     eval { &{$last_func_ref}($line) };
223     warn "$@\n" if $@;
226 sub handle_read_file
228     my( $line ) = @_;
229     my @read_parts = split( /\s+/, $line );
230     
231     push( @converted_lines, $line );    
233     # Crudely try and work out if the file is readable, and if it is add it
234     # to the list of further files to convert.
235     #
236     # This won't handle having to interpolate out any env vars set via
237     # SetEnv, or worse yet, outside of FVWM's environment.  The user will
238     # just have to run this script on that file manually.
239     my $fname = $read_parts[1];
240     return unless defined $fname and $fname ne '';
242     if( -e $fname )
243     {
244         push( @additional_files, [$fname] );
245         
246         # We're done.
247         return;
248     }
250     # If we have this:
251     #
252     # Read foo
253     #
254     # Or this:
255     #
256     # Read $./foo
257     #
258     # Then we assume FVWM_USERDIR ("$HOME/.fvwm/"), and if that file can't 
259     # be found there, try CWD, and if that fails we just give up.
260     
261     # Canonicalise the starting point by removing "$." -- we can guess what
262     # it ought to be replaced with.
263     $fname =~ s/^\$\.\/?//;
265     if( -e "$ENV{FVWM_USERDIR}/$fname" )
266     {
267         push( @additional_files,
268             ["$ENV{FVWM_USERDIR}/$fname"] );
269         return;
270     }
272     if( -e "$ENV{HOME}/.fvwm/$fname" )
273     {
274         push( @additional_files,
275             ["$ENV{HOME}/.fvwm/$fname"] );
276         return;
277     }
279     my $cwd_path = getcwd();
281     if( -e "$cwd_path/$fname" )
282     {
283         push( @additional_files, [$fname] );
284         return;
285     }
287     warn "Unable to follow:  $line\n";
290 sub check_func_definition
292     my( $line ) = @_;
294     if( $line !~ /^addtofunc\s+(?:start|init|restart)function.*/i )
295     {
296         $last_func_ref = '';
297     }
299     # Then we have a standard function line in the form:
300     #
301     # + I SomeCommand
302     #
303     # Ensure we run it all through __convert_conditionals()
304     my @func_parts = split( /(\s+)/, $line, 4 );
305     __convert_conditionals( \@func_parts );
306     
307     push( @converted_lines, join '', @func_parts );
311 sub convert_initfunc
313     my( $line ) = @_;
314     $last_func_ref = "convert_initfunc";
316     if( $line =~ /addtofunc\s+initfunction\s+\"??[icmhd]{1}\"??\s+.*/i ||
317         $line =~ /addtofunc\s+initfunction\s*/i )
318     {
319         $line =~ s/addtofunc\s+initfunction\s*//i;
320     }
322     $line =~ s/^\s*\+//;
324     return if !defined $line || $line eq '';
326     # What we need to do now is convert this from:
327     #
328     # + I Foo
329     #
330     # to:
331     #
332     # + I Test (Init) Foo
334     my @func_cmd = split( /\s+/, $line, 3 );
335     unshift( @func_cmd, '' ) unless @func_cmd > 2;
337     # Remove any quotes around the action type --- they're not needed
338     # anymore.
339     $func_cmd[1] =~ s/\"//g;
340     $func_cmd[1] .= q| Test (Init) |;
342     # Run the command through the conditional function to ensure we
343     # handle those correctly.
344     __convert_conditionals( \@func_cmd );
346     push( @{ $converted_funcs{initfunction} }, join ' ', @func_cmd );
349 sub convert_restartfunc
351     my( $line ) = @_;
352     $last_func_ref = "convert_restartfunc";
353     
354     # We treat this exactly like startfunction.
355     if( $line =~ /addtofunc\s+restartfunction\s+\"??[icmhd]{1}\"??\s+.*/i )
356     {
357         # Split this string.  We can throw away the "AddToFunc" part as this
358         # is irrelevant.  But we want the following result:
359         # ( 'I', 'Some Command' )
360         $line =~ s/addtofunc\s+restartfunction\s*//i;
361     }
363     $line =~ s/addtofunc\s+restartfunction\s*//i;
364     
365     return if $line eq '';
367     # Remove the continuation prefix as we can add this in when writing out
368     # the function definitions later. 
369     $line =~ s/^\s*\+//;
370     
371     my @func_cmd = split( /\s+/, $line, 2 );
372     $func_cmd[1] =~ s/\"//g;
374     # Run the command through the conditional function to ensure we
375     # handle those correctly.
376     __convert_conditionals( \@func_cmd );
378     push( @{ $converted_funcs{startfunction} }, join ' ', @func_cmd );
381 sub convert_startfunc
383     my( $line ) = @_;
385     # Now, it's possible that we have something like this:
386     #
387     # AddToFunc StartFunction I Some Command
388     #
389     # Extract the command part, add it to the hash for our functions, and
390     # flag the fact we're dealing with StartFunction at this point for any
391     # continuation lines (+ I Foo) since we can't determine the context of
392     # them without such a thing.
394     if( $line =~ /addtofunc\s+startfunction\s+\"??[icmhd]{1}\"??\s+.*/i )
395     {
396         # Split this string.  We can throw away the "AddToFunc" part as this
397         # is irrelevant.  But we want the following result:
398         # ( 'I', 'Some Command' )
399         $line =~ s/addtofunc\s+startfunction\s*//i;
400     }
401     $line =~ s/addtofunc\s+startfunction\s*//i;
402     
403     # Remove the continuation prefix as we can add this in when writing out
404     # the function definitions later. 
405     $line =~ s/^\s*\+//;
407     return if !defined $line || $line eq '';
408     
409     my @func_cmd = split( /\s+/, $line, 2 );
410     $func_cmd[1] =~ s/\"//g;
412     # Run the command through the conditional function to ensure we
413     # handle those correctly.
414     __convert_conditionals( \@func_cmd );
416     push( @{ $converted_funcs{startfunction} }, join ' ', @func_cmd );
417     $last_func_ref = "convert_startfunc";
420 sub write_out_file
422     my( $dest_file ) = @_;
423     open( my $f, '>', $dest_file ) or
424         die "Couldn't open $dest_file: $!\n";
426     # If we had any continuation lines, preserve them as best we can.
427     @converted_lines = map {
428         join "\\\n", split /\\/, $_ 
429     } @converted_lines;
430     
431     print $f join( "\n", @converted_lines );
433     # Write out the functions.
434     if( defined $converted_funcs{initfunction} or
435         defined $converted_funcs{startfunction} )
436     {
437         print $f qq|\n\nDestroyFunc StartFunction\nAddToFunc StartFunction\n|;
439         # Put the Init stuff before anything else.
440         for( @{ $converted_funcs{initfunction} }, 
441             @{ $converted_funcs{startfunction } } )
442         {
443             print $f "+ $_\n";
444         }
445     }
447     close( $f );
450 sub dispatch_line
452     my( $line ) = @_;
454     if( $line =~ /^style/i )
455     {
456         convert_styles($line);
457     } elsif( $line =~ /^\s*\*fvwmtheme:??/i ) {
458         convert_fvwmtheme($line);
459     } elsif( $line =~ /^\s*modulepath\s*/i ) {
460         handle_modulepath( $line );
461     } elsif( $line =~ /^\s*windowshadesteps.*/i ) {
462         convert_windowshadesteps($line);
463     } elsif( $line =~ /^\s*module(?:synchronous)?.*?fvwmtheme$/i ) {
464         convert_fvwmtheme($line);
465     } elsif( $line =~ /^\s*edgeresistance\s*\d+\s*\d+/i ) {
466         convert_edge_resistance($line);
467     } elsif( $line =~ /^\s*key|mouse/i ) {
468         convert_key_mouse_bindings($line);
469     } elsif( $line =~ /^\s*snap(?:attraction|grid)/i ) {
470         convert_snapattraction( $line );
471     } elsif( $line =~ /^\s*addtofunc\s+initfunction/i ) {
472         convert_initfunc( $line );
473     } elsif( $line =~ /^\s*addtofunc\s+startfunction.*/i ) {
474         convert_startfunc( $line );
475     } elsif( $line =~ /^\s*addtofunc\s+restartfunction/i ) {
476         convert_restartfunc( $line );
477     } elsif( $line =~ /^\s*addtofunc\s+\w+.*/i ) {
478         check_func_definition( $line );
479     } elsif( $line =~ /^\s*\+\s*\"??[ichmd]{1}\s*\"??\s+.*/i ) {
480         handle_continuation( $line );
481     } elsif( $line =~ /^\s*read\s*[\/\w]+/i ) {
482         handle_read_file( $line );
483     } else {
484         # Could be a comment, or a continuation, or simply something we
485         # don't need to convert.  As far as continuation lines are
486         # concerned, these are kept in order just by pushing them onto the
487         # array --- but converting continuation lines is tricky since we'd
488         # need to determine the context of the continuation.  I can't be
489         # bothered.
490         push( @converted_lines, $_ );
491     }
494 sub usage
496     print "fvwm-convert-2.6 [-f] [-h] source-file destination-file\n";
497     exit;
500 GetOptions(
501     "help|h" => \&usage,
502     "follow-read|f" => \$follow_read,
503 ) || usage();
505 # But we still require @ARGV to be populated with our filenames.
506 usage() unless( @ARGV > 0 and @ARGV <=2 );
508 my @files = [@ARGV];
509 process_files( \@files );
511 if( @additional_files && !$follow_read )
513     print "The following files were detected, but not processed:\n\n",
514     join("\n", @$_ ) for @additional_files,;
515     print "\n";
518 # Only do this is we've been asked.
519 if( @additional_files && $follow_read )
521     $process_read = 1;
522     process_files( \@additional_files );