File Coverage

blib/lib/Web/PerlDistSite/MenuItem.pm
Criterion Covered Total %
statement 18 122 14.7
branch 0 40 0.0
condition 0 24 0.0
subroutine 6 19 31.5
pod 0 7 0.0
total 24 212 11.3


line stmt bran cond sub pod time code
1             package Web::PerlDistSite::MenuItem;
2:
use utf8;
3:
4: our $VERSION = '0.001011';
5:
6: use Moo;
7: use Web::PerlDistSite::Common -lexical, -all;
8:
9: use HTML::HTML5::Writer;
10: use HTML::HTML5::Sanity;
11: use XML::LibXML::PrettyPrint;
12:
13: has project => (
14: is => 'rw',
15: isa => Object,
16: weak_ref => true,
17: trigger => sub ( $self, $new_val, $old_val=undef ) {
18: $_->project( $new_val ) for $self->children->@*;
19: },
20: );
21:
22: has name => (
23: is => 'ro',
24: isa => Str,
25: required => true,
26: );
27:
28: has title => (
29: is => 'ro',
30: isa => Str,
31: required => true,
32: );
33:
34: has href => (
35: is => 'lazy',
36: isa => Str,
37: builder => true,
38: );
39:
40: has rel => (
41: is => 'ro',
42: isa => Str,
43: default => 'related',
44: );
45:
46: has target => (
47: is => 'ro',
48: isa => Str,
49: default => '_self',
50: );
51:
52: has icon => (
53: is => 'ro',
54: isa => Str,
55: );
56:
57: has children => (
58: is => 'rw',
59: isa => ArrayRef->of(
60: InstanceOf
61: ->of( 'Web::PerlDistSite::MenuItem' )
62: ->plus_constructors( HashRef, 'from_hashref' )
63: ),
64: coerce => true,
65: default => sub { [] },
66: );
67:
68: has meta => (
69: init_arg => undef,
70: is => 'lazy',
71: isa => ArrayRef->of( HashRef ),
72: builder => true,
73: );
74:
75: has _meta_merge => (
76: init_arg => 'meta',
77: is => 'ro',
78: isa => ArrayRef->of( HashRef ),
79: );
80:
81: sub from_hashref ( $class, $hashref ) {
82:
83: if ( exists $hashref->{divider} ) {
84: $class .= '::Divider';
85: }
86: elsif ( exists $hashref->{pod} ) {
87: $class .= '::Pod';
88: }
89: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.pod/ ) {
90: $class .= '::PodFile';
91: }
92: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.md/ ) {
93: $class .= '::MarkdownFile';
94: }
95: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.html/ ) {
96: $class .= '::HTMLFile';
97: }
98:
99: return Module::Runtime::use_module( $class )->new( $hashref );
100: }
101:
102: sub system_path ( $self ) {
103: path( $self->project->dist_dir )->child( $self->name . '.html' )
104: }
105:
106: sub write_page ( $self ) {
107: return $self;
108: }
109:
110: sub write_pages ( $self ) {
111: $self->write_page;
112: $_->write_pages for $self->children->@*;
113: }
114:
115: sub _build_href ( $self ) {
116: if ( $self->name ) {
117: if ( $self->name eq 'github' ) {
118: return $self->project->github;
119: }
120: if ( $self->name eq 'metacpan' ) {
121: return 'https://metacpan.org/dist/' . $self->project->name;
122: }
123: if ( $self->name eq 'issues' ) {
124: return $self->project->issues // ( $self->project->github . '/issues' );
125: }
126: }
127: return $self->project->root_url . $self->name . '.html';
128: }
129:
130: sub _make_safe_class ( $self, $classname ) {
131: ( $classname = lc( $classname ) )
132: =~ s{\W+}{-}g;
133: return $classname;
134: }
135:
136: sub _compile_dom ( $self, $dom ) {
137: my $body = $dom->getElementsByTagName( 'body' )->shift;
138: $body->setAttribute(
139: 'class',
140: join(
141: ' ',
142: grep { defined($_) && length($_) }
143: $body->getAttribute( 'class' ),
144: $self->_make_safe_class( 'pagetype-' . ref($self) ),
145: $self->_make_safe_class( 'page-' . $self->name ),
146: ),
147: );
148:
149: state $p = do {
150: my $pp = XML::LibXML::PrettyPrint->new;
151: push $pp->{element}{preserves_whitespace}->@*, sub ( $node ) {
152: return undef unless $node->can( 'tagName' );
153: return 1 if $node->tagName eq 'code' and $node->parentNode->tagName eq 'pre';
154: return undef;
155: };
156: unshift $pp->{element}{inline}->@*, sub ( $node ) {
157: return undef unless $node->can( 'tagName' );
158: return 1 if $node->tagName eq 'pre' and $node->getElementsByTagName( 'code' )->size;
159: return undef;
160: };
161: unshift $pp->{element}{compact}->@*, 'a';
162: $pp;
163: };
164: state $w = HTML::HTML5::Writer->new( markup => 'xhtml', polyglot => true );
165: my $sane = fix_document( $dom );
166: $p->pretty_print( $sane );
167: return $w->document( $sane );
168: }
169:
170: sub nav_item ( $self, $active_item ) {
171: my $icon = $self->icon // '';
172: if ( length $icon ) {
173: $icon .= ' ';
174: }
175:
176: if ( $self->children->@* ) {
177: my @items = map $_->dropdown_item( $active_item ), $self->children->@*;
178: return sprintf(
179: '<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">%s%s</a><ul class="dropdown-menu">%s</ul></li>',
180: $icon,
181: esc_html( $self->title ),
182: join( q{}, @items ),
183: );
184: }
185: elsif ( $self == $active_item ) {
186: return sprintf(
187: '<li class="nav-item"><a class="nav-link active" rel="%s" target="%s" href="%s">%s%s</a></li>',
188: esc_html( $self->rel ),
189: esc_html( $self->target ),
190: esc_html( $self->href ),
191: $icon,
192: esc_html( $self->title ),
193: );
194: }
195: else {
196: return sprintf(
197: '<li class="nav-item"><a class="nav-link" rel="%s" target="%s" href="%s">%s%s</a></li>',
198: esc_html( $self->rel ),
199: esc_html( $self->target ),
200: esc_html( $self->href ),
201: $icon,
202: esc_html( $self->title ),
203: );
204: }
205: }
206:
207: sub dropdown_item ( $self, $active_item ) {
208: my $icon = $self->icon // '';
209: if ( length $icon ) {
210: $icon .= ' ';
211: }
212:
213: if ( $self == $active_item ) {
214: return sprintf(
215: '<li><a class="dropdown-item active" rel="%s" target="%s" href="%s">%s%s</a></li>',
216: esc_html( $self->rel ),
217: esc_html( $self->target ),
218: esc_html( $self->href ),
219: $icon,
220: esc_html( $self->title ),
221: );
222: }
223: else {
224: return sprintf(
225: '<li><a class="dropdown-item" rel="%s" target="%s" href="%s">%s%s</a></li>',
226: esc_html( $self->rel ),
227: esc_html( $self->target ),
228: esc_html( $self->href ),
229: $icon,
230: esc_html( $self->title ),
231: );
232: }
233: }
234:
235: sub page_title ( $self ) {
236: return sprintf( '%s — %s', $self->project->name, $self->title );
237: }
238:
239: sub _build_meta ( $self ) {
240: my @meta = @{ $self->_meta_merge // [] };
241: if ( my $title = $self->title ) {
242: push @meta, {
243: name => 'title',
244: property => 'rdfs:label dc:title og:title',
245: content => $title,
246: };
247: }
248: return \@meta;
249: }
250:
251: 1;
252: