#!/usr/bin/perl -w # # $Id: initidy,v 1.10 2006/06/19 21:01:47 jmates Exp $ # # The author disclaims all copyrights and releases this script into the # public domain. # # Reformats .ini style preference files in format supported by the # Config::Tiny perl module. # # TODO options to customize output (blank line handling, comment string, # indents, sorting). use strict; # whether to mangle comments with autoformat() my $DO_AUTOFORMAT = 0; # how to match comments my $COMMENT_MATCH = qr/[#;]/; # how much to indent section elements by my $INDENT_AMOUNT = q{ }; my $exit_value = 0; use Getopt::Std; my %opts; getopts 'h?a', \%opts; print_help() if $opts{h} or $opts{'?'}; if ( exists $opts{a} ) { $DO_AUTOFORMAT = 1; } eval { require Text::Autoformat; }; if ( $@ and $DO_AUTOFORMAT ) { warn "warning: Text::Autoformat not found\n"; $DO_AUTOFORMAT = 0; } else { require Text::Autoformat; } # no args: operate on standard input if ( !@ARGV ) { print initidy( input => \*STDIN, filename => '-' ); close STDOUT or die "error: could not close stdout: errno=$!\n"; exit; } # otherwise, tidy files on command line in place for my $file (@ARGV) { if ( !-f $file ) { warn "notice: skipping non-file: file=$file\n"; $exit_value = 1; next; } if ( !open INPUT, '<', $file ) { warn "warning: skipping as could not read: errno=$!, file=$file\n"; $exit_value = 1; next; } my $output = initidy( input => \*INPUT, filename => $file ); close INPUT; # TODO use File::Temp and rename() instead, or inplace edit if ( !open OUTPUT, '>', $file ) { warn "warning: skipping as could not write: errno=$!, file=$file\n"; $exit_value = 1; next; } print OUTPUT $output; if ( !close OUTPUT ) { warn "warning: problem closing file: errno=$!, file=$file\n"; $exit_value = 1; } } exit $exit_value; sub initidy { my %param = @_; my $fh = $param{input}; my @ini_blocks; my $default = 1; my $previous_type = q{}; my $output = q{}; while ( my $line = <$fh> ) { $line =~ s/^\s+//; $line =~ s/\s+$//; my %ini_block; if ( $line =~ m/^ \[ ([^\]]+) \] $/x ) { $ini_block{type} = 'section'; $ini_block{lines} = [$1]; if ( $previous_type eq $ini_block{type} ) { # TODO error if empty configuration block? } # disable as in a block now $default = 0; } elsif ( $line =~ m/^$/x ) { $ini_block{type} = 'blank'; if ( $previous_type eq $ini_block{type} ) { $ini_blocks[-1]->{count}++; next; } else { $ini_block{count} = 1; } } elsif ( $line =~ m/^ ($COMMENT_MATCH) (.*) $/x ) { $ini_block{comment_str} = "$1 "; my $line = $2 || q{}; $line =~ s/^\s+//; $ini_block{type} = 'comment'; $ini_block{indent} = $default ? q{} : $INDENT_AMOUNT; if ( $line =~ m/ \[ [^\]]+ \] /x ) { $ini_block{indent} = q{}; } if ( $previous_type eq $ini_block{type} ) { push @{ $ini_blocks[-1]->{lines} }, $line; next; } else { $ini_block{lines} = [$line]; } } elsif ( $line =~ m/=/ ) { $ini_block{type} = 'statement'; $ini_block{indent} = $default ? q{} : $INDENT_AMOUNT; my ( $key, $value ) = split /\s*=\s*/, $line, 2; if ( $previous_type eq $ini_block{type} ) { push @{ $ini_blocks[-1]->{lines} }, { key => $key, value => $value }; $ini_blocks[-1]->{keylength} = length $key if length $key > $ini_blocks[-1]->{keylength}; next; } else { $ini_block{lines} = [ { key => $key, value => $value } ]; $ini_block{keylength} = length $key; } } else { die "error: ini parse error: file=$param{filename}, line=$.\n"; } push @ini_blocks, \%ini_block; $previous_type = $ini_block{type}; } # walk over in-memory representation of configuration and write out tidy for my $i ( 0 .. $#ini_blocks ) { my $entry = $ini_blocks[$i]; my $indent = $entry->{indent} || q{}; if ( $entry->{type} eq 'section' ) { $output .= "[$entry->{lines}->[0]]\n"; } elsif ( $entry->{type} eq 'comment' ) { my $comment_str = $entry->{comment_str} || '# '; my $tmp_comments = join "\n", @{ $entry->{lines} }; # Avoid autoformat if comment contains ini-esque looking data to # avoid reformat of disabled blocks. my $may_contain_code = 0; if ( $tmp_comments =~ m/ \[ [^\]]+ \] /x or $tmp_comments =~ m/ [\w-]+ \s* = /x ) { $may_contain_code = 1; } # no indent pre-section comments, or disabled code blocks if ( ( $i != $#ini_blocks and $ini_blocks[ $i + 1 ]->{type} eq 'section' ) or $may_contain_code ) { $indent = q{}; } if ($may_contain_code) { # KLUGE nick off whitespace if commenting code blocks. May break # things if comment needs whitespace... $comment_str =~ s/\s+$//; } my $comment = $indent . $comment_str . join "\n$indent$comment_str", @{ $entry->{lines} }; if ( not $DO_AUTOFORMAT or $may_contain_code ) { $output .= $comment . "\n"; } else { $output .= Text::Autoformat::autoformat( $comment, { all => 1 } ); } } elsif ( $entry->{type} eq 'blank' ) { # TODO does this work?? if ( $i != $#ini_blocks ) { $output .= "\n"; } } elsif ( $entry->{type} eq 'statement' ) { for my $statement ( sort { $a->{key} cmp $b->{key} } @{ $entry->{lines} } ) { $output .= $indent . $statement->{key} . ' ' x ( 1 + $entry->{keylength} - length $statement->{key} ) . '= ' . $statement->{value} . "\n"; } } } return $output; } sub print_help { print <<"HELP"; Usage: $0 [options] [file [file2 ..]] Tidy .ini format data. Options: -h/-? Display this message. -a Enable autoformat. HELP exit 100; }