Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 6/240: « 2 3 4 5 6 7 8 9 10 »

「技術書典」参加しましたれぽ - Jun 26, 2016

技術書典に参加してきましたので、MozLondonの技術面以外の話をほったらかして先にこっちの話を書いておきます。 (会場で掲示していた即席ポスターの写真)

技術書オンリーイベントとは?

個人や小規模の団体などによる自費出版物=同人誌の即売会には「オールジャンルイベント」と「オンリーイベント」の2種類があります。「コミックマーケット」はオールジャンルイベントの代表例で、マンガ小説評論写真集その他色々な種類・内容の作品が取り扱われています。一方のオンリーイベントでは、取り扱われる作品が「艦これオンリー」や「弱虫ペダルオンリー」のように特定のタイトルのファンアートだけだったり、「耳キャラオンリー」のように特定のキーワードに関係する作品だけだったりという風に、イベント全体が特定のジャンル性を帯びています。

オールジャンルイベントには電子工作の話だったりプログラミングの話だったりという技術的な話題を扱う作品も出展されていることがあり、これらは大まかに「技術系ジャンル」という括りになっています。このジャンルの(おそらく初の)(自分が知らなかっただけで前例はあったようです)オンリーイベントが、今回の「技術書典」というわけです。

そういう文脈なので、イベントの体裁は自分が見たところまさしく「同人誌即売会」という感じでした。他のオンリーイベントとの違いというと、そこに「企業ブロック」という扱いで、OSCの会場で見かけるような翔泳社やオライリーといった技術書に強い出版社の販売ブースが普通のサークルと机を並べて存在していたという点でしょうか。

シス管系女子のスペース

今回は、自分は「シス管系女子」の名前で企業として参加しました。企業参加とはいっても日経BP主導ではなく、僕個人が技術書典の情報を見つけて「参加したい!」と言ってゴネて、頒布物作りや当日の作業は自分でやるということでスポンサードして頂いた感じです。基本的に商業出版物は企業参加で申し込むようにというレギュレーションもありましたし。

(写真を撮り忘れたので戸倉さんのツイートを引用)

頒布物は新作描き下ろし(ただし下描きクオリティ)の8ページのコピー本と、既刊のムック2冊でした。 コピー本の内容はpixivにまるっと上げてあります。 そのうちシス管系女子の特設サイトにも載せるつもり。

シス管系女子BEGINS 第0.1話 by Piro on pixiv

最初は普通にスペースに置いておいて、本を買って下さった方に渡したり、見てくれた人に「無料です」と言ってそのまま持って行ってもらったりというつもりでいたため、100部持ち込んで(コミケの技術島だったら「多すぎやろ」レベルの数)余ったらOSC等の会場でチラシのスペースに置いてもらうとかすればいいかなーと思っていたのですが、ヘルプで入ってもらった売り子さんの提案で「どうせ無料ならどんどん配った方がいいのでは?」という事になって、配り始めたらあっという間に足りなくなってしまいました。幸い、会場から徒歩で行ける距離にキンコーズがあったため、なくなりそうになったら行ってセルフコピーで200部増刷するという事を2回繰り返して、閉会30分前くらいの時点で合計500部を配りきりました。 (イベント側でも当日増刷システムなどの試みをしていたようですが、自分は制作フォーマットが違った&毎度の通り作業がギリギリになってしまって申し込めなかったので、自力解決したという次第……)

ムックの方は各30部ずつ持ち込んで、それぞれ残り5~6冊くらいになるまでは出ました。という所から売り上げはすぐに計算できるのですが、まぁ企業として動くには明らかに赤字なので、今回は日経BPサイドにはプロモーションと割り切って頂いた感じです。

見ていた感じだと、手に取っていただいた方には「初めて知った」という人が多かった印象で、費用対効果はさておき「今まで到達できていなかった人に認知してもらう」という事はそれなりに実現できたのではないか?と思っています。 内容の質にはわりと自信がありますので、今後もこんな感じで、今まで届けられていなかった方に届けられる方法を考えていきたいです。

あと、今回スケブ依頼は無かったのですが、会場では何人かの方にサインのご依頼を頂いたので書かせて頂きました。焦りもあって線が結構ヨレヨレになってしまいました……すみません。

会場の様子

着いてみると結構会場が狭くて、開会直後から行列がすごいことになっていたようですが、早々に入場方式を整理券方式に切り替えたらしく、会場内の人口密度が一定以上にならないようコントロールされていました。そのため、外の「何時間待ち」といった情報とは裏腹に、中は割合ゆったりとした雰囲気が保たれていたのが印象的でした。 技術系の同人誌は試し読みをするにもじっくり読む必要のある物が多いと思われるので、これは本当に良い判断だったと思います。運営のファインプレーですね。

自分も比較的ゆっくり会場を回って他のスペースの頒布物を見て回ることができ、会場の空気にあてられて結構買い込んでました。 (買った物・頂いた物の写真) 技術系の同人誌は分厚かったり部数が少なかったりで製造原価が高いために、頒布価格の相場が結構高いのが、普段自分がコミケ等で参加するマンガ系ジャンルとは違うものなんだなあ……と今更実感。

前例の無いイベントということで一体どれくらいの人が来るのか全く予想ができず、もしかしたら会場内のサークル参加者同士でお互いに見て回って終わりくらいの規模になるのかもと思っていたのですが、主催者発表によると一般入場者が最終集計で1300人に達していたとのことで、想像を遙かに上回る盛況ぶりに参加者として驚くばかりです。

商業出版物の流通経路に載せるほどの売り上げは見込めないけれども、この事について書きたいんだ!とか、こういう技術本を作りたいんだ!というような作り手側の思いから作られた作品達。 そういった物が集まり、読み手は作り手から直接その思いを聞けて、作り手は読み手の反応をダイレクトに得られる、というのはオフラインイベント独特の魅力だと改めて感じました。 技術書典 当日の様子でも次回開催を望む声が多く見られますし、小説・評論ジャンルのオンリーイベント「文学フリマ」が回を重ねるのみならず地方開催も行っているように、技術書典も「技術ジャンルのオンリーイベント」として確かな地位を確立していってくれるといいなあ、と思います。

続き:技術書典2のレポート

Mozilla All Hands in London 2016 イベントレポート:技術編 - Jun 20, 2016

現地時刻で2016年6月13日から17日にかけて行われたMozilla All Hands in London、通称MozLondonに参加してきました。 (オープニングセッションの開始前の様子)

最近はこの種のイベントに参加する機会が減ってしまい、「えっ、Mozilla関係で何かやってる人だったの?」みたいになってそうな気がするので、ちょっと記録を残しておこうと思います。

Mozillaの活動に関わっている人はMozillaの雇用スタッフもそうでない人も世界中に散らばっていてコミュニケーションコストが高いので、半年ごとにAll Handsと称して社員と外部の開発者を一箇所に集めて色々話し合っています。 ただ、社員は全社員が招集されるのに対して、自分のような外部の人は活動のアクティブさの度合いなどに応じてボランティア枠で招待される形です。 自分は連絡を頂いた時点ではBugzilla上でそんなにアクティブではなかったのですが、今回はアドオン作者へのヒアリングということでアドオンチームの方から推薦を頂いたようです。 「ボランティア」とはいっても招待なので、交通費・食費・宿泊費は全部Mozilla持ちでした。

続きを表示する ...

Migration story of the Popup ALT Attribute from XUL/XPCOM to WebExtensions - Apr 19, 2016

Recently I wrote a blog entry for developers of addons for Firefox, who are planning to migrate his/her XUL-based addon to WebExtensions. This is the full version which includes some side topics not related to WebExtensions. I hope this helps you to migrate your ancient addon to WebExtensions.

続きを表示する ...

タブのツリーを最適な段数で段組表示する - Mar 06, 2016

ツリー型タブの最近の改善でやったことをまた地味に解説するよ。誰得な記事。

