ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

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 で描き直したい。
  • Denovscode 拡張入れた後上手く設定できてなくて 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=customcustom_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 がある場合に、通知を表示してインストールしてもらう

  1. class 作る (名前空間としてだけ使っている、作法はわかっていない)
  2. functions.phprequire_once して呼び出す

コード

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 で一応プラグインが有効化されているかどうかの判定ができる

参考

How would you require and automatically download dependent plugins? - WordPress Development Stack Exchange

感想

  • IT お仕事、割と WordPress で解決することもあるので知っておくと便利かもしれない
  • 今のところ以下でやりくりしている
    • フックとフィルターを使ってイベント駆動で書く
    • ちょっとしたデータ永続化は wp_options に入れる
    • 本体やテーマやプラグインのコード読む
  • でも本体の運用はできればしたくないからテーマで提供するスタンス

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 コンポーネントがクリックされたことを、spark コンポーネントに伝え、点滅を開始してもらうという動作になります。

See the Pen Vue Slot Props by ushumpei (@ushumpei) on CodePen.

実用的では無い例です。自分が実際にこれを必要としたのは、タブのコンポーネントで、タブがアクティブになったら子コンポーネントに伝えてデータフェッチし直す、的なやつです。

感想

  • v3 で統一されたらしく v-slot が 2 つの意味で使われていることに気がつくのが遅かった。
  • slot タグに属性値書かないで v-slot で取り出そうとしてもエラー出ないのハマりそう。
  • slot の説明読むとき、親子コンポーネントだけじゃなくてそれらを組み合わせる第三のコンポーネントが存在することを忘れがち。
  • あと親子って言い方が自分にはわかりにくくて、slot タグ持っている方とか、差し込まれる方とかいいがち。