ushumpei’s blog

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

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 で書きたい時はそれを使う。

Node.js でオブジェクトの配列からストリームを作成する

多分知っておくべきこととして、ストリームはバージョン 12.3.0 でかなり変化があった。この記事は 11.15.0 で書いている。

Node でオブジェクトの配列からストリームを作る方法。

const { Readable } = require("stream");

const readable = Readable.from({ objectMode: true, read() {} })
オブジェクトの配列.map((o) => readable.push(o))
readable.push(null)

12.3.0 以降 は以下で良い。

const { Readable } = require("stream");

const readable = Readable.from(オブジェクトの配列)

ストリーム自体の使い方があまりわかっていない。

とりあえず動かす

エラー時、ストリームを頑張って閉じなければいけないと思う、 destroy() とか使う。

const { Readable, Transform } = require("stream");

// テストデータ作成
const testData = new Array(1000)
  .fill("")
  .map((_, i) => ({ id: i, val: Math.random() }));
testData.push(null)

// Readable ストリーム作成
const readable = new Readable({
  objectMode: true,
  read() {},
});

// Transform ストリーム作成
const stringifyTransform = new Transform({
  transform(data, _encoding, callback) {
    this.push(JSON.stringify(data));
    callback();
  },
  objectMode: true,
});

// この辺微妙
stringifyTransform.on("error", () => readable.destroy());
process.stdout.on("error", () => {
  readable.destroy();
  stringifyTransform.destroy();
});

// 入力 -> 変換 -> 出力
readable.pipe(stringifyTransform).pipe(process.stdout);

// 入力へデータを入れる
testData.forEach((o) => readable.push(o));

標準出力にオブジェクトの文字列が表示される。

ストリームの閉じる閉じないの話

ストリームが閉じているかどうか調べる方法がわからない。イベントを監視していればできるがもっと良い方法はないだろうか? (12.3.0 以降とかだとプロパティがある)

以下はストリームの挙動を確認するための、ストリームを標準出力へパイプするコード。失敗させたりしてイベント拾う。

const { Readable } = require("stream");

// Readable ストリーム作成
const readable = new Readable({
  objectMode: true,
  read() {},
});

console.log(0, readable.readableFlowing);
// => null

const pipe = readable.pipe(process.stdout);

console.log(1, readable.readableFlowing);
// => true

console.log(2, readable.push("ok\n"));
// => true
// 出力あり

console.log(3, readable.push({}));
// => true
// pipe 先の process.stdout がエラー起こして失敗

pipe.on("error", (e) => {
  console.log(4, readable.readableFlowing);
  // => false

  console.log(5, readable.push("ok\n"));
  // => true
  // ただ pipe が切れているので出力はない

  readable.destroy();
  // readable を切る
});

readable.on("close", (e) => {
  console.log(6, "close"); // destroy() で close が発生する。pipe 先のエラーでは切れたりしないようだ
});

出力

0 null
1 true
2 true
3 true
ok
4 false
5 true
6 'close'
  • readableFlowing: パイプされているかどうか。最初 (0) では繋がっていないので null。繋がると (1) true、エラー発生後 (4) は切れてて false
  • pipeerror を拾うようにして、その中で各ストリームを切っていけば良いっぽい。

感想

API ドキュメントのストリーム部分 ちゃんと読んだほうがいいのだろうな、と思いました。英語読むのちょっと気合がいる。

疑問たくさんある

  • エラー時に transform も切る必要があるだろうか?
  • 各種イベントの正しい使い方。error 起きた時に閉じるとかちゃんとしなきゃダメだがあまり整理できてない。finish? end?
  • ストリーム作成時の read() {}pipe だと不要?実装してみる?
  • TransformReadableWritable を継承しているらしいが Writable っぽさが見えてない。
  • gzipTransform の実装どんなだ

fetchbody でストリーム取得できるから画像を canvas に書き込むとか楽しそう。何に使えるかはわからない。加工とかしてみる?RGB から一色なくすとかはできそう。ただそれは何のために?

ストリーム Java でしか使ったことがなかった

GZIPOutputStream と ByteArrayOutputStream と try with resources

Javatry with resources は途中で return してもリソースの close をしてくれるけど、なんか間違えた。

GZIPOutputStreamByteArrayOutputStream を使って以下のような、データを圧縮してバイト列にして返す処理を書いていました。( InputStream の部分は実際は外部のファイルのストリームとかになると思います)

String input = "hogehogehoge";
InputStream in = new ByteArrayInputStream(input.getBytes());

