2021年買ったもの

今年も早いもので残すところ数時間となってきました。
昨年は「今年買ってよかったもの」を紹介しましたが振り返ってみると今年はそんなに買ったものがないなと思い、買ったものをつらつらと書き残しておこうかなと思います。

notfounds.hatenablog.com

ウルトラワイドディスプレイ

オフィスではディスプレイを縦置きにしており、Slack や長めの GitHub Issue などを眺めるのが非常に便利で自宅にも欲しかったので買いました。
買ってよかったかというと微妙で、よく調べずに縦置きディスプレイとして使えそうだなーと思い買ってしまいました。
実際はアスペクト比的に縦置きは向いていないですが買い換えるのも面倒なのでそのまま使っています。
もし縦置きディスプレイが欲しいなら WQHD か 4K の 16:9 のディスプレイをおすすめします。

デスクライト

部屋の照明が一つしかなく作業机が暗くて目によろしくないので買いました。
色味や明るさを調整することができ、MTG 時は自分の顔にライトを向けることで顔色をよく見せることもできて便利です。
BenQ が出しているものが有名ですが半額以下の価格でほぼ同じような機能が使えるので個人的にはこちらで十分です。

ライミングシューズ

10ヶ月ほど履いていた2足目のクライミングシューズに穴が空いてきたので新しいものを探していました。
結局色々探した結果2足目と同じ靴を買いました。
少しダウントゥになっていて小さいホールドなどにも乗りやすいです。
機能面だけでなくデザイン的にもめちゃくちゃ気に入っています。 shop.adidas.jp

スマホ用三脚

ボルダリングの様子を撮影するために買いました。やはり客観的に見るということは大事で自分の登っている様子を後ろから撮ってみると想像と全然違っていて面白いなと思いつつ、思い通りにならないもどかしさがあります。
三脚自体は伸ばすと結構高さがありますが(室内で使う上では)割と安定します。
足と伸縮部分が別々になっていて分解しないとカバンに入らないため、毎回組み立てるのが面倒です。

無印のブラシ

こちらもクライミング関係のグッズとなっています。
もともと洋服用のブラシですが、豚毛を使っておりクライミングシューズだけではなくボテなどの大きなホールドを磨くのにも重宝しております。
www.muji.com

激落ちクロス

ライミングシューズを拭くために買いました。
少し濡らして拭くときれいにチョークを拭きとることができます。

Pixel 5a(5G)

これまで使っていたスマホは Pixel 3a でバッテリーの持ちが不安になってきたのと 5G 回線が欲しくなったので買い替えました。
Pixel 3a の前までは iPhone を使っていたのですが、Pixel *a シリーズは安価でありながら普段使いでは十分すぎるスペックがあり満足しています。
端末間のファイル送信で AirDrop が使えないのが結構辛いので AppleGoogle は頑張って規格を共通化して欲しいなと思います。
Google Pixel 5a (5G) - Google ストア

GoPro Hero9

ボルダリングとダイビングの両方で使えるカメラが欲しいなと思い自分への誕生日プレゼントとして GoPro を買いました。
結局今年はあまりダイビングに行くことができなかったのとボルダリングではスマホのカメラで十分ということで全然使い倒せていませんが来年こそは活用していきたいです💪
標準で10m防水がついているため多少雑に扱っても安心して使えるのと、圧倒的な手振れ補正が推しポイントです。 gopro.com

Sony α7IV

カメラ自体は大学生の頃から興味があったのですがなかなか踏ん切りがつかずに買わずじまいでした。
10月に同僚と伊豆大島に行き Sony のカメラを勧められたことがきっかけで今回購入しました。

海外では先行して10月末に発売された Sony の新型ミラーレス一眼で、日本では12/17発売だったのですが運よく当日に入手することができました。
正直初めてのカメラということもあり α7iii でもいいのでは?と迷いましたが、自分の性格上新しいものを買っておかないと絶対後悔するなと思ったのでこれにしました。
www.sony.jp

Tamron 17-28mm F/2.8 Di III RXD

以前 Rentio で Sony の α7ii と純正の 50mm F/1.8(SEL50F18F) レンズを借りたことがあり、最初のレンズとして広角 or 標準域、単焦点 or ズームレンズなど非常に悩みました。
ギリギリまで Tamron の標準域ズームレンズ 28-70mm F/2.8 Di III VXD G2 と迷いましたが、今カメラを買って一番撮りたいものは風景や星景写真でそれにあったレンズということでこれを選びました。
www.tamron.jp

三脚

上で書いたとおり僕はカメラで星景写真を撮りたいと考えています。
星景写真はカメラを固定し、シャッタスピードを長くして撮るため三脚は必須のアイテムとなっています。
正直重さは気にしないのでアルミでもカーボンでも良かったのですが丁度いいアルミの三脚が見つからなかったのでカーボンにしました。
実はまだ届いたばかりで実際に撮影で使えていないのですが、少し触ってみた感じ品質は良さそうだなという感想です。

実は10月頃からほぼ毎週花屋で切花を数輪買うようにしていて部屋に飾っています。
きっかけは友人の家に行ったとき机の上に(造花でしたが)花が飾ってあり素敵だなと思ったからです。
殺風景な部屋に花があると少しばかり心に余裕ができた気がしますし、蕾から咲く姿を見ると頑張ろうと元気をもらえます。

