CSS::Tiny::Styleについて

CSS::Tiny::Styleについて、下記の問題にぶち当たった。

  • capnモジュールでインストールする際、Test::Perl::Criticの検査に引っかかる
  • 属性名や擬似クラス名にハイフンやアンダースコアが入ると、きちんとパースされない

これらについて少し調べた。

Test::Perl::Criticに引っかかる件

エラーで指示されていた箇所のソースを見てみた。
まず三行目。

#Code before strictures are enabled at line 3, column 14. See page 429 of PBP. (Severity: 5)
package CSS::Tiny::Style;

use version; $VERSION = qv('0.0.3'); # 3行目

use warnings;
use strict;
use Carp;

このエラーメッセージが怒っているのは、strictプラグマの前にコードがあること。したがって、strictプラグマがuseされた後にコードを持っていくと、エラーメッセージが出なくなる。しかし、$VERSIONは、スコープの宣言がされてないのでstrictプラグマに引っかかる。versionモジュールの「BEST PRACTICES」を見るとour宣言してるのでそれに従う。

use warnings;
use strict;
use version; our $VERSION = qv('0.0.3');


もう一つのエラー。

# Stricture disabled at line 138, column 5.  See page 429 of PBP.  (Severity: 5)
no strict 'refs'; 

PBPでは、no strictは許していないみたい。CSS::Tiny::Styleでは、下記のように関数を呼び出したいからno strictしている。

    for (qw/tag id class/) {
        my $sub = "_$_";

        next unless (my $val = &$sub($sel));  # strict 'ref'では禁止されている関数呼び出し

これをやらなければno strictする必要はなくなる。もし面倒だったりどうしても必要な場合は、下記のように記述すれば、Test::Perl::Criticの検査の対象とならなくなる。

 no strict 'refs'; ## no critic

以上の対処で、Test::Perl::Criticの検査に通るかどうかを下記のプログラムを書いて確かめた。

#!/usr/bin/perl
use strict;
use Test::More;

eval { require Test::Perl::Critic; Test::Perl::Critic->import(-profile => 't/perlcriticrc') };
plan skip_all => "Test::Perl::Critic is not installed." if $@;
all_critic_ok("CSS/Tiny/");

okもらった。

1..1
ok 1 - Test::Perl::Critic for "CSS/Tiny/Style.pm"

きちんとパースされない件

セレクタにアンダースコアやハイフンが入ると、
CSS::Tiny::Styleの_sel_arrメソッドの下記の部分で無限ループが起こる。

    while ($_) {
        my ($tag, $op);


        s/([a-zA-Z0-9.\#\*]+)\s*$//; $tag = $1;
        $op  = $1 if (s/(\s*[+>]*\s*)$//);


        push @d, $tag if $tag;

        for ($op) {
            /\+/    && do { push @d, 'left';    last; };
            /\>/    && do { push @d, 'parent';  last; };
            /^\s+$/ && do { push @d, 'lineage'; last; };
        }
    }

「s/([a-zA-Z0-9.\#\*]+)\s*$//;」の部分で、ハイフンとアンダースコアがマッチしないようになっている。マッチしたものは削除していき、$_が空になったらループを抜け出すようになっているので、無限ループになる。例えば「hato_uhouho」というセレクタがあったら「hato」だけがマッチし、2回目以降のループでは、$_の値は「_uhouho」になるので、ずっとマッチしない。
ということで、単純にハイフンとアンダースコアにマッチするように下記のように書き換えた。

s/([a-zA-Z0-9.\#\*_-]+)\s*$//; $tag = $1;

これで一応、無限ループは回避された。
しかし、ちゃんと動くかどうかはまだ確認していない。
もう少し、CSS::Tinyを細かく読んでことにする。