try (
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(out)))
) {

  String line;
  while ((line = reader.readLine()) != null) {
    writer.write(line);
    writer.newLine();
  }

  return out.toByteArray();

} catch (IOException e) {
  e.printStackTrace();
  throw new RuntimeException(e);
}

すると次の例外が発生。

java.io.EOFException: Unexpected end of ZLIB input stream

これは GZIPOutputStream を閉じていないときに出る例外で、「 try with resources だから閉じられるのでは?」と思いましたが、 toByteArray しているタイミングはまだ処理が try を抜けていないので閉じられていなかったようです。

以下のように変更して解決しました。バイト列を取得するのは try を抜けてから行うようにしました。

String input = "hogehogehoge";
InputStream in = new ByteArrayInputStream(input.getBytes());

ByteArrayOutputStream out = new ByteArrayOutputStream();

try (
  BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new GZIPOutputStream(out)))
) {

  String line;
  while ((line = reader.readLine()) != null) {
    writer.write(line);
    writer.newLine();
  }

} catch (IOException e) {
  e.printStackTrace();
  throw new RuntimeException(e);
}
return out.toByteArray();

感想

すぐ終わると思って書いたコードが動かなくて夜が明けてしまった。

最近景気が悪いので、仕事がなくなったら各地のお祭りを巡る旅とかしたい。

修了証明書

Certificate of Completion

スライドとかでよかった気がするけど、なんとなく html で修了証明書を書いてみました。css は無限に時間を溶かすと思います。

See the Pen Certificate of Completion by ushumpei (@ushumpei) on CodePen.

MacChrome でしか確認していない。もしかしたら他の環境でフォントとかダメかもしれないです。

COURSE DETAILS (リボンの下の細かい文字) が短すぎると良い感じの形にならないです。三文くらいあると印刷した時に A4 サイズにうまくおさまる。

参考

ありがとうございました。

リボン

saruwakakun.com

リボンのコード、border で三角形を作るやつの応用で白い三角形を使って切り口を表現する。

mrtc.jp

画像使わないと縁取りかっこよくならないかと思ったけど、radial-gradientlinear-gradient を使って丸みを出している。ただコードちゃんと追えていない。

感想

効力のない、お気持ちのやつ。というか正式なフォーマットがどこかにあるんだろうな。人に教えるほど何かを極めているわけでもないのにどこへ向かっているんだろうか私は。

スマホで見るとビールのラベルっぽい。

VSCode で Ruby 書くための設定

PC 変えた時に vim & ruby のいい感じの設定が失われてしまいしばらく書かないから放置していたらまた書くことになり VSCode でなんとなく書いてたらいい加減不便さを感じてきたので本当にとりあえずの設定を調べました。(早口)

この辺ができるように設定。

  • 構文チェック
  • 補完と定義ジャンプ

あとこれは

でやってます。

やること

VSCodeRuby の拡張があるので入れる (名前そのまんま Ruby のやつ)

GitHub - rubyide/vscode-ruby: Provides Ruby language and debugging support for Visual Studio Code

拡張を動かすために rubocop があればいいそうなので入れる

gem install rubocop

VSCode の設定ファイルに以下を追加

    "ruby.lint": { "rubocop": true },
    "ruby.useLanguageServer": true,
    "ruby.intellisense": "rubyLocate",

説明

  1. "ruby.lint": { "rubocop": true }: Linter は rubocop
  2. "ruby.useLanguageServer": true: Language Server 使う (構文チェックしてくれるようになる)
  3. "ruby.intellisense": "rubyLocate": インテリセンスの設定 (ある程度、補完、定義ジャンプができるようになる)

終わり

rubyLocate のオプションには定義検索するパスが設定できるようで、うまくやれば結構便利になるかもです?脳死で初期設定できないと割と放置しがちな癖があり、よく無いですね。

open-uri の close とか

rubyopen-uriopen で、サイトをスクレイピングするプログラムを見かけたのだけれど、「close 呼ばなくていいの?」と思って色々調べたのでメモしておきます。結論としては、ブロック渡した時は呼ばなくていいけど基本的には呼ぶ。

調べたのは ruby 2.3 なので古くなるかもです、がそんなに変わらなそうな部分?

ブロック渡した時は呼ばなくていい

open メソッドの実装を見てみます。

> require 'open-uri'
=> true
> method(:open).source_location
=> ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 29]

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L29-L39

  def open(name, *rest, &block) # :doc:
    if name.respond_to?(:open)
      name.open(*rest, &block)
    elsif name.respond_to?(:to_str) &&
          %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
          (uri = URI.parse(name)).respond_to?(:open)
      uri.open(*rest, &block)
    else
      open_uri_original_open(name, *rest, &block)
    end
  end

