#!/usr/bin/env perl # # Copyright (C) 2001-2021 Graeme Walker # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # === # # make-format # # Runs clang-format with some post-processing to get close to the preferred # style. # # usage: make-format [options] { [--list] -d | [ ...] } # use strict ; use FileHandle ; use File::Find ; use File::Copy ; use Data::Dumper ; use Getopt::Long ; # configure my $cfg_formatter = "clang-format" ; my $cfg_tab = 4 ; # in .clang-format my @cfg_stop = qw( /gdef\.h$ /gconfig_defs\.h$ /moc_ /mbedtls/ ) ; # parse the command-line my %opt = () ; GetOptions( \%opt , "quiet|q" , "verbose|v" , "formatter|f=s" , # clang-format "nofixup" , # no post-processing "noformat|n" , # no clang-format "nobackup|x" , "list" , # list files, with -d "d=s" , # base directory for --list ) or die ; $cfg_formatter = $opt{formatter} if $opt{formatter} ; my @files = @ARGV ; die "usage error\n" if ( $opt{list} && !$opt{d} ) ; # deal with "--list -d ..." if( $opt{list} && $opt{d} ) { my @files = find_files( $opt{d} , \@cfg_stop ) ; print join( "\n" , (@files,"") ) ; exit 0 ; } # sanity checks if( !$opt{noformat} ) { chomp( my $version = `$cfg_formatter --version` ) ; my ($v1,$v2) = ( $version =~ m/(\d+)\.(\d+)/ ) ; die "make-format: error: cannot run [$cfg_formatter --version]\n" if ( !$v1 && !$v2 ) ; die "make-format: error: $cfg_formatter is too old [$v1.$v2]\n" if ( $v1 < 3 || ( $v1 == 3 && $v2 < 10 ) ) ; die "make-format: error: no .clang-format file in cwd\n" if ! -e ".clang-format" ; } # check for future command-line options my $formatter_options = "" ; { my $fh = new FileHandle( "$cfg_formatter --help |" ) ; while(<$fh>) { chomp( my $line = $_ ) ; if( $line =~ m/sort.includes/ ) # new in v11 { $formatter_options = "--sort-includes=false" ; } } } # build the "-d" file list if( $opt{d} ) { @files = find_files( $opt{d} , \@cfg_stop , $opt{verbose} ) ; } # format each file for my $fname ( @files ) { my $cmd = "$cfg_formatter $formatter_options --style=file -i $fname" ; print "make-format: [$fname]\n" unless $opt{quiet} ; # make a backup if( !$opt{nobackup} ) { my $t = time() ; File::Copy::copy( $fname , "$fname.$t.bak" ) or die "make-format: error: cannot create a backup for [$fname]\n" ; } # run clang-format if( !$opt{noformat} ) { my $rc = system( $cmd ) ; if( $rc ) { die "make-format: error: failed to run clang-format on [$fname]\n $cmd\n" ; } } # fix things that clang-format has messed up if( !$opt{nofixup} ) { fixup( $fname ) ; } } sub fixup { my ( $fname ) = @_ ; my $fh_in = new FileHandle( $fname ) or die ; my $fh_out = new FileHandle( "$fname.new" , "w" ) or die ; my $old_indent ; my $bad_indent ; while(<$fh_in>) { chomp( my $line = $_ ) ; if( $line =~ m:^// clang-format off: ) { $fh_out->close() ; unlink( "$fname.new" ) ; return ; } # clang-format tries to align with tabs followed by spaces -- # here we make sure that only tabs are used for indenting, with # the indenting level increasing by no more than one tab on # consecutive lines my ( $indent ) = ( $line =~ m/^(\s*)/ ) ; if( $line eq "" ) { $old_indent = undef ; $bad_indent = undef ; } elsif( defined($old_indent) && sizeof($indent) > (sizeof($old_indent)+$cfg_tab) ) { $bad_indent = $indent ; $line =~ s/^\s*/$old_indent\t/ ; } elsif( $indent =~ m/\t / ) { $bad_indent = $indent ; $line =~ s/^\s*/$old_indent\t/ ; } elsif( defined($bad_indent) && sizeof($indent) == sizeof($bad_indent) ) { $line =~ s/^\s*/$old_indent\t/ ; } else { $old_indent = $indent ; $bad_indent = undef ; } # add some more whitespace $line =~ s:(\S);$:$1 ;: ; $line =~ s:(\S); //(.*):$1 ; //$2: ; $line =~ s:(\S),:$1 ,:g unless ( $line =~ m/["']/ or $line =~ m://.*,: ) ; print $fh_out $line , "\n" or die ; } $fh_in->close() ; $fh_out->close() or die ; rename( "$fname.new" , $fname ) or die ; } sub sizeof { my ( $s ) = @_ ; my $spaces = ' ' x $cfg_tab ; $s =~ s/\t/$spaces/g ; return length($s) ; } sub find_files { my ( $base , $stoplist , $verbose ) = @_ ; my @files = () ; my @stopped = () ; my $callback = sub { my $filename = $_ ; my $path = $File::Find::name ; return if( -d $path ) ; if( $filename =~ m/\.cpp$|\.h$/ ) { my $stop = 0 ; map { my $re = $_ ; $stop = 1 if $path =~ m/$re/ } @$stoplist ; $path =~ s:^\./:: ; push @files , $path unless $stop ; push @stopped , $path if $stop ; } } ; File::Find::find( $callback , $base ) ; map { print "ignoring [$_]\n" } @stopped if $verbose ; return @files ; }