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