今回は引数として url 文字列の見渡すので、引数 name は URL として解釈され、URI#open が呼ばれるみたいです。そこだけ切り出して実行してみます。

> (uri = URI.parse('https://ushumpei.hatenablog.com')).respond_to?(:open)
=> true
> uri
=> #<URI::HTTPS https://ushumpei.hatenablog.com>
> uri.method(:open).source_location
=> ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 716]

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L716-L718

OpenURI::OpenRead#open が呼ばれるみたい。その中から OpenURI.open_uri が呼ばれている。

https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L132-L166 (抜粋)

    if block_given?
      begin
        yield io
      ensure
        if io.respond_to? :close!
          io.close! # Tempfile
        else
          io.close if !io.closed?
        end
      end
    else
      io
    end

153 行目で block_given? によりブロックが与えられていた時は close を呼ぶように実装されている (ローンパターンか)

なのでブロック渡せば close は明示的に呼ばなくてもいいことがわかりました。

気になったコード

html = open('https://ushumpei.hatenablog.com').read

え、これ絶対 close されないのでは?と思ったので lsof で開きっぱなしなことを確認しました。確認コマンドとその意味は以下の感じになります。

$ lsof -c ruby | grep open-uri | wc -l
  • ruby が開いているファイルを見たいので -c ruby でコマンド名を指定しました。
  • 一時ファイル名に open-uri が付いていたのでそれで絞り込み
  • 行数取得

該当コード実行前の lsof -c ruby | grep open-uri | wc -l0

該当コード実行 (irb は開きっぱなし)

> require 'open-uri'
=> true
> 10.times { |n| open('https://ushumpei.hatenablog.com').read }
=> 10

該当コード実行後の lsof -c ruby | grep open-uri | wc -l10irb を終了するとファイル数は 0 に戻りました。

ブロック渡した時は irb 開きっぱなしの状態でもファイル数は 0 のままです。

(注意: open-uriopen した時に、lsofopen-uri がひっついていている行が出力される、という事象は、ソースコードレベルでは確認しきれていません。ちょっとお粗末になり申し訳ないですが、「多分 open 時に作成された開かれているファイルの行だ」という予想に基づいている、と断っておかなければいけません)

まとめ

  • open(XXX).readopen(XXX) { |f| f.read } とかに変えよう
  • lsof って list open files なんですね。ポート使っているプロセス調べるコマンドかと思っていた。
  • というか linux が全てをファイルという形で抽象化しているアレのアレなのか。(よく知らない)
  • そういえば open って |ls とかでパイプつけたコマンド渡すと実行できたりしてアレらしいので、OpenURI.open_uri 呼んじゃった方がいいっぽい。

追記 2019/04/07

  • 可読性微妙かもだけど、 open(XXX).read の書き換え open(XXX, &:read) もいけるのか、 ruby すごい

静的 html を BASIC 認証付きで雑に公開する方法

雑なメモです (なんかやばかったら教えていただきたいです)

1. 適当なリポジトリを作成する

$ mkdir private
$ cd private
$ git init

2. html ファイルとかを配置する

$ echo '<!DOCTYPE html><html><head><title>private</title></head><body><h1>I am private!</h1></body></html>' > index.html

3. heroku アプリを作成して、 php のビルドパックを追加する。php プロジェクトだと認識させるために composer.json を作成する。

これ、 php じゃないのに php 使う気持ち悪さがあります、多分違う方法ありそう。

$ heroku create
$ heroku buildpacks:set heroku/php
$ composer init

4. .htaccess.htpasswd を作成する

デフォルトの設定で apache を使用するようになっているけど、Procfileweb: heroku-php-apache2 とか書くほうが確実かも

$ echo 'AuthUserFile /app/.htpasswd
AuthType Basic
AuthName "Restricted Access"
Require valid-user' > .htaccess
$ htpasswd -c ./.htpasswd なんか好きなユーザー名

(名前が .ht* のファイルにはアクセス制限かけてるみたいだけど、リポジトリ.htpasswd 含めるのってどうなのだろう)

5. 必要なものステージしてコミットして heroku に push する。

$ git add .
$ git commit -m 'Initial commit.'
$ git push heroku master

6. 終わり

$ heroku open

PostgreSQL 10 でパーティションをまるっと切り替えてみる

PostgreSQL の 10 では宣言的パーティショニングが使えるようになったそうですね (しかし僕は 10 以前でパーティショニングしたことがなかったので感想がない)

これを使ってデータの一括削除、一括追加を実現するために、パーティションの切り替えについて練習します。

準備

Docker で PostgreSQL 10 を用意します

$ docker run --name postgres -p 5432:5432 -d postgres:10

テーブルの作成

こんな感じのテーブルを作ります。ユーザーがいつどこに居たかをひたすら集める謎のテーブルです。

user_id latitude longitude created_at
integer decimal(11, 8) decimal(11, 8) timestamp

パーティションcreated_atrange で切って見ます。

create database testgres;

\c testgres

create table locations (
  user_id integer not null,
  latitude decimal(11, 8) not null,
  longitude decimal(11, 8) not null,
  created_at timestamp not null
) partition by range (created_at);

パーティションの作成

パーティションは partitions スキーマ以下に作っていきます。一秒刻みで適当なデータを作成します。 20181010 と 20181011 のパーティションを作成しました。

create schema partitions;

create table partitions.locations_20181011 partition of locations for values from ('2018-10-11') to ('2018-10-12');

insert into partitions.locations_20181011 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-12'::timestamp - '1 sec'::interval, '2018-10-11', -'1 sec'::interval)
;

create table partitions.locations_20181010 partition of locations for values from ('2018-10-10') to ('2018-10-11');

insert into partitions.locations_20181010 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-11'::timestamp - '1 sec'::interval, '2018-10-10', -'1 sec'::interval)
;

クエリしてみると、ちゃんとパーティションを使った検索が行われているのがわかります。

explain select * from locations where created_at = '20181010 12:00:00';

                                    QUERY PLAN
-----------------------------------------------------------------------------------
  Append  (cost=0.00..1716.00 rows=1 width=28)
    ->  Seq Scan on locations_20181010  (cost=0.00..1716.00 rows=1 width=28)
          Filter: (created_at = '2018-10-10 12:00:00'::timestamp without time zone)
(3 rows)

パーティションの切り替え

f:id:ushumpei:20181015011230j:plain

以下が概要になります。

  1. 普通のテーブルとして locations_20181011 を作成します。
  2. 現在のパーティション partitions.locations_20181011locations から Detach します
  3. 新しいパーティションとして locations_20181011locations に Attach します
  4. 必要なくなった partitions.locations_20181011 を削除します
  5. (跡片付け) 新しいパーティションとして Attach した locations_20181011partitions.locations_20181011 にリネームします

手順 3, 4 と、なるべく alter table locations をまとめて行うことで、テーブルロック時間を少なくしていこうと思います。

新しく locations_20181010 を普通のテーブルとして public スキーマに作成します。(すでにあるものと被らなければ他の方法でもいい)

ここでは check 制約を追加して、Attach される際のパーティションの制約チェックを先立って行っています。これがないとテーブルロック時間が attach の際に増えてしまうそうです。

create table locations_20181011 (
  user_id integer not null,
  latitude decimal(11, 8) not null,
  longitude decimal(11, 8) not null,
  created_at timestamp not null,
  check (created_at >= '2018-10-11' and created_at < '2018-10-12')
);
insert into locations_20181011 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-12'::timestamp - '1 sec'::interval, '2018-10-11', -'1 sec'::interval)
;

パーティションの切り替えを行います。現在使用されている partitions.locations_20181011 を切り離し、新しく作った locations_20181011 をくっつけます。

begin;
alter table locations detach partition partitions.locations_20181011;
alter table locations attach partition locations_20181011 for values from ('2018-10-11') to ('2018-10-12');
commit;

いらなくなったパーティションの削除、跡片付けとして作成したパーティションのリネームを行います。

drop table partitions.locations_20181011;
alter table locations_20181011 set schema partitions;

以上です。

感想

  • テーブルロック怖い
  • トリガー怖い
  • インデックスも適切に

iOS 版 Chrome の <input type="date">

解決策とかわかったら書きますが、なんなんでしょうかこれ?

iOSChrome の input type="date" 入力時、何かの条件を満たすと Picker から「消去」が消える


ScreenRecording 07 03 2018 01 51 23

  • iOS 11.4
  • Google Chrome: 67.0.3396.87 (Official Build) stable (64 ビット)

WebAssembly って?

ブラウザからアセンブリ言語を実行できる仕組みが WebAssembly という理解です (雑魚)。

とりあえず、動かしてみます。

Emscripten

C/C++ から WebAssembly で実行可能なアセンブリコンパイルするツールだそうです。C/C++ に特に思い入れはなく、仕事で使ったことはないですが、例えば C/C++ で書かれたライブラリを JavaScript ライブラリに変換するとかできるのかなーと思います。

C/C++からWebAssemblyにコンパイルする を参考に emscripten をインストールします。(すっごい時間かかりますね)

使う

適当な cpp ファイル (main.cpp) を作成します (これ c といってもいいのでは)

#include <stdio.h>
#include <emscripten/emscripten.h>

extern "C" {
  int main()
  {
    puts("Hello, World");
  }

  int myFunction(int x)
  {
    return ++x;
  }
}

em++ main.cpp -s EXTRA_EXPORTED_RUNTIME_METHODS="['ccall']" -s EXPORTED_FUNCTIONS="['_main', '_myFunction']" を実行します。ここでは

  • クライアント側から関数を呼び出す Module.ccall を使用するために EXTRA_EXPORTED_RUNTIME_METHODSccall を指定します。
  • 呼び出せる関数を EXPORTED_FUNCTIONS で指定します。関数名に _ プレフィックスをつけなければいけないそうです。

以下のファイルが生成されました。

  • a.out.js
  • a.out.wasm
  • main.cpp

index.html を作成してそこから a.out.js を読み込みます。

<html>
  <head>
    <script src="a.out.js"></script>
    <script>
      function callMyFunction() {
        var count = document.getElementById('count')
        var nextCount = Module.ccall('myFunction', 'number', ['number'], [count.innerText])
        count.innerText = nextCount
      }
    </script>
  </head>
  <body>
    <p>Count: <span id="count">0</span></p>
    <button onclick="callMyFunction()">Call C++ Function</button>
  </body>
</html>

しかしこれでは動きません。http 経由で配信しなければいけないそうです。express で配信するようにします。yarn init && yarn add express でサーバーを準備します。index.js を以下のように記述しました。

var express = require('express');
var app = express();

app.use(express.static('public'));

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/public/hello.html');
});

app.listen(3000);

またディレクトリ構造を少し変えます。コンパイルしたものは public 以下に放置しています。

.
├── index.js
├── node_modules
├── package.json
├── public
│   ├── a.out.js
│   ├── a.out.wasm
│   ├── index.html
│   └── main.cpp
└── yarn.lock

サーバーを node index.js で起動すると localhost:3000 でアクセスできるようになます。

f:id:ushumpei:20180619104114g:plain

感想

また何かに入門だけしているやつです

Pro Git 2nd Edition 読んでる

最近手が痛くてプログラミング時間を少々減してて、久しぶりに本でも読もうかという気分になっています。

git ちゃんとわかっていなかったので、 Pro Git 2nd Edition を読み始めました。ちょっと面白いことがあったのでメモします。

前提: git は差分ではなくファイルのスナップショットを保持している

git が既存の VCS (Version Control System) と大きく異なった点として、ファイル変更履歴の管理方法が 変更されたファイルの差分ではなく変更されたファイル全体のスナップショット であることだと書かれています。自分の理解だと、ファイル容量などを考えると変更差分を保持していた方がいいと思いますが、ぶっちゃけテキストファイルが主だしまあスナップショットでも大丈夫なんだろうなー、くらいの軽い感じでした。しかしこのことが結構重要で、 git がめちゃくちゃ速い理由につながっているようです。

リポジトリ内のファイル全体のスナップショットが作成されるのは commit 時で、次の要素が .git/objects 以下にファイルとして作成されます。ファイル形式は blob でファイル名はそのデータのハッシュ値です。

  • blob オブジェクト: 変更後のファイル
  • tree オブジェクト: ディレクトリツリー。変更対象のファイルを持っていたディレクトリに対して、ツリーのノードの参照先 (tree, blob オブジェクトのハッシュ値) を書き換えた tree オブジェクトが作成される。(毎回リポジトリルートの tree は更新される)
  • commit オブジェクト: 新しく作成されたリポジトリルートの tree への参照と、parent commit オブジェクトへの参照と作成者情報やコミットメッセージ。

言葉だとややこしいですが実物は以下のようになります(ハッシュ値は適当です)。

blob オブジェクト: .git/objects/f5/83c304ea36b6fa554eb01381e781b04e45477f

# Pro Git

