#!/usr/bin/env perl use strict; use warnings; my %msg = (); my %reorder = (help=>-1); # We do not want every Makefile syntax error to break help completely, # so we just disable the kind of features where the syntax error occurs # In that case we store also the error message here my %brokenFeatures = (); my @blocks = (); my @plainCode = (); READ: while(<>) { my $lnum = $.; if(m/^# HELP HEADER START/) { my @start = (); while(<>) { do { push @blocks, { kind=>'start', text=>\@start, line=>$lnum }; next READ } if m/^# HELP HEADER END/; if(m/# (.*)/) { push @start, $1 } else { die "$0: Invalid HELP HEADER section in Makefile!\n"; } } push @blocks, { kind=>'start', text=>\@start, line=>$lnum }; next READ } if(m/^# HELP((?:\((?:[^\(\)]|\([^\(\)]+\))+\))?):\s*(.*)/) { my ($target, $message) = ($1, $2); if($target=~m/\((.*)\)/) { $target = $1; } else { my $line = <>; die "$0: Unexpected end of file, target expected!\n" if not defined $line; $line=~m/^([^:]+):/ or die "$0: HELP marker expects target but no target found!\n"; $target = $1 } push @blocks, { kind=>'target', target=>$target, message=>$message, line=>$lnum }; next READ } if(m/^# HELPVAR\((.*)\)/) { push @blocks, { kind=>'helpvar', content=>$1, line=>$lnum }; next READ; } push @plainCode, [$lnum, $_] } my %kindBlocks = (); for(@blocks) { push @{$kindBlocks{$_->{kind}}}, $_ } my @matchies = (); HELPVARS: { for my $hv(@{$kindBlocks{helpvar}}) { my $reVar = qr((?:\$\((?[A-Z]+)(?:,(?[^\(\)]+))?\))); my $reVarNoGroups = qr((?:\$\((?:[A-Z]+)(?:,(?:[^\(\)]+))?\))); my @parts = split /($reVarNoGroups)/, $hv->{content}; my %setvars = (); my @reParts = (); for my $p(@parts) { if($p=~m#^$reVar$#) { my $name = $+{name}; my $re = $+{re}; if(defined $re) { if(exists $setvars{$name}) { $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Multiple definitions of variable $name found!"; last HELPVARS } $setvars{$name} = $re; push @reParts, [1,qr((?<$name>$re))]; } else { if(not exists $setvars{$name}) { $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Variable $name used without definition!"; last HELPVARS } push @reParts, [0,$name]; #qr((?:\k<$name>)); -- problem: cannot reference variable when not defined } } else { push @reParts, [1,qr((?:\Q$p\E))]; } } my $reCol = qr((?:)); for my $r(@reParts) { my ($isRe, $reg) = @$r; if($isRe) { $reCol = qr((?:$reCol$reg)); } else { eval { $reCol = qr((?:$reCol(?:\k<$reg>))); }; if($@) { $brokenFeatures{helpvars} = "Makefile line $hv->{line}: Variable $reg caused an internal error ($@)!"; last HELPVARS } } } push @matchies, $reCol } } my %helpvars = (); for my $rowr(@plainCode) { my ($lnum, $row) = @$rowr; my @matches = (); for my $re(@matchies) { if($row=~m#^$re$#) { my %h = %+; push @matches, \%h } } if(@matches>1) { $brokenFeatures{helpvars} = "Makefile line $lnum: Multiple variable definitions match on row '$row'\n"; } if(1==@matches) { my %h = %{$matches[0]}; for my $k(keys %h) { push @{$helpvars{$k}}, $h{$k} } } } my @start = (); @start = @{$kindBlocks{start}} if exists $kindBlocks{start}; for my $tg(@{$kindBlocks{target}}) { my $tgv = $tg->{target}; my @build = (); $tgv=~s#\$\(([A-Z]+)(?:,[^\(\)]*)?\)#push @build, {name=>$1,elm=>$helpvars{$1}}; $1#ge; unshift @build, $tg->{message}; $msg{$tgv} = \@build; } sub msgprinter { my $el = shift; if('HASH' eq ref $el) { print " Where $el->{name} is one of\n"; print " $_\n" for @{$el->{elm}}; return } $el =~ s#(.{40,76})\s+#$1\n #g; print " $el\n"; } for(@start) { print "$_\n" for @{$_->{text}} } print "\n" if @start; for my $tar(sort {($reorder{$a}||0) <=> ($reorder{$b}||0) || $a cmp $b } keys %msg) { print "$tar\n"; msgprinter($_) for @{$msg{$tar}}; print "\n"; } for my $k(keys %brokenFeatures) { warn "\n!!!!!\nFailed to complete construct the help message from the Makefile.\nFeature '$k' disabled, because: $brokenFeatures{$k}\n\n"; }