ツリー型タブには、「ブックマークからタブを複数開いた時に1つのツリーになってた方がいいけど、でもツリーにするには親になるタブが必要で、だからといって同列のブックマークの中で1つだけが親になるのってなんか違和感あるよね」という場面のための「グループ化専用のダミーのタブ」を開く機能がある。というか、about:treestyletab-groupというURIを読み込んだタブはそういう用途のタブとして扱うようになる。そのときタブの内容が空っぽだと味気ないので、そのタブ配下のタブのツリーをタブの内容として表示するようになっている。

(画像:配下のタブのツリーを表示しているタブ)

それとはまた別の話で、ツリーになっているタブの上でしばらく待つとそのタブ1つだけじゃなくて配下タブの名前も一緒にツリー表示して、さらにもう少し待つとそれらがリンクに切り替わってクリックしたらそのタブに切り替えられる、という機能もある。

(画像:ツリーを簡易表示したツールチップ) (画像:リッチな形式でツリーを表示したツールチップ)

見た目で分かるかもだけど、これらのツリーを生成する部分は共通のpseudoTreeBuilder(疑似ツリー)というモジュールになってる。

この疑似ツリーは、ちょっと前までのバージョンでは、タブの数が多くなるとそのままツリーが下に伸びていって、ウィンドウやツールチップの高さに収まらなくなった分はスクロールして見るという仕様になってた。 元々、おまけの機能だからそれほど作り込む必要性も感じてなかったんだけど、未処理のissueがたくさん溜まってたのを断捨離してたらその中に「このツリーをマルチカラム表示して欲しい」という要望があった事に気がついた。

このエントリを書くにあたって改めて調べ直してたら、別のもっと古いissueでは否定的なことを自分で言ってたみたいなんだけど、実際自分で使ってて「長いタイトルが折り返されるという事もなく巨大なツールチップが画面を覆い尽くして、そのくせちびちびスクロールしないと全項目は確認できない」という状況に意外とイライラしていたため、この機会に「じゃあやってみっか」と手を着けてみた。

で、最終的にはどうにかそれらしくマルチカラム表示できるようなった。

(画像:タブの中で疑似ツリーがマルチカラム表示されている) (画像:ツールチップの中で疑似ツリーがマルチカラム表示されている)

基本的にはCSSのマルチカラム機能を使ってるんだけど、ツールチップの方で僕の意図したとおりの結果を得るためにはちょっと工夫が必要だった。

「ツールチップの方で」と限定したのは、後で分かったんだけど、タブの中に表示する方はそこまで苦労しなくても勝手にいい感じにGeckoがレンダリングしてくれるから。 なので先にそっちの方から書いていきます。

コンテナの幅や高さが最初から決まっている場合(コンテンツ領域の中)

先に要件を整理しよう。

  • 項目をコンテンツ領域の中で2~3列くらいで段組表示したい。
  • 個々の列はコンテンツ領域の高さを越えないようにしたい。 スクロールバーを出すのは横方向だけにしたい。

まずコンテナとして、コンテンツ領域いっぱいに広がるボックスを置いておく。 コンテナには、内容が溢れたらスクロールできるようにoverflow:autoも指定しておく。

#tree {
  overflow: auto;
  box-flex: 1;
  -moz-box-flex: 1;
}

で、この中に置くツリーをマルチカラムにしたいわけなんだけど。

CSSのマルチカラム表示を有効にするだけなら、マルチカラムにしたい内容を含む上位の要素に列の数か幅を指定してやればいい。

2列なら、こう。この場合、列の幅はコンテナの半分になる(自動的に決定される)。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-count: 2;
  /* -moz-columns: 2 auto; と同等 */
}

列幅固定なら、こう。この場合、コンテナの中に1列しか収まらない時はマルチカラムにならないという、いわゆるレスポンシブデザイン的な挙動になる。

#tree > .treestyletab-pseudo-tree-root {
  -moz-column-width: 20em;
  /* -moz-columns: auto 20em; と同等 */
}

列数固定だとウィンドウの幅が広いときに余白ばかり広がってしまってあまり嬉しくないので、ツリー型タブではとりあえず主観で20emと指定してみた。 自分の使ってる環境では、これでだいたい2~3列になる。

マルチカラム表示したいツリーを構成しているXUL要素の中で、内容が複数列に泣き別れてもいい物(=「1つのタブ」に対応する「アイコンとラベル文字列のペア」以外のすべてのボックス)は明示的にdisplay:blockにしないといけない。 display:-moz-boxのままだと、要素の作るボックスは途中でぶった切られる事が無いので、マルチカラム表示にはならない。

/* required to apply CSS multi columns */
#tree > .treestyletab-pseudo-tree-root,
#tree > .treestyletab-pseudo-tree-root vbox {
  display: block;
}

当初はこれに気付いてなくて、わざわざXHTMLとXULを混ぜて実装してた。 でも、後になってからdisplayの値を変えればXUL要素でも問題ない事が分かったので、今は全部XULに戻してある。

問題1:縦に長くなる

しかし、これだけだと項目の数が多い時に「縦に長いツリーがマルチカラムで表示される」「画面に収まらなかった部分は下にスクロールしないと見えない」ということになる。

(画像:縦方向にスクロールバーが出ているのが分かる)

こういうのは一覧性が悪くて死ねる。 だって、左の列を上から順に下まで見た後、次の項目を見るにはまた一番上までスクロールし直さなきゃいけないんですよ? あり得ないでしょ……

つまり、各列がコンテンツ領域の高さより高くなられると困る。 項目の数が多いときは、「縦に長ーいリストを2列で表示する」んじゃなくて、「コンテンツ領域の高さに収まる短いリストを3列、4列と列を増やして表示させる」方がいい。 ということ。

じゃあってんでさっきの要素に最大の高さを100%と指定してみても、これは効果がなかった。相変わらず、親のボックスの大きさを超えてスクロール可能なまま。

#tree > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  height: 100%;
}

なので、resizeイベントでコンテナの高さを調べて、その都度ピクセル値で適切な高さを指定するようにしてみたら、ようやく縦のスクロールバーが消えて横方向にスクロールできるようになった。

...
  this.window.addEventListener('resize', this, false);
...
handleEvent : function GT_handleEvent(aEvent)
{
  switch (aEvent.type)
  {
    ...
    case 'resize':
      return this.onResize();
    ...
  }
},
...
onResize : function GT_onResize()
{
  ...
  var container = this.document.getElementById('tree');
  var tree = container.firstChild;
  PseudoTreeBuilder.columnizeTree(tree); // ここでcontainerの高さをtreeのheightにピクセル値で指定し直す。
},

CSSのマルチカラムでは「列の数」の指定は絶対ではなくて、コンテナの幅と高さから溢れた分の内容は自動的に列の数を増やして表示するようになってる。 コンテナがoverflow:autoになっていれば、溢れた分は横スクロールすれば見える。

問題2:下に余白ができる時がある

全体の高さを指定していても、項目の数が「全部1列で表示するには多いが、2列を埋めるには足りない」程度の時には、領域の下の方に妙に余白が出てしまう。

(画像:バランス良く配置されたせいで下の方に余白ができている)

これは-moz-column-fill:balanceという初期値のせい。この指定の時には、各列の内容の高さがなるべく揃うようにレイアウトされるため、結果として下の方に余白が産まれることになる。

まぁそれはそんなに悪くはないんだけど、今までコンテンツ領域の高さいっぱいまで並んでた物が急にその半分とかくらいの高さになるのは気持ち悪い。 違和感が無いのは、やはり、領域の高さに収まらなくなった分からちょっとずつ次のカラムに流し込まれていくという感じだろう。

そのための指定が-moz-column-fill:autoで、これを指定しておくと、1列目は下まで項目が埋まって、入りきらなかった分だけが2列目に溢れるようになる。

(画像:1列目は高さいっぱいまで項目があり、溢れた分だけが2列目に行っている)

実は、この-moz-column-fill:autoを使うのにも、ボックス全体の高さを明示的に制限しておく必要がある。 というのも、-moz-column-fill:autoの時は「高さが指定の高さに収まらなかったときに、その分を次のカラムに流す」という事になるので、高さが未指定だと「内容が溢れることもないからいつまで経ってもマルチカラムにはならず、縦にどんどんリストが伸びていく」という結果になってしまう。 ……というわけなので、やっぱり全体の高さはスクリプトで動的にピクセル値で指定してやらないといけないのでした。