この時期は週に1回程度の水替えでも長持ちしてくれるのでとりあえずで試しやすい季節だと思います。
花屋に行くとワンコインくらいで買えるミニ花束もあるので是非飾ってみてはいかがでしょうか。

f:id:notfounds8080:20211221004421j:plain
白のカーネーションと赤リンゴ

git sync

これまで git checkout mastergit fetchgit pull を one line で書いたものを sync という alias で ~/.gitconfig に登録して使っていた

[alias]
  sync = !git checkout master && git pull origin master && git fetch -p origin && git branch -d $(git branch --merged | grep -v master | grep -v '*')

しかし最近はデフォルトブランチが mainリポジトリが増えてきており、この one liner が動かないことも多い
そこで mastermain のどちらにも対応した git sync script を作った

gist.github.com

やってくれること

  • master/main ブランチもしくは current ブランチの更新
  • マージ済みのブランチの削除

使い方

  1. 適当に path の通った場所に置き、実行権限をつけておく
  2. ~/.gitconfigsync という alias を用意し、1で保存した script を登録する
  3. git sync もしくは git sync -c を叩く

免責

ソースコードの使用に起因するいかなる損害に対しても責任を負わないものとします。

deno_std で始めるゼロからの OSS contribution

f:id:notfounds8080:20211211041437p:plain
Photo by Philipp Berndt on Unsplash

これは Deno の AdventCalendar 2021 の11日目の記事です。

qiita.com

この記事では自分が過去に deno_std に投げた PR をもとに、OSS への contribution の第一歩として deno_std はおすすめじゃないか?という話をします。
deno_std は Deno の標準ライブラリで Deno のコアチームによってメンテナンスされており、ほとんどのコードが JavaScript/TypeScript で書かれています*1

とはいえ、僕自身 OSS にバリバリ contribute しているわけではなく deno_std への PR はまだ2つ投げただけの初心者です🔰。OSS に興味があるけど「普段バグ踏んだりしてないし...」「よく聞くけど何していいのか分からない...」*2のようにまだ手を出せていない人がこの記事を読んで、自分にもできそう!やってみよう!となってくれるといいなと思っています。

少しだけ自己紹介をすると、都内で自社のWebサービスを開発しているエンジニアです。業務では主に Rails や TypeScript を書いています。
就職してから業務以外での開発が減り、なにか手を動かしたいなーと思って OSS 活動に手を出してみました。

OSS への貢献の種類

ひとえに OSS への貢献と言っても様々なものがあると思っています。

  • ドキュメントの typo やリンク切れなどの修正
  • バグ報告や自分も再現したよ!という報告
  • こういった機能あったらいいななどの機能追加の提案
  • バグの修正
  • 機能追加

etc...

もちろんそれぞれ貢献の難易度は異なりますが、どれも大事な貢献です。
また、貢献としてよく挙げられるものは上のような例だと思いますがまずいちばん重要なことはそのツールやライブラリを使うことです。*3

deno_std を選んだ理由

僕が OSS 活動する対象として deno_std を選んだ理由は以下です。

もともと Deno 自体に興味があったが、Deno 本体は Rust で書かれている。
とはいえ Rust は全く分からないし今の所 Rust に対するモチベーションは高くない。
deno_std という標準ライブラリは TypeScript で書かれてるっぽい?
とりあえず全ての issue を Watch してみよう!

大体これが今年の8月くらいでした。 その後最初の PR を投げるまでは以下のような流れです。

どうやら collections という module に新しく API を作っているっぽいぞ? ref: https://github.com/denoland/deno_std/issues/1065
配列操作がメインなので http や byte 周りの molude よりは気軽に手が出せるかも?

というわけで投げたのがこちらの PR です。

github.com

PR を投げるまでの流れとしては、元の issue 上でまず「この機能実装したいんだけど何か気をつけたほうがいいことある?」のようなコメントをしました。
それに対して他の contributer の方が「(意訳)こういうデフォルトパラメータがあると便利かもしれないから検討してみて。あと途中で処理を短絡できるときは短絡してね。」というコメントをくれたので、早速実装に入りました。

命名や引数などで少々議論があり初めての PR がマージされるかビクビクしながら見ていましたが無事取り込まれました 🎉

実際は常に collections のように取っ掛かりやすい*4機能実装があるわけではないため、自分が Pull Request を投げることができたのはタイミング的にも運がよかったなと思っています。 現在は Deno の開発方針として Node との互換性を高めるフェーズ(?)なので Node 関連の実装が多いです。
Node 互換性の対応が落ち着いたらまた機能開発などが活発になっていくんじゃないかなぁと期待しています。

「どの OSS を選べば良いかわからない」といった人は以下の項目に当てはまる物が良いと考えています。

  • 自分が普段使っている/好きな言語で書かれている
  • その OSS 自体に興味がある/好き
  • issue のラベルに good first issue のようなラベルがあり、新規参加者が貢献しやすい環境がある
  • (英語に自信がない場合) 日本人の開発者がいる*5

そういう点で普段 TypeScript を書いている人で特にこれがやりたいというものがない場合、 deno_std は割とおすすめできるんじゃないかなと思っています。

最初に何をするか

さて、この OSS に貢献するぞ!と決めたはいいものの「普段使っていてもバグなんて見つけてないしな...」という方も多いと思います。
そういった方におすすめなのは上で述べたように

  1. バグ報告に対して自分も再現したよ!というコメントをする プログラミングをする上で避けて通れないのはバグで、いくらテストコードを書いていたとしてもバグをなくすことは現実的ではありません。
    もちろんそれは OSS のプロダクトでも同じで、毎日たくさんのバグが報告され修正されています。

