Update todo-2.6 to reflect development status.
[fvwm.git] / bin / fvwm-convert-2.6.in
blobc8228f6289a2e7228d8636d69e26d58722ec92b5
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 Getopt::Long;
26 # Global array for all our converted lines.
27 my @converted_lines = ();
29 # Global softref for addtofunc continuations.
30 my $last_func_ref;
31 my %converted_funcs = ();
33 # Global for additional files...
34 my @additional_files = ();
36 # GetOpts
37 my $follow_read = '';
38 my $process_read = 0;
40 # Convert conditional command syntax correctly.
41 sub __convert_conditionals
43     my( $cond ) = @_;
44     my( $line ) = $cond->[-1];
46     $line = "$1 ". join( ', ', split( /\s+/, $2 ) ) . " $3" if $line =~
47     /(all|current|direction|next|none|prev|pick|thiswindow|windowid)\s*(\(.*?\))(.*)/i;
49     $cond->[-1] = $line;
52 # Process the files specified and output them to a destination file.
53 sub process_files
55     my( $files ) = @_;
57     no strict "refs";
58     foreach my $f ( @$files )
59     {
60         my( $s, $d ) = @$f;
62         warn "Following:  Read $s...\n" if $process_read;
64         if( !defined $d or $d eq '' ) 
65         {
66             $d = "$s.converted";
67         }
69         if( -e $d ) {
70             die "Destination file:  $d exists\n";
71         }
73         open( my $in_file, '<', $s ) or die
74             "Unable to open source file:  $!\n";
76         while( <$in_file> )
77         {  
78             chomp;
80             # We have to handle continuation lines here, such as:
81             #
82             # Style foo !Bar, !Baz, \
83             # NoSomethingElse
84             if( /\\\s*$/ )
85             {
86                 $_ .= <$in_file>;
87                 redo;
88             }
89             dispatch_line($_);
90         }
91         
92         write_out_file($d);
93         @converted_lines = ();
94         %converted_funcs = ();
95     }
98 # Convert style syntax over where applicable.
99 sub convert_styles
101     my( $line ) = @_;
102     my @converted;
104     # At the very least, we can cheat and just negate everything.  Whilst it
105     # isn't deprecated yet, it will be -- so it won't hurt to do it here.
107     # Split the line out first of all, between the "Style foo" part, and the
108     # actual styles being applied.
109     my( @style_parts ) = ($line =~ /^(style\s+\"??[\w+*?]\"??)(.*)$/i);
111     # Convert the second part over.
112     foreach( split( /\s*,\s*/, $style_parts[1] ) )
113     {
114         # There is no !PPosition style, but there is !UsePPosition
115         s/(?:No)(.*)/\!$1/ unless $_ =~ /nopposition/i;
116         s/nopposition/!UsePPosition/i;
117         push @converted, $_;
118     }
120     push @converted_lines, $style_parts[0] . join(', ',
121         @converted);
124 # Buckshot approach at turning fvwmthemes into colorsets.  Can't really do
125 # much more than this, but at least gives the user something to go on.
126 sub convert_fvwmtheme
128     my( $line ) = @_;
130     $line =~ s/^\*fvwmtheme\s*:?//i;
131     $line = undef if $line =~ /modulesynchronous.*?fvwmtheme/i;
133     push @converted_lines, $line;
136 # Comment out the modulepath line -- grr.
137 sub handle_modulepath
139     my( $line ) = @_;
140     $line = "# " . $line;
142     push( @converted_lines, $line );
145 # This should have happened in the fvwm-2.4 convert script, but handle it
146 # here anyway.
147 sub convert_windowshadesteps
149     my( $line ) = @_;
150     $line =~ /(\d+)p?/ ? 
151         $line = "Style * WindowShadeSteps $1" : 
152         $line = "Style * " . $line;
154     push( @converted_lines, $line );
157 sub convert_edge_resistance
159     my( $line ) = @_;
161     # This gets converted into two parts.  One is the EdgeResistance
162     # command, the other is a style line.
163     #
164     # We could only ever have had two numbers as arguments to
165     # EdgeResistance.
166     my( $edge_res_arg, $move_res_arg ) = 
167         ( $line =~ /edgeresistance\s*(\d+)\s*(\d+)/i );
169     push( @converted_lines,
170         qq|
171         EdgeResistance $edge_res_arg
172         Style * EdgeMoveResistance $move_res_arg| );
175 sub convert_snapattraction
177     my( $line ) = @_;
179     push( @converted_lines, "Style * " . $line );
182 sub convert_key_mouse_bindings
184     my( $line ) = @_;
185     my @components = split( /\s+/, $line, 5 );
187     # Take the last component.  We no longer care for "[*]" as conditional
188     # command parameters.  But we shouldn't really put another conditional
189     # in its place, so we'll just remove it.
190     $components[-1] =~ s/\[\*\]//;
192     # Also, conditional commands should now be separated with commas and not
193     # whitespace, so try and fix these up where we can.  It's not the
194     # intention we'll catch them all, but at least try and do so based on
195     # where they're likely to be used.
196     __convert_conditionals(\@components);
198     push( @converted_lines, join ' ', @components );
201 sub handle_continuation
203     no strict "refs"; # Yes, yes...
204     my( $line ) = @_;
206     return if !defined $last_func_ref || $last_func_ref eq '';
208     eval { &{$last_func_ref}($line) };
210     warn "$@\n" if $@;
213 sub handle_read_file
215     my( $line ) = @_;
216     my @read_parts = split( /\s+/, $line );
217     
218     push( @converted_lines, $line );    
220     # Crudely try and work out if the file is readable, and if it is add it
221     # to the list of further files to convert.
222     #
223     # This won't handle having to interpolate out any env vars set via
224     # SetEnv, or worse yet, outside of FVWM's environment.  The user will
225     # just have to run this script on that file manually.
226     my $fname = $read_parts[1];
227     return unless defined $fname and $fname ne '';
229     if( -e $fname )
230     {
231         push( @additional_files, [$fname] );
232         
233         # We're done.
234         return;
235     }
237     # If we have this:
238     #
239     # Read foo
240     #
241     # Or this:
242     #
243     # Read $./foo
244     #
245     # Then we assume FVWM_USERDIR ("$HOME/.fvwm/"), and if that file can't 
246     # be found there, try CWD, and if that fails we just give up.
247     
248     # Canonicalise the starting point by removing "$." -- we can guess what
249     # it ought to be replaced with.
250     $fname =~ s/^\$\.\/?//;
252     if( -e "$ENV{FVWM_USERDIR}/$fname" )
253     {
254         push( @additional_files,
255             ["$ENV{FVWM_USERDIR}/$fname"] );
256         return;
257     }
259     if( -e "$ENV{HOME}/.fvwm/$fname" )
260     {
261         push( @additional_files,
262             ["$ENV{HOME}/.fvwm/$fname"] );
263         return;
264     }
266     my $cwd_path = getcwd();
268     if( -e "$cwd_path/$fname" )
269     {
270         push( @additional_files, [$fname] );
271         return;
272     }
274     warn "Unable to follow:  $line\n";
277 sub check_func_definition
279     my( $line ) = @_;
281     if( $line !~ /^addtofunc\s+(?:start|init|restart)function.*/i )
282     {
283         $last_func_ref = '';
284     }
287 sub convert_initfunc
289     my( $line ) = @_;
290     $last_func_ref = "convert_initfunc";
292     if( $line =~ /addtofunc\s+initfunction\s+\"??[icmhd]{1}\"??\s+.*/i ||
293         $line =~ /addtofunc\s+initfunction\s*/i )
294     {
295         $line =~ s/addtofunc\s+initfunction\s*//i;
296     }
298     $line =~ s/^\s*\+//;
300     return if !defined $line || $line eq '';
302     # What we need to do now is convert this from:
303     #
304     # + I Foo
305     #
306     # to:
307     #
308     # + I Test (Init) Foo
310     my @func_cmd = split( /\s+/, $line, 3 );
311     unshift( @func_cmd, '' ) unless @func_cmd > 2;
313     # Remove any quotes around the action type --- they're not needed
314     # anymore.
315     $func_cmd[1] =~ s/\"//g;
316     $func_cmd[1] .= q| Test (Init) |;
318     # Run the command through the conditional function to ensure we
319     # handle those correctly.
320     __convert_conditionals( \@func_cmd );
322     push( @{ $converted_funcs{initfunction} }, join ' ', @func_cmd );
325 sub convert_restartfunc
327     my( $line ) = @_;
328     $last_func_ref = "convert_restartfunc";
329     
330     # We treat this exactly like startfunction.
331     if( $line =~ /addtofunc\s+restartfunction\s+\"??[icmhd]{1}\"??\s+.*/i )
332     {
333         # Split this string.  We can throw away the "AddToFunc" part as this
334         # is irrelevant.  But we want the following result:
335         # ( 'I', 'Some Command' )
336         $line =~ s/addtofunc\s+restartfunction\s*//i;
337     }
339     $line =~ s/addtofunc\s+restartfunction\s*//i;
340     
341     return if $line eq '';
343     # Remove the continuation prefix as we can add this in when writing out
344     # the function definitions later. 
345     $line =~ s/^\s*\+//;
346     
347     my @func_cmd = split( /\s+/, $line, 2 );
348     $func_cmd[1] =~ s/\"//g;
350     # Run the command through the conditional function to ensure we
351     # handle those correctly.
352     __convert_conditionals( \@func_cmd );
354     push( @{ $converted_funcs{startfunction} }, join ' ', @func_cmd );
357 sub convert_startfunc
359     my( $line ) = @_;
361     # Now, it's possible that we have something like this:
362     #
363     # AddToFunc StartFunction I Some Command
364     #
365     # Extract the command part, add it to the hash for our functions, and
366     # flag the fact we're dealing with StartFunction at this point for any
367     # continuation lines (+ I Foo) since we can't determine the context of
368     # them without such a thing.
370     if( $line =~ /addtofunc\s+startfunction\s+\"??[icmhd]{1}\"??\s+.*/i )
371     {
372         # Split this string.  We can throw away the "AddToFunc" part as this
373         # is irrelevant.  But we want the following result:
374         # ( 'I', 'Some Command' )
375         $line =~ s/addtofunc\s+startfunction\s*//i;
376     }
377     $line =~ s/addtofunc\s+startfunction\s*//i;
378     
379     # Remove the continuation prefix as we can add this in when writing out
380     # the function definitions later. 
381     $line =~ s/^\s*\+//;
383     return if !defined $line || $line eq '';
384     
385     my @func_cmd = split( /\s+/, $line, 2 );
386     $func_cmd[1] =~ s/\"//g;
388     # Run the command through the conditional function to ensure we
389     # handle those correctly.
390     __convert_conditionals( \@func_cmd );
392     push( @{ $converted_funcs{startfunction} }, join ' ', @func_cmd );
393     $last_func_ref = "convert_startfunc";
396 sub write_out_file
398     my( $dest_file ) = @_;
399     open( my $f, '>', $dest_file ) or
400         die "Couldn't open $dest_file: $!\n";
402     # If we had any continuation lines, preserve them as best we can.
403     @converted_lines = map {
404         join "\\\n", split /\\/, $_ 
405     } @converted_lines;
406     
407     print $f join( "\n", @converted_lines );
409     # Write out the functions.
410     print $f qq|\n\nDestroyFunc StartFunction\nAddToFunc StartFunction\n|;
412     # Put the Init stuff before anything else.
413     for( @{ $converted_funcs{initfunction} }, 
414          @{ $converted_funcs{startfunction } } )
415     {
416         print $f "+ $_\n";
417     }
419     close( $f );
422 sub dispatch_line
424     my( $line ) = @_;
426     if( $line =~ /^style/i )
427     {
428         convert_styles($line);
429     } elsif( $line =~ /^\s*\*fvwmtheme:??/i ) {
430         convert_fvwmtheme($line);
431     } elsif( $line =~ /^\s*modulepath\s*/i ) {
432         handle_modulepath( $line );
433     } elsif( $line =~ /^\s*windowshadesteps.*/i ) {
434         convert_windowshadesteps($line);
435     } elsif( $line =~ /^\s*module(?:synchronous)?.*?fvwmtheme$/i ) {
436         convert_fvwmtheme($line);
437     } elsif( $line =~ /^\s*edgeresistance\s*\d+\s*\d+/i ) {
438         convert_edge_resistance($line);
439     } elsif( $line =~ /^\s*key|mouse/i ) {
440         convert_key_mouse_bindings($line);
441     } elsif( $line =~ /^\s*snap(?:attraction|grid)/i ) {
442         convert_snapattraction( $line );
443     } elsif( $line =~ /^\s*addtofunc\s+initfunction/i ) {
444         convert_initfunc( $line );
445     } elsif( $line =~ /^\s*addtofunc\s+startfunction.*/i ) {
446         convert_startfunc( $line );
447     } elsif( $line =~ /^\s*addtofunc\s+restartfunction/i ) {
448         convert_restartfunc( $line );
449     } elsif( $line =~ /^\s*addtofunc\s+\w+.*/i ) {
450         check_func_definition( $line );
451     } elsif( $line =~ /^\s*\+\s*\"??[ichmd]{1}\s*\"??\s+.*/i ) {
452         handle_continuation( $line );
453     } elsif( $line =~ /^\s*read\s*[\/\w]+/i ) {
454         handle_read_file( $line );
455     } else {
456         # Could be a comment, or a continuation, or simply something we
457         # don't need to convert.  As far as continuation lines are
458         # concerned, these are kept in order just by pushing them onto the
459         # array --- but converting continuation lines is tricky since we'd
460         # need to determine the context of the continuation.  I can't be
461         # bothered.
462         push( @converted_lines, $_ );
463     }
466 sub usage
468     print "fvwm-convert-2.6 [-f] [-h] source-file destination-file\n";
469     exit;
472 GetOptions(
473     "help|h" => \&usage,
474     "follow-read|f" => \$follow_read,
475 ) || usage();
477 # But we still require @ARGV to be populated with our filenames.
478 usage() unless( @ARGV > 0 and @ARGV <=2 );
480 my @files = [@ARGV];
481 process_files( \@files );
483 if( @additional_files && !$follow_read )
485     print "The following files were detected, but not processed:\n\n",
486     join("\n", @$_ ) for @additional_files,;
487     print "\n";
490 # Only do this is we've been asked.
491 if( @additional_files && $follow_read )
493     $process_read = 1;
494     process_files( \@additional_files );