|
| 1 | +unit class DocSite::Generator; |
| 2 | + |
| 3 | +use lib 'lib'; |
| 4 | + |
| 5 | +use DocSite::Document::Registry; |
| 6 | +use DocSite::Pod::To::HTML; |
| 7 | +use DocSite::TypeGraph::Viz; |
| 8 | +use DocSite::TypeGraph; |
| 9 | +use Pod::Convenience; |
| 10 | +use Pod::Htmlify; |
| 11 | +use Term::ProgressBar; |
| 12 | +use URI::Escape; |
| 13 | + |
| 14 | +has Bool $!overwrite-typegraph; |
| 15 | +has Bool $!disambiguation; |
| 16 | +has Bool $!search-file; |
| 17 | +has Bool $!highlight; |
| 18 | +has Bool $!inline-python; |
| 19 | +has Bool $!verbose; |
| 20 | +has Int $!sparse; |
| 21 | +has Int $!threads; |
| 22 | +has IO::Path $!root; |
| 23 | + |
| 24 | +has DocSite::Document::Registry $!registry = DocSite::Document::Registry.new; |
| 25 | +has DocSite::TypeGraph $!type-graph; |
| 26 | + |
| 27 | +my @viz-formats = ( |
| 28 | + %( :format<svg> ), |
| 29 | + %( :format<png>, :size<8,3> ), |
| 30 | +); |
| 31 | + |
| 32 | +method BUILD ( |
| 33 | + Bool :$!overwrite-typegraph, |
| 34 | + Bool :$!disambiguation, |
| 35 | + Bool :$!search-file, |
| 36 | + Bool :$!highlight, |
| 37 | + Bool :$!inline-python, |
| 38 | + Bool :$!verbose, |
| 39 | + Int :$!sparse, |
| 40 | + Int :$!threads, |
| 41 | + IO::Path :$!root, |
| 42 | +) { } |
| 43 | + |
| 44 | +method run { |
| 45 | + self!maybe-write-type-graph-images; |
| 46 | + self!process-language-pod; |
| 47 | + self!process-type-pod; |
| 48 | +} |
| 49 | + |
| 50 | +method !maybe-write-type-graph-images { |
| 51 | + my $image-dir = IO::Path.new( $*SPEC.catdir( $!root, 'html', 'images' ) ); |
| 52 | + my $any-svg = $*SPEC.catfile( $image-dir, 'type-graph-Any.svg' ).IO; |
| 53 | + if $any-svg ~~ :e && !$!overwrite-typegraph { |
| 54 | + self!maybe-say( qq:to/END/ ); |
| 55 | + Not writing type graph images, it seems to be up-to-date. To forcibly |
| 56 | + overwrite the type graph images, supply the --overwrite-typegraph |
| 57 | + option at the command line, or delete the file |
| 58 | + $any-svg |
| 59 | + END |
| 60 | + return; |
| 61 | + } |
| 62 | + |
| 63 | + my $tg-file = 'type-graph.txt'; |
| 64 | + self!maybe-say: "Reading type graph from $tg-file ..."; |
| 65 | + $!type-graph = DocSite::TypeGraph.new-from-file($tg-file); |
| 66 | + self!write-type-graph-images($image-dir); |
| 67 | + self!write-specialized-type-graph-images($image-dir); |
| 68 | +} |
| 69 | + |
| 70 | +method !write-type-graph-images (IO::Path $image-dir) { |
| 71 | + self!maybe-say: "Writing type graph images to $image-dir {$!threads > 1 ?? qq{with $!threads threads } !! q{}}..."; |
| 72 | + self!run-with-progress( |
| 73 | + $!type-graph.sorted.cache, |
| 74 | + sub ($type) { self!write-one-type( $type, $image-dir ) }, |
| 75 | + ); |
| 76 | +} |
| 77 | + |
| 78 | +method !write-one-type (DocSite::Type $type, IO::Path $image-dir) { |
| 79 | + my $viz = DocSite::TypeGraph::Viz.new-for-type($type); |
| 80 | + for @viz-formats -> $args { |
| 81 | + my $file = $*SPEC.catfile( $image-dir, "type-graph-{$type}.{$args<format>}" ); |
| 82 | + $viz.to-file( $file, |$args ); |
| 83 | + } |
| 84 | +} |
| 85 | + |
| 86 | +method !write-specialized-type-graph-images (IO::Path $image-dir) { |
| 87 | + self!maybe-say: "Writing specialized visualizations to $image-dir ..."; |
| 88 | + my %by-group = $!type-graph.sorted.classify(&viz-group); |
| 89 | + %by-group<Exception>.append: $!type-graph.types< Exception Any Mu >; |
| 90 | + %by-group<Metamodel>.append: $!type-graph.types< Any Mu >; |
| 91 | + |
| 92 | + self!run-with-progress( |
| 93 | + %by-group.pairs.cache, |
| 94 | + sub (Pair $pair) { self!write-one-type-group( $pair.key, $pair.value, $image-dir ) }, |
| 95 | + ); |
| 96 | +} |
| 97 | + |
| 98 | +method !write-one-type-group (Str $group, Array $types, IO::Path $image-dir) { |
| 99 | + my $viz = DocSite::TypeGraph::Viz.new( |
| 100 | + :types($types), |
| 101 | + :dot-hints( viz-hints($group) ), |
| 102 | + :rank-dir<LR>, |
| 103 | + ); |
| 104 | + for @viz-formats -> $args { |
| 105 | + my $file = $*SPEC.catfile( $image-dir, "type-graph-{$group}.{$args<format>}" ); |
| 106 | + $viz.to-file($file, |$args); |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +sub viz-group ($type) { |
| 111 | + return 'Metamodel' if $type.name ~~ /^ 'Perl6::Metamodel' /; |
| 112 | + return 'Exception' if $type.name ~~ /^ 'X::' /; |
| 113 | + return 'Any'; |
| 114 | +} |
| 115 | + |
| 116 | +sub viz-hints ($group) { |
| 117 | + return q{} unless $group eq 'Any'; |
| 118 | + |
| 119 | + return Q:to/END/; |
| 120 | + subgraph "cluster: Mu children" { |
| 121 | + rank=same; |
| 122 | + style=invis; |
| 123 | + "Any"; |
| 124 | + "Junction"; |
| 125 | + } |
| 126 | + subgraph "cluster: Pod:: top level" { |
| 127 | + rank=same; |
| 128 | + style=invis; |
| 129 | + "Pod::Config"; |
| 130 | + "Pod::Block"; |
| 131 | + } |
| 132 | + subgraph "cluster: Date/time handling" { |
| 133 | + rank=same; |
| 134 | + style=invis; |
| 135 | + "Date"; |
| 136 | + "DateTime"; |
| 137 | + "DateTime-local-timezone"; |
| 138 | + } |
| 139 | + subgraph "cluster: Collection roles" { |
| 140 | + rank=same; |
| 141 | + style=invis; |
| 142 | + "Positional"; |
| 143 | + "Associative"; |
| 144 | + "Baggy"; |
| 145 | + } |
| 146 | + END |
| 147 | +} |
| 148 | + |
| 149 | +method !process-language-pod { |
| 150 | + my $kind = 'Language'; |
| 151 | + my @files = self!find-pod-files-in($kind); |
| 152 | + if $!sparse { |
| 153 | + @files = @files[^(@files / $!sparse).ceiling]; |
| 154 | + } |
| 155 | + |
| 156 | + self!maybe-say("Reading and process $kind pod files ..."); |
| 157 | + self!run-with-progress( |
| 158 | + @files, |
| 159 | + sub ($file) { |
| 160 | + self!process-one-pod( $file, $kind ); |
| 161 | + } |
| 162 | + ) |
| 163 | +} |
| 164 | + |
| 165 | +method !process-type-pod { |
| 166 | +} |
| 167 | + |
| 168 | +method !find-pod-files-in (Str $dir) { |
| 169 | + self!maybe-say: "Finding pod sources in $dir ..."; |
| 170 | + return gather { |
| 171 | + for self!recursive-files-in($dir) -> $file { |
| 172 | + take $file if $file.path ~~ / '.pod' $/; |
| 173 | + } |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +method !recursive-files-in($dir) { |
| 178 | + my @todo = $*SPEC.catdir( $!root, 'doc', $dir ); |
| 179 | + return gather { |
| 180 | + while @todo { |
| 181 | + my $d = @todo.shift; |
| 182 | + for dir($d) -> $f { |
| 183 | + if $f.f { |
| 184 | + self!maybe-say: " ... found $f"; |
| 185 | + take $f; |
| 186 | + } |
| 187 | + else { |
| 188 | + self!maybe-say: " ... descending into $f"; |
| 189 | + @todo.append( $f.path ); |
| 190 | + } |
| 191 | + } |
| 192 | + } |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +method !process-one-pod (IO::Path $file, Str $kind) { |
| 197 | + my $pod = EVAL( $file.slurp ~ "\n\$=pod[0]" ); |
| 198 | + my $pth = DocSite::Pod::To::HTML.new; |
| 199 | + my $html = $pth.pod-to-html($pod); |
| 200 | + |
| 201 | + self!spurt-html-file( $file, $kind, $html); |
| 202 | +} |
| 203 | + |
| 204 | +method !spurt-html-file (IO::Path $file, Str $kind, Str $html) { |
| 205 | + my $dir = IO::Path.new( $*SPEC.catfile( $!root, 'html', $kind.lc ) ); |
| 206 | + unless $dir ~~ :e { |
| 207 | +# $dir.mkdir(0o755); |
| 208 | + } |
| 209 | + |
| 210 | + IO::Path.new( $*SPEC.catfile( $dir, $file.basename.subst( / '.pod' $ /, '.html' ) ) ) |
| 211 | + .spurt($html); |
| 212 | +} |
| 213 | + |
| 214 | +method !run-with-progress ($items, Routine $sub, Str $msg = q{ done}) { |
| 215 | + my $prog = Term::ProgressBar.new( :count( $items.elems ) ) |
| 216 | + if $!verbose; |
| 217 | + |
| 218 | + my $supply = $items.Supply; |
| 219 | + |
| 220 | + if $!threads > 1 { |
| 221 | + my $sched = ThreadPoolScheduler |
| 222 | + .new( :max_threads($!threads) ); |
| 223 | + $supply.schedule-on($sched); |
| 224 | + } |
| 225 | + |
| 226 | + my $i = 1; |
| 227 | + $supply.tap( |
| 228 | + sub ($item) { |
| 229 | + $sub($item); |
| 230 | + $prog.?update($i); |
| 231 | + $i++; |
| 232 | + } |
| 233 | + ); |
| 234 | + $prog.?message($msg); |
| 235 | +} |
| 236 | + |
| 237 | +method !maybe-say (*@things) { |
| 238 | + return unless $!verbose; |
| 239 | + # We chomp in case we were given a multi-line string ending with a |
| 240 | + # newline. |
| 241 | + .say for @things.map( { .chomp } ); |
| 242 | +} |
0 commit comments