hogashi.*

日記から何から

ブログのpreにコピーボタンつける

 素朴に button をつけて pre の innerText を textarea に入れてコピーする感じで書いた。たまに便利。

f:id:hogashi:20210924231303p:plain
こういう感じ
/* pre に copy ボタンつける */
[...document.querySelectorAll('article.entry pre')]
.map(pre => {
  const button = document.createElement('button');
  pre.parentNode.insertBefore(button, pre);
  button.textContent = 'copy';
  button.classList.add('hogashi-pre-copy');
  let timer;
  button.addEventListener('click', () => {
    const textarea = document.createElement('textarea');
    pre.appendChild(textarea);
    textarea.value = pre.innerText;
    textarea.select();
    document.execCommand('copy');
    pre.removeChild(textarea);
    button.textContent = 'copied';
    clearTimeout(timer);
    timer = setTimeout(() => {
      button.textContent = 'copy';
    }, 500);
  });
});
.entry .entry-inner pre {
  padding-top: 20px;
}
button.hogashi-pre-copy {
  top: 20px;
  left: calc( 100% - 34px );
  position: relative;
  padding: 2px;
  font-size: .8em;
  cursor: pointer;
}

 数日旅行に出ていた。一人で行く旅行は多分初めてで、連れて行ってもらったときの記憶とか出張のときの記憶とかを頼りに、なんとか行って帰ってきた。それなりにゆっくりできたとは思いつつ、初めてというのがあってか、充電できたとか心機一転とかはそれほど感じられていない。どうしても携帯電話とタブレットくらいは持って行きたくてやはり毎日欠かさず画面を見ていた、というのもあるかも。あと旅行先は過ごしやすかったけど帰路の大きな駅はそこそこ人が流れているので疲れた。もともと移動で疲れるタイプなので、旅行先の滞在日数をもう少し増やすと旅行全体で見た元気の収支がプラスになるかもしれないので、そのうち長めにどこかにいきたい。ともあれ旅行の自信が少しついたので良かった。地理に強くないので観光にそれほど興味がなく、泊まりたい宿を選んだ感じだったので、次は見たいものから考えてみても良いかもしれない。

f:id:hogashi:20210924015744j:image

f:id:hogashi:20210924020158j:image

 「元気を失ったけど気合いで外出する必要がある」と「元気を失ったので気合いで外出する必要がある」はどっちもそれなりに意味のある文で、前者は「外出する必要がある」が新情報で「元気のないときに外出するには気合いが必要」は前提、後者は「外出する必要がある」が前提で「元気のないときに外出するには気合いが必要」は前提でも新情報でも大丈夫な補足情報になる。前者は外出に、後者は気合いにイントネーションの強調がされる気がする。後者は「必要がある」が気合いと外出どっちにかかるのか微妙になるので、はっきりさせるなら「元気を失ったので外出するのに気合いが必要」となりそう。

 日々たまにこういうことを考えて暮らしています。

cpanfileをなんとなくパースする正規表現書いた

github.com

 Module::CPANfile のテスト (t/parse.t) を見ながら JavaScript でつくりました。まだ完全ではなさそう (他にもテストあるし) だけど、それなりになったのでオ〜という気持ちで、うまく使って何かやりたい。文字種とかかなり雑なので、読む用がよさそう、バリデーションとかには向きません。
 README に書いてるけどこういう感じになる。

[
  [
    '\n' +
      '# https://github.com/miyagawa/cpanfile/blob/5e89c54bb388402db3a0bb61a44de875860df3d1/cpanfile\n'
  ],
  [
    "requires 'CPAN::Meta', 2.12091;",
    "requires 'CPAN::Meta', 2.12091;"
  ],
  [
    "\nrequires 'CPAN::Meta::Prereqs', 2.12091;",
    "requires 'CPAN::Meta::Prereqs', 2.12091;"
  ],
  [
    "\nrequires 'parent';",
    "requires 'parent';"
  ],
  [
    "\n\nrecommends 'Pod::Usage';",
    "recommends 'Pod::Usage';"
  ],
  [
    '\n' +
      '\n' +
      'on test => sub {\n' +
      "    requires 'Test::More', 0.88;\n" +
      "    requires 'File::pushd';\n" +
      '};',
    "requires 'File::pushd';"
  ]
]

 ちなみに実装にあたって、正規表現をバリバリ組み合わせられる便利関数をつくった。

  • '/abc\n/' みたいな RegExp オブジェクトは
  • .toString() すると '/abc\\n/' と (必要に応じてエスケープされた) スラッシュ付きの文字列になるので、
  • 前後のスラッシュを取り除いてから
  • 文字列結合して new RegExp() し直すことで