しかし、中には報告されたものの他の開発者の手元で再現しなかったり、特殊なケースでのみ発生するため優先度を落として対応しているものもあります。 そういったものに対して「自分の環境でも再現したよ!」とコメントすることでバグの原因がわかったり、優先度の見直しが行われ早急に対応が進むことがあり、バグが再現する環境を報告することには十分が意義があると考えています。

  1. good first issue に取り組む good first issue とは GitHub のデフォルトラベルとして用意されているもので、このページによると以下のような説明となっています。

good first issue Indicates a good issue for first-time contributors

初めて contribute するひと向けに、取り組みやすい issue などにこのラベルがつけられています。
大体そういった issue は description がしっかり書かれていたり、対応方針が明確になっていることが多い印象です。
また、よく分からなかった場合でも「この issue に取り組みたいんだけど、どうやって進めればいい?」のようなコメントをすると親切に答えてくれると思います。

注意すること

いくら OSS だからといって無闇矢鱈に PR を投げていいものではありません。
mentainer も暇ではなく限りある時間の中で PR をレビューするので、しっかりマージされる PR にするためには最低限のルールを守り reviewer の負担を下げる必要があります。

例えば以下のようなものです。

  • PR の description を書く
    「なぜこの PR が必要なのか」「この PR でなにを解決したのか」など書いておくと親切です。また、issue を紐付けておきましょう。
  • テストを書く
    変更内容だけ書いても本当に動くのか確認する必要があります。reviewer が手元でコードを動かして動作確認するのは負担になるため、なるべくテストコードを書き CI 上で動作確認できることが望ましいです。 どうしてもテストコードが書けない場合はその旨を description に書きましょう。
  • コードのスタイルを整える
    普段開発している code base とコードのスタイルが異なることは多々あります。最近は CI でコードスタイルをチェックしていたり、手元で format できる仕組みが整えられていることが多いです。
    「郷に入れば郷に従え」と言うとおり指摘されたら素直に直しましょう。どうしてもそのスタイルに賛同できない場合は別の issue を立ててみてはいかがでしょうか?
  • PR のサイズはなるべく小さくする
    OSS に限った話ではなく普段の開発を行う上でも重要ですが、気付いたら diff が大変なことになってしまうこともあると思います。
    これも reviwer の負担を下げるために大事なことなので、diff が大きいなと思ったら PR を分けましょう。

いくつか基本的な注意事項を挙げましたがプロジェクトによって気をつけなければならないことは異なります。
contribution に関しては各プロジェクトでガイドラインを用意していることが多いです。

Deno の例を上げると公式ドキュメントやプロジェクトの README.md に contribution のガイドが載っているので、PR を立てる前は一通り目を通しておきましょう。

deno.land

github.com

まとめ

自分が deno_std に PR を投げた体験をもとに、deno_std は初めて OSS 活動する人におすすめという話をしています。 また、OSS への貢献の種類と注意事項についても簡単に書いてみました*6

この記事を読んで OSS 活動に興味を持ったり、活動のきっかけになると幸いです。

(おまけ) 自分が英文書くために使っているツール

自分はお世辞にも英語が得意だとは言えないです。
OSS 活動をする上で英語でのコミュニケーションは避けて通ることはできないと考えていて、OSS 活動への参加を躊躇う理由の一つでした。 しかし、最近の機械翻訳が非常に優秀なこともあり OSS 活動への心理的ハードルは大分取り除かれました。
そんなわけで普段どのようなツールを使って英文を書いているかメモしておきます。

  • DeepL DeepL Translate: The world's most accurate translator みんな大好きなやつですね。英文書くときは大体これで再翻訳してみて意味が通じるかの確認に使うことが多いです。自分が論文読むときに欲しかった...
  • Grammarly Grammarly: Free Online Writing Assistant 自分は本当に英作文に自信がないので課金しています。ニュアンス?ライティングのスタイル指定ができたり、改善理由の説明を表示してくれるので学ぶことが多いです。
  • GitHub コミットメッセージの例文や issue の例文探したりと日々眺めています。

*1:wasm 周りで Rust が使われているようです

*2:これは去年までの自分です

*3:特に使ってみて少しでもいいなと思ったら GitHub の Star をつけるなども非常に重要です

*4:配列操作系はネットワークや非同期処理などに比べて必要な知識は少なく、普段自分で実装したり他のライブラリの実装を参考にしやすいなと感じます

*5:これは結構大きいです。まず自分の場合は心理的負担は下がるかなと思っています。また、内部の話などが聞けたりするとモチベーションなどにも繋がります

*6:僕自身完璧にできているとは思っていなくて、普段の開発で指摘されることも多いです...

URLPattern を Deno で試してみる 🦕

f:id:notfounds8080:20210917235329p:plain
Photo by Andrew Ridley on Unsplash

こんにちは、最近自分の中で Deno が熱いです。 Deno は JavaScript / TypeScript のランタイムであり、Node.js の作者である Ryan Dahl 氏が 2018年に行った「Node.jsに関する10の反省点」という発表の中で発表されました。
強力エコシステムや Permission 設定によるセキュリティの強化、ブラウザとの互換性など魅力的です。 あとロゴの恐竜がかわいいです(大事)。

deno.land

そんな訳で今回は Deno で遊んでみようと思います

はじめに

先日 9/14 に Deno の v1.14 がリリースされました🎉 (Release Notes)
日本語だとこちらの記事がわかりやすいです。
zenn.dev

自分が気になったトピックはこの辺です。

  • deno lintdeno fmt のスタイルを設定できるようになった
  • URLPattern が実装された
  • URL の parse が3倍速くなった

また、同日に Deno の標準ライブラリである deno_std の v0.107.0 もリリースされました 🎉
こちらは collections モジュールの機能追加と http モジュールの改良が主な変更点です。

Deno v1.13 で HTTP Server のネイティブ実装が stable になりました。これによって std/http は削除される予定*1だったのですが、今回ネイティブ実装を使うようになったため std/http は残されるようです。*2 また、ネイティブ実装を利用することでパフォーマンスが大幅に改善しています。

今回のリリースでは Deno で Web Server を実装するにあたって嬉しい内容が多いなという感想です。
この記事では新たに追加された URLPattern API を用いて、外部のライブラリに頼らずルーティングを行う例を示します。
簡単な Web Server であれば標準ライブラリだけで割と書けるようになっています。

URLPattern とは

web.dev

まず URLPattern とは何かについて紹介したいと思います。
URLPattern は新しめの WebAPI で、URL によるルーティングパターンを標準化するためのものです。 ルーティングで使われるパターンには明確な標準はありませんが正規表現ワイルドカード、名前付きのトークングループなどがよく使われます。 URLPattern で構文を定義することによって、ルーティングパターンの再発明を避けることが目的です。