コンテナの幅や高さが決まっていない場合(ツールチップ)

こちらも要件を整理する。

  • ツールチップの中で項目を2~3列くらいで段組表示したい。
  • ツールチップは画面を覆い尽くすほどの大きさにはなって欲しくない。 最大の幅と高さは制限したい。
  • 必要以上の余白は要らない。 小さく表示して問題無いなら、小さく表示したい。

ここで曲者なのが3つ目の要件。 コンテンツ領域の中であれば「与えられた領域の中を最大限使って、残りは余白扱いにすればいい」ということになるんだけど、ツールチップだとそうもいかない。 というか、必要最小限のサイズで出てくれないと困る。 「内容を表示するのに必要な最小の大きさをどうやって決めるのか?」 これがツールチップの場合の難しい所だ。

先に結論を言ってしまうと、「とりあえず許されてる最大の大きさで試しにレンダリングしてみて、その後、実際に必要な大きさで改めて表示し直す」というのが答えになる。

大きく表示してから小さく縮めるのって格好悪くない?

とはいうものの、ツールチップを1回表示した後そこから小さく縮めるというのは見た目が宜しくない(一瞬だけとはいえ目に見える状態で大きなツールチップが出るというのはイラつきますよね)。

じゃあどうすれば?という事になるんだけど、逆転の発想というかなんというか、ツリー型タブでは「そもそも最初からベストのサイズで表示する事は諦める」という解決策をとっている。 どういう事かというと、いきなり違うサイズのツールチップを出すとギャップで見当識を失うので、まずは元の普通のツールチップと同じ大きさ・位置で1回表示して、そこからアニメーションでツールチップを必要な大きさまで拡大する、という事をしてる。

(画像:ツールチップの大きさが拡大される様子のアニメーション)

ツリー型タブの高機能なツールチップはだいたい元のツールチップよりは大きくなるので、この「1回小さめのサイズで表示した」状態で必要なサイズを確定させて、後からゆっくり拡大すればいい。

元より小さなツールチップで何故「最大の大きさで試しにレンダリングしてみる」ができるのかというと、XULのarrowscrollbox要素を使ってるから。 これは元々は、ツールチップが画面いっぱいに広がっていてもまだ内容が収まりきらない時のための対策として使っていた。 arrowscrollboxは中にどんなに大きな物を置いてもそれ自体やその親(ツールチップ)の大きさが広がらないので、安心して「めいっぱい大きく表示して、最小限必要なサイズをその結果から割り出す」ということができる。

(画像:arrowscrollboxの中で大きなツリーが準備されている様子)

ツリーを収めるのに最適なサイズの求め方

「幅も高さもいい感じにする」というのは難しいので、ツリー型タブの場合はまず「画面の全体を覆い尽くさない程度」ということで画面の高さの70%(例えば画面が縦1024ピクセルなら、その7割の717ピクセル)を最大の高さとして、その中でマルチカラム表示の指定を反映させている。

arrowscrollbox > .treestyletab-pseudo-tree-root {
  -moz-columns: auto 20em;
  -moz-column-fill: auto;
  height: 717px; /* ←実際にはその都度計算する */
  max-height: 717px; /* ←実際にはその都度計算する */
}

こうすると、高さについては指定の高さまでに収まって、幅は必要な分だけ自動的に列が増えていくという形で、ツリー全体を収めるのに必要な領域を確定する事ができる。

高さは既に分かっているので、残るは幅なんだけど、これはツリー自体のboxObject.widthclientWidthからは分からない。 というのも、この時点でのマルチカラム表示されたツリーは「列の幅は指定されているが列の数は指定されていないので、要素自身の幅を超えて列が表示されている(可能性がある)」という状態で、要素自体の幅を調べても意味がないのです。 overflow:autoになってる要素の幅を計測しても中身の幅は分からない、というのと同じね。

こういう時の「本来の内容の幅」は、RangeのgetBoundingClientRect()で調べられる。 これは、DOMのRangeが含んでいる範囲の全要素が収まる矩形の位置や大きさを計測してくれるという便利なAPI。 これを使ってツリーの要素の「内容を」選択してそのサイズを測れば、ツリー全体を収めるのに本当に必要な最小のサイズが分かる。

var range = tree.ownerDocument.createRange();
range.selectNodeContents(tree);
var rect = range.getBoundingClientRect();
range.detach();

(図:ツリーの幅の計測基準)

あとは、ツールチップ全体を広げるだけ。 「ツリー全体を収めるのに必要な幅」と「画面の幅さの80%(例えば画面が横1280ピクセルなら、その8割の1024ピクセル)」のどちらか小さい方をツールチップの新しい幅にしてやる。 ツリーはツールチップの中のarrowscrollbox要素の中に置かれているので、ツールチップがツリーよりも小さければ、はみ出た部分はスクロールして見る事ができる。

まとめ

以上、マルチカラムなポップアップをいい感じに表示するためのノウハウの話でした。

実は、ここに書いた内容はmasterのHEADでの話で、解説を書くにあたって調べ直した結果の洗練された内容になってる。 今リリースしてるバージョンでは、そこまで洗練されてない状態の(でもだいたい同じような結果を得られている)コードになってるので、どこが無駄だったのかを洗い出して「こいつ技術力低いなあ!」と笑いものにしてくれていいです。

それにしても、昔なつかしNetscape Communicator 4では、フォルダの中にブックマークが大量にあるときはこんな風に段組表示してくれて一覧性が高くてものすごく便利だったんだけど、Geckoエンジンベースで作り直されたNetscape 6以降や、その流れの先にあるFirefoxには、その機能はついぞ引き継がれなかった。 業を煮やして複数のポップアップを並べることで擬似的に再現できないか?という実験をしてみたこともあったんだけど、不安定でダメだった。

今だったら、このやり方で行けるんじゃないか?という気がする。 というか、SUMOの質問に寄せられてる回答でStylish用のスタイルシートが公開されてて、2つの例のうち1つはCSSマルチカラムでやるようになってたので、間違いなくできる。

ただ、例に挙がってるStylish用のスタイルシートはカラム数が固定されていて、1列でいい時にまで常に2列3列になってしまうという問題がある。 ネスケ4の頃のそれのような使い勝手を実現するには、ここで解説したような細かい調整をやらないといけないんだと思う。 僕自身はやる元気がないので、誰か代わりにやってくれないかなあ……と、実現する見込みの低そうな事をネットの片隅でこっそり呟いてみる次第です。

スクロールバーの見た目を保ったまま細くする - Feb 18, 2016

ツリー型タブではタブバーに表示するスクロールバーについて、普通のスクロールバーよりも細く表示するようにしてるんだけど、その実装方法に少し改善があったので誰得だけど解説しておく。

ちょっと前までのバージョンでは、単純にCSSのmax-widthで以下のように実現していた。

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"],
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] * {
  max-width: 10px;
  min-width: 10px; /* この指定がないと逆に最小サイズが大きくなってしまう */
}

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] {
  font-size: 10px;
}

この方法の難点は、一部のプラットフォームで……というかWindowsでスクロールバーの端のボタンの表示がずれるということ。 (スクリーンショット) スクリーンショットを見ると、「▼」がちょっと右にずれてるのが分かる。 何故こうなるかというと、Windowsのスクロールバーの端のボタンのアイコンは最小サイズが大きめに定義されているようで、それよりも小さなサイズを指定してもWindows側の最小サイズが優先されてしまうせい。

この問題は、実用上は問題がないので長らく放置してたんだけど、最近ツリー型タブのissue trackerで閉じられてないままの古いissueを断捨離してる時にもっとこうしたらいいよという提案があったことに今頃気がついて、その方針を最近のバージョンで採用してみた。 (スクリーンショット)

これはどうやってるのかというと、スクロールバーの幅を小さくする代わりに、「スクロールバーの左右にマイナスのマージンを設定して他の要素の下に潜り込ませる」ことで、擬似的に細く見せている。 何ピクセル潜り込ませるかは環境によって変わってくるので、document.getComputedStyle()なども使ってその都度計算するという面倒なことをしている。 これで、「▼」がずれるということがなくなった。

しかし、今度は副作用としてタブバーの背景色を変えている時に表示が変になるという問題が起こってしまった。 例えばClassic Theme Restorerをインストールして且つ「Mixed」スキンを選択した状態だと、水色の背景の一部がグレーになってしまってた。 (スクリーンショット) これは、先の「マイナスのマージンを設定して他の要素の下に潜り込ませる」という手法において、スクロールバーが潜り込む先となる(スクロールバーの上に載って一部を覆い隠す)ボックスの背景色の指定が必要となってしまって、決め打ちで指定した色がユーザのカスタマイズ後の色と違うせいで見えてしまっているということ。 タブバーの背景色を変える方法はuserChrome.cssやらテーマやらClassic Theme Restorerやら色々とあるので、すべての場合にマッチする万能の対策は事実上無い。

ということで頭抱えてしまって、なんとかスクロールバーの潜り込んだ部分(はみ出た部分)を綺麗に消す方法はないかと、思いついた方法を色々試してみた。

  • スクロールバー全体にネガティブマージンを指定するのがアウトなら、ボタン部分だけネガティブマージンを指定するのはどうか?(元々、ボタン部分以外はmax-widthでちゃんと細くできてたんだし)と思って試してみたら、ボタン部分だけがスクロールバーからはみ出すという結果になってしまった。 (スクリーンショット)
  • 横方向にはみ出した部分を見えなくする、というとoverflow-x:hiddenだな!と思って、スクロールバー全体にこれを指定してはみ出したボタンだけをどうにかしようと思ったけど、実際やってみたらスクロールバー全体の動作がぶっ壊れてしまうので駄目だった。 (スクリーンショット)

万策尽きてDOMインスペクタの画面とにらめっこしてた所、clipという名前が見えた。そういえばCSSにはそういう機能もあるんだった。試したこと無かったから、じゃあそれで行けるんじゃないか? ということで実際試してみたんだけど、これもスクロールバーのボタンには効いてくれなかった。

でも、MDNのプロパティ解説を見るとdeprecatedと書いてあって、誘導先を見たらclip-pathを使えと書いてある。 clip-pathというとFirefoxのタブの見た目などを丸くするのに実際使われてるプロパティだったはずなので、こっちなら大丈夫だろうと思って再挑戦したら、ようやくうまくいった。 (スクリーンショット) Classic Theme Restorerとの併用時にも何ら問題がないことが見て取れる。

実装は以下のようになってる。

tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] {
  font-size: 10px;
  max-width: 10px;
  min-width: 10px;
  clip-path: url(#treestyletab-box-clip-path);
}
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] * {
  font-size: 10px;
  max-width: 100%;
  min-width: 10px;
}
tabs.tabbrowser-tabs
  .tabbrowser-arrowscrollbox
  > scrollbox
  > scrollbar[orient="vertical"] scrollbarbutton {
  font-size: 10px;
  margin-left: -3px;
  margin-right: -2px;
}

参照してるSVGのクリッピングパスは以下の通り。

<svg:svg height="0">
  <svg:clipPath id="treestyletab-box-clip-path"
    clipPathUnits="objectBoundingBox">
    <svg:path d="m0,0 V1 H1 V-1 H-1 z"/>
  </svg:clipPath>
</svg:svg>

このクリッピングパスはどういう形かというと、仮想的なキャンバス全体を覆い尽くす矩形になってる。mから始まる相対指定で(0,0)から(1,1)までを覆う矩形を作っている(左上から始めて、下、右、上、左と反時計回りに進んでパスを閉じてる。最初は逆方向の時計回りに回ってしまってうまくいかなかった)ので、クリッピングパスの適用対象になるボックスがどんなサイズであってもそれと同じ大きさになる。 スクロールバーに対してこのクリッピングパスを反映すると、この矩形からはみ出る部分、つまりネガティブマージンではみ出してるボタンが切り取られて表示されるようになる、というわけ。

今回の実装自体のノウハウをそのまま流用できる場面はそうそうなさそうだけど、なんかの機会にまた使うかもしれないので、解説してみた。 要点をまとめると、「要素のスクロールの可否といった動作に影響を与えないで、見た目の表示サイズだけを小さくしたい時は、clip-pathを使うとよい」といったところでしょうか。

以上、ちっこい三角ひとつのためだけに四苦八苦させられた話なのでした。

Firefoxのマルチプロセス機能を強制的に有効化する方法まとめ - Feb 17, 2016

アドオンのe10s対応のための作業をしようと思うと当然e10s有効状態でFirefoxを動かしとかないと意味ないんだけど、まだ完成度が低いということでかe10sは事あるごとに勝手に無効化される。すぐ死ぬ。マンボウかスペランカーかってくらいに、気がついたら勝手に無効化されてる。

なので、主に法人ユーザ向けの集中管理機構を使って、アドオンのデバッグ用の環境ではe10sを有効化するための設定を強制するように設定した。 autoconfig.cfgの内容は以下のようにした。

// 1行目は必ずコメントとする。

// 基本的な有効化の設定(設定ダイアログにあるチェックボックスに対応)
lockPref("browser.tabs.remote.autostart", true);

// アクセシビリティ機能によるe10s無効化を抑止
// https://bugzilla.mozilla.org/show_bug.cgi?id=1198459
lockPref("browser.tabs.remote.force-enable", true);

// アドオンがあることによるe10s無効化を抑止
// https://bugzilla.mozilla.org/show_bug.cgi?id=1232274
lockPref("extensions.e10sBlocksEnabling", false);
lockPref("extensions.e10sBlockedByAddons", false);

これで心置きなくe10s状態でのデバッグができます。

シェルスクリプトでだいたい1時間の間隔であれをやる - Jan 03, 2016

前のエントリに引き続いて、またシェルスクリプトの話。

「1時間間隔で決まった処理を行う」という目的だと、普通に考えたらまあcrontabを使う場面ですよね。

だから素直にそうしときゃいいんだけど、シェルスクリプト製のTwitter用botで自発的な自動投稿をやらせるにあたってどういうわけか「きっかり同じ時間間隔じゃなくて、確率でちょっとだけ揺らぎを持たせたい。その方が人間くさいよね。」と思ってしまって、それをやるのに一苦労しました……という話です。これは。

目指す状態

そもそも「きっかり同じ時間間隔じゃなくて、ちょっとだけ揺らぎをもって定期実行したい」というのは、一体どういう状態のことを指しているのか。 これをはっきりさせないことには話が始まりません。

僕が思ってる事をアスキーアートで図にすると以下のようになります。

00:00 基準時刻
  |
00:15
  |
00:30
  |
00:45
  |           ̄\
01:00 目標時刻   >だいたいこの範囲で必ず1回実行する
  |          _/
01:15
  |
01:30
  |
01:45
  |           ̄\
02:00 目標時刻   >だいたいこの範囲で必ず1回実行する
  |          _/
02:15
  |
02:30
  |

人間の行動で言うと、こんな感じ。

  • 時計を持って「1時間に1回これをやるように」と言われて、時計を見てその時刻に近かったらそれをやる。
  • ちょっと早くても「まぁもうやっちゃってもいいか」ということでやる。
  • ちょっと遅くても「まぁこのくらいの遅れは大丈夫でしょ」ということでやる。
  • 「やらない」という選択肢は無い。「やべっ時計見落としてた!」と気がついたらその時点で慌ててそれをやる。

ちょっとばかり時間にルーズな人の取るような行動、という事ですね。

これをもうちょっと厳密に、コンピュータにも分かりやすいであろう表現に直すと、以下のように言えるでしょうか。

  • 目標時刻の前後N分の範囲の時間帯を、処理を実行する可能性がある時間帯とする。
    • それより早かったり遅かったりしたら実行しない。
  • 目標時刻に近ければ近いほど実行の確率は高く、遠ければ遠いほど実行の確率は低い。
    • 目標時刻ちょうどで最大確率、目標時刻のN分前およびN分後の時点で最低確率とし、その間は確率が線形に変化するものとする。
  • ただし、その範囲の時間帯が過ぎようとしている時にまだ1回も処理が実行されていなければ、時間帯の終わりで必ず実行する。
  • 計算は分単位で行う。(cronjobでも1分未満の指定はできないので)

実行確率をパーセンテージで算出できれば、あとは前のエントリでやった「何パーセントの確率であれをやる」がそのまま使えます。

となると、問題は「どうやって実行の確率を計算するか」という話になります。

今の時刻が目標時刻から何分ずれているかを計算する

先の定義に基づいて「ある時点での実行確率」を計算しようと思った時に、時刻を時刻の形式のまま扱おうとするとややこしいというか自分にはお手上げなので、「その日の0時0分を起点として、そこから何分経過したか」を使って計算していこうと思います。

そのために、こんな関数を用意しました。

# "03:20"のような時刻を与えると、00:00からの経過分数を出力する
time_to_minutes() {
  local now="$1"
  local hours=$(echo "$now" | sed -r 's/^0?([0-9]+):.*$/\1/')
  local minutes=$(echo "$now" | sed -r 's/^[^:]*:0?([0-9]+)$/\1/')
  echo $(( $hours * 60 + $minutes ))
}

これに与える現在時刻は、コマンド置換とdateコマンドを使って$(date +%H:%M)とします。 例えば現在時刻が07:58なら、以下のような出力が得られます。

$ time_to_minutes $(date +%H:%M)
478

これを「処理を実行したい時間間隔(分)」で割った余りを得ると、現在時刻が目標時刻から何分ずれているかが分かります。 60分間隔ならこうです。

$ interval=60
$ lag=$(( 478 % $interval ))
$ echo $lag
58

58分ずれている……という結果ですが、これはどっちかというと「目標時刻からマイナス方向に2分ずれている」と扱いたいところです。 なので、実際のずれが実行間隔の半分よりも大きい場合は「マイナス方向にN分のずれ」と見なすようにします。

$ half_interval=$(( $interval / 2 ))
$ [ $lag -gt $half_interval ] && lag=$(( $interval - $lag ))
$ echo $lag
2

これで「目標時刻ピッタリから何分ずれているのか」が求まったので、次はいよいよ確率の計算です。

今の時刻での実行確率を計算する

目標時刻ピッタリで確率100%としてしまうとそこで必ず実行されてしまうので、目標時刻ちょうどでの最大の確率を90%、許容されるずれの最大時点での最低の確率を10%とすることにします。

全体の振れ幅は10%から90%までの「80」ですので、「目標時刻ちょうどで100%、目標時刻からのずれが許容範囲の最大になった時を0%」とした割合に80をかけた結果に10を足せば、確率は10%から90%までの範囲に収まることになります。 式にすると、こうです。

$ probability=$(( (($max_lag - $lag) / $max_lag) * 80 + 10 ))
$ echo $probability
10

……おや? どうも計算結果がおかしいですね。 実は算術展開の$((~))は整数のみの計算なので、計算の過程で小数が出てくると小数点以下切り捨ての計算になってしまうのです。

こうならないようにするには、小数が出てこないように注意して計算するか、小数があっても大丈夫な計算方法を使う必要があります。 例えば、先に100倍してパーセンテージを求めてから後で100で割るという方法を取るなら以下のようになります。

$ probability=$(( (($max_lag - $lag) * 100 / $max_lag) * 80 / 100 + 10 ))
$ echo $probability
58

小数として計算するのであれば、数値計算用のコマンドのbcを使います。 これは、標準入力で与えられた式の計算結果を出力するコマンドなのですが、scale=1;という指定で計算時の小数点以下の桁数を指定すると、小数部を考慮した計算結果を返してくれます。

$ probability=$(echo "scale=1; (($max_lag - $lag) / $max_lag) * 80 + 10" | bc)
$ echo $probability
58.0

ただし、if [ ... ]での条件分岐では今度は整数しか扱えないので、出力される計算結果の小数部は取り除いておく必要があります。 これはsedで行えます。

$ probability=$(echo "scale=1; (($max_lag - $lag) / $max_lag) * 80 + 10" | bc | sed -r -e 's/\.[0-9]+$//')
$ echo $probability
58

ということで、ここまでをまとめて「算出した実行確率を出力する関数」にしてみましょう。

interval=60
half_interval=$(( $interval / 2 ))
max_lag=5

calculate_probability() {
  local target_minutes=$1

  local lag=$(($target_minutes % $interval))
  [ $lag -gt $half_interval ] && lag=$(($interval - $lag))

  local probability=$(( (($max_lag - $lag) * 100 / $max_lag) * 80 / 100 + 10 ))
  # 最小の実行確率より小さい時=実行する可能性がある範囲の
  # 時間帯の外の時は、確率0%とする
  if [ $probability -lt 10 ]
  then
    echo 0
  else
    echo $probability
  fi
}

同じ時間帯では重複実行しない

単にこの確率に基づいて実行するかどうかを決めるだけだと、00:55から01:05までの範囲で「実行時刻が揺らぐ」のではなく「その範囲で、確率次第で何度も実行される」という結果になります。 そうしないためには、同じ時間帯の中での再実行を防ぐ必要があります。

そのためには、最後に処理を実行した時刻を保持しておいて、現在時刻が最終実行時刻から一定の範囲内にある時は問答無用で処理をスキップする、ということになります。 とりあえず、最終実行時刻(として、00:00からの経過時間)を保存するようにしてみます。

current_minutes=$(time_to_minutes $(date +%H:%M))
probability=$(calculate_probability $current_minutes)
if [ $(($RANDOM % 100)) -lt $probability ]
then
  # ここで定時処理を実行
  echo $current > /path/to/last_done
fi

ここで保存した値を次の実行の可否の判断時に使うのですが、「最後の実行からN分間は絶対に実行しない」という条件を加えても良ければ、以下のようにできます。

current_minutes=$(time_to_minutes $(date +%H:%M))

forbidden_minutes=10
last_done=$(cat /path/to/last_done)
if [ "$last_done" != '' ]
then
  delta=$(($current_minutes - $last_done))
  [ $delta -le $forbidden_minutes ] && exit 0
fi

...

現在時刻から最後の実行時刻を引いた結果の「最終実行時刻からの経過時間」を求めて、それが指定の範囲内であれば何もしないで終了するということです。 比較の演算子が-lt)ではなく-le)である点に注意して下さい。 -ltで比較してしまうと、00:55に実行してから10分後の01:05ちょうどの時点で「10分未満の範囲で実行されていないので、再実行してよい」と判断されてしまいます。

ただ、これだけだと日付をまたいだ時に判定が期待通りに行われません。 最後の実行時刻が例えば前日23時ちょうどだったとすると、last_doneは23*60=1380ですが、現在時刻が00:04だったとすると4-1380=-1376になってしまって、負の数は「何分間は再実行しない」という指定=正の数よりも必ず小さいので、永遠に再実行されないことになってしまいます。

なので、現在時刻から最終実行時刻を引いた結果が負の場合は、「最終実行時から0時までの経過時間」と「0時から現在までに経過した時間」の和を「最終実行時刻からの経過時間」として使う必要があります。

current_minutes=$(time_to_minutes $(date +%H:%M))

forbidden_minutes=10
last_done=$(cat /path/to/last_done)
if [ "$last_done" != '' ]
then
  delta=$(($current_minutes - $last_done))
  if [ $delta -lt 0 ]
  then
    one_day_in_minutes=$(( 24 * 60 ))
    delta=$(( $one_day_in_minutes - $last_done + $current_minutes ))
  fi
  [ $delta -le $forbidden_minutes ] && exit 0
fi

...

その時間帯で投稿が無い時は時間帯の最後のタイミングで必ず実行する

ここまで来たらあともう一息。 最後は「その時間帯で必ず1回は実行する」という要件です。