組み合わせる作戦。
 その結果の正規表現なので、実体を見ると激ヤバとなっていて、これを直に書くのは無理ですねという気持ちになった (Perl だったら正規表現で改行とインデントしまくれるので書けるのかもしれない) 。
 追記: その後ちょっと長めの cpanfile で試したら 15分経っても終わらなくて、色々見直したら 1秒かからずにすっと行けるようになったし、正規表現が 1607文字 → 496文字と大幅に減った。規模としては、もし直で正規表現を作り上げるとなっても「大変だけど面白い」と思えそうなくらい。

/(?:\s*(?:((?:(?:requires|author_requires|configureRequires|test_requires|conflicts|recommends)\s*(?:(?:[^'"]+|['"][^'"]+['"])(?:(?:\s*(?:,|=>)\s*)(?:[0-9.]+|(?:['"][^'"]+['"])))?)(?:\s*;)))|(?:on\s*(?:[^'"]+|['"][^'"]+['"])(?:\s*(?:,|=>)\s*)(?:sub\s*\{(?:\s*(?:((?:(?:requires|author_requires|configureRequires|test_requires|conflicts|recommends)\s*(?:(?:[^'"]+|['"][^'"]+['"])(?:(?:\s*(?:,|=>)\s*)(?:[0-9.]+|(?:['"][^'"]+['"])))?)(?:\s*;)))|#[^\n]*(?:\n|$)))*\s*\})(?:\s*;))|#[^\n]*(?:\n|$)))/g
$ pbpaste | wc -c
     496


 Module::CPANfile とはこれ。 Perl のモジュールで、パースするのに cpanfile をエイッと eval するつくりになっていて、 JavaScript にそのまま持ってくることは不可能だった (Node 版 PPI がもし存在すればできるのかもしれない)。 metacpan.org

 ちなみに npm にある ppi は画像の EXIF からピクセルパーインチをゲットするやつっぽい。そもそも「node PPI perl」とか検索すると木としてのノードということで普通に PerlPPI が登場してしまって本当に存在するのか確かめるのが難しかった。

日記

f:id:hogashi:20210802205133j:image

 今日はなぜかかなり滅入っていて、ひとまず歩くかと思って外に出たけど、目当ての店は閉まっており、そうか〜とか言いながら川まで来たりしている。下りたらところどころ花火の煙が上がっていて、地面から蛙と虫の声がして、水が背景になっていた。

 目に映る景色のうち、かなりのものが残せるようになった。言葉は元より、写真も技術の進歩が進み、きめ細かで鮮やかな場面を切り取る。切り取ってからさらに手を入れている、という非難もあるけど、少なくとも自分がそのときを思い出すための道具としてならどうあってもいいと勝手に思っている。

f:id:hogashi:20210802205930j:image

 まだ難しいなと感じるのは連続したシーンで、録画などをしてもあまりしっくりこないことが多い。どちらかといえば、その残した録画を見たときに、当時のハッとした心持ちを思い出せない。これは、技術的に写真ほど細やかに残らないからなのか、残してみるとなんだそんなものかとなってしまうからなのかわからない。後者は写真にも少し感じるものだけど、動画で顕著だと感じる。

f:id:hogashi:20210802211406j:image
 ところで、普段おもしろいと感じるときのひとつに、わからない部分がある光景を、自分の頭の引き出しをどんどん開けて理解しようとするとき、があるのではないかと思っている。落語を見て実際のシーンを勝手に思い浮かべるのが典型的だけど、映画でも速さや重さなどをイメージして確かに疲れそうと考えたりする。

 似た話題で、録画に対して考える余地を見出だせず、つまり記憶を呼び起こせないので、当時の気持ちとずれるのかも、と考えることはできそう。写真は時間の軸の分の余地があるので、より思い出す余地が残されているということになる。

f:id:hogashi:20210802212853j:image

 情報量が減ればよいというものではなくて、それぞれの量によって生まれる余地が変わりそう。言葉だけなら色も想像することになるし、録画でも気温やその前後なども思い浮かべられるはずではある。個人的に今そのバランスが合うのが言葉〜落語〜写真くらいなのかな、と思った。

f:id:hogashi:20210802203823j:image

 結局今日は渡ったことのない橋を渡れたので収穫があった。そのうちパックマンみたいに歩き尽くしてしまうだろうけど、幸いこちらは忘れることができるし、意外と道の様子ががらりと変わることもあるはず。ここに楽しむ余地がある。

 

>はてなインターネット文学賞「記憶に残っている、あの日」

 

Chrome拡張機能をManifestV3に上げる活動

 規模の小さい chrome-usercss-hogashi - Chrome ウェブストア から始めてみている。

 Manifest V3 migration checklist - Chrome Developers を見ると結構色々ある。いっこずつ対応していったらよいのだけど、難関だったのは background pages の Service Worker 化。

 色々 Google で検索しまくった結果、 localStorage を使っていたので Service Worker として invalid だったっぽい。のだけどエラーメッセージに何も現れてこなくてめちゃくちゃむずかった。

 Service Worker で使える localStorage の代替を調べると、 chrome.storage というのがある。これは Chrome 拡張機能の機能として存在していて、 permission に storage とか足すと使える。

developer.chrome.com

 データとしては key/value なのだけど、呼び出しが非同期になっている(あと callback 形式)ので、単純に置き換えはできなくて、ちまちま Promise に包んで使うように変えたりした。

 これでいいかと思ったらもう一回ハマって、 background pages からの sendMessage への応答 (sendResponse) が非同期だとおかしくなることがわかった。具体的には Unchecked runtime.lastError: The message port closed before a response was received みたいなエラーが出ていて、 sendResponse で返したはずの値が返っていない。

stackoverflow.com

 この stackoverflow の回答はめちゃくちゃおもてなし度があって、 Chrome 拡張機能を使っている側としてはそのエラーを出してる拡張機能を消したら解決するけど、開発側としてはそのエラー自体への対処を知りたいよね、そういう目的で見に来た人に向けて書くね、みたいな内容で助かった。

 それっぽいコードを書くと、 Promise が返る関数を呼んだとして、その then で sendResponse するような書き方にしたときにこうなる。

chrome.runtime.onMessage.addListener(() => {
  myAsyncFunction().then(result => {
    sendResponse(result);
  });
});

 MDN を見ると、非同期に返す方法は 2通りあると書かれている。

To send an asynchronous response, there are two options:

  • return true from the event listener. This keeps the sendResponse() function valid after the listener returns, so you can call it later. See an example.
  • return a Promise from the event listener, and resolve when you have the response (or reject it in case of an error). See an example.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage

 イベントリスナから Promise を返すほうでやってみたけど、全然うまくいかなかった (なんかミスっているかもしれない……)。ここで Chrome 拡張機能API ドキュメントを見に行くと、非同期ならイベントリスナからは true を返してくれと書かれていた。実際 true を返すほうでやって解決した。 Chrome 拡張機能のほうは Promise を返すほうに対応してないのかも?

This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).

https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage

 のでこういう感じでできた。

  chrome.runtime.onMessage.addListener(() => {
    myAsyncFunction().then(result => {
      sendResponse(result);
    });
+   return true;
  });

 chrome.storage に切り替えるけど、 localStorage のほうで育ててきた UserCSS が消えるともの悲しいので、ちゃんと localStorage からのデータ移行もやることにしている。 onInstalled で自動で移行するので基本的には意識しなくていいようにしたけど、何かの拍子におかしくなったとき用に、 localStorage からのエクスポートも別に用意した。ということは、移行中は (localStorage 使うので) Manifest V3 にはできないので、もうしばらく V2 のままでいる必要がある。

f:id:hogashi:20210801184132p:plain

 これでみなさまがデータ移行できたら Manifest V3 に上げてよい、となるのだけど、できたよ〜みたいなデータを送信したりしないことにしているので、どのくらい待つのがいいのかむずい (普通に使っていたら数ヶ月くらいで十分な気もする)。