pod_generator.pl 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. #!/usr/bin/perl
  2. use utf8;
  3. use 5.016;
  4. use strict;
  5. use autodie;
  6. use warnings;
  7. use feature 'fc';
  8. use lib qw(.);
  9. use open IO => ':encoding(UTF-8)';
  10. use File::Find qw(find);
  11. use List::Util qw(first);
  12. use File::Basename qw(basename);
  13. use File::Spec::Functions qw(curdir splitdir catfile);
  14. my $dir = shift() // die "usage: $0 sidef/lib\n";
  15. my %esc = (
  16. '>' => 'gt',
  17. '<' => 'lt',
  18. );
  19. my %ignored_subs = map { $_ => 1 } qw<
  20. BEGIN
  21. ISA
  22. AUTOLOAD
  23. DESTROY
  24. >;
  25. my %ignored_methods = (
  26. 'Sidef' => [qw(new)],
  27. 'Sidef::Sys::Sys' => [qw(new)],
  28. 'Sidef::Math::Math' => [qw(new)],
  29. 'Sidef::Time::Date' => [qw(new)],
  30. 'Sidef::Types::Glob::DirHandle' => [qw(new)],
  31. 'Sidef::Types::Glob::FileHandle' => [qw(new)],
  32. 'Sidef::Types::Glob::Backtick' => [qw(new)],
  33. 'Sidef::Types::Glob::Stat' => [qw(new)],
  34. 'Sidef::Types::Block::For' => [qw(new)],
  35. 'Sidef::Types::Block::Try' => [qw(new)],
  36. 'Sidef::Types::Regex::Match' => [qw(new)],
  37. 'Sidef::Types::Regex::Regex' => [qw(new)],
  38. );
  39. my %singletons = map { $_ => 1 } qw(
  40. Sidef::Sys::Sys
  41. Sidef::Sys::Sig
  42. Sidef::Math::Math
  43. );
  44. my %ignored_modules = map { $_ => 1 } qw(
  45. Sidef
  46. Sidef::Parser
  47. Sidef::Optimizer
  48. Sidef::Deparse::Perl
  49. Sidef::Deparse::Sidef
  50. );
  51. my $name = basename($dir);
  52. if ($name ne 'lib') {
  53. die "error: '$dir' is not a lib directory!";
  54. }
  55. chdir $dir;
  56. find {
  57. no_chdir => 1,
  58. wanted => sub {
  59. /\.pm\z/ && -f && process_file($_);
  60. },
  61. } => curdir();
  62. sub parse_pod_file {
  63. my ($file) = @_;
  64. my %data;
  65. open my $fh, '<', $file;
  66. my $meth = 0;
  67. while (defined(my $line = <$fh>)) {
  68. if ($meth) {
  69. my $sec = '';
  70. $sec .= $line;
  71. until ($line =~ /^=cut\b/ or eof($fh)) {
  72. $sec .= ($line = <$fh>);
  73. }
  74. if ($sec =~ /^=head2\h+(.*\S)/m) {
  75. $data{$1} = $sec;
  76. }
  77. }
  78. else {
  79. $data{__HEADER__} .= $line;
  80. }
  81. if ($meth == 0 && $line =~ /^=head1\h+METHODS/) {
  82. $meth = 1;
  83. }
  84. }
  85. close $fh;
  86. return \%data;
  87. }
  88. sub parse_pm_file {
  89. my ($file) = @_;
  90. my %data;
  91. open my $fh, '<', $file;
  92. while (defined(my $line = <$fh>)) {
  93. if ($line =~ /^\s*sub\s+(\w+)\s*\{/) {
  94. my $name = $1;
  95. next if ($name eq 'new');
  96. for (1 .. 2) {
  97. my $sig_line = scalar <$fh>;
  98. if ($sig_line =~ m{^\s*my\s*\((.*?)\)\s*=\s*\@_}) {
  99. my $sig = $1;
  100. $sig =~ s{\$}{}g;
  101. $sig =~ s{\@}{*}g;
  102. $sig =~ s{\%}{:}g;
  103. my @params = split(/\s*,\s*/, $sig);
  104. $data{$name} = \@params;
  105. }
  106. }
  107. }
  108. }
  109. close $fh;
  110. return \%data;
  111. }
  112. sub transform_method_names {
  113. map { [$_->[0], ($_->[1] =~ /[a-z]/) ? ('B_' . $_->[1]) : ('A_' . $_->[1])] } @_;
  114. }
  115. sub sort_methods_by_length {
  116. #<<<
  117. map { $_->[0] }
  118. sort {
  119. (length($a->[1] =~ tr/_//dr) <=> length($b->[1] =~ tr/_//dr))
  120. || (fc($a->[1]) cmp fc($b->[1]))
  121. || ($b->[1] cmp $a->[1])
  122. } transform_method_names(@_);
  123. #>>>
  124. }
  125. sub sort_methods_by_name {
  126. #<<<
  127. map { $_->[0] }
  128. sort {
  129. (fc($a->[1] =~ tr/_//dr) cmp fc($b->[1] =~ tr/_//dr))
  130. || (fc($a->[1]) cmp fc($b->[1]))
  131. || ($a->[1] cmp $b->[1])
  132. } transform_method_names(@_);
  133. #>>>
  134. }
  135. sub process_file {
  136. my ($file) = @_;
  137. my (undef, @parts) = splitdir($file);
  138. require join('/', @parts);
  139. $parts[-1] =~ s{\.pm\z}{};
  140. my $module = join('::', @parts);
  141. exists($ignored_modules{$module})
  142. && return;
  143. my $mod_methods = do {
  144. no strict 'refs';
  145. \%{$module . '::'};
  146. };
  147. my %subs;
  148. foreach my $sub (keys %{$mod_methods}) {
  149. next if $sub eq 'get_value';
  150. next if $sub =~ /^[(_]/;
  151. next if $sub =~ /^[[:upper:]]./;
  152. next if exists $ignored_subs{$sub};
  153. my $code;
  154. if (defined &{$module . '::' . $sub}) {
  155. $code = \&{$module . '::' . $sub};
  156. }
  157. else {
  158. next;
  159. }
  160. if (exists $ignored_methods{$module}) {
  161. if (first { $_ eq $sub } @{$ignored_methods{$module}}) {
  162. next;
  163. }
  164. }
  165. push @{$subs{$code}{aliases}}, $sub;
  166. }
  167. my $signatures = parse_pm_file(join('/', @parts) . '.pm');
  168. while (my ($key, $value) = each %subs) {
  169. my @sorted = sort_methods_by_length(map { [$_, $_] } @{$value->{aliases}});
  170. my $sig_key = first { exists($signatures->{$_}) } @sorted;
  171. $value->{name} = shift @sorted;
  172. @{$value->{aliases}} = @sorted;
  173. my $sub = $value->{name};
  174. my $orig_name = $sub;
  175. my $is_method = lc($sub) ne uc($sub);
  176. #$sub =~ s{([<>])}{E<$esc{$1}>}g;
  177. #my $sig = "$parts[-1].$sub()";
  178. my $sig = "self.$sub";
  179. if (exists $singletons{$module}) {
  180. $sig = "$parts[-1].$sub";
  181. }
  182. if (defined($sig_key)) {
  183. my @params = @{$signatures->{$sig_key}};
  184. my $self = shift(@params);
  185. if (exists($singletons{$module}) or $self eq 'undef') {
  186. $self = $parts[-1];
  187. }
  188. $sig = $self . '.' . $orig_name;
  189. if (@params) {
  190. $sig .= '(' . join(', ', @params) . ')';
  191. }
  192. }
  193. my $doc = $is_method ? <<"__POD__" : <<"__POD2__";
  194. \=head2 $orig_name
  195. $sig
  196. Returns the
  197. __POD__
  198. \=head2 $orig_name
  199. a $sub b
  200. Returns the
  201. __POD2__
  202. if (@{$value->{aliases}}) {
  203. $doc .= "\nAliases: " . join(
  204. ", ",
  205. map {
  206. my $sub = $_;
  207. $sub =~ s{([<>])}{E<$esc{$1}>}g;
  208. "I<$sub>";
  209. } @{$value->{aliases}}
  210. )
  211. . "\n";
  212. }
  213. $doc .= "\n=cut\n";
  214. $subs{$key}{doc} //= $doc;
  215. }
  216. my @keys = keys %subs;
  217. if ($#keys == -1) {
  218. warn "[!] No method found for module: $module\n";
  219. return;
  220. }
  221. my $pod_file = catfile(@parts) . '.pod';
  222. say "** Writing: $pod_file";
  223. my $pod_data = {};
  224. (-e $pod_file) && do {
  225. $pod_data = parse_pod_file($pod_file);
  226. };
  227. while (my ($key, $value) = each %subs) {
  228. my $alias;
  229. if (exists $value->{aliases}) {
  230. $alias = first { exists($pod_data->{$_}) } @{$value->{aliases}};
  231. }
  232. if ($alias // exists($pod_data->{$value->{name}})) {
  233. my $doc = $pod_data->{$alias // $value->{name}};
  234. if (not $doc =~ /^Returns? the$/m) {
  235. $subs{$key}{doc} = $doc;
  236. }
  237. }
  238. }
  239. open my $fh, '>', $pod_file;
  240. my $header = $pod_data->{__HEADER__};
  241. #if (not defined($header) or $header =~ /^This class implements \.\.\.$/m) {
  242. if (not defined($header)) {
  243. $header = <<"HEADER";
  244. \=encoding utf8
  245. \=head1 NAME
  246. $module
  247. \=head1 DESCRIPTION
  248. This class implements ...
  249. \=head1 SYNOPSIS
  250. var obj = $parts[-1]\(...)
  251. HEADER
  252. my @isa = @{exists($mod_methods->{ISA}) ? $mod_methods->{ISA} : []};
  253. if (@isa) {
  254. $header .= <<"HEADER";
  255. \=head1 INHERITS
  256. Inherits methods from:
  257. HEADER
  258. $header .= join("\n", map { (" " x 7) . "* $_" } @isa);
  259. $header .= "\n\n";
  260. }
  261. $header .= <<"HEADER";
  262. \=head1 METHODS
  263. HEADER
  264. }
  265. # Print the header
  266. print {$fh} $header;
  267. # Print the methods
  268. foreach my $method (sort_methods_by_name(map { [$_, $_->{name}] } values %subs)) {
  269. print {$fh} $method->{doc};
  270. }
  271. }