とはいえ、これはそんなに難しく考えなくても大丈夫。 前項の段階で「指定の範囲内の時間での再実行はしない」という判定が既に行われているので、その判定の後であれば、「実行するべき時間帯の最後の瞬間で、その時間帯の中ですでに実行済みである」という場面はあり得ない事になります。 なので、単純に「今この瞬間は、実行しても良い時間帯の範囲の最後の瞬間かどうか?」を判断して、そうであれば確率100%で実行するということにすればいいです。

current_minutes=$(time_to_minutes $(date +%H:%M))

forbidden_minutes=10
last_done=$(cat /path/to/last_done)
if [ "$last_done" != '' ]
then
  delta=$(($current_minutes - $last_done))
  if [ $delta -lt 0 ]
  then
    one_day_in_minutes=$(( 24 * 60 ))
    delta=$(( $one_day_in_minutes - $last_done + $current_minutes ))
  fi
  [ $delta -le $forbidden_minutes ] && exit 0
fi

# 目標時刻からのずれを計算
lag=$(($current_minutes % $interval))
if [ $lag -eq $max_lag ]
then
  # ずれが、許容されるずれの最大値と等しければ、今がまさに
  # その時間帯の最後の瞬間である。
  probability=100
else
  probability=$(calculate_probability $current_minutes)
fi

...

まとめ

ここまでのコード片を全てまとめた物が、以下になります。

time_to_minutes() {
  local now="$1"
  local hours=$(echo "$now" | sed -r 's/^0?([0-9]+):.*$/\1/')
  local minutes=$(echo "$now" | sed -r 's/^[^:]*:0?([0-9]+)$/\1/')
  echo $(( $hours * 60 + $minutes ))
}

interval=60
half_interval=$(( $interval / 2 ))
max_lag=5

calculate_probability() {
  local target_minutes=$1

  local lag=$(($target_minutes % $interval))
  [ $lag -gt $half_interval ] && lag=$(($interval - $lag))

  local probability=$(( (($max_lag - $lag) * 100 / $max_lag) * 80 / 100 + 10 ))
  if [ $probability -lt 10 ]
  then
    echo 0
  else
    echo $probability
  fi
}

current_minutes=$(time_to_minutes $(date +%H:%M))

forbidden_minutes=10
last_done=$(cat /path/to/last_done)
if [ "$last_done" != '' ]
then
  delta=$(($current_minutes - $last_done))
  if [ $delta -lt 0 ]
  then
    one_day_in_minutes=$(( 24 * 60 ))
    delta=$(( $one_day_in_minutes - $last_done + $current_minutes ))
  fi
  [ $delta -le $forbidden_minutes ] && exit 0
fi

lag=$(($current_minutes % $interval))
if [ $lag -eq $max_lag ]
then
  probability=100
else
  probability=$(calculate_probability $current_minutes)
fi

if [ $(($RANDOM % 100)) -lt $probability ]
then
  # ここで定時処理を実行
  echo $current > /path/to/last_done
fi

人間くさい振る舞いをする何かを作る時の参考にしてみて下さい。

追記:もっと単純なやり方

Qiitaのクロスポストの方に頂いたコメントで、以下のようにcronjobを設定すれば良いのでは?とのご指摘がありました。

55 * * * * sleep $(( $RANDOM \% 10 ))m; (実行したい処理)

実行の可能性がある時間帯の最初の瞬間にsleepを呼び、何秒間待つかは0~10分の間でランダムに決定する。その後、やりたい処理を実行する。という方法です。

「指定の時間間隔ちょうどの実行確率を最も高くしたい」「その時間帯の最初の瞬間から最後の瞬間までの間に運用を開始した時も、すぐに動作させたい」といったいくつかの要件を除外すれば、この方法が最もシンプルですね。 というか最初この指摘を見た時には「完全に置き換え可能じゃん!」とすら思ってしまいました。 (よくよく見返して、要件のいくつかがカバーされていない事にようやく気づくレベル)

無駄に複雑な要件を全て満たそうとすると手間がかかるけれども、要件の8割9割ほどを満たせれば良いという割り切りができれば手間を大きく減らせる場合がある、「そもそも本当にその要件は必要なの?」というレベルからの再考次第で実現手法を大きく簡素化できるという、いい例だと思いました。 そのあたりの絞り込みが足りないままこの記事を世に出してしまって、お恥ずかしい限りです……。

シェルスクリプトでランダムにあれをやる - Dec 30, 2015

「何分の一で」とかの情報は出てくるんだけど、知りたかったことそのものズバリの「何パーセントの確率でアレをやる」という例がなかなか見つからなかったので、まとめてみました。

シェルスクリプトで乱数

まず根底にある「ランダムに」っていう所だけど、これはBashかそうでないかでやり方が変わる。 Bashでは$RANDOMを参照すると0から32767の範囲でランダムな結果が得られる。

$ echo $RANDOM
15999

Bash以外では、/dev/urandomodコマンドを組み合わせて似たような事ができるようだ。

$ od -vAn --width=4 -tu4 -N4 </dev/urandom
 1939740834

0~Nの範囲で乱数を得る

以下、説明を簡単にするために$RANDOMの方でコードを書くけど、違うシェルでは適宜読み替えて下さいという事で。 あと、ここからは数値計算が出てくるので、中に書いた式を計算した結果を得る$((計算式))の書き方(算術展開)を使っていく。

気を取り直して、0~Nの範囲でランダムに1つを選ぶ方法。 これは割り算の余りを使う。 乱数をNで割った余りを求めれば、0~Nのいずれかの数字が得られる。 例えば$(($RANDOM % 10))とすれば、0~9のいずれかの数字が得られる(つまり、10パターンに分岐できる)。

$ echo $(($RANDOM % 10))
0
$ echo $(($RANDOM % 10))
5
$ echo $(($RANDOM % 10))
3

1/Nの確率で何かやる

先の結果がどれか1つの選択肢に等しくなった時だけ処理を実行すれば、「約1/Nの確率で実行」ということになる。 [ $(($RANDOM % 3)) -eq 0 ]なら、約1/3の確率で真になり&&以下が実行される。

$ [ $(($RANDOM % 3)) -eq 0 ] && echo 'Run!'
$ [ $(($RANDOM % 3)) -eq 0 ] && echo 'Run!'
Hit!
$ [ $(($RANDOM % 3)) -eq 0 ] && echo 'Run!'

ここまではすぐ例文が出てくるんだけど、ここから先が出てこなかったので自分で考える必要があった。

N%の確率で何かやる

実際に「ランダムに何かをやりたい」時というのは、多分、だいたいは「パーセンテージとか割合で頻度を指定したい」って場面だと思う。 「60%の確率で分岐したい」みたいな。

これは、「1/Nの確率で」の例を発展させるとできる。 1/100までの精度だったら、まず0~99のいずれか1つをランダムに得る。 次に、これを-lt演算子(less thanだから、左辺が右辺より小さい<の意味)で「何パーセントでやりたい」という数字と比較する。 結果が真の時だけ処理を実行すれば、つまり「何パーセントの確率で実行」ということになる。

絵を描くのが面倒なのでアスキーアートでやると、

0--------------------99

こういう数直線があって

0-----+-------------99
      ↑30

この位置に線を引いて、0から99までのどれか1つをランダムに選んだ結果が線より左にある時だけ実行するということです。

 ↓この時だけ実行  ↓こっちだったら実行しない
 ○ ○   ×    × ×
0-----+-------------99
      ↑30

これを踏まえて、30%の確率でRun!という文字列を出すコマンド列なら、以下のようになる。

$ if [ $(($RANDOM % 100)) -lt 30 ]; then echo 'Run!'; fi

30の所を変えれば任意のパーセンテージにできる。 関数にするならこんな感じか。

run_with_probability() {
  local probability=$1
  if [ $(($RANDOM % 100)) -lt $probability ]
  then
    echo 'Run!'
  fi
}

ほんとに狙ったとおりの結果を得られているか、同じ物を1000回くらい繰り返し実行して確かめてみる。 与えた数の連番を出力するseqコマンドとforループを組み合わせて、先の関数を1000回実行し、Run!が出力される頻度を見てみる。 (forループの出力結果をパイプラインでwc -lに渡して行数を数えれば、実際に出力された回数が分かる。)

