2016-07-07 04:58:54 +03:00
#!/usr/bin/perl
use strict ;
use Text::Tabs ;
2016-11-17 13:32:34 +03:00
use Getopt::Long ;
use Pod::Usage ;
2016-07-07 04:58:54 +03:00
2016-11-17 13:32:34 +03:00
my $ debug ;
my $ help ;
my $ man ;
2016-07-07 04:58:54 +03:00
2016-11-17 13:32:34 +03:00
GetOptions (
"debug" = > \ $ debug ,
'help|?' = > \ $ help ,
man = > \ $ man
) or pod2usage ( 2 ) ;
2016-08-31 12:41:40 +03:00
2016-11-17 13:32:34 +03:00
pod2usage ( 1 ) if $ help ;
pod2usage ( - exitstatus = > 0 , - verbose = > 2 ) if $ man ;
pod2usage ( 2 ) if ( scalar @ ARGV < 2 || scalar @ ARGV > 3 ) ;
2016-07-07 04:58:54 +03:00
my ( $ file_in , $ file_out , $ file_exceptions ) = @ ARGV ;
my $ data ;
my % ioctls ;
my % defines ;
my % typedefs ;
my % enums ;
my % enum_symbols ;
my % structs ;
2016-11-17 13:32:34 +03:00
require Data::Dumper if ( $ debug ) ;
2016-07-07 04:58:54 +03:00
#
# read the file and get identifiers
#
my $ is_enum = 0 ;
2016-07-07 20:13:12 +03:00
my $ is_comment = 0 ;
2016-07-07 04:58:54 +03:00
open IN , $ file_in or die "Can't open $file_in" ;
while ( <IN> ) {
2016-07-07 20:13:12 +03:00
$ data . = $ _ ;
2016-07-07 12:52:10 +03:00
my $ ln = $ _ ;
2016-07-07 20:13:12 +03:00
if ( ! $ is_comment ) {
$ ln =~ s , /\*.*(\*/ ) , , g ;
2016-07-07 12:52:10 +03:00
2016-07-07 20:13:12 +03:00
$ is_comment = 1 if ( $ ln =~ s , / \ * . * , , ) ;
} else {
if ( $ ln =~ s , ^ ( . * \ * / ) , , ) {
$ is_comment = 0 ;
} else {
next ;
}
}
2016-07-07 04:58:54 +03:00
2016-07-07 13:06:05 +03:00
if ( $ is_enum && $ ln =~ m/^\s*([_\w][\w\d_]+)\s*[\,=]?/ ) {
2016-07-07 04:58:54 +03:00
my $ s = $ 1 ;
my $ n = $ 1 ;
$ n =~ tr /A-Z/ a - z / ;
$ n =~ tr /_/ - / ;
2016-08-31 12:44:21 +03:00
$ enum_symbols { $ s } = "\\ :ref:`$s <$n>`\\ " ;
2016-07-07 04:58:54 +03:00
$ is_enum = 0 if ( $ is_enum && m/\}/ ) ;
next ;
}
$ is_enum = 0 if ( $ is_enum && m/\}/ ) ;
2016-07-07 13:06:05 +03:00
if ( $ ln =~ m/^\s*#\s*define\s+([_\w][\w\d_]+)\s+_IO/ ) {
2016-07-07 04:58:54 +03:00
my $ s = $ 1 ;
my $ n = $ 1 ;
$ n =~ tr /A-Z/ a - z / ;
2016-08-31 12:44:21 +03:00
$ ioctls { $ s } = "\\ :ref:`$s <$n>`\\ " ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-07-07 13:06:05 +03:00
if ( $ ln =~ m/^\s*#\s*define\s+([_\w][\w\d_]+)\s+/ ) {
2016-07-07 04:58:54 +03:00
my $ s = $ 1 ;
my $ n = $ 1 ;
$ n =~ tr /A-Z/ a - z / ;
$ n =~ tr /_/ - / ;
2016-08-31 12:44:21 +03:00
$ defines { $ s } = "\\ :ref:`$s <$n>`\\ " ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ ln =~ m/^\s*typedef\s+([_\w][\w\d_]+)\s+(.*)\s+([_\w][\w\d_]+);/ ) {
my $ s = $ 2 ;
my $ n = $ 3 ;
2016-07-07 04:58:54 +03:00
2016-08-31 12:44:21 +03:00
$ typedefs { $ n } = "\\ :c:type:`$n <$s>`\\ " ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-07-07 13:06:05 +03:00
if ( $ ln =~ m/^\s*enum\s+([_\w][\w\d_]+)\s+\{/
2016-07-07 13:20:27 +03:00
|| $ ln =~ m/^\s*enum\s+([_\w][\w\d_]+)$/
|| $ ln =~ m/^\s*typedef\s*enum\s+([_\w][\w\d_]+)\s+\{/
|| $ ln =~ m/^\s*typedef\s*enum\s+([_\w][\w\d_]+)$/ ) {
2016-07-07 04:58:54 +03:00
my $ s = $ 1 ;
2016-08-31 12:44:21 +03:00
$ enums { $ s } = "enum :c:type:`$s`\\ " ;
2016-07-07 04:58:54 +03:00
$ is_enum = $ 1 ;
next ;
}
2016-07-07 13:06:05 +03:00
if ( $ ln =~ m/^\s*struct\s+([_\w][\w\d_]+)\s+\{/
2016-07-07 13:20:27 +03:00
|| $ ln =~ m/^\s*struct\s+([[_\w][\w\d_]+)$/
|| $ ln =~ m/^\s*typedef\s*struct\s+([_\w][\w\d_]+)\s+\{/
|| $ ln =~ m/^\s*typedef\s*struct\s+([[_\w][\w\d_]+)$/
) {
2016-07-07 04:58:54 +03:00
my $ s = $ 1 ;
2016-08-31 12:44:21 +03:00
$ structs { $ s } = "struct :c:type:`$s`\\ " ;
2016-07-07 04:58:54 +03:00
next ;
}
}
close IN ;
#
# Handle multi-line typedefs
#
2016-07-07 14:09:37 +03:00
my @ matches = ( $ data =~ m/typedef\s+struct\s+\S+?\s*\{[^\}]+\}\s*(\S+)\s*\;/g ,
$ data =~ m/typedef\s+enum\s+\S+?\s*\{[^\}]+\}\s*(\S+)\s*\;/g , ) ;
2016-07-07 04:58:54 +03:00
foreach my $ m ( @ matches ) {
2016-08-31 12:44:21 +03:00
my $ s = $ m ;
2016-07-07 04:58:54 +03:00
2016-08-31 12:44:21 +03:00
$ typedefs { $ s } = "\\ :c:type:`$s`\\ " ;
2016-07-07 04:58:54 +03:00
next ;
}
#
# Handle exceptions, if any
#
2016-08-31 12:44:21 +03:00
my % def_reftype = (
"ioctl" = > ":ref" ,
"define" = > ":ref" ,
"symbol" = > ":ref" ,
"typedef" = > ":c:type" ,
"enum" = > ":c:type" ,
"struct" = > ":c:type" ,
) ;
2016-07-07 04:58:54 +03:00
if ( $ file_exceptions ) {
open IN , $ file_exceptions or die "Can't read $file_exceptions" ;
while ( <IN> ) {
next if ( m/^\s*$/ || m/^\s*#/ ) ;
# Parsers to ignore a symbol
if ( m/^ignore\s+ioctl\s+(\S+)/ ) {
delete $ ioctls { $ 1 } if ( exists ( $ ioctls { $ 1 } ) ) ;
next ;
}
if ( m/^ignore\s+define\s+(\S+)/ ) {
delete $ defines { $ 1 } if ( exists ( $ defines { $ 1 } ) ) ;
next ;
}
if ( m/^ignore\s+typedef\s+(\S+)/ ) {
delete $ typedefs { $ 1 } if ( exists ( $ typedefs { $ 1 } ) ) ;
next ;
}
if ( m/^ignore\s+enum\s+(\S+)/ ) {
delete $ enums { $ 1 } if ( exists ( $ enums { $ 1 } ) ) ;
next ;
}
if ( m/^ignore\s+struct\s+(\S+)/ ) {
delete $ structs { $ 1 } if ( exists ( $ structs { $ 1 } ) ) ;
next ;
}
2016-07-07 20:26:51 +03:00
if ( m/^ignore\s+symbol\s+(\S+)/ ) {
delete $ enum_symbols { $ 1 } if ( exists ( $ enum_symbols { $ 1 } ) ) ;
next ;
}
2016-07-07 04:58:54 +03:00
# Parsers to replace a symbol
2016-08-31 12:44:21 +03:00
my ( $ type , $ old , $ new , $ reftype ) ;
2016-07-07 04:58:54 +03:00
2016-08-31 12:44:21 +03:00
if ( m/^replace\s+(\S+)\s+(\S+)\s+(\S+)/ ) {
$ type = $ 1 ;
$ old = $ 2 ;
$ new = $ 3 ;
} else {
die "Can't parse $file_exceptions: $_" ;
}
if ( $ new =~ m/^\:c\:(data|func|macro|type)\:\`(.+)\`/ ) {
$ reftype = ":c:$1" ;
$ new = $ 2 ;
} elsif ( $ new =~ m/\:ref\:\`(.+)\`/ ) {
$ reftype = ":ref" ;
$ new = $ 1 ;
} else {
$ reftype = $ def_reftype { $ type } ;
}
$ new = "$reftype:`$old <$new>`" ;
if ( $ type eq "ioctl" ) {
$ ioctls { $ old } = $ new if ( exists ( $ ioctls { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ type eq "define" ) {
$ defines { $ old } = $ new if ( exists ( $ defines { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ type eq "symbol" ) {
$ enum_symbols { $ old } = $ new if ( exists ( $ enum_symbols { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ type eq "typedef" ) {
$ typedefs { $ old } = $ new if ( exists ( $ typedefs { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ type eq "enum" ) {
$ enums { $ old } = $ new if ( exists ( $ enums { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
2016-08-31 12:44:21 +03:00
if ( $ type eq "struct" ) {
$ structs { $ old } = $ new if ( exists ( $ structs { $ old } ) ) ;
2016-07-07 04:58:54 +03:00
next ;
}
die "Can't parse $file_exceptions: $_" ;
}
}
if ( $ debug ) {
print Data::Dumper - > Dump ( [ \ % ioctls ] , [ qw( *ioctls ) ] ) if ( % ioctls ) ;
print Data::Dumper - > Dump ( [ \ % typedefs ] , [ qw( *typedefs ) ] ) if ( % typedefs ) ;
print Data::Dumper - > Dump ( [ \ % enums ] , [ qw( *enums ) ] ) if ( % enums ) ;
print Data::Dumper - > Dump ( [ \ % structs ] , [ qw( *structs ) ] ) if ( % structs ) ;
print Data::Dumper - > Dump ( [ \ % defines ] , [ qw( *defines ) ] ) if ( % defines ) ;
print Data::Dumper - > Dump ( [ \ % enum_symbols ] , [ qw( *enum_symbols ) ] ) if ( % enum_symbols ) ;
}
#
# Align block
#
$ data = expand ( $ data ) ;
$ data = " " . $ data ;
$ data =~ s/\n/\n /g ;
$ data =~ s/\n\s+$/\n/g ;
$ data =~ s/\n\s+\n/\n\n/g ;
#
# Add escape codes for special characters
#
2016-08-16 19:25:41 +03:00
$ data =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / \ | \ % \ $ \ # \ { \ } \ ~ \ ^ ] ) , \ \ $ 1 , g ;
2016-07-07 04:58:54 +03:00
2016-07-07 12:31:21 +03:00
$ data =~ s , DEPRECATED , ** DEPRECATED ** , g ;
2016-07-07 04:58:54 +03:00
#
# Add references
#
2016-07-07 12:27:54 +03:00
my $ start_delim = "[ \n\t\(\=\*\@]" ;
my $ end_delim = "(\\s|,|\\\\=|\\\\:|\\;|\\\)|\\}|\\{)" ;
2016-07-07 04:58:54 +03:00
foreach my $ r ( keys % ioctls ) {
2016-08-31 12:44:21 +03:00
my $ s = $ ioctls { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/($start_delim)($r)$end_delim/$1$s$3/g ;
2016-07-07 04:58:54 +03:00
}
foreach my $ r ( keys % defines ) {
2016-08-31 12:44:21 +03:00
my $ s = $ defines { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/($start_delim)($r)$end_delim/$1$s$3/g ;
2016-07-07 04:58:54 +03:00
}
foreach my $ r ( keys % enum_symbols ) {
2016-08-31 12:44:21 +03:00
my $ s = $ enum_symbols { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/($start_delim)($r)$end_delim/$1$s$3/g ;
2016-07-07 04:58:54 +03:00
}
foreach my $ r ( keys % enums ) {
2016-08-31 12:44:21 +03:00
my $ s = $ enums { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/enum\s+($r)$end_delim/$s$2/g ;
2016-07-07 04:58:54 +03:00
}
foreach my $ r ( keys % structs ) {
2016-08-31 12:44:21 +03:00
my $ s = $ structs { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/struct\s+($r)$end_delim/$s$2/g ;
2016-07-07 04:58:54 +03:00
}
foreach my $ r ( keys % typedefs ) {
2016-08-31 12:44:21 +03:00
my $ s = $ typedefs { $ r } ;
2016-07-07 04:58:54 +03:00
$ r =~ s , ( [ \ _ \ ` \ * \ <\> \ & \ \ \ \ : \ / ] ) , \ \ \ \ $ 1 , g ;
print "$r -> $s\n" if ( $ debug ) ;
2016-07-07 12:27:54 +03:00
$ data =~ s/($start_delim)($r)$end_delim/$1$s$3/g ;
2016-07-07 04:58:54 +03:00
}
2016-08-31 12:44:21 +03:00
$ data =~ s/\\ ([\n\s])/\1/g ;
2016-07-09 15:35:34 +03:00
2016-07-07 04:58:54 +03:00
#
# Generate output file
#
my $ title = $ file_in ;
$ title =~ s , . * / , , ;
open OUT , "> $file_out" or die "Can't open $file_out" ;
print OUT ".. -*- coding: utf-8; mode: rst -*-\n\n" ;
print OUT "$title\n" ;
print OUT "=" x length ( $ title ) ;
print OUT "\n\n.. parsed-literal::\n\n" ;
print OUT $ data ;
close OUT ;
2016-11-17 13:32:34 +03:00
__END__
= head1 NAME
parse_headers . pl - parse a C file , in order to identify functions , structs ,
enums and defines and create cross - references to a Sphinx book .
= head1 SYNOPSIS
B <parse_headers.pl> [ <options> ] <C_FILE> <OUT_FILE> [ <EXCEPTIONS_FILE> ]
Where <options> can be: - - debug , - - help or - - man .
= head1 OPTIONS
= over 8
= item B <--debug>
Put the script in verbose mode , useful for debugging .
= item B <--help>
Prints a brief help message and exits .
= item B <--man>
Prints the manual page and exits .
= back
= head1 DESCRIPTION
Convert a C header or source file ( C_FILE ) , into a ReStructured Text
included via .. parsed - literal block with cross - references for the
documentation files that describe the API . It accepts an optional
EXCEPTIONS_FILE with describes what elements will be either ignored or
be pointed to a non - default reference .
The output is written at the ( OUT_FILE ) .
It is capable of identifying defines , functions , structs , typedefs ,
enums and enum symbols and create cross - references for all of them .
It is also capable of distinguish #define used for specifying a Linux
ioctl .
The EXCEPTIONS_FILE contain two types of statements: B <ignore> or B <replace> .
The syntax for the ignore tag is:
= over 8
ignore B <type> B <name>
= back
The B <ignore> means that it won ' t generate cross references for a
B <name> symbol of type B <type> .
The syntax for the replace tag is:
= over 8
replace B <type> B <name> B <new_value>
= back
The B <replace> means that it will generate cross references for a
B <name> symbol of type B <type> , but , instead of using the default
replacement rule , it will use B <new_value> .
For both statements , B <type> can be either one of the following:
= over 8
= item B <ioctl>
The ignore or replace statement will apply to ioctl definitions like:
#define VIDIOC_DBG_S_REGISTER _IOW('V', 79, struct v4l2_dbg_register)
= item B <define>
The ignore or replace statement will apply to any other #define found
at C_FILE .
= item B <typedef>
The ignore or replace statement will apply to typedef statements at C_FILE .
= item B <struct>
The ignore or replace statement will apply to the name of struct statements
at C_FILE .
= item B <enum>
The ignore or replace statement will apply to the name of enum statements
at C_FILE .
= item B <symbol>
The ignore or replace statement will apply to the name of enum statements
at C_FILE .
For replace statements , B <new_value> will automatically use : c:type:
references for B <typedef> , B <enum> and B <struct> types . It will use : ref :
for B <ioctl> , B <define> and B <symbol> types . The type of reference can
also be explicitly defined at the replace statement .
= back
= head1 EXAMPLES
ignore define _VIDEODEV2_H
= over 8
Ignore a #define _VIDEODEV2_H at the C_FILE.
= back
ignore symbol PRIVATE
= over 8
On a struct like:
enum foo { BAR1 , BAR2 , PRIVATE } ;
It won ' t generate cross - references for B <PRIVATE> .
= back
replace symbol BAR1 : c:type: `foo`
replace symbol BAR2 : c:type: `foo`
= over 8
On a struct like:
enum foo { BAR1 , BAR2 , PRIVATE } ;
It will make the BAR1 and BAR2 enum symbols to cross reference the foo
symbol at the C domain .
= back
= head1 BUGS
Report bugs to Mauro Carvalho Chehab <mchehab@s-opensource.com>
= head1 COPYRIGHT
Copyright ( c ) 2016 by Mauro Carvalho Chehab <mchehab@s-opensource.com> .
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