Deno で簡単な重複行削除スクリプトを書いた
ファイルの重複行削除がしたかったのでスクリプトを書こうと思ったのですが、せっかくだし Deno で書いてみることにしました。
https://github.com/ushumpei/scripts/blob/main/remove_duplicate_lines.ts
import { iter } from "https://deno.land/std@0.93.0/io/util.ts"; const N = Deno.env.get("NEWLINE") || "\n"; export type M = { b: boolean; d: string[]; h: { [k: string]: boolean }; }; const i = Deno.args[0]; const o = Deno.args[1]; const f: Deno.File = Deno.openSync(i, { read: true }); const m: M = { d: [], h: {}, b: false }; const t: string = await Deno.makeTempFile(); let r = ""; for await (const ck of iter(f)) { const ls = (r + new TextDecoder().decode(ck)).split(N); r = ls[ls.length - 1]; const ot = ls .slice(0, -1) .reduce((_m: M, l: string) => { if (!_m.h[l]) { _m.h[l] = true; _m.d.push(l); } return _m; }, m) .d.join(N); if (ot.length === 0) continue; const of = new TextEncoder().encode(m.b ? N + ot : ot); Deno.writeFileSync(t, of, { append: true }); m.d = []; m.b = true; } f.close(); Deno.copyFileSync(t, o);
github にあげていて、こんな感じで URL 指定でも実行できます。
$ deno run --allow-env --allow-read --allow-write \ > https://raw.githubusercontent.com/ushumpei/scripts/main/remove_duplicate_lines.ts \ > 入力ファイル名 \ > 出力ファイル名
感想
groovy
のようにライブラリのインポート含めてスクリプトが一枚のファイルで完結するので、結構楽でいいです。しかも URL 指定でインポートできるのでより手軽。なので書くときにインポートするものをちゃんと精査しないといけないと思います。--allow-net
などオプションで権限が指定できるので、その辺もしっかり吟味していきたいです。(権限つけ忘れた時のエラー文がわかりやすいのも良い感じ)- 書いたスクリプトで宣言している変数名は一文字とかばかりなのですが、なんかテンション上がって極力短くしてみました。楽しかった。
- 環境変数による改行コードの指定は未テストで、いらないんじゃないかなーと思っています。
--allow-env
も消せるしそうしたい気がしてきた。 - メモリに一気に乗せないように書いたつもりだけどどうなんだろう。
- 既出行を格納しているオブジェクトが巨大になって死ぬとかありそう。
- あと
copyFileSync
は中身をみていないけどちょろちょろコピーしてくれるのだろうか? - あとこれ
TransformStream
で描き直したい。
Deno
のvscode
拡張入れた後上手く設定できてなくてcannot find name Deno
とか言われてたけどコマンドパレットからDeno: Initialize Workspace Configuration
実行したらなんか上手くいった。- 無限インポートループとかどうなるんだろうか。
- https://doc.deno.land/builtin/stable 見て書いた。
- Deno Deploy はまだちゃんと触っていない、リクエスト処理は
fetch
イベントのハンドラー書いてやるみたいだけど、ローカルだと発火しなくてちょっと手間取った、なんか公開してくださっているライブラリ入れたら動いた。https://deno.land/x/fetch_event_adapter/listen.ts
WordPress で独自の rss フィードを設定する
意外に簡単だったけど忘れそうなのでメモ。 functions.php
に以下を記述
<?php add_action('init', function() { add_feed('custom', function() { header('Content-Type: application/rss+xml'); include_once 'custom_feed.php'; }); });
デフォルト?だと サイトURL?feed=custom
で custom_feed.php
の中身が返ってくるようになる。でも WordPress から「ちゃんとした xml じゃない!」みたいな怒られ方するので、custom_feed.php
を作るのがまた一苦労な感じだと思いました。正しい feed 生成方法とかあるのだろうか。
感想
WordPress いじるときは wp-env
と言う WordPress ローカル環境を立ち上げてくれる npm ライブラリを使っていてなかなか快適です。思い出したけど Feedly 整理しなければいけない。
Java でストリームの末尾数行を捨てる
末尾の行を捨てるためには一旦ファイルを全部読まなきゃいけないかと思ったけど、捨てたい行数を先読みしておけば良いという感じでした。
import java.io.*; import java.util.LinkedList; import java.util.Queue; // ファイルの末尾数行捨てる BufferedReader class TailIgnoringBufferedReader extends Reader { private final BufferedReader _reader; private final int num; Queue<String> queue = new LinkedList<>(); TailIgnoringBufferedReader(Reader in, int num) { this._reader = new BufferedReader(in); this.num = num; } public String readLine() throws IOException { while (this.queue.size() <= this.num) { String line = this._reader.readLine(); if (line == null) return null; this.queue.add(line); } return this.queue.poll(); } @Override public int read(char[] cbuf, int off, int len) throws IOException { return this._reader.read(); } @Override public void close() throws IOException { this._reader.close(); } }
( lines
とかは未実装。BufferedReader と名付けていいかは微妙なところかも。)
こんな感じで使えます
InputStream data = new ByteArrayInputStream("1\n2\n3\n4\n5\n6\n7\n8\n9".getBytes()); try (TailIgnoringBufferedReader reader = new TailIgnoringBufferedReader(new InputStreamReader(data), 5)) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); }
先頭も捨てられるようにすれば任意の切り出し方ができるようになる
感想
InputStream 系のコードは書いていて楽しい
read
とかでも対応するにはどうするべきなんだろうか?(先読みは同じで、 currentLine
みたいなインスタンス変数持ってそこから返していく、空になったら Queue からロードする感じかな)
WordPress のテーマで、依存している Plugin をインストールしてもらう
必要な plugin がある場合に、通知を表示してインストールしてもらう
コード
required_plugins.php
<?php class RequiredPlugins { public static function setup($plugins) { add_action('init', function () use ($plugins) { if (!current_user_can('activate_plugins')) return; foreach ($plugins as $plugin) { if (self::is_plugin_active($plugin['key'])) continue; add_action('admin_notices', function () use ($plugin) { $name = $plugin['name']; $url = network_admin_url( "plugin-install.php?tab=search&type=term&s=${name}&plugin-search-input=Search+Plugins" ); echo "<div class=\"error\"><p>The <a href=\"${url}\">${name}</a> is required.</p></div>"; }); } }); } public static function is_plugin_active($key) { include_once(ABSPATH . 'wp-admin/includes/plugin.php'); return is_plugin_active("${key}/${key}.php"); } }
呼び出す時
functions.php
<?php ... require_once 'required_plugins.php'; ... RequiredPlugins::setup([ ['key' => 'import-users-from-csv', 'name' => 'Import Users From CSV'] ]);
注意
['key' => '...', 'name' => '...']
の形の要素の配列で、複数の通知を表示させるkey
はプラグインのディレクトリ名、wp-content/plugins
以下から確認してくるname
は何でもいいが、ユーザーがわかるようにするis_plugin_active
で一応プラグインが有効化されているかどうかの判定ができる
参考
感想
Vue.js の slot と props
内容としては これ (Vue.js のガイドの「スロット」の「スコープ付きスロット」) を読んで理解できなかった自分向けのメモです。
Vue.js で slot を使ったコンポーネントの持つ値を、slot に差し込まれるコンポーネントに props として渡す方法を調べました。
slot
を使ったコンポーネントを Hoge
コンポーネント、差し込むコンポーネントを Fuga
コンポーネントとすると、以下の感じでいけます。
Hoge コンポーネントで slot
の部分に <slot :hoge="hoge" />
みたいに渡したい値書いて、実際このコンポーネントを使うときに
<Hoge v-slot="{ hoge }"> <Fuga :fuga="hoge" /> </Hoge>
のように v-slot
という構文で hoge
を取り出して子のコンポーネントに props として渡してあげればいけます。
要するに slot タグに露出させたい属性値書いておけば v-slot
で取り出せる感じ。
サンプル
登場するコンポーネントは 3 つで、
- blur コンポーネント (slot タグを含むコンポーネント): クリックされるまで子コンポーネントをボヤかす
- spark コンポーネント (差し込まれるコンポーネント): props の
isSparking
が true になると点滅するコンポーネント - app コンポーネント (ルートコンポーネント。上記二つを実際に組み合わせる)
になります。
blur コンポーネントがクリックされたことを、spark コンポーネントに伝え、点滅を開始してもらうという動作になります。
See the Pen Vue Slot Props by ushumpei (@ushumpei) on CodePen.
実用的では無い例です。自分が実際にこれを必要としたのは、タブのコンポーネントで、タブがアクティブになったら子コンポーネントに伝えてデータフェッチし直す、的なやつです。