URL: [https://git-scm.com/book/ja/v2](https://git-scm.com/book/ja/v2)

tree オブジェクト: .git/objects/20/4bfe00b89a265e7c16e8688a90dfb86e52c5eb

100644 blob f583c304ea36b6fa554eb01381e781b04e45477f README.md

commit オブジェクト: .git/objects/31/ba323616cc8cbc543882c5a4eef6aa95eef803

tree 204bfe00b89a265e7c16e8688a90dfb86e52c5eb
author ushumpei <mail@example.com> 1528220889 +0900
committer ushumpei <mail@example.com> 1528220889 +0900

Initial commit.

(.git/objects 以下のファイルは git cat-file コマンドに -p オプションをつけてハッシュ値の頭から 6 文字を引数にして実行すると、中身を閲覧することができます: .git/objects/f5/83c304ea36b6fa554eb01381e781b04e45477f なら git cat-file -p f583c3)

要するに?

要するに私が面白いと感じたのは、特定のコミットに入っているファイルを取り出したいときは、commit オブジェクトのハッシュを使って、

commit オブジェクト -> tree オブジェクト -> (ツリーの探索) -> blob オブジェクト

という風に取り出してくれるので、めっちゃ速い、すげー、ということです。これは過去のコミットでも、分岐してある程度進んだブランチのコミットでも同じように行われます。(多分)

感想

「git 速い」って何と比較して、というと svn なのですが、ベンチマークとか取っていないので (どう比較するか謎ですが) 怒られそうですね。「考え方の変化で処理が変わった」ということにテンションが上がっただけです。

React 360 で vnc クライアントを作って Oculus Go のブラウザから自分のPCを見る (未完成)

注意: チラシの裏です

こんにちは

Oculus Go に Mac 用のリモートデスクトップがなかったので (現在: 2018/05/31) なんとかできないかと思って色々やっています。理想的には HMD つけながら無線キーボードでお布団に入りながらコーディングしたいという思いがあります。

でも全然完成までの道のりが見えないので、現状の整理のためのメモを書きます。

(これ以降進展なし)

なんで作ってるんですか?

Oculus Go で使える Mac 用のリモートデスクトップアプリが見つからなかったためです (有料のやつある?)。ブラウザで画面共有できる Web サービスもあるのですが、仕事のコードとか書くことを考えるとローカルネットワークで完結するのが気分的に一番良いんじゃないか?と思ったため作り始めました。(自作すること自体はそれはそれでリスクですが)

Mac にはデフォルトで「vnc」と呼ばれる (?) リモートデスクトップのサーバーが入っているため、それ用にクライアントのコード書いたら良いんじゃないか、ということで vnc 周りを調べつつ作っています。

なんで React 360 なんですか?

  • Android studioAndroid Mobile SDK: C++ 混じってて読めなかった
  • Unity: 入ってるけど使ったことない
  • React 360: 既存の知識使ってできる

という消極的な理由からです。 1 週間くらいで終わらせたかったのでがっつり学習する必要があるものは避けました(終わってないけど)。今考えると Unity は空間すでにあるし知見も多いため一番良い気がしてます。

どんな感じで作りますか?

  • Mac
    • 8080 port: http で React 360 のページを返すサーバー
    • 5900 port: 標準の vnc サーバー
    • 5901 port: websockify というライブラリを使用して WebSocket の 5901 への接続と、 vnc の 5900 への接続をつなぐ

という構成を元に、

  1. 同じローカルネットワーク内のブラウザから WebSocket で Macvnc サーバーと接続
  2. vnc サーバーから送られてくる画面データを React 360 内の canvas に描画
  3. ブラウザのキー入力やポインター移動イベントをサーバーに通知する

していくように作っています。

できたこと

  • React 360 の平面オブジェクト (Plane) に canvas を貼り付ける
  • RFB 3.8 プロトコル (vncプロトコル) を実装してサーバーとの接続を確立する
  • ブラウザでサーバーから画面データを受信する
  • 受信したデータを canvas に描画する

できてないこと

  • ブラウザのイベントをサーバーに通知する
    • ブラウザ -> サーバー への通信は、初めはいらないと思っていたんですがユーザーのサインインが必要なので、ないとログイン画面を延々とみていなければならなくなります
  • 画面が全更新になっているので差分更新で済むようにしたい

どうします?

  • RFB プロトコルのコードを整理する
    • RFB プロトコルが接続を確立するまでに複数回のやりとりが必要になるのですが、全てのメッセージを長い onmessage で受け取っていろんなフラグで if else と長々と書いているので破綻してる
    • ドメインの知識が増えて来たのでちゃんと書いてみたい
    • Redux を React 以外の文脈で使ってみたい
    • オレオレイベント emitter 消す
  • React 360 を一旦やめる
    • canvas を貼り付けた Plane の取り扱いが不安定すぎるので単純な html として書き直します (PC めちゃくちゃ熱くなるし)
      • React でも良い気がするけど
        • ブラウザ vnc クライアントの vnc.js とかあるっぽいけど

一応リポジトリ: GitHub - ushumpei/VncClient: Vnc client for browser

感想

遊んでないで仕事探す

既存の Laravel プロジェクトに自動構文チェックを軽く入れる

Laravel 始めました。今回はコードの自動構文チェックをすごく軽く入れる話。ゆくゆくは CI に含める予定だけどまだその時間と根回しがないので簡単にやります。

どんな効果を狙っているか?

  • レビューにおいて構文に関する指摘が大半を占めているのでその時間を削減する
  • 構文に関する規約が暗黙知化しているので (レビューで指摘が多く上がる原因のひとつ)、規約を管理できる構成にする
  • 書くのが面白そう!

どんなものを作るか?

  • git のプレコミットフック (git/hooks/pre-commit) で追加/変更したコードの構文チェックをして、違反していたらコミットできなくする (コミットできない、はやりすぎか?)
  • 違反している箇所については構文チェックライブラリのエラーを出す (変更行だけチェックできないか? -> 今回はできなかった...)
  • 緊急時のためにフックを無視する手段も作っておく(すでにプレコミットフックとして仕組みがある? -> --no-verify option)
  • プレコミットフックがない環境に影響を与えない (まあ多分 pre-commit 配置しなければ良いだけと思う)
  • 構文チェックのルールはファイルで管理できるようにする (構文チェックライブラリが標準で備えているはず)
  • とりあえずチェック対象は、jsphp (当面は拡張子によって判定)

準備

使うものとしては以下を考えています

  • phpcs (PHPCodeSniffer)
  • eslint

Laravel と言っていますが、git 使ってれば応用効くと思います。それぞれ composer , npm (or yarn) で開発環境の依存ライブラリとして入れます。

(これらがそもそもグローバルインストールされている状態だとどうなるんだろう…?それも忘れずに確認しなきゃ…)

pre-commit 作ってみる

まずは何も構文チェックライブラリ入れずにフックだけ作成してみます。処理の流れとしては、 git commit を実行した直後に、pre-commit が呼ばれ、スクリプトの戻り値(?) が 0 以外の時に commit が停止されるというものです。

.git/hooks/pre-commit

#!/bin/sh

echo hoge
exit 1

また、スクリプトの実行権限を 755 に変更しておきましょう。

$ touch fuga
$ git add .
$ git commit
hoge

とりあえず commit 時にフックが実行されるのがわかりました。あとは処理を書いていきます。

対象ファイルの抽出

git のコマンドを使用して HEAD との比較で、追加、更新があったファイルを取得します。コードは以下のようになります。

#!/bin/sh

# Get against commit hash to compare.
if git rev-parse --verify HEAD >/dev/null 2>&1
then
    against=HEAD
else
    # Initial commit: diff against an empty tree object
    against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

# Redirect output to stderr.
exec 1>&2

# Get added and modified files.
files=`git diff-index --cached --name-only --diff-filter=AM ${against}`

echo ${files}

比較結果は files に、スペース区切りの文字列で入ってきます (配列として取り扱うかどうかは悩みましたが、特に必要ないので git diff-index ... の戻り値をそのまま使います。)

ここでは --diff-filter=AM によって追加 (A)、更新 (M) のあったファイルのみを抽出しています。削除やリネームしただけのファイルはチェックしません。 (削除ファイルやリネームファイルのチェックは、自分が書いたわけでも無いファイルの構文を直せということになり、プロジェクトの整理作業に負のモチベーションを与える気がします。)

exec 1>&2 は標準出力をエラー出力にリダイレクトしています。このスクリプトで出力が行われることは、即ちエラーだからです。 (と偉そうにいってますが、この行までは全て .git/hooks/pre-commit.sample から盗んできたものです。)

対象ファイルを拡張子で分類

ここまでくると特別なことはありません。シェルスクリプトを書くだけです。

...
# ↓チェック対象のファイル取得後の処理

# Check syntax.
is_error=0
output=""

php_files=""
js_files=""
for f in ${files}
do
  extension=${f##*.}
  case ${extension} in
    php)
      php_files+="${f} "
    ;;
    js)
      js_files+="${f} "
    ;;
  esac
