From: Marius Gavrilescu Date: Fri, 30 Sep 2016 20:01:09 +0000 (+0300) Subject: Initial commit X-Git-Tag: 0.001^0 X-Git-Url: http://git.ieval.ro/?a=commitdiff_plain;h=1c93f4de2cd1172dc66a27d6f92e1949834a8032;p=plack-middleware-basicstyle.git Initial commit --- 1c93f4de2cd1172dc66a27d6f92e1949834a8032 diff --git a/Changes b/Changes new file mode 100644 index 0000000..fd16b64 --- /dev/null +++ b/Changes @@ -0,0 +1,4 @@ +Revision history for Perl extension Plack::Middleware::BasicStyle. + +0.001 2016-09-30T23:01+03:00 + - Initial release diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..96b6cd1 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,6 @@ +Changes +Makefile.PL +MANIFEST +README +t/Plack-Middleware-BasicStyle.t +lib/Plack/Middleware/BasicStyle.pm diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..b1b533c --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,31 @@ +use 5.014000; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Plack::Middleware::BasicStyle', + VERSION_FROM => 'lib/Plack/Middleware/BasicStyle.pm', + ABSTRACT_FROM => 'lib/Plack/Middleware/BasicStyle.pm', + AUTHOR => 'Marius Gavrilescu ', + MIN_PERL_VERSION => '5.14.0', + LICENSE => 'perl', + SIGN => 1, + PREREQ_PM => { + qw/HTML::Parser 0 + Plack::Middleware 0 + Plack::Request 0 + Plack::Util 0 + Plack::Util::Accessor 0/, + }, + TEST_REQUIRES => { + qw/HTTP::Request::Common 0 + Plack::Builder 0 + Plack::Test 0 + Test::More 0/, + }, + META_ADD => { + dynamic_config => 1, + resources => { + repository => 'https://git.ieval.ro/?p=plack-middleware-basicstyle.git', + }, + } +); diff --git a/README b/README new file mode 100644 index 0000000..47387f1 --- /dev/null +++ b/README @@ -0,0 +1,49 @@ +Plack-Middleware-BasicStyle version 0.001 +========================================= + +Plack::Middleware::BasicStyle is a Plack middleware that adds a basic + + +INSTALLATION + +To install this module type the following: + + perl Makefile.PL + make + make test + make install + +DEPENDENCIES + +This module requires these other modules and libraries: + +* HTML::Parser +* Plack + +COPYRIGHT AND LICENCE + +Copyright (C) 2016 by Marius Gavrilescu + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.24.0 or, +at your option, any later version of Perl 5 you may have available. + + diff --git a/lib/Plack/Middleware/BasicStyle.pm b/lib/Plack/Middleware/BasicStyle.pm new file mode 100644 index 0000000..1692c8a --- /dev/null +++ b/lib/Plack/Middleware/BasicStyle.pm @@ -0,0 +1,250 @@ +package Plack::Middleware::BasicStyle; + +use 5.014000; +use strict; +use warnings; + +use parent qw/Plack::Middleware/; + +use HTML::Parser; +use Plack::Request; +use Plack::Util; +use Plack::Util::Accessor qw/style any_content_type even_if_styled use_link_header/; + +our $VERSION = '0.001'; +our $DEFAULT_STYLE = < + body { + margin:40px auto; + max-width: 650px; + line-height: 1.6; + font-size:18px; + color:#444; + padding:0 10px + } + + h1,h2,h3 { + line-height:1.2 + } + +EOF + +sub prepare_app { + my ($self) = @_; + $self->{link_header} = + sprintf '<%s>; rel=stylesheet', $self->use_link_header + if $self->use_link_header; + $self->style($self->style // $DEFAULT_STYLE); +} + +sub _content_type_ok { + my ($self, $hdrs) = @_; + return 1 if $self->any_content_type; + my $content_type = + Plack::Util::header_get($hdrs, 'Content-Type'); + return '' unless $content_type; + $content_type =~ m,text/html,i; +} + +sub call { + my ($self, $env) = @_; + if ($self->use_link_header) { + my $req = Plack::Request->new($env); + if (lc $req->path eq lc $self->use_link_header) { + my $days30 = 30 * 86400; + my @hdrs = ( + 'Content-Length' => length $self->style, + 'Content-Type' => 'text/css', + 'Cache-Control' => "max-age=$days30", + ); + return [200, \@hdrs, [$self->style]] + } + } + + my $res = $self->app->($env); + if (ref $res ne 'ARRAY' + || @$res < 3 + || ref $res->[2] ne 'ARRAY' ) { + $res + } elsif (!$self->_content_type_ok($res->[1])) { + $res + } else { + my ($styled, $html_end, $head_end, $doctype_end); + my $parser_callback = sub { + my ($tagname, $offset_end, $attr) = @_; + $html_end //= $offset_end if $tagname eq 'html'; + $head_end //= $offset_end if $tagname eq 'head'; + $doctype_end //= $offset_end if $tagname eq 'doctype'; + $styled = 1 if $tagname eq 'style'; + $styled = 1 if $tagname eq 'link' + && ($attr->{rel} // '') =~ /stylesheet/i; + }; + + my $p = HTML::Parser->new(api_version => 3); + $p->report_tags(qw/style link html head/); + $p->handler(start => $parser_callback, 'tagname,offset_end,attr'); + $p->handler(declaration => $parser_callback, 'tagname,offset_end,attr'); + $p->parse($_) for @{$res->[2]}; + $p->eof; + + return $res if $styled && !$self->even_if_styled; + + if ($self->use_link_header) { + push @{$res->[1]}, 'Link', $self->{link_header}; + } else { + # If there's a , put the style right after it + # Otherwise, if there's a , put the style right after it + # Otherwise, if there's a , put the style right after it + # Otherwise, put the style at the very beginning of the body + if ($head_end || $html_end || $doctype_end) { + my $body = join '', @{$res->[2]}; + my $pos = $head_end // $html_end // $doctype_end; + substr $body, $pos, 0, $self->style; + $res->[2] = [$body] + } else { + unshift @{$res->[2]}, $self->style + } + } + + $res + } +} + +1; +__END__ + +=encoding utf-8 + +=head1 NAME + +Plack::Middleware::BasicStyle - Add a basic ', + any_content_type => 1, + even_if_styled => 1, + use_link_header => '/basic-style.css'; + ... + } + +=head1 DESCRIPTION + +Plack::Middleware::BasicStyle is a Plack middleware that adds a basic + + +The middleware takes the following arguments: + +=over + +=item B + +Foo + + +

Bar

+ + +EXPECTED + +local $Plack::Middleware::BasicStyle::DEFAULT_STYLE = ''; + +run_test [], $default_hdrs, <<'BODY', <<'EXPECTED', 'no head'; + +content +BODY + +content +EXPECTED + +run_test [], $default_hdrs, <<'BODY', <<'EXPECTED', 'no html'; + +content +BODY + +content +EXPECTED + +run_test [], $default_hdrs, 'content', 'content', 'no head, no html'; + +run_test [], $default_hdrs, '', '', 'just doctype'; + +run_test [], [], 'no change', 'no change', 'no content-type'; + +run_test [any_content_type => 1], [], 'yes change', 'yes change', 'no content-type + any_content_type'; + +run_test [], $default_hdrs, (<<'BODY') x 2, 'has +content +BODY + +run_test [], $default_hdrs, (<<'BODY') x 2, 'has external stylesheet'; + +> + +content +BODY + +run_test [even_if_styled => 1], $default_hdrs, + <<'BODY', <<'EXPECTED', 'has +content +BODY + + +content +EXPECTED + +run_test [style => ''], $default_hdrs, 'content', 'content', 'style'; + +run_test [use_link_header => '/basic-style.css'], + $default_hdrs, 'test', ['Link', '; rel=stylesheet'], 'use_link_header'; + +run_test [use_link_header => '/basic-style.css'], + $default_hdrs, 'test', '', 'use_link_header - /basic-style.css', '/basic-style.css';