Perlのレキシカルサブルーチンとperlcritic ― 2022年12月21日 02時48分
この記事はPerl Advent Calendar 2022の21日目の分です。
Perlでは、関数内で定義した関数も外部から見えてしまいます。
use feature 'say';
sub foo {
sub bar {
say 'bar';
}
bar();
}
# foo関数の外でもbar関数を呼び出せる。
bar();
特定のスコープでのみ参照できる関数を定義したいときは、関数定義をsub
ではなくmy sub
(またはstate sub
)から始めます。この機能はレキシカルサブルーチン(lexical subroutines)と呼ばれます。
use feature 'say';
sub foo {
my sub bar {
say 'bar';
}
bar();
}
# 未定義の関数呼び出しによる例外が発生する。
bar();
ちょっとした処理をまとめるのに便利なレキシカルサブルーチンですが、perlcritic
との組み合わせに難がありました。レキシカルサブルーチンを使ったコードをperlcritic
にかけると、Subroutines::ProhibitNestedSubs
ポリシーとSubroutines::ProhibitBuiltinHomonyms
ポリシーのエラーが出てしまうのです。(perlcritic
はPerl向けのリンターです。詳しくは「perlcriticとのつきあい方 - 私が歌川です」などを参照してください。)
Subroutines::ProhibitNestedSubs
ポリシーは関数の入れ子を禁止します。入れ子の内側の関数が意図せず外部に公開されるのを避けるためのものなので、もともと外部に公開されないレキシカルサブルーチンに対しては禁止する意味がありません。
Subroutines::ProhibitBuiltinHomonyms
ポリシーは組み込み関数と同名の関数を禁止します。perlcritic
の内部で使われるで使われるPPI
モジュールの不具合により、レキシカルサブルーチンの名前は常にsub
であるとみなされていました。
これらの問題を解決するため、昨年perlcritic
とPPI
に以下のプルリクエストを提出しました。
- Allow lexical subroutines to be inside subroutines by nanto · Pull Request #971 · Perl-Critic/Perl-Critic
- Stop improper violation for lexical subroutines in Subroutines::ProhibitBuiltinHomonyms by nanto · Pull Request #973 · Perl-Critic/Perl-Critic
- Return correct name for lexical subroutines by nanto · Pull Request #261 · Perl-Critic/PPI
この年末までにこれらの変更がすべて取り込まれたので、perlcritic
(とPPI
)のバージョンを最新にすれば、心置きなくレキシカルサブルーチンを使えます。
CSSのconic-gradientで直線的な模様を作る ― 2022年12月19日 11時11分
この記事はCSS Advent Calendar 2022の19日目の分です。
CSSのconic-gradient
(扇形グラデーション)関数を使うと、円グラフや集中線のような表現ができます。ここであえて扇形の中心角の部分に注目し、直線的な模様を作ってみることはできないでしょうか? いくつか試してみました。
例
例1. 二重のボーダー
「二重のボーダー」のCSSコード
p {
--outer-border-width: 1em;
--inner-border-width: 1em;
--outer-border-top-left-color: #1bf;
--inner-border-top-left-color: #35b;
--inner-border-bottom-right-color: #dc6;
--outer-border-bottom-right-color: #3a8;
margin: 0;
padding: 1em;
border: calc(var(--outer-border-width) + var(--inner-border-width)) solid transparent;
background:
conic-gradient(from 180deg at var(--outer-border-width) var(--outer-border-width), var(--outer-border-top-left-color) 270deg, transparent 270deg) no-repeat border-box 0 0 / calc(100% - var(--outer-border-width)) 100%,
conic-gradient(from 180deg at var(--inner-border-width) var(--inner-border-width), var(--inner-border-top-left-color) 270deg, transparent 270deg) no-repeat border-box var(--outer-border-width) var(--outer-border-width) / calc(100% - 2 * var(--outer-border-width) - var(--inner-border-width)) calc(100% - 2 * var(--outer-border-width)),
conic-gradient(from 0deg at calc(100% - var(--inner-border-width)) calc(100% - var(--inner-border-width)), var(--inner-border-bottom-right-color) 270deg, transparent 270deg) no-repeat border-box calc(var(--outer-border-width) + var(--inner-border-width)) var(--outer-border-width) / calc(100% - 2 * var(--outer-border-width) - var(--inner-border-width)) calc(100% - 2 * var(--outer-border-width)),
conic-gradient(from 0deg at calc(100% - var(--outer-border-width)) calc(100% - var(--outer-border-width)), var(--outer-border-bottom-right-color) 270deg, transparent 270deg) no-repeat border-box var(--outer-border-width) 0 / calc(100% - var(--outer-border-width)) 100%;
}
例2. 矢印
「矢印」のCSSコード
p {
--arrow-bg-color: #aaa;
--arrow-color: #ff5;
--arrow-angle: 120deg;
--arrow-from-angle: calc(180deg + var(--arrow-angle) / 2);
--arrow-outer-angle: calc(360deg - var(--arrow-angle));
--arrow-margin-left: 1em;
--arrow-width: 2em;
--arrow-center: calc(var(--arrow-width) / 2);
--arrow-offset: 1em;
margin: 0;
padding: 1em;
border: solid transparent;
border-width: 0 0 0 calc(var(--arrow-margin-left) + var(--arrow-width));
border-radius: 0.5em;
background: #ddd no-repeat border-box var(--arrow-margin-left) 0 / var(--arrow-width) 100%;
background-image:
conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(1 * var(--arrow-offset)), var(--arrow-bg-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(2 * var(--arrow-offset)), var(--arrow-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(3 * var(--arrow-offset)), var(--arrow-bg-color) var(--arrow-outer-angle), transparent var(--arrow-outer-angle)),
conic-gradient(from var(--arrow-from-angle) at var(--arrow-center) calc(4 * var(--arrow-offset)), var(--arrow-color) var(--arrow-outer-angle), var(--arrow-bg-color) var(--arrow-outer-angle));
}
例3. 星形
「星形」のCSSコード
p {
--outer-block-angle: 6deg;
--outer-inline-angle: 22deg;
--cone-angle: calc(90deg - var(--outer-block-angle) - var(--outer-inline-angle));
--cone-top-left-color: #bde;
--cone-top-right-color: #cae;
--cone-bottom-right-color: #ecc;
--cone-bottom-left-color: #ffb;
margin: 0;
padding: 2em 3em;
background:
conic-gradient(from calc(90deg + var(--outer-block-angle)) at 0 0, var(--cone-top-left-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 0 0 / 50% 50%,
conic-gradient(from calc(180deg + var(--outer-inline-angle)) at 100% 0, var(--cone-top-right-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 100% 0 / 50% 50%,
conic-gradient(from calc(270deg + var(--outer-block-angle)) at 100% 100%, var(--cone-bottom-right-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 100% 100% / 50% 50%,
conic-gradient(from var(--outer-inline-angle) at 0 100%, var(--cone-bottom-left-color) var(--cone-angle), transparent var(--cone-angle)) no-repeat 0 100% / 50% 50%;
}
例4. 吹き出し
「吹き出し」のCSSコード
p {
--bg-color: #fed;
--padding: 1em;
--border-width: 0.5em;
--border-color: #f93;
--border-radius: 1em;
--balloon-tail-angle: 60deg;
--balloon-tail-from-angle: calc(90deg - var(--balloon-tail-angle) / 2);
--balloon-tail-width: 2em;
--balloon-tail-height: calc(1.15470054 * var(--balloon-tail-width)); /* 1.15470054 = 2 / sqrt(3) */
--balloon-tail-bottom-offset: 1em;
margin: 0 0 0 var(--balloon-tail-width);
padding: var(--padding);
border: var(--border-width) solid var(--border-color);
border-radius: var(--border-radius);
background: var(--bg-color);
}
p::after {
content: '';
position: absolute;
display: block;
width: calc(var(--balloon-tail-width) + var(--border-width));
height: var(--balloon-tail-height);
background:
conic-gradient(from var(--balloon-tail-from-angle) at calc(2 * var(--border-width)) calc(var(--balloon-tail-height) / 2), var(--bg-color) var(--balloon-tail-angle), transparent var(--balloon-tail-angle)) no-repeat,
conic-gradient(from var(--balloon-tail-from-angle) at 0 calc(var(--balloon-tail-height) / 2), var(--border-color) var(--balloon-tail-angle), transparent var(--balloon-tail-angle)) no-repeat;
transform: translate(calc(0em - var(--padding) - var(--balloon-tail-width) - var(--border-width)), calc(var(--padding) + var(--border-width) - var(--border-radius) - var(--balloon-tail-height) - var(--balloon-tail-bottom-offset)));
}
感想
やってはみたもののどうも微妙です。直線的な模様ならだいたいはlinear-gradient
関数で表現できますし、要素を切り抜くのならclip-path
プロパティのほうが自由度が高いです。conic-gradient
関数を使えば記述量を減らせるかというと、そういうこともそんなになく、やはり目的外の利用はあまりうまくいかないのかもしれません。
今回ひとつ得られた教訓は、背景画像を使って疑似的にボーダーを表現する場合、疑似的なボーダーと同じ大きさの透明なボーダー(border: <border-wdith> solid transparent
)を指定したほうがよいということです。そうすることで、overflow: auto
で内容がはみ出したときなどにスクロール領域が疑似的なボーダーにかからず、より自然に見えます。
CSSの絶対配置の要素の静的位置矩形 ― 2022年12月11日 12時52分
この記事はCSS Advent Calendar 2022の11日目の分です。
CSSでpositoin: absolute
(絶対配置)の要素の位置を指定するときには、top
、left
、right
、bottom
プロパティ(またはこれらを一括指定するinset
プロパティ)がよく使われます。もし絶対配置の要素にそれらのプロパティが指定されていなかったら、その要素の位置はどこになるでしょうか?
その場合、絶対配置の要素は原則として「その要素がpositoin: static
(静的配置)だった場合の位置(静的位置矩形; static-position rectangle)」に置かれます。絶対配置の要素の親要素がインライン要素だった場合、絶対配置の要素自身がdisplay: inline
(インライン要素)かdisplay: block
(ブロック要素)かによって位置が異なってくることになります(インライン要素の子要素が絶対配置のときのデモ)。
この挙動をうまく利用すれば、絶対配置の要素の親要素にposition: relative
をつけて回らなくても、絶対配置の要素を期待する位置に置けることがあります。その場合、細かな位置の調整にinset
プロパティなどを使うことはできないので、transform: translate(...)
やmargin
プロパティを使うことになります。
フレックスアイテムが絶対配置のとき
「原則として」というからには例外もあります。絶対配置のフレックスアイテムでinset
プロパティなどが指定されていないものは、フレックスコンテナの位置に置かれます。
(「フレックスコンテナ」はdisplay: flex
またはdisplay: inline-flex
が指定された要素、「フレックスアイテム」はフレックスコンテナの子要素です。)
グリッドアイテムが絶対配置のとき
グリッドアイテムが絶対配置のときはちょっと複雑です。グリッドコンテナが静的配置なら、絶対配置のグリッドアイテムでinset
プロパティなどが指定されていないものは、グリッドコンテナの位置に置かれます。
グリッドコンテナが静的配置でないなら、絶対配置のグリッドアイテムでinset
プロパティが指定されていないものは、grid
プロパティなどで指定されたグリッド領域の位置に置かれます(グリッドアイテムが絶対配置のときのデモ)。
(「グリッドコンテナ」はdisplay: grid
またはdisplay: inline-grid
が指定された要素、「グリッドアイテム」はグリッドコンテナの子要素です。)
とはいえ、フレックスボックスやグリッドと絶対配置を組み合わせるとCSSのコードがだいぶ複雑になるので、普段は避けたほうがよいと思います。
PerlのText::Markdown::Discountで囲い付きコードブロックを扱う ― 2022年12月11日 01時31分
この記事はPerl Advent Calendar 2022の11日目の分です。
PerlのText::Markdown::Discount
モジュールを使うと、MarkdownをHTMLに変換できます。
use feature qw(say);
use Encode qw(encode_utf8);
use Text::Markdown::Discount qw(markdown);
say encode_utf8 markdown(<<'MARKDOWN');
こんにちは、世界。
* 順序
* なし
* リスト
MARKDOWN
<p>こんにちは、世界。</p>
<ul>
<li>順序</li>
<li>なし</li>
<li>リスト</li>
</ul>
オリジナルのMarkdownにはありませんが、CommonMarkおよびその拡張であるGitHub Flavored Markdownなどには囲い付きコードブロック(fenced code block)が存在します。3つ以上のバッククォート(`
)の並びまたはチルダ(~
)の並びで囲んだ部分が、HTMLのpre
要素とcode
要素を使って出力されるというものです。
Text::Markdown::Discount
モジュール(の内部で使われているDiscountというMarkdown処理系)でも囲い付きコードブロックに対応していますが、扱い方がバージョンによって異なります。
Text::Markdown::Discount
0.14以降
囲い付きコードブロックを有効にするには、markdown
関数の第2引数にMKD_FENCEDCODE
フラグ(0x02000000
)を指定します。
my $html = markdown(<<'MARKDOWN', Text::Markdown::Discount::MKD_NOHEADER | Text::Markdown::Discount::MKD_NOPANTS | 0x02000000);
```
fenced code block
```
MARKDOWN
<pre><code>fenced code block
</code></pre>
MKD_FENCEDCODE
フラグはDiscount処理系で定義されているものの、Text::Markdown::Discount
モジュールでは定数が定義されていていないため、フラグの値である0x02000000
を直接指定しています。(定数を定義するpull requestが提出されています。)
MKD_NOHEADER
フラグおよびMKD_NOPANTS
フラグはmarkdown
関数の第2引数を省略したときにデフォルトで適用されるフラグなので、元の挙動を変えずに別のフラグを追加する際には、このふたつのフラグも明示的に指定する必要があります。
MKD_FENCEDCODE
フラグを指定しない場合、3つ以上のバッククォートの並びはインラインのコード範囲として解釈されます。
my $html = markdown(<<'MARKDOWN');
```
fenced code block
```
MARKDOWN
<p><code>
fenced code block
</code></p>
Text::Markdown::Discount
0.13
特にフラグを指定しなくても、囲い付きコードブロックが有効になっています。
my $html = markdown(<<'MARKDOWN');
```
fenced code block
```
MARKDOWN
<pre><code>fenced code block
</code></pre>
Text::Markdown::Discount
0.12以前
囲い付きコードブロックに対応していません。3つ以上のバッククォートの並びはインラインのコード範囲として解釈されます。
CSSでスクロールバーの有無によるがたつきをなくす ― 2022年12月09日 09時42分
この記事はCSS Advent Calendar 2022の9日目の分です。
CSSのボックスモデルにおいては、ボーダーの内辺とパディングの外辺の間にスクロールバーが配置されます。最近はどのOSでもオーバーレイスクロールバー(スクロールバーが内容の前面に覆いかぶさるようなもの)が主流となり、スクロールバーが存在してもしなくても内容の幅が変わらないようになっています。一方、クラシックスクロールバー(スクロールバーが常に表示されるようなもの)が使われる環境では、overflow: auto
な要素において内容がはみ出すときとはみ出さないときで内容の幅が変わってきます。
クラシックスクロールバーが使われる環境でも内容の幅を一定に保ちたいという場合は、scrollbar-gutter
プロパティを使います。scrollbar-gutter: stable
を指定すれば、スクロールバーが表示されないときでもスクロールバーと同じだけの領域が確保され、内容の幅はその分狭くなります。左右中央に配置したいのにスクロールバーの領域の分だけずれて困るというときは、scrollbar-gutter: stable both-edges
を指定することで、左右どちらにもスクロールバーと同じだけの領域が確保されます。
scrollbar-gutter
プロパティの各値を指定した例(上図はWindows 10 Firefox nightly 109.0a1での表示)
scrollbar-gutter
プロパティはoverflow: hidden
な要素にも適用されますが、overflow: visible
な要素には適用されません。また、scrollbar-gutter
プロパティの効果は、縦スクロールバー(縦書きなら横スクロールバー)にのみ影響します。
オーバーレイスクロールバーが使われる環境では、scrollbar-gutter: stable
の効果はありません。
使用事例
使用事例としては、@about_hiroppyさんの紹介している「HTMLのdialog
要素によるモーダルダイアログが開いているときに、背景の文書をスクロールさせない」というのが考えられます(scrollbar-gutter
プロパティを指定した例と指定しなかった例)。
html {
scrollbar-gutter: stable;
}
html:has(dialog:modal) {
overflow: hidden;
}
クラシックスクロールバーが使われる環境では、文書をスクロールさせないためにoverflow: hidden
を指定すると、スクロールバーが消えて文書の内容ががたついてしまいます。scrollbar-gutter: stable
をあらかじめ指定しておけば、スクロールバーが消えてもその分の領域が確保され続けるので、文書の内容ががたつきません。
最近のコメント