いくつか例を挙げると次のようなパターンが利用できます。
Ruby on Rails や Express などのフレームワークを使ったことのある方にとっては馴染みのある表記ではないでしょうか。

  • 文字列の完全一致
  • /posts/* のように任意の文字に一致させるためのワイルドカード
  • /books/:idのように一致したURLの一部を抽出するために使用する名前付きグループ
  • /books{/old}?のようにパターンの一部をオプションにする、もしくは複数回一致させるために使用できる非キャプチャグループ(Non-capturing groups)
  • /books/(^\d)のように任意の正規表現による一致

引用: https://pr8734.content.dev.mdn.mozit.cloud/en-US/docs/Web/API/URLPattern

引用元のMDN(draft?)で URLPatter の Browser Compatibility を見てみると利用できるブラウザは現時点(2021/09/17)で Chrome、Edge だけようです。
ちなみに表中にはないですが、Node.js ではまだ利用できないようです。

url_pattern_browser_compatibility
URLPattern - Browser compatibility | Web APIs | MDN

使い方

ここでは簡単に URLPattern の使い方を説明します。
URLPattern はその名の通り(判定したい) URL のパターンをプロパティとして持ち、正規表現を扱う RegExp のように exectest といった実際にパターンマッチを行うためのメソッドを持つクラスです。

const pattern1 = new URLPattern("https://example.com/users/*");
const pattern2 = new URLPattern("https://example.com/users/:id");
const pattern3 = new URLPattern({ pathname: "/users/:id" });

console.log(pattern1.test("https://example.com/users"));     // false
console.log(pattern1.test("https://example.com/users/123")); // true

console.log(pattern2.exec("https://example.com/users"));                      // null
console.log(pattern2.exec("https://example.com/users/123")?.pathname.groups); // { id: "123" }
console.log(pattern3.exec("https://example.com/users/123")?.pathname.groups); // { id: "123" }

test メソッドは、与えられた文字列がパターンにマッチするかを判定し boolean を返します。
exec メソッドは、与えられた文字列がパターンにマッチするかを判定しマッチした場合は URLPatternResult というオブジェクトを返し、マッチしなかった場合は null を返します。
ここで得られる URLPatternResult は pathname.groups に名前付きグループにマッチした部分を抽出しているのですが Record<string, string> 型となっています。

他にもたくさんのパターンが利用できますが、ここでは省略いたします。

利用例

次に Deno でシンプルな Web Server を立て、そこで URLPattern を利用してみたいと思います。

Deno 公式のマニュアル を見てみると Deno 本体にある HTTP API を用いたサーバーの実装例標準ライブラリ std/http を用いたサーバーの実装例を見つけることができます。
今回はせっかくなのでネイティブ実装の API を使うようになって速くなった std/http を使ってみようと思います。

一旦公式マニュアルのものをそのまま引用します。

webserver.ts

import { listenAndServe } from "https://deno.land/std@0.107.0/http/server.ts";

const addr = ":8080";

const handler = (request: Request): Response => {
  let body = "Your user-agent is:\n\n";
  body += request.headers.get("user-agent") || "Unknown";

  return new Response(body, { status: 200 });
};

console.log(`HTTP webserver running. Access it at: http://localhost:8080/`);
await listenAndServe(addr, handler);

ルーティングなど無いシンプルなサーバーです。
deno run --allow-net webserver.ts で起動できます。
適当な path にアクセスすると Request Header から User-Agent を取得し返します。

これをベースに URLPattern を使ってみようと思います。

webserver_with_routing.ts

import { listenAndServe } from "https://deno.land/std@0.107.0/http/server.ts";

const addr = ":8080";

const patternsToHandlers = new Map([
  [{ pathname: "/" }, rootHandler],
  [{ pathname: "/ping" }, pingHandler],
  [{ pathname: "/users/:id" }, userHandler],
]);

const routingMap = new Map();
for (const [pathPattern, handler] of patternsToHandlers) {
  const compiledPattern = new URLPattern(pathPattern);
  routingMap.set(compiledPattern, handler);
}

const routingHandler = (request: Request): Response => {
  for (const [pattern, handler] of routingMap) {
    const matched = pattern.exec(request.url);
    if (matched) {
      return handler(request, matched);
    }
  }

  return new Response(null, { status: 404 });
};

console.log(`HTTP webserver running. Access it at: http://localhost${addr}/`);
await listenAndServe(addr, routingHandler);

// request handlers
function rootHandler(req: Request, matchRes: URLPatternResult | null) {
  return new Response("Hello, World!", { status: 200 });
}

function pingHandler(req: Request, matchRes: URLPatternResult | null) {
  return new Response("pong", { status: 200 });
}

function userHandler(req: Request, matchRes: URLPatternResult | null) {
  return new Response(`User ID: ${matchRes?.pathname.groups.id}`, { status: 200 });
}

いくつか新しい path を生やしてみました。
deno run --unstable --allow-net webserver_with_routing.ts で起動できます。
これは次のような挙動をします。

  • / にアクセスしたときは "Hello, World!" を返す
  • /ping にアクセスしたときは "pong" を返す
  • /users/:id にアクセスしたときは UserID: <id> を返す( は path param)
  • 上記以外の場合は status code 404 を返す

handler のインターフェースなど改善したい気持ちはありますが、基本的なルーティングを行うことができました。

おまけ:簡易 Router の実装

上で実装した例では HTTP Method による分岐は実装しておらず、それぞれの handler 内部で実装する必要がありました。
ここでは簡易的なルーティングを行うクラスを実装し、HTTP Method による分岐も実装してみたいと思います。

以下のような Router クラスを実装します。
routers オブジェクトでは HTTP Method ごとの routing map を持つようにし getpost などのインスタンスメソッド経由でルーティングを定義します。

router.ts

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD';
type Handler = (req: Request, urlRes: URLPatternResult | null) => Response;
type RoutingMap = Map<URLPattern, Handler>;

export class Router {
  routers: Record<HTTPMethod, RoutingMap>;
  constructor() {
    this.routers = {
      'GET': new Map<URLPattern, Handler>(),
      'POST': new Map<URLPattern, Handler>(),
      'PUT': new Map<URLPattern, Handler>(),
      'PATCH': new Map<URLPattern, Handler>(),
      'DELETE': new Map<URLPattern, Handler>(),
      'HEAD': new Map<URLPattern, Handler>(),
    };
  }

  get(pathname: string, handler: Handler) {
    this.routers["GET"].set(new URLPattern({ pathname }), handler);
    return this;
  }

  post(pathname: string, handler: Handler) {
    this.routers["POST"].set(new URLPattern({ pathname }), handler);
    return this;
  }

  put(pathname: string, handler: Handler) {
    this.routers["PUT"].set(new URLPattern({ pathname }), handler);
    return this;
  }

  patch(pathname: string, handler: Handler) {
    this.routers["PATCH"].set(new URLPattern({ pathname }), handler);
    return this;
  }

  delete(pathname: string, handler: Handler) {
    this.routers["DELETE"].set(new URLPattern({ pathname }), handler);
    return this;
  }

  head(pathname: string, handler: Handler) {
    this.routers["HEAD"].set(new URLPattern({ pathname: pathname }), handler);
    return this;
  }

  route(req: Request): Response {
    try {
      for (const [pattern, handler] of this.routers[req.method as HTTPMethod]) {
        const matched = pattern.exec(req.url);
        if (matched) {
          return handler(req, matched);
        }
      }
      return new Response(null, { status: 404 });
    } catch(e) {
      console.error(e);
      return new Response(null, { status: 500 });
    }
  }
}

これを使うと以下ようにルーティングを記述することができます。

server.ts

import { listenAndServe } from "https://deno.land/std@0.107.0/http/server.ts";
import { Router } from "./router.ts";

const addr = ":8080";
const router = new Router()
  .get("/users/:id", showUserHandler)
  .post("/users", createUserHandler)
  .put("/users/:id", updateUserHandler)
  .patch("/users/:id", updateUserHandler)
  .delete("/users/:id", deleteUserHandler);

console.log(`HTTP webserver running. Access it at: http://localhost${addr}/`);
await listenAndServe(addr, (req) => router.route(req));

// request handlers
function showUserHandler(req: Request, urlRes: URLPatternResult | null) {
  return new Response(`Get the user: ${urlRes?.pathname.groups.id}`, { status: 200 });
}

function createUserHandler(req: Request, urlRes: URLPatternResult | null) {
  return new Response(`Create a user`, { status: 201 });
}

function updateUserHandler(req: Request, urlRes: URLPatternResult | null) {
  return new Response(`Update the user: ${urlRes?.pathname.groups.id}`, { status: 201 });
}

function deleteUserHandler(req: Request, urlRes: URLPatternResult | null) {
  return new Response(`Delete the user: ${urlRes?.pathname.groups.id}`, { status: 201 });
}
~/URLPattern ❯❯❯ curl "localhost:8080/ping"
pong%
~/URLPattern  ❯❯❯ curl -X"GET" "localhost:8080/users/123"
Get the user: 123%
~/URLPattern  ❯❯❯ curl -X"POST" "localhost:8080/users"
Create a user%
~/URLPattern ❯❯❯ curl -X"PUT" "localhost:8080/users/123"
Update the user: 123%
~/URLPattern ❯❯❯ curl -X"PATCH" "localhost:8081/users/123"
Update the user: 123%
~/URLPattern ❯❯❯ curl -X"DELETE" "localhost:8080/users/123"
Delete the user: 123%
~/URLPattern ❯❯❯ curl -X"GET" -s -o /dev/null -w "%{http_code}" "localhost:8080/"
404%

まとめ

Deno v1.14 で追加された URLPattern という WebAPI の紹介を行い、Deno で簡単なルーティングを行う例を示しました。
URLPattern 自体によってルーティング処理がすごく書きやすくなった!という印象はないですが、ルーティングのパターンを統一することの意義は大きいと思います。

また、最後に実装した Router では HTTP Method と URL のパターンによるルーティングを行えるようにしました。 Router を自分で実装するならどうするか?という部分は面白かったですが、 エラーハンドリングやロギングなどを考えると普通に oak などのフレームワークを使いたくなりました 😇

フレームワークを使って開発をしていると URLPattern API を直接触ることはなかなか無いかもしれませんが、覚えているといざというとき役に立つかもしれないですね。

References

*1:https://deno.com/blog/v1.13#stabilize-native-http-server-api では"If you are currently using std/http we encourage you to upgrade to the native HTTP server. std/http will be available in std for a few more releases, but will be removed soon. " と書かれていて、この時点では削除する予定でした

*2:https://github.com/denoland/deno_std/discussions/1034

ContentEditable を用いた Auto Expandable(?)なテキストエリアの実装

ContentEditableとは

HTML 要素を編集可能にする attribute *1*2HTML5 で実装された。
基本的な機能はほぼすべての主要なブラウザで実装されている。 *3

どんなときに使うか?

複数行の入力欄を実装するときにまず思いつくのは textarea *4 だと思う。
しかし標準の textarea では特定の文字の色を変えたり、WYSIWYG*5 エディタのようなリッチなテキストエリアを実装することができない。
ContentEditable を用いることによってリッチなテキストエリアを実現することができる。

Twitter のツイートの入力欄にも使われている。

twitter-draft-editor
Twitter のツイート欄

今回のTopic

一般的に textarea は縦方向に対して auto expandable(適当に名付けた。autoresize?)ではない。
表示される高さ(row count)が固定されており、行数が増えた場合は scrollable になる。
行数が変わったときに要素の高さも変えたい場合、不便である。

以下のような解決手法があるが、実装が面倒であったり自然な挙動にならない場合がある。 - JavaScript を用いて仮想的にコンテンツを描画して高さを取得し、動的に調整する - JavaScript を用いて 改行 を検知し、 row count を変更する そこで、適当な HTML element に対して contenteditable を付与することによって擬似的に auto expandable な textarea を実現できる。

実際に以下のコードで試してみる。

<p classname="Editor" role="textbox" contenteditable="true">
  Hello World
</p>

contenteditable-demo
contenteditable のデモ

React で使ってみる

普段 React を使って開発しているので React で使うときの注意点をいくつか書いておく。
contenteditable で変更を行った値は React で管理していないため、正しく store していないと "変更した結果が保存されないよ!" という旨の warning がでる。*6 suppressContentEditableWarning={true} を指定すると warning は消える。

contenteditable をつけたからと言って onChange が実装されているわけではないので、onChange は動かない。
そこで onBlur + ref などを用いてフォーカスが外れた際などに保存する必要がある。 onKeyDown は動くが、マウスを使って貼り付けを行った場合などは onKeyDown event は発行されないので注意する必要がある。

(追記 2021/05/25) input event が発行されるのでそちらを使ったほうが良さそう。*7

その他

  • フォーカスしたときに ブラウザが outline を勝手につけた場合は outline: 0px solid transparent;*8 を指定することによって outline を消せる
  • role="textbox" を指定することによってブラウザに入力可能要素であることを伝えられる*9

Example

サンプルコード

/* styles.css */
.App {
  font-family: sans-serif;
}

.Editor {
  padding: 16px;
  border-radius: 6px;
  border: solid 1px rgba(0, 0, 0, 0.12);
  background-color: rgba(0, 0, 0, 0.03);
  outline: 0px solid transparent;
}

.Editor:active,
.Editor:focus {
  background-color: rgba(0, 0, 0, 0.06);
  outline: 0px solid transparent;
}
/* App.jsx */
import { useState, useRef } from "react";
import "./styles.css";

export default function App() {
  const [value, setValue] = useState("Hello, World!");
  const ref = useRef(null);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
        <p
          className="Editor"
          ref={ref}
          role="textbox"
          contentEditable={true}
          suppressContentEditableWarning={true}
          onBlur={() => {
            setValue(ref.current.innerText);
          }}
        >
          {value}
        </p>
        <p>{value}</p>
    </div>
  );
}

最後に

ContentEditable を使って Auto Expandable なテキストエリアを実現する方法について書いてみたが、実際のプロダクトではメンテナンス性や安定性を考えると third-party のライブラリを使ったほうが良い。

2020年買ってよかったもの

こんにちは NotFounds です。 今年はアウトプットをしっかりしようと思っていたのですが気づいたら前回記事を書いてから一年経っていました。

年末定番ネタ(?)の「今年買ってよかったもの」を紹介したいと思います。

家電

テレビ 📺

今年買った一番大きい買い物*1です。
テレビ番組を見ることが目的ではなくリモートワークで使うために大きいディスプレイ*2が欲しいなぁと思って買ったのですが、遅延が気になったので YouTubeNetflix のための端末となってます。
有機 EL だけあってやはりがきれいです。

www.sony.jp

ウォータースタンド

購入というかサブスクなのですが、ウォータースタンドを導入しました。
水道直結型のウォーターサーバーで、本体のレンタル代と電気・水道代で利用することができます。
浄水されているとはいえ元は水道水なのでミネラルウォーターに味は劣りますが、一人暮らしで月に使う水の量が安定せず、またボトルの置き場所やゴミ出しを行う必要がないなど自分にはあってました。
今使っている機種はタンクがないためコンパクトなのですが、お湯を瞬間沸騰させるため微妙に煩わしいです。

waterstand.jp

空気清浄機

引っ越しして間もない花粉症の時期(2・3月頃)に購入しました。
加湿器や除湿機付きの機種も考えましたがメンテナンスが面倒そうなので空気清浄機機能のみの機種にしました。
効いているのか体感だとイマイチわかりませんが、半年のタイミングでフィルターを交換したところフィルターが悲惨なことになってたので無いよりはいいのかもしれません。

store.blueair.jp

電動歯ブラシ :toothbrush:

歯磨きが下手なので買いました。
スマホアプリで磨き残しがあったら分かるらしいですが、3日ほど使ったところ褒められてばかりで信用できなくなったのでアプリは使っていません。

分離キーボード ⌨️

今年買って一番良かった家電です。
以前から分離キーボードは持っており(BAROCCO MD650L | 株式会社アーキサイト)、今回はそれの後継っぽいものを買いました*3
静音赤軸本当に打ちやすいです。また、接続が USB Type-c になったのも地味に嬉しいです。
Bluetooth対応してほしいです。お願いします。

趣味

ダイビングライト 🤿🔦

御存知の通り、光は水中で吸収・散乱するため水深が深くなるにつれ波長の長い赤色は届かなくなります。
ダイビングでは10m~20mの水深にいることが多いですが、体感だと10m以深は灰っぽい色に見えます。
水中ライトを使って照らしてあげると、サンゴや魚の実際の色を見ることができます。
また、今回買ったライトはナイトダイブにも使える明るさで実際にナイトダイブで使いましが十分でした。

Tovatec Fusion 1500 - Top Rated Dive Lighttovatec.com

ライミングシューズ 🧗👟

今年に入ってからボルダリングをはじめました。
最近やっと中級レベルのグレードに手が届くようになってきたかなという感じで成長を実感しています。
EB の Electron というフラットタイプのシューズで ザ・入門 といった履きやすいシューズです。 そろそろ次のステップに向けてダウントゥのシューズが欲しくなってきました。

eb-climbing.com

その他

鉄フライパン 🍳

引っ越ししてコンロがガスになり、せっかくなので鉄フライパンを使ってみようということで買いました。
鉄フライパンについて調べると手入れが大変だと言う声が多いですが、温かいうちにたわしで洗うだけなのでそこまで苦ではないです。
肉がいい感じに焼ける気がします。気のせいかもしれません。

ディスプレイアーム

会社からリモートワークのための設備投資として補助金が出たので購入しました。
なおディスプレイが対応していなかったので2枚あるうちの1枚しかアームにつけていません。*4
ディスプレイの足を置く必要がないので、机が少し広くなった気がします。ちなみにディスプレイを動かしたことは半年間一度も無かったです。

Alfred

今年買ったものの中で最も生産性を上げてくれました。🎉
Mac を使っているなら絶対に使ったほうが良いと思います。
普段よく使う順に並べると以下のような感じです。

  • Web Search
  • GitHub 連携
  • Snippet 機能
  • Spotify 連携
  • Application 起動・切り替え
  • Clipboard 履歴

www.alfredapp.com

*1:物理的にも財布的にも

*2:65inchが欲しかったのですが部屋の入口が無くなりそうだったので諦めました

*3:正確には MD650L の方はロープロ仕様で Cherry ML 軸を使っていますが、MD770 は Cherry MX 軸を使っており シルバー・青・茶・赤・静音赤軸から選べます

*4:空いている方にはWebカメラを付けています。地味に便利です。

論文のバージョン管理とCI

皆さんこんにちは,ちゃんと論文書いていますか?もしかするとすでに書き終わっている方もいると思いますが,私は絶賛執筆中というか筆が全然進まなくて困っています. こんな記事を書いている場合ではない

「論文(だけじゃなくて形あるもの)はちゃんとバックアップをとっておけ!」と教員や先輩に口を酸っぱくして言われていると思います.実際バックアップを取っておいて損することはないですし絶対取っておいたほうが良いです(複数の環境にバックアップは起きましょう). いつ何時データが飛ぶかわかりません.私もつい先日,7年間ほどお世話になったPCの電源がつかなくなりました...

それはさておき複数の版を管理する場合,次のようにファイル名を変えて異なるバージョンを保存していく事があるかと思いますが,最終的にどれが"本当の"最終Verなのか分かりません.

卒論.tex
卒論_1.tex
卒論修正版.tex
卒論修正版(1).tex
卒論_先生コメント.tex
卒論最終.tex
卒論rev.tex
卒論_図rev.tex
sotsuron_final.tex
sotsuron.tex
thesis.tex