$ for i in $(seq 1000); do run_with_probability 30; done | wc -l
303
$ for i in $(seq 1000); do run_with_probability 30; done | wc -l
292
$ for i in $(seq 1000); do run_with_probability 30; done | wc -l
316

1000回中の300回前後なので、まあだいたい30%になっている。 ばらつきがあるけど、試行回数を増やせば指定のパーセンテージに収束していくはず。

実際は「一定の確率で文字列を出力する」というのを汎用的にやりたかったので、こういう風にした。

probability() {
  [ $(($RANDOM % 100)) -lt $1 ] && cat
}

# 95%の確率で出力→だいたいは出力される
output_message | probability 95
# 10%の確率で出力→滅多に出ない
output_message | probability 10

入力された複数行の中からランダムに1行抜き出す

ちょっと毛色が違うけど、これもついでに。

入力に対してその中からランダムに1つをピックアップするという場面では、これはQiitaにクロスポストした方の記事のコメントで指摘を頂いて知ったんだけど、そのものずばりのshufというコマンドがある。これは標準入力で受け取った内容を行ごとにシャッフルして出力するコマンドで、-nで取り出す行数を指定できるので、以下のようにすれば「ランダムに1行取り出す」という結果になる。

# 他のコマンドから渡された結果の中からランダムに1行を出力してみる
read_messages | shuf -n 1

shufコマンドの存在を知らなかった時にそれを使わずに解いてみた時には、先の「0~Nのいずれかを得る」の応用で以下のようにしてた。

choose_random_one() {
  // 標準入力を一旦変数に保持
  local input="$(cat)"
  // 入力の行数を得る
  local n_lines="$(echo "$input" | wc -l)"
  // 「1~最終行の行番号」の範囲でどれか1つを得る
  local index=$(( ($RANDOM % $n_lines) + 1 ))
  // 得た行番号を使って、sedで「指定された番号の行だけを取り出す」操作を行う
  echo "$input" | sed -n "${index}p"
}

# 他のコマンドから渡された結果の中からランダムに1行を出力してみる
read_messages | choose_random_one

入力を「行数を数える時」と「実際に抽出する時」の2回使わないといけないので、一旦全部catで読み取って変数に保持してるというのがポイントでしょうか。

まとめ

ということで、「シェルスクリプトでランダムにアレをやる」色々でした。

なんでこんな事やってるかというと、シス管系女子の宣伝を自動化したくて、宣伝用アカウントの運用をボットにやらせたかったのですが、「コマンド&シェルスクリプト」の連載なんだからボットもシェルスクリプトの方がネタになるよね&自分で作れば「お、作者はちゃんと技術分かってる人なんだな」と技術的な信頼に繋がるかな?と思って、TwitterクライアントボットをBashでゴリゴリ書いているからなのでした。 ……って、単に宣伝を投稿するだけならTwitterクライアントができた時点でcronjobでやってしまえばよかったはずなのに、「何パーセントの確率で会話を継続する」とかそんな領域に足を踏み入れてるのは明らかにおかしいですね。ほんとに「どうしてこうなった」だ。

BtoBの仕事だったり実用のアドオンだったりでしかコード書いてないと、一定の確率で何かやるという事が必要になる場面が全く無くて(確実に何かやる、という事ばっかりだから……)、ぱっとやり方を思いつけなくて参りました。 という情けないお話。

非公開のアドオンの署名手続きからのレビューの分離について - Dec 17, 2015

De-coupling Reviews from Signing Unlisted Add-ons | Mozilla Add-ons Blogの私的な翻訳です。


長いので3行で説明 – 今週中(2015年12月4日)までに、私達は非公開のアドオンに対する署名を完全に自動化し、人力レビューを発生させないようにすることを考えています。

ここ数日、アドオンの署名手続きの最初のステップについての議論がありました。「バリデータ(検証器)」として知られる一連のコードによる、アドオン登録時の機械的レビューの改善についてです。バリデータは登録されるアドオンについて様々な理由から人力のレビューを喚起し、署名手続きを中断させます。これはアドオンのリリースを遅らせることに繋がります。なぜなら、署名を要求するという要件がFirefox 43以降のバージョンでは強制されるからです。

過去には、バリデータの有用性について議論が持ち上がったこともありました。悪意ある開発者であればバリデータによる検証を回避するコードを書くことができるからです。私達は、バリデータのできることには限界があることを承知しています。現実には、あくまで既知の危険なコードに付いてしか検出できず、それでは対応できない未知の危険なコードはたくさんあります。しかし、バリデータはレビューの過程の1要素に過ぎず、私達は開発者がアドオンをより提供しやすく、それを使う人達がより安全になるようにしたいと願っています。私達はバリデータについて、完全なマルウェア検出機構として動作することを意図しておらず、むしろ、Firefoxユーザにより適切な形でアドオンを届けられるように開発者を手助けすることを意図しています。

このことを考慮して、私達は非公開のアドオンに対する関門としてのバリデーションを取り除こうとしています。私達は開発者が非公開のアドオンを提供しやすいようにしたいと考えており、従ってレビューは署名手続きとは独立して行っていくことになります。今週末(2015年12月4日)、私達は非公開のアドオンに対する署名を完全に自動化し、人力レビューを発生させないようにすることを考えています。この日付は不確定な物で、これを可能にするために必要な技術的・手続き的・ポリシー的な変更を我々がどれだけ早くできるかに依ります。今月の初めに導入されたアドオンの署名APIは、署名手続きの完全な自動化が可能となり、この件についての解決策の一部となるでしょう。

私達は、アドオン開発者の方々には引き続き、MDNで概説されているFirefoxアドオンのポリシーへの同意を求め、署名のための登録に先立ってそれらのポリシーに自身のアドオンが違反していないかを確認することをお願いしていきます。開発者の方々はアドオンのレビュアー向けガイド(アドオンがレビューを通過しなかったりブロックリストに入れられてしまったりする主な理由を概説しています)の内容も把握するようにして下さい。

私は先週を通じて得られたそれらの情報と洞察をもたらしてくれたすべての人に感謝しています。私達はアドオン開発者やユーザの方々にとってFirefox上での体験を可能な限り痛みを伴わない物にしていきたいと思っています。また、「生き辛い」やり方であるように思われる場合があったとしても、そのような生き辛さは、私達の目標には絶対に含まれません。どうか、私や他のチームメンバーに直接、気軽に意見を言い続けて下さい。

私はそれらを可能にするための次のステップのより具体的な概要を投稿するつもりで、その進捗はbug 1229197で見ることができます。前もって、あなたの我慢強さに感謝します。

kev(訳注:Kev Needham、Mozilla Corporationの雇用スタッフ。)


ということなのですが、これはあくまで非公開(unlisted)のアドオンに限定した話のようで、公開(listed)のアドオンについてはこの限りではない模様です。実際、先日新バージョンを公開しようとした公開のアドオンでは人力レビューを経なければ署名は得られませんでした。

このエントリ中では触れられていませんが、1229197 – Allow unlisted add-ons to be signed without passing the validatorでは「非公開アドオンなのであればサイドローディングの可否に依らず署名手続きにレビューが不要なようにする」という話になっているようで、実際に行われた変更でも確かにそのようになっています。

なお、このエントリでは、「アドオンが人力レビューを通過できないのでいつまで経っても署名を得ることができず、テストも配布もできない」という状況が発生していた事について、「人力レビューの通過を署名の必須要件に含めなくした」という事が述べられていますが、その後の人力レビュー自体については言及されていません。 なので、Mozillaのポリシー的に許容され得ない危険なアドオン(例えば、リンク先のローカルファイルのショートカットを自動で開くような物)については、仮にバリデータ(Linterに名前が変わったようですが)での機械的チェックを通過して署名を得られたとしても、その後のエディターによる判断で「これはマルウェア的だからやっぱりブロックリストに入れよう」とされてしまう可能性は依然としてある、と思っておいた方が良さそう……というのが今のところの自分の認識です。

ちなみに、第一報からだいぶ時間が過ぎた今頃になって何で訳してるの? と思われそうですが、それまでは「マルウェアなどの危ないアドオンを排除したいから署名義務化します、危険なアドオンはレビューで弾くから安全です」と説明されていたのが、ここに来ての「機械的チェックで問題無ければ原則署名します、危険なアドオンでもレビュー無しで署名します」への方針転換だったので、そりゃちゃぶ台返しすぎないか?!と驚いて話を鵜呑みにできずにいたからです。

しかし実際に読んでみた限りでは、確かに非公開のアドオンについては署名を完全に自動で得られるようになったものの、その後の人力レビューで弾かれる可能性は否定されていませんでした。「先に全部閉め出しておいて安全な物だけ通す」から「先に全部通しておいて危険な物だけ潰す」への方針転換だったという事になるので、タイミングはずれはするものの危険な物はやはり許容されず、一般ユーザとしては安心して使えて、開発者は依然としてレビューを通過できないことの恐怖に怯えなくてはならないという、大枠の所は変わらないようでした。やれやれ。

system-admin-girl.comのこと - Dec 04, 2015

シス管系女子の特設サイトができました、というか例によって自分で作りました。 3日ほど夜なべして。

どうしてこうなった

電子書籍はいわゆる印税契約だけど紙の方は原稿料買い切り(書籍じゃなくムックだから)なので、プロモーションに工数かけてもあまり得にならないんですよね。なのに何故やったのかというと……要するに、欲しかったんですよ!!! 僕が!!!!!

いやね、連載5年目に入ろうとしてるのにWeb上では相変わらず知名度が低くて、知ってる人は知ってる的な立ち位置がいいかげん辛くなってきたというか、この間なんて「たまたま日経Linuxを見たらこんなの(#!シス管系女子Season3 Petitまとめ読み)あったんだけど、これってもしかしてシェルスクリプトマガジンのシェル女子の便乗企画……?」みたいに思われてしまった、というのは被害妄想もいいとこなんですが、「それもこれもみんな、ここ見れば大体分かるっていう位置付けの公式サイトが無いせいなんや!」と大人げなく嫉妬に狂いまして、手元にあった素材と原稿データをイラレの上で切り貼りしていわゆる1枚ペラのページのこんな妄想画像 を作って「こういうのがほしいんだよこういうのがああああああ!! 日経Linuxのサイトの中に特設ページ作ってもらえませんかね!?」と日経BPサイドに提案してみたものの、会社の方針とかであんまり他のページと違う物は載せられないのでPDF置いとくだけならまぁなんとか……と言われてしまって「そういうことじゃないんだよおおおおお!!!」と血の涙流しながらHTMLとCSSをゴリゴリ書いてさくらのレンタルサーバの一番安いプラン借りてお名前.comでドメイン取って(独自ドメイン取るのこれが初めてですよ! なんと!)突貫工事で作った、というのが真相ですハイ。 説明文が「Piro氏」とか微妙に客観なのは、元が日経BPへの提案用だったからで。

3日でできたのは十数年越しのイメトレのおかげや……

思い返せばかれこれ14~5年は前ですかね? CSSコミューンで偉そうなこと言ってて「Web業界に進みたいな」とか一瞬思ってたあの頃。 CSS2の仕様通りに書いた物をInternet Explorerが微妙にまともにレンダリングしてくれなくて、それでもNetscape Communicator 4での悲惨な対応具合に比べればまだマシというネスケ派の自分にとっては「ギギギ……!」と歯ぎしりせずにはおれない状況で、NC4でもIEでもきちんと表示できてW3C的にも(というかAnother HTML-lint的に?)Validで且つそこそこ凝った見た目を実現して「W3Cの理想は非現実的な絵空事なんかとちゃうんやで!!!」と世界の片隅でアピールしたい!という思いからこんなスタイルシートあんなスタイルシートそんなスタイルシートといったいろんな実験作を書いて粋がってた日々。 当時最もCSS2の実装が進んでたGeckoエンジンでさえできることは全然限られていて、「あぁ……この仕様にある:nth-child(2n-1)ってのが使えれば装飾の左右振り分けも簡単なのに……!」と思いながらclass="even"とかclass="odd"とか書いて騙し騙しやってましたとも。 ドロップシャドウひとつ作るのにも画像を作ってスライスして……よくあんなめんどくさいことやってたもんだ。 しかも人力で。 (素人でお金も無いのでDream WeaverだのFireworksだのは手が出なかった)

素人の僕でこんなんだった訳だけど、当時から業務として手がけておられた方々は僕なんかよりはるかに切実な思いでこういう事と向き合っていたのであろう。 CSS昔話 Advent Calendar 2015 - Adventarにはそういう時期の苦労話がたくさん集まってきそうな気配を感じている。

それが、今じゃどうですか。 文字の影text-shadowボックスの影box-shadow角丸border-radiusも背景色の半透明もグラデーションも奇数番目と偶数番目での振り分け:nth-child(2n-1)も、テキストでちょちょいっと書けば即反映ですよ。 当時は想像もしてなかった、CSSメディアクエリーなんて物もできてるし。 あの頃「こういう風に作れればいいのになあああ!!!」とイメージトレーニングしていた理想のCSS世界がまさに目の前にあるという感慨深さよ。 document.querySelectorAll()で要素をガッと集めてきて制御したり、今画面内にある画像をdocument.elementFromPoint()でダイレクトに取得して位置合わせしたり、Firefoxのアドオン開発でも苦労してた部分があっさりクロスブラウザで動いてくれちゃってて。 FirefoxとChromeのWindows版とAndroid版でだけ検証してリリースしちゃいましたが、後でIE11で見たら全く支障なく完璧に表示されてたし。 あまりのあっけなさに目がテンになり、その後感涙でむせび泣きかねない勢いでした。

SNSでシェアしやすく

……とまあ、昔取った杵柄で一枚ペラ&試し読み簡易マンガビューワーを作るところまではよかったんですが、宣伝のためのサイトなのにSNS連携のシェア用ボタンを入れてなかったり、FacebookやTwitterでシェアされたときにいい感じに画像を出す工夫をしてなかったり、「それやらないの今時あり得ないでしょ……」な手落ちだらけで、「エッなにそれいつの間にそんな事になってたの」と完全に浦島太郎でした。 かろうじてGoogleアナリティクスは存在を知っていたので、それだけは入れてましたが。

皆さんの助けが無ければ、作ったはいいものの結局やっぱり誰にも見られない廃墟サイト化一直線……という末路を辿っていたところでした。 大変お世話になりました。

良い物作ってるつもりでもプロモーションできてなきゃ存在しないのと同じ

思い出話8割に実用情報2割くらいでこんなエントリを書き記して何やってんのって自分でも思いますが、今僕が主たる仕事の場にしてるフリーソフトウェアの世界でも、プロモーションはやっぱ大事だなって思うんですよね。 先行実装があってそれなりに頑張って丁寧に作ってたのに、その存在を知らなかった人が後から作った荒い出来の物が「これ新しい!!! こんなの欲しかった!!!!」って人気をかっさらってって、先達の頑張りが全く誰からも評価されないまま消えていってしまう……俺達は、あと何回そんな悲劇を目にすればいい? 俺はあと何回、顧みられることの無い先駆者を目にすればいいんだ?

というわけで、今後仕事絡みで何か作って公開する時にまた参照したいので自分用のメモとして今回参照した情報をまとめたというのがこのエントリの趣旨なのでした。

まぁ、このsystem-admin-girl.comが実際どれだけ成果が出るかというのは分からない、ともすれば結局やっぱり廃墟になっちゃったねというオチもあり得そうではありますが、「だからこういう風にして欲しかったんだよぉぉおおおお!!」と地団駄踏んで不満溜め込んでるよりは、「思ってたやりたかったとおりの事やったけど駄目でしたわハハハ……」となる方がまだ精神衛生上良さそうなので、これで安心して眠れます。 おやすみなさい。

Page 6/240: « 2 3 4 5 6 7 8 9 10 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき