]>
Commit | Line | Data |
---|---|---|
bbc249f2 MCC |
1 | #!/usr/bin/perl |
2 | ||
3 | use strict; | |
4 | use Pod::Usage; | |
5 | use Getopt::Long; | |
6 | use File::Find; | |
7 | use Fcntl ':mode'; | |
8 | ||
9 | my $help; | |
10 | my $man; | |
11 | my $debug; | |
33e3e991 | 12 | my $prefix="Documentation/ABI"; |
bbc249f2 MCC |
13 | |
14 | GetOptions( | |
15 | "debug|d+" => \$debug, | |
33e3e991 | 16 | "dir=s" => \$prefix, |
bbc249f2 MCC |
17 | 'help|?' => \$help, |
18 | man => \$man | |
19 | ) or pod2usage(2); | |
20 | ||
21 | pod2usage(1) if $help; | |
22 | pod2usage(-exitstatus => 0, -verbose => 2) if $man; | |
23 | ||
33e3e991 | 24 | pod2usage(2) if (scalar @ARGV < 1 || @ARGV > 2); |
bbc249f2 | 25 | |
33e3e991 MCC |
26 | my ($cmd, $arg) = @ARGV; |
27 | ||
28 | pod2usage(2) if ($cmd ne "search" && $cmd ne "rest"); | |
29 | pod2usage(2) if ($cmd eq "search" && !$arg); | |
bbc249f2 MCC |
30 | |
31 | require Data::Dumper if ($debug); | |
32 | ||
33 | my %data; | |
34 | ||
35 | # | |
36 | # Displays an error message, printing file name and line | |
37 | # | |
38 | sub parse_error($$$$) { | |
39 | my ($file, $ln, $msg, $data) = @_; | |
40 | ||
41 | print STDERR "file $file#$ln: $msg at\n\t$data"; | |
42 | } | |
43 | ||
44 | # | |
45 | # Parse an ABI file, storing its contents at %data | |
46 | # | |
47 | sub parse_abi { | |
48 | my $file = $File::Find::name; | |
49 | ||
50 | my $mode = (stat($file))[2]; | |
51 | return if ($mode & S_IFDIR); | |
52 | return if ($file =~ m,/README,); | |
53 | ||
54 | my $name = $file; | |
55 | $name =~ s,.*/,,; | |
56 | ||
d0ebaf51 MCC |
57 | my $nametag = "File $name"; |
58 | $data{$nametag}->{what} = "File $name"; | |
59 | $data{$nametag}->{type} = "File"; | |
60 | $data{$nametag}->{file} = $name; | |
33e3e991 | 61 | $data{$nametag}->{filepath} = $file; |
d0ebaf51 MCC |
62 | $data{$nametag}->{is_file} = 1; |
63 | ||
bbc249f2 MCC |
64 | my $type = $file; |
65 | $type =~ s,.*/(.*)/.*,$1,; | |
66 | ||
67 | my $what; | |
68 | my $new_what; | |
69 | my $tag; | |
70 | my $ln; | |
6619c661 | 71 | my $xrefs; |
4e6a6234 | 72 | my $space; |
d0ebaf51 MCC |
73 | my @labels; |
74 | my $label; | |
bbc249f2 MCC |
75 | |
76 | print STDERR "Opening $file\n" if ($debug > 1); | |
77 | open IN, $file; | |
78 | while(<IN>) { | |
79 | $ln++; | |
4e6a6234 | 80 | if (m/^(\S+)(:\s*)(.*)/i) { |
bbc249f2 | 81 | my $new_tag = lc($1); |
4e6a6234 MCC |
82 | my $sep = $2; |
83 | my $content = $3; | |
bbc249f2 MCC |
84 | |
85 | if (!($new_tag =~ m/(what|date|kernelversion|contact|description|users)/)) { | |
86 | if ($tag eq "description") { | |
4e6a6234 MCC |
87 | # New "tag" is actually part of |
88 | # description. Don't consider it a tag | |
89 | $new_tag = ""; | |
bbc249f2 MCC |
90 | } else { |
91 | parse_error($file, $ln, "tag '$tag' is invalid", $_); | |
92 | } | |
93 | } | |
94 | ||
95 | if ($new_tag =~ m/what/) { | |
4e6a6234 | 96 | $space = ""; |
bbc249f2 MCC |
97 | if ($tag =~ m/what/) { |
98 | $what .= ", " . $content; | |
99 | } else { | |
4e6a6234 MCC |
100 | parse_error($file, $ln, "What '$what' doesn't have a description", "") if ($what && !$data{$what}->{description}); |
101 | ||
bbc249f2 | 102 | $what = $content; |
d0ebaf51 | 103 | $label = $content; |
bbc249f2 MCC |
104 | $new_what = 1; |
105 | } | |
d0ebaf51 | 106 | push @labels, [($content, $label)]; |
bbc249f2 | 107 | $tag = $new_tag; |
6619c661 | 108 | |
d0ebaf51 | 109 | push @{$data{$nametag}->{xrefs}}, [($content, $label)] if ($data{$nametag}->{what}); |
bbc249f2 MCC |
110 | next; |
111 | } | |
112 | ||
4e6a6234 MCC |
113 | if ($new_tag) { |
114 | $tag = $new_tag; | |
bbc249f2 | 115 | |
4e6a6234 | 116 | if ($new_what) { |
d0ebaf51 MCC |
117 | @{$data{$what}->{label}} = @labels if ($data{$nametag}->{what}); |
118 | @labels = (); | |
119 | $label = ""; | |
4e6a6234 | 120 | $new_what = 0; |
bbc249f2 | 121 | |
4e6a6234 MCC |
122 | $data{$what}->{type} = $type; |
123 | $data{$what}->{file} = $name; | |
33e3e991 | 124 | $data{$what}->{filepath} = $file; |
4e6a6234 MCC |
125 | print STDERR "\twhat: $what\n" if ($debug > 1); |
126 | } | |
bbc249f2 | 127 | |
4e6a6234 MCC |
128 | if (!$what) { |
129 | parse_error($file, $ln, "'What:' should come first:", $_); | |
130 | next; | |
131 | } | |
132 | if ($tag eq "description") { | |
133 | next if ($content =~ m/^\s*$/); | |
134 | if ($content =~ m/^(\s*)(.*)/) { | |
135 | my $new_content = $2; | |
136 | $space = $new_tag . $sep . $1; | |
137 | while ($space =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} | |
138 | $space =~ s/./ /g; | |
139 | $data{$what}->{$tag} .= "$new_content\n"; | |
140 | } | |
141 | } else { | |
142 | $data{$what}->{$tag} = $content; | |
143 | } | |
bbc249f2 MCC |
144 | next; |
145 | } | |
bbc249f2 MCC |
146 | } |
147 | ||
4e6a6234 | 148 | # Store any contents before tags at the database |
d0ebaf51 MCC |
149 | if (!$tag && $data{$nametag}->{what}) { |
150 | $data{$nametag}->{description} .= $_; | |
6619c661 MCC |
151 | next; |
152 | } | |
bbc249f2 | 153 | |
4e6a6234 MCC |
154 | if ($tag eq "description") { |
155 | if (!$data{$what}->{description}) { | |
156 | next if (m/^\s*\n/); | |
157 | if (m/^(\s*)(.*)/) { | |
158 | $space = $1; | |
159 | while ($space =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} | |
160 | $data{$what}->{$tag} .= "$2\n"; | |
161 | } | |
162 | } else { | |
163 | my $content = $_; | |
164 | if (m/^\s*\n/) { | |
165 | $data{$what}->{$tag} .= $content; | |
166 | next; | |
167 | } | |
168 | ||
169 | while ($content =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e) {} | |
170 | $space = "" if (!($content =~ s/^($space)//)); | |
171 | ||
172 | # Compress spaces with tabs | |
173 | $content =~ s<^ {8}> <\t>; | |
174 | $content =~ s<^ {1,7}\t> <\t>; | |
175 | $content =~ s< {1,7}\t> <\t>; | |
176 | $data{$what}->{$tag} .= $content; | |
177 | } | |
178 | next; | |
179 | } | |
bbc249f2 MCC |
180 | if (m/^\s*(.*)/) { |
181 | $data{$what}->{$tag} .= "\n$1"; | |
182 | $data{$what}->{$tag} =~ s/\n+$//; | |
183 | next; | |
184 | } | |
185 | ||
186 | # Everything else is error | |
187 | parse_error($file, $ln, "Unexpected line:", $_); | |
188 | } | |
d0ebaf51 | 189 | $data{$nametag}->{description} =~ s/^\n+//; |
bbc249f2 MCC |
190 | close IN; |
191 | } | |
192 | ||
33e3e991 MCC |
193 | # |
194 | # Outputs the book on ReST format | |
195 | # | |
45f96517 | 196 | |
bbc249f2 | 197 | sub output_rest { |
45f96517 MCC |
198 | foreach my $what (sort { |
199 | ($data{$a}->{type} eq "File") cmp ($data{$b}->{type} eq "File") || | |
200 | $a cmp $b | |
201 | } keys %data) { | |
bbc249f2 MCC |
202 | my $type = $data{$what}->{type}; |
203 | my $file = $data{$what}->{file}; | |
45f96517 | 204 | my $filepath = $data{$what}->{filepath}; |
bbc249f2 MCC |
205 | |
206 | my $w = $what; | |
207 | $w =~ s/([\(\)\_\-\*\=\^\~\\])/\\$1/g; | |
208 | ||
4e6a6234 | 209 | |
d0ebaf51 MCC |
210 | foreach my $p (@{$data{$what}->{label}}) { |
211 | my ($content, $label) = @{$p}; | |
212 | $label = "abi_" . $label . " "; | |
213 | $label =~ tr/A-Z/a-z/; | |
214 | ||
215 | # Convert special chars to "_" | |
216 | $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g; | |
217 | $label =~ s,_+,_,g; | |
218 | $label =~ s,_$,,; | |
219 | ||
220 | $data{$what}->{label} .= $label; | |
221 | ||
222 | printf ".. _%s:\n\n", $label; | |
223 | ||
224 | # only one label is enough | |
225 | last; | |
6619c661 MCC |
226 | } |
227 | ||
6619c661 | 228 | |
45f96517 MCC |
229 | $filepath =~ s,.*/(.*/.*),\1,;; |
230 | $filepath =~ s,[/\-],_,g;; | |
231 | my $fileref = "abi_file_".$filepath; | |
232 | ||
233 | if ($type eq "File") { | |
234 | my $bar = $w; | |
235 | $bar =~ s/./-/g; | |
236 | ||
237 | print ".. _$fileref:\n\n"; | |
238 | print "$w\n$bar\n\n"; | |
239 | } else { | |
240 | my @names = split /\s*,\s*/,$w; | |
241 | ||
242 | my $len = 0; | |
243 | ||
244 | foreach my $name (@names) { | |
245 | $len = length($name) if (length($name) > $len); | |
246 | } | |
247 | ||
248 | print "What:\n\n"; | |
249 | ||
250 | print "+-" . "-" x $len . "-+\n"; | |
251 | foreach my $name (@names) { | |
252 | printf "| %s", $name . " " x ($len - length($name)) . " |\n"; | |
253 | print "+-" . "-" x $len . "-+\n"; | |
254 | } | |
255 | print "\n"; | |
256 | } | |
257 | ||
258 | print "Defined on file :ref:`$file <$fileref>`\n\n" if ($type ne "File"); | |
bbc249f2 MCC |
259 | |
260 | my $desc = $data{$what}->{description}; | |
261 | $desc =~ s/^\s+//; | |
262 | ||
263 | # Remove title markups from the description, as they won't work | |
264 | $desc =~ s/\n[\-\*\=\^\~]+\n/\n/g; | |
265 | ||
4e6a6234 MCC |
266 | if (!($desc =~ /^\s*$/)) { |
267 | if ($desc =~ m/\:\n/ || $desc =~ m/\n[\t ]+/ || $desc =~ m/[\x00-\x08\x0b-\x1f\x7b-\xff]/) { | |
268 | # put everything inside a code block | |
269 | $desc =~ s/\n/\n /g; | |
bbc249f2 | 270 | |
4e6a6234 MCC |
271 | print "::\n\n"; |
272 | print " $desc\n\n"; | |
273 | } else { | |
274 | # Escape any special chars from description | |
275 | $desc =~s/([\x00-\x08\x0b-\x1f\x21-\x2a\x2d\x2f\x3c-\x40\x5c\x5e-\x60\x7b-\xff])/\\$1/g; | |
bbc249f2 | 276 | |
4e6a6234 MCC |
277 | print "$desc\n\n"; |
278 | } | |
bbc249f2 | 279 | } else { |
d0ebaf51 | 280 | print "DESCRIPTION MISSING for $what\n\n" if (!$data{$what}->{is_file}); |
bbc249f2 | 281 | } |
6619c661 | 282 | |
d0ebaf51 MCC |
283 | if ($data{$what}->{xrefs}) { |
284 | printf "Has the following ABI:\n\n"; | |
285 | ||
286 | foreach my $p(@{$data{$what}->{xrefs}}) { | |
287 | my ($content, $label) = @{$p}; | |
288 | $label = "abi_" . $label . " "; | |
289 | $label =~ tr/A-Z/a-z/; | |
290 | ||
291 | # Convert special chars to "_" | |
292 | $label =~s/([\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xff])/_/g; | |
293 | $label =~ s,_+,_,g; | |
294 | $label =~ s,_$,,; | |
295 | ||
296 | # Escape special chars from content | |
297 | $content =~s/([\x00-\x1f\x21-\x2f\x3a-\x40\x7b-\xff])/\\$1/g; | |
298 | ||
299 | print "- :ref:`$content <$label>`\n\n"; | |
300 | } | |
301 | } | |
bbc249f2 MCC |
302 | } |
303 | } | |
304 | ||
33e3e991 MCC |
305 | # |
306 | # Searches for ABI symbols | |
307 | # | |
308 | sub search_symbols { | |
309 | foreach my $what (sort keys %data) { | |
310 | next if (!($what =~ m/($arg)/)); | |
311 | ||
312 | my $type = $data{$what}->{type}; | |
313 | next if ($type eq "File"); | |
314 | ||
315 | my $file = $data{$what}->{filepath}; | |
316 | ||
317 | my $bar = $what; | |
318 | $bar =~ s/./-/g; | |
319 | ||
320 | print "\n$what\n$bar\n\n"; | |
321 | ||
322 | my $kernelversion = $data{$what}->{kernelversion}; | |
323 | my $contact = $data{$what}->{contact}; | |
324 | my $users = $data{$what}->{users}; | |
325 | my $date = $data{$what}->{date}; | |
326 | my $desc = $data{$what}->{description}; | |
327 | $kernelversion =~ s/^\s+//; | |
328 | $contact =~ s/^\s+//; | |
329 | $users =~ s/^\s+//; | |
330 | $users =~ s/\n//g; | |
331 | $date =~ s/^\s+//; | |
332 | $desc =~ s/^\s+//; | |
333 | ||
334 | printf "Kernel version:\t\t%s\n", $kernelversion if ($kernelversion); | |
335 | printf "Date:\t\t\t%s\n", $date if ($date); | |
336 | printf "Contact:\t\t%s\n", $contact if ($contact); | |
337 | printf "Users:\t\t\t%s\n", $users if ($users); | |
338 | print "Defined on file:\t$file\n\n"; | |
339 | print "Description:\n\n$desc"; | |
340 | } | |
341 | } | |
342 | ||
343 | ||
bbc249f2 MCC |
344 | # |
345 | # Parses all ABI files located at $prefix dir | |
346 | # | |
347 | find({wanted =>\&parse_abi, no_chdir => 1}, $prefix); | |
348 | ||
349 | print STDERR Data::Dumper->Dump([\%data], [qw(*data)]) if ($debug); | |
350 | ||
351 | # | |
33e3e991 | 352 | # Handles the command |
bbc249f2 | 353 | # |
33e3e991 MCC |
354 | if ($cmd eq "rest") { |
355 | output_rest; | |
356 | } else { | |
357 | search_symbols; | |
358 | } | |
bbc249f2 MCC |
359 | |
360 | ||
361 | __END__ | |
362 | ||
363 | =head1 NAME | |
364 | ||
365 | abi_book.pl - parse the Linux ABI files and produce a ReST book. | |
366 | ||
367 | =head1 SYNOPSIS | |
368 | ||
33e3e991 MCC |
369 | B<abi_book.pl> [--debug] <COMAND> [<ARGUMENT>] |
370 | ||
371 | Where <COMMAND> can be: | |
372 | ||
373 | =over 8 | |
374 | ||
375 | B<search> [SEARCH_REGEX] - search for [SEARCH_REGEX] inside ABI | |
376 | ||
377 | B<rest> - output the ABI in ReST markup language | |
378 | ||
379 | =back | |
bbc249f2 MCC |
380 | |
381 | =head1 OPTIONS | |
382 | ||
383 | =over 8 | |
384 | ||
33e3e991 MCC |
385 | =item B<--dir> |
386 | ||
387 | Changes the location of the ABI search. By default, it uses | |
388 | the Documentation/ABI directory. | |
389 | ||
bbc249f2 MCC |
390 | =item B<--debug> |
391 | ||
392 | Put the script in verbose mode, useful for debugging. Can be called multiple | |
393 | times, to increase verbosity. | |
394 | ||
395 | =item B<--help> | |
396 | ||
397 | Prints a brief help message and exits. | |
398 | ||
399 | =item B<--man> | |
400 | ||
401 | Prints the manual page and exits. | |
402 | ||
403 | =back | |
404 | ||
405 | =head1 DESCRIPTION | |
406 | ||
33e3e991 MCC |
407 | Parse the Linux ABI files from ABI DIR (usually located at Documentation/ABI), |
408 | allowing to search for ABI symbols or to produce a ReST book containing | |
409 | the Linux ABI documentation. | |
410 | ||
411 | =head1 EXAMPLES | |
412 | ||
413 | Search for all stable symbols with the word "usb": | |
414 | ||
415 | =over 8 | |
416 | ||
417 | $ scripts/get_abi.pl search usb --dir Documentation/ABI/stable | |
418 | ||
419 | =back | |
420 | ||
421 | Search for all symbols that match the regex expression "usb.*cap": | |
422 | ||
423 | =over 8 | |
424 | ||
425 | $ scripts/get_abi.pl search usb.*cap | |
426 | ||
427 | =back | |
428 | ||
429 | Output all obsoleted symbols in ReST format | |
430 | ||
431 | =over 8 | |
432 | ||
433 | $ scripts/get_abi.pl rest --dir Documentation/ABI/obsolete | |
434 | ||
435 | =back | |
bbc249f2 MCC |
436 | |
437 | =head1 BUGS | |
438 | ||
439 | Report bugs to Mauro Carvalho Chehab <mchehab@s-opensource.com> | |
440 | ||
441 | =head1 COPYRIGHT | |
442 | ||
33e3e991 | 443 | Copyright (c) 2016-2017 by Mauro Carvalho Chehab <mchehab@s-opensource.com>. |
bbc249f2 MCC |
444 | |
445 | License GPLv2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>. | |
446 | ||
447 | This is free software: you are free to change and redistribute it. | |
448 | There is NO WARRANTY, to the extent permitted by law. | |
449 | ||
450 | =cut |