このような問題を解決する方法として,バージョン管理システムを使う方法があります.
また,最近はWordやGoogle document自体に版を管理する方法があるようですがTeXファイルなどのテキストファイルの版を管理するにはやはりバージョン管理システムを使うほかありません.
バージョン管理システムと検索すると大体SubversionかGitが出てくると思いますが,近年の流れとしてGitを使うのが主流です.正直個人で利用する分にはどちらでも問題ないです.

バージョン管理システムを導入することにより論文のバージョンをいい感じに管理することができましたが,まだバックアップは取れていません.
しかし,先に述べたバージョン管理システムGithubやBitbucketなどのソースコードホスティングサービスを使用することができ,そこにアップロード(Push)しておくことでバックアップを取る事ができます. ここで特に気をつけなければならないのは,GithubやBitbucketなどのリポジトリをPrivateにしておくことです.Publicにしてしまうと第三者が自由に見ることができてしまいます.

またGithubやBitbucketにはCI(Continuous Integration, 継続的インテグレーション)のための機能が含まれており,何らかのイベント(例えばコードの変更)のタイミングで指定したアクションを実行できます.
これを使うと新しく変更を加えPushしたタイミングでTeXファイルをコンパイルし,生成されたPDFをアップロードする事ができます.
メリットとしては, - ローカルにコンパイルする環境がなくても良い(環境に依存しない) - コンパイルに失敗する変更が特定できる - バージョンとそれに対応するPDFが保存される 等があると思います.

CIの設定

ここでは,GithubとBitbucketでの設定方法を述べます.
まず双方に共通するのがLaTeXコンパイル設定を記してある以下のファイルです.
ファイル名は.latexmkrcとしてリポジトリの直下に置いておきます.

#!/usr/bin/env perl
$pdf_mode         = 3;
$latex            = 'platex -halt-on-error';
$latex_silent     = 'platex -halt-on-error -interaction=batchmode';
$bibtex           = 'pbibtex';
$dvipdf           = 'dvipdfmx %O -o %D %S';
$makeindex        = 'mendex %O -o %D %S';

Github/Bitbucketともにpaperist/alpine-texlive-jaという公開されているDocker imageを使用させていただいています. それぞれの設定方法は以下に示します.

Github

Github用の設定です.
GithubではGithub Actionsという機能を使うのですが./github/workflow/というディレクトリに設定ファイルを置くと利用できます.
ここでは次にようにmain.ymlファイルを作成し,上記ディレクトリに配置します.

name: Build
on: [push]

jobs:
  build:
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v1
    - name: Build latex file
      uses: docker://paperist/alpine-texlive-ja
      with:
        args: latexmk index.tex

    - uses: actions/upload-artifact@v1
      with:
        name: index.pdf
        path: index.pdf

これをcommitしてGithubリポジトリにPushすると自動でコンパイルを行いPDFを生成します.
ここではTeXファイルの名前をindex.texとしていますが,異なるファイル名の場合適宜設定ファイルを変更してください.
結果はGithubのActionsというタブを開くと見ることができ,それぞれのBuildを開くとの右上にArtifactsというメニューが有りそこからPDFをダウンロードすることができます. Github Actionsの注意点としてはフリープランではPrivateリポジトリに対するGithub Actionsは2000分/月しか使うことができません.(一回のコンパイルは1~2分なのでそんなに影響ないですが...)複数のリポジトリを使っている場合は気をつけたほうが良いです.

Bitbucket

Bitbucket用の設定です.
BitbucketではPipelineという名前でGithubと同様の機能があります.これはbitbucket-pipelines.ymlという名前で設定ファイルを作成し,ディレクトリの直下に配置すると利用できます.

image: paperist/alpine-texlive-ja

pipelines:
  default:
    - step:
        script:
          - latexmk index.tex
        artifacts:
          - index.pdf

これをcommitしてButbucketのリポジトリにPushすると自動でコンパイルを行いPDFを生成します.
ここではTeXファイルの名前をindex.texとしていますが,異なるファイル名の場合適宜設定ファイルを変更してください.
結果はBitbucketのPipelinesというタブを開くと見ることができ,それぞれのBuildを開くとの右上に雲のマークのボタンが有りそこからPDFをダウンロードすることができます. Bitbucket pipelinesの注意点としてはフリープランでは50分/月しか使うことができません.Githubに比べて大分短いです.ちなみにStandard/Academicプランでは500分/月使うことができるのでこちらをおすすめします.

まとめ

論文はバージョン管理しましょう.
バージョン管理には適切なソフトを使用して決してファイル名で複数の版を管理するようなことをしてはいけません. リポジトリはPrivateに! GithubやBitbucket上でのCIの設定方法を示しました.

今後論文を書く方の参考になれば幸いです.

余談

弊研究室では行っていませんが,聞いた話によると論文のリポジトリに教授を招待し,IssueやPull Requestを通してレビューや修正を行っている研究室もあるようです.
レビューに関してはそれぞれやりやすい方法があると思うので一概には言えませんが,差分表示や行単位でのコメントに対応しているのでGithub等を用いてやり取りするのもいいですね.(作業量も可視化されてモチベーション上がるかもしれない)

また,ここでは紹介しませんでしたがtextlintと呼ばれる文書校正ツールが有りこれもCIに組み込むことができます.
ゼミ資料等を作成するときに使っていますが大変便利です.時間があればいつか書くかもしれません.