ushumpei’s blog

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

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 タグ持っている方とか、差し込まれる方とかいいがち。

Node.js の stream.pipe のエラーハンドリング

stream.pipe の代わりにバージョン 10 で追加された stream.pipeline を使うと良さそうです。

stream.pipe では ここ に書かれているように、エラーハンドリングとしてストリームの後始末を書かなければいけないためです。

One important caveat is that if the Readable stream emits an error during processing, the Writable destination is not closed automatically. If an error occurs, it will be necessary to manually close each stream in order to prevent memory leaks.

あと pipe(...).on('error', ...).pipe(...).on('error', ...)... みたいに書かなきゃいけない気がするし。

確認コード

stream.pipeline でどのようなエラーハンドリングが行われるかをコードで確認しました。ファイルを読んで、変換をかまして、ファイルへ書き込むサンプルです。変換部分でエラーを任意に発生させられるようにし、

  1. エラーが発生しない時の挙動
  2. エラーが発生した時の挙動

の二つを確認します。

(動かすには入力用のファイル from.txt が必要になります)

const { Transform, pipeline } = require("stream");
const { createReadStream, createWriteStream } = require("fs");

// Readable ストリーム作成
const readable = createReadStream("from.txt", "utf8");

// Writable ストリーム作成
const writable = createWriteStream("to.txt", "utf8");

// Transform ストリーム作成 (何もしないけどエラー投げられるようにしたやつ)
const getTransform = (fail) =>
  new Transform({
    transform(data, _encoding, callback) {
      if (fail) {
        callback(new Error("fail"));
      }
      this.push(data);
      callback();
    },
  });

// [NOTE] ここでエラーの発生を任意に切り替える
// const transform = getTransform(true);
const transform = getTransform();

// イベントの発生をロギング。`i-th` は各ストリームに対応 (0-th: readable, 1-th: transform, 2-th: writable)
[readable, transform, writable].forEach((s, i) => {
  s.on("end", () => {
    console.log(`${i}-th stream:`, "end");
  });
  s.on("finish", () => {
    console.log(`${i}-th stream:`, "finish");
  });
  s.on("close", () => {
    console.log(`${i}-th stream:`, "close");
  });
});

// 入力 -> 変換 -> 出力
pipeline(readable, transform, writable, (e) => {
  if (e) {
    console.log(e);
    console.log(
      "エラーが起きた時は全てのストリームに close イベントが発行される"
    );
  } else {
    console.log(
      "エラーなく終了した時は全てのストリームにそれぞれの終了イベント、加えて入出力ストリームに close イベントが発行される"
    );
  }
});

1. エラーが発生しない時の挙動

0-th stream: end
1-th stream: finish
1-th stream: end
2-th stream: finish
エラーなく終了した時は全てのストリームにそれぞれの終了イベント、加えて入出力ストリームに close イベントが発行される
0-th stream: close
2-th stream: close

2. エラーが発生した時の挙動 ([NOTE] と書いてあるコメント行近くの transform 変数定義をコメントアウト/インして実行)

1-th stream: close
2-th stream: close
Error: fail
    at Transform.transform [as _transform] (/Users/ushumpei/node-stream/index.js:15:18)
    at Transform._read (_stream_transform.js:189:10)
    at Transform._write (_stream_transform.js:177:12)
    at doWrite (_stream_writable.js:428:12)
    at writeOrBuffer (_stream_writable.js:412:5)
    at Transform.Writable.write (_stream_writable.js:302:11)
    at ReadStream.ondata (_stream_readable.js:722:22)
    at ReadStream.emit (events.js:209:13)
    at addChunk (_stream_readable.js:305:12)
    at readableAddChunk (_stream_readable.js:282:13)
エラーが起きた時は全てのストリームに close イベントが発行される
0-th stream: close

各イベントは、

  • end: Readable ストリームが読まれ終わった時に発火
  • finish: Writable ストリームが書かれ終わった時に発火
  • close: ストリームとその使用しているリソースが閉じた時に発火 (でも発火しないストリームもあるらしい)

と言う感じか?

まとめ

確認できたこととしては以下

  • どちらのケースでも close が呼ばれて使用しているリソースを閉じている。
  • transform は DuplexReadable かつ Writable なので endfinish が呼ばれている。

感想

Transformclose されるのはどう言うことだ?と悩む。

あと、根本的な問題として、ストリーム閉じなくて良いケースを理解できていない。文字列から単なるメモリに乗っている Readable なストリーム作った場合は、閉じなくて良いのか?GC されるか?

公式ドキュメントに promisifypipeline を Promise 化した時の例も書いてあったので、async/await で書きたい時はそれを使う。