done

if [ -n "${php_files}" ]
then
  output+=`phpcs ${php_files}`
  is_error+=$?
fi

if [ -n "${js_files}" ]
then
  output+=`eslint ${js_files}`
  is_error+=$?
fi

if [ ${is_error} -gt 0 ]
then
  is_error=1
  echo "${output}" | less
else
  is_error=0
fi

exit ${is_error}

これで一応、複数ファイルに対してまとめて構文チェックを行えるようになりました ( 上のスクリプトは構文チェックコマンド入っていないと動かないですが )。出力が流れてしまうので、構文エラーに関しては less に渡して表示するようにしています。色々思うところは以下かなと思います。

  • 文字列連結の半角スペースがダサいので配列使うべきでは?
  • 拡張子ごとにほぼ同じ処理を書いているので、新しい構文チェック追加時に変更大きいよ?

本当は、構文チェックライブラリに変更対象ファイル全部渡して、そっちでフィルタリングしてほしいという気持ちなので今の所シンプルすぎるくらいがちょうどいいのかなと思います。

phpcs, eslint の設定

phpcs

開発環境の依存パッケージに PHPCodeSniffer を追加します。

composer require --dev 'squizlabs/php_codesniffer'

phpcs./vendor/bin/phpcs に入っているのでスクリプトからはそれを読むことにします。

phpcs は実行時に --standard= オプションで使用するルールや設定ファイルを指定することができます。そのほかにも設定ファイルを phpcs.xml という名前で作成しておけば自動的にそのファイルを読んでくれるようです。(自分ディレクトリから親のディレクトリを順に登って行って phpcs.xml を見つけたら使用してくれるみたいです)

PSR2 を継承しつつ色々調整できるように雛形 phpcs.xml を作成してみました。

<?xml version="1.0"?>
<ruleset name="MyRule">
  <description>Coding standard based on PSR2 with some additions for my project.</description>
    <!--
      You can add your rules below.
      For example, you can include new standard, like that;
      <rule ref="PEAR" />

      If you want to know more about phpcs,
      See: https://github.com/squizlabs/PHP_CodeSniffer/wiki
      See also to know the notation of this file: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml
    -->

  <!-- Include the whole PSR2 standard -->
  <rule ref="PSR2">
    <!--
      You can exclude specific standard by adding exclude_tag and name_attribute here;
      <exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace" />
    -->
  </rule>
</ruleset>

次は .git/hooks/pre-commit で、ローカルのコマンドと、設定ファイルのパスを使うようにスクリプトを修正します。(設定ファイルは存在チェックだけ必要としています)

app_root_path=`git rev-parse --git-dir`/..
phpcs=${app_root_path}/vendor/bin/phpcs
phpcs_config=${app_root_path}/phpcs.xml

## Execute check.
### PHP
if [ -n "${php_files}" ]
then
  # Check installation of phpcs.
  if [ -e "${phpcs}" ]
  then
    # Check existence of cording standard file.
    if [ -e "${phpcs_config}" ]
    then
      # For upgrading rule file, add -s option to display rule's name.
      output+=`${phpcs} -s ${php_files}`
      is_error+=$?
    else
      output+='\nNOTE: phpcs configuration file is not found. going to check based on psr-2'
      output+='\n'
      output+=`${phpcs} --standard=PSR2 ${php_files}`
      is_error+=$?
    fi
  else
    output+='\nNOTE: phpcs is not installed. php syntax checking is skipped.'
  fi
fi

うん、すごく読みにくい気がします。他の構文チェックする前にシェルスクリプトの構文チェックしろよ的なブーメランですね。

phpcs.xml が見つかった時だけ -s オプションを渡してルール名を表示しておいてあげます。ルールファイルの修正の障壁にならないためにも。

ファイルがないときは何もしない、コマンドがないときはお知らせ、設定ファイルあるときはそれで実行、ないときはPSR2で実行という流れです。

eslint

急に疲れてきてしまったのですが、だいたい phpcs と同じになります。こちらのインストールは yarn add --dev eslintnpm install --dev eslint で行います。

開発環境の依存性でインストールされた後は、初期の雛形ファイルを作成します。 Use popular style guide などで適当に js ファイルとして作っておきました。(ファイル形式も pre-commit でみなきゃいけないかもしれないけど今は js 決め打ちで行きます)

$ yarn add --dev eslint
$ ./node_modules/.bin/eslint --init
$ ls .eslint.js
.eslint.js
$ yarn # init 後にもう一回パッケージのダウンロードが必要...

先ほど同様に .git/hooks/pre-commit を修正します。変更内容みたい方いましたら github でご確認ください。 pre-commit (すみません疲れてしまいました)

まとめ

shell じゃなくて php か js で書けばよかったのではと今更ながら思います。特に制御構文の省略記法とかどれを使っていいか悩んでしまい、ものすごくベタに書きました。それこそ構文チェックが必要な気がします。

まあ、ないよりはマシか、、、という気持ちで書きました。最低限動いたら後は使いながら修正していけばいい、という言い訳をしておきます。