468 lines · 10.1 KB · Perl
RawDownload
#!/usr/bin/perl# SPDX-License-Identifier: GPL-2.0 use strict;use Pod::Usage;use Getopt::Long;use File::Find;use Fcntl ':mode'; my $help;my $man;my $debug;my $prefix="Documentation/ABI"; GetOptions(	"debug|d+" => \$debug,	"dir=s" => \$prefix,	'help|?' => \$help,	man => \$man) or pod2usage(2); pod2usage(1) if $help;pod2usage(-exitstatus => 0, -verbose => 2) if $man; pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2); my ($cmd, $arg) = @ARGV; pod2usage(2) if ($cmd ne "search" && $cmd ne "rest" && $cmd ne "validate");pod2usage(2) if ($cmd eq "search" && !$arg); require Data::Dumper if ($debug); my %data; ## Displays an error message, printing file name and line#sub parse_error($$$$) {	my ($file, $ln, $msg, $data) = @_; 	print STDERR "file $file#$ln: $msg at\n\t$data";} ## Parse an ABI file, storing its contents at %data#sub parse_abi {	my $file = $File::Find::name; 	my $mode = (stat($file))[2];	return if ($mode & S_IFDIR);	return if ($file =~ m,/README,); 	my $name = $file;	$name =~ s,.*/,,; 	my $nametag = "File $name";	$data{$nametag}->{what} = "File $name";	$data{$nametag}->{type} = "File";	$data{$nametag}->{file} = $name;	$data{$nametag}->{filepath} = $file;	$data{$nametag}->{is_file} = 1; 	my $type = $file;	$type =~ s,.*/(.*)/.*,$1,; 	my $what;	my $new_what;	my $tag;	my $ln;	my $xrefs;	my $space;	my @labels;	my $label; 	print STDERR "Opening $file\n" if ($debug > 1);	open IN, $file;	while(<IN>) {		$ln++;		if (m/^(\S+)(:\s*)(.*)/i) {			my $new_tag = lc($1);			my $sep = $2;			my $content = $3; 			if (!($new_tag =~ m/(what|where|date|kernelversion|contact|description|users)/)) {				if ($tag eq "description") {					# New "tag" is actually part of					# description. Don't consider it a tag					$new_tag = "";				} elsif ($tag ne "") {					parse_error($file, $ln, "tag '$tag' is invalid", $_);				}			} 			# Invalid, but it is a common mistake			if ($new_tag eq "where") {				parse_error($file, $ln, "tag 'Where' is invalid. Should be 'What:' instead", $_);				$new_tag = "what";			} 			if ($new_tag =~ m/what/) {				$space = "";				if ($tag =~ m/what/) {					$what .= ", " . $content;				} else {					parse_error($file, $ln, "What '$what' doesn't have a description", "") if ($what && !$data{$what}->{description}); 					$what = $content;					$label = $content;					$new_what = 1;				}				push @labels, [($content, $label)];				$tag = $new_tag; 				push @{$data{$nametag}->{xrefs}}, [($content, $label)] if ($data{$nametag}->{what});				next;			} 			if ($tag ne "" && $new_tag) {				$tag = $new_tag; 				if ($new_what) {					@{$data{$what}->{label}} = @labels if ($data{$nametag}->{what});					@labels = ();					$label = "";					$new_what = 0; 					$data{$what}->{type} = $type;					$data{$what}->{file} = $name;					$data{$what}->{filepath} = $file;					print STDERR "\twhat: $what\n" if ($debug > 1);				} 				if (!$what) {					parse_error($file, $ln, "'What:' should come first:", $_);					next;				}				if ($tag eq "description") {					next if ($content =~ m/^\s*$/);					if ($content =~ m/^(\s*)(.*)/) {						my $new_content = $2;						$space = $new_tag . $sep . $1;						while ($space =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}						$space =~ s/./ /g;						$data{$what}->{$tag} .= "$new_content\n";					}				} else {					$data{$what}->{$tag} = $content;				}				next;			}		} 		# Store any contents before tags at the database		if (!$tag && $data{$nametag}->{what}) {			$data{$nametag}->{description} .= $_;			next;		} 		if ($tag eq "description") {			if (!$data{$what}->{description}) {				next if (m/^\s*\n/);				if (m/^(\s*)(.*)/) {					$space = $1;					while ($space =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}					$data{$what}->{$tag} .= "$2\n";				}			} else {				my $content = $_;				if (m/^\s*\n/) {					$data{$what}->{$tag} .= $content;					next;				} 				while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {}				$space = "" if (!($content =~ s/^($space)//)); 				# Compress spaces with tabs				$content =~ s<^ {8}> <\t>;				$content =~ s<^ {1,7}\t> <\t>;				$content =~ s< {1,7}\t> <\t>;				$data{$what}->{$tag} .= $content;			}			next;		}		if (m/^\s*(.*)/) {			$data{$what}->{$tag} .= "\n$1";			$data{$what}->{$tag} =~ s/\n+$//;			next;		} 		# Everything else is error		parse_error($file, $ln, "Unexpected line:", $_);	}	$data{$nametag}->{description} =~ s/^\n+//;	close IN;} ## Outputs the book on ReST format# my %labels; sub output_rest {	foreach my $what (sort {				($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") ||				$a cmp $b			       } keys %data) {		my $type = $data{$what}->{type};		my $file = $data{$what}->{file};		my $filepath = $data{$what}->{filepath}; 		my $w = $what;		$w =~ s/([\(\)\_\-\*\=\^\~\\])/\\$1/g;  		foreach my $p (@{$data{$what}->{label}}) {			my ($content, $label) = @{$p};			$label = "abi_" . $label . " ";			$label =~ tr/A-Z/a-z/; 			# Convert special chars to "_"			$label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g;			$label =~ s,_+,_,g;			$label =~ s,_$,,; 			# Avoid duplicated labels			while (defined($labels{$label})) {			    my @chars = ("A".."Z", "a".."z");			    $label .= $chars[rand @chars];			}			$labels{$label} = 1; 			$data{$what}->{label} .= $label; 			printf ".. _%s:\n\n", $label; 			# only one label is enough			last;		}  		$filepath =~ s,.*/(.*/.*),\1,;;		$filepath =~ s,[/\-],_,g;;		my $fileref = "abi_file_".$filepath; 		if ($type eq "File") {			my $bar = $w;			$bar =~ s/./-/g; 			print ".. _$fileref:\n\n";			print "$w\n$bar\n\n";		} else {			my @names = split /\s*,\s*/,$w; 			my $len = 0; 			foreach my $name (@names) {				$len = length($name) if (length($name) > $len);			} 			print "What:\n\n"; 			print "+-" . "-" x $len . "-+\n";			foreach my $name (@names) {				printf "| %s", $name . " " x ($len - length($name)) . " |\n";				print "+-" . "-" x $len . "-+\n";			}			print "\n";		} 		print "Defined on file :ref:`$file <$fileref>`\n\n" if ($type ne "File"); 		my $desc = $data{$what}->{description};		$desc =~ s/^\s+//; 		# Remove title markups from the description, as they won't work		$desc =~ s/\n[\-\*\=\^\~]+\n/\n/g; 		if (!($desc =~ /^\s*$/)) {			if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/  || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) {				# put everything inside a code block				$desc =~ s/\n/\n /g; 				print "::\n\n";				print " $desc\n\n";			} else {				# Escape any special chars from description				$desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; 				print "$desc\n\n";			}		} else {			print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file});		} 		if ($data{$what}->{xrefs}) {			printf "Has the following ABI:\n\n"; 			foreach my $p(@{$data{$what}->{xrefs}}) {				my ($content, $label) = @{$p};				$label = "abi_" . $label . " ";				$label =~ tr/A-Z/a-z/; 				# Convert special chars to "_"				$label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g;				$label =~ s,_+,_,g;				$label =~ s,_$,,; 				# Escape special chars from content				$content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; 				print "- :ref:`$content <$label>`\n\n";			}		}	}} ## Searches for ABI symbols#sub search_symbols {	foreach my $what (sort keys %data) {		next if (!($what =~ m/($arg)/)); 		my $type = $data{$what}->{type};		next if ($type eq "File"); 		my $file = $data{$what}->{filepath}; 		my $bar = $what;		$bar =~ s/./-/g; 		print "\n$what\n$bar\n\n"; 		my $kernelversion = $data{$what}->{kernelversion};		my $contact = $data{$what}->{contact};		my $users = $data{$what}->{users};		my $date = $data{$what}->{date};		my $desc = $data{$what}->{description};		$kernelversion =~ s/^\s+//;		$contact =~ s/^\s+//;		$users =~ s/^\s+//;		$users =~ s/\n//g;		$date =~ s/^\s+//;		$desc =~ s/^\s+//; 		printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion);		printf "Date:\t\t\t%s\n", $date if ($date);		printf "Contact:\t\t%s\n", $contact if ($contact);		printf "Users:\t\t\t%s\n", $users if ($users);		print "Defined on file:\t$file\n\n";		print "Description:\n\n$desc";	}}  ## Parses all ABI files located at $prefix dir#find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); ## Handles the command#if ($cmd eq "rest") {	output_rest;} elsif ($cmd eq "search") {	search_symbols;}  __END__ =head1 NAME abi_book.pl - parse the Linux ABI files and produce a ReST book. =head1 SYNOPSIS B<abi_book.pl> [--debug] [--man] [--help] [--dir=<dir>] <COMAND> [<ARGUMENT>] Where <COMMAND> can be: =over 8 B<search> [SEARCH_REGEX] - search for [SEARCH_REGEX] inside ABI B<rest>                  - output the ABI in ReST markup language B<validate>              - validate the ABI contents =back =head1 OPTIONS =over 8 =item B<--dir> Changes the location of the ABI search. By default, it usesthe Documentation/ABI directory. =item B<--debug> Put the script in verbose mode, useful for debugging. Can be called multipletimes, to increase verbosity. =item B<--help> Prints a brief help message and exits. =item B<--man> Prints the manual page and exits. =back =head1 DESCRIPTION Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI),allowing to search for ABI symbols or to produce a ReST book containingthe Linux ABI documentation. =head1 EXAMPLES Search for all stable symbols with the word "usb": =over 8 $ scripts/get_abi.pl search usb --dir Documentation/ABI/stable =back Search for all symbols that match the regex expression "usb.*cap": =over 8 $ scripts/get_abi.pl search usb.*cap =back Output all obsoleted symbols in ReST format =over 8 $ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete =back =head1 BUGS Report bugs to Mauro Carvalho Chehab <mchehab+samsung@kernel.org> =head1 COPYRIGHT Copyright (c) 2016-2019 by Mauro Carvalho Chehab <mchehab+samsung@kernel.org>. License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. =cut