File Coverage

blib/lib/Web/PerlDistSite/MenuItem.pm
Criterion Covered Total %
statement 18 114 15.7
branch 0 38 0.0
condition 0 22 0.0
subroutine 6 18 33.3
pod 0 7 0.0
total 24 199 12.0


line stmt bran cond sub pod time code
1             package Web::PerlDistSite::MenuItem;
2:
use utf8;
3:
4: our $VERSION = '0.001010';
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: sub from_hashref ( $class, $hashref ) {
69:
70: if ( exists $hashref->{divider} ) {
71: $class .= '::Divider';
72: }
73: elsif ( exists $hashref->{pod} ) {
74: $class .= '::Pod';
75: }
76: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.pod/ ) {
77: $class .= '::PodFile';
78: }
79: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.md/ ) {
80: $class .= '::MarkdownFile';
81: }
82: elsif ( exists $hashref->{source} and $hashref->{source} =~ /.html/ ) {
83: $class .= '::HTMLFile';
84: }
85:
86: return Module::Runtime::use_module( $class )->new( $hashref );
87: }
88:
89: sub system_path ( $self ) {
90: path( $self->project->dist_dir )->child( $self->name . '.html' )
91: }
92:
93: sub write_page ( $self ) {
94: return $self;
95: }
96:
97: sub write_pages ( $self ) {
98: $self->write_page;
99: $_->write_pages for $self->children->@*;
100: }
101:
102: sub _build_href ( $self ) {
103: if ( $self->name ) {
104: if ( $self->name eq 'github' ) {
105: return $self->project->github;
106: }
107: if ( $self->name eq 'metacpan' ) {
108: return 'https://metacpan.org/dist/' . $self->project->name;
109: }
110: if ( $self->name eq 'issues' ) {
111: return $self->project->issues // ( $self->project->github . '/issues' );
112: }
113: }
114: return $self->project->root_url . $self->name . '.html';
115: }
116:
117: sub _make_safe_class ( $self, $classname ) {
118: ( $classname = lc( $classname ) )
119: =~ s{\W+}{-}g;
120: return $classname;
121: }
122:
123: sub _compile_dom ( $self, $dom ) {
124: my $body = $dom->getElementsByTagName( 'body' )->shift;
125: $body->setAttribute(
126: 'class',
127: join(
128: ' ',
129: grep { defined($_) && length($_) }
130: $body->getAttribute( 'class' ),
131: $self->_make_safe_class( 'pagetype-' . ref($self) ),
132: $self->_make_safe_class( 'page-' . $self->name ),
133: ),
134: );
135:
136: state $p = do {
137: my $pp = XML::LibXML::PrettyPrint->new;
138: push $pp->{element}{preserves_whitespace}->@*, sub ( $node ) {
139: return undef unless $node->can( 'tagName' );
140: return 1 if $node->tagName eq 'code' and $node->parentNode->tagName eq 'pre';
141: return undef;
142: };
143: unshift $pp->{element}{inline}->@*, sub ( $node ) {
144: return undef unless $node->can( 'tagName' );
145: return 1 if $node->tagName eq 'pre' and $node->getElementsByTagName( 'code' )->size;
146: return undef;
147: };
148: unshift $pp->{element}{compact}->@*, 'a';
149: $pp;
150: };
151: state $w = HTML::HTML5::Writer->new( markup => 'xhtml', polyglot => true );
152: my $sane = fix_document( $dom );
153: $p->pretty_print( $sane );
154: return $w->document( $sane );
155: }
156:
157: sub nav_item ( $self, $active_item ) {
158: my $icon = $self->icon // '';
159: if ( length $icon ) {
160: $icon .= ' ';
161: }
162:
163: if ( $self->children->@* ) {
164: my @items = map $_->dropdown_item( $active_item ), $self->children->@*;
165: return sprintf(
166: '<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>',
167: $icon,
168: esc_html( $self->title ),
169: join( q{}, @items ),
170: );
171: }
172: elsif ( $self == $active_item ) {
173: return sprintf(
174: '<li class="nav-item"><a class="nav-link active" rel="%s" target="%s" href="%s">%s%s</a></li>',
175: esc_html( $self->rel ),
176: esc_html( $self->target ),
177: esc_html( $self->href ),
178: $icon,
179: esc_html( $self->title ),
180: );
181: }
182: else {
183: return sprintf(
184: '<li class="nav-item"><a class="nav-link" rel="%s" target="%s" href="%s">%s%s</a></li>',
185: esc_html( $self->rel ),
186: esc_html( $self->target ),
187: esc_html( $self->href ),
188: $icon,
189: esc_html( $self->title ),
190: );
191: }
192: }
193:
194: sub dropdown_item ( $self, $active_item ) {
195: my $icon = $self->icon // '';
196: if ( length $icon ) {
197: $icon .= ' ';
198: }
199:
200: if ( $self == $active_item ) {
201: return sprintf(
202: '<li><a class="dropdown-item active" rel="%s" target="%s" href="%s">%s%s</a></li>',
203: esc_html( $self->rel ),
204: esc_html( $self->target ),
205: esc_html( $self->href ),
206: $icon,
207: esc_html( $self->title ),
208: );
209: }
210: else {
211: return sprintf(
212: '<li><a class="dropdown-item" rel="%s" target="%s" href="%s">%s%s</a></li>',
213: esc_html( $self->rel ),
214: esc_html( $self->target ),
215: esc_html( $self->href ),
216: $icon,
217: esc_html( $self->title ),
218: );
219: }
220: }
221:
222: sub page_title ( $self ) {
223: return sprintf( '%s — %s', $self->project->name, $self->title );
224: }
225:
226: 1;
227: