読者です 読者をやめる 読者になる 読者になる

Let's write β

趣味で書いたこととか、RustとLispが好き

rlwrapがMacでの子プロセスのディレクトリの追跡をサポートしたようです

前回の記事でも触れましたが最近はplan9portのrcをつかって生活をしています。
rcではヒストリ機能はサポートしておらず、readline系のなんかを使うと良いんじゃないかみたいなアドバイスがされています。

そんなときに便利なのはやっぱりrlwrapです。任意のコマンドを readlineで囲むことができます。

github.com


rlwrapの仕組みとしては、引数としてもらったコマンドを子プロセスとして把握していて、入出力の処理をするという仕組みになっています。

また、その一貫としてファイル名の補完というのがあり、子プロセスのカレントディレクトリ(a.k.a CWD)を見て、ファイル名を保管してくれるというのが有ります。

この機能、元々は/procファイルシステムを持つUNIX系システムでしかサポートされておらず、Macではrlwrapを最初に立ち上げたフォルダの中身がいつまでも保管されるようになっており不便でした。

macでもlibprocというものがあり、

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libproc.h>

int main (int argc, char* argv[])
{
        int ret;
        pid_t pid;
        char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
        struct proc_vnodepathinfo vpi;

        if (argc > 1) {
                pid = (pid_t) atoi(argv[1]);
                ret = proc_pidpath (pid, pathbuf, sizeof(pathbuf));
                if (ret <= 0) {
                        fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
                        fprintf(stderr, "    %s\n", strerror(errno));
                        return 1;
                }
                ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi,
                                   sizeof(vpi));
                if (ret <= 0) {
                        fprintf(stderr, "PID %d: proc_pidinfo ();\n", pid);
                        fprintf(stderr, "    %s\n", strerror(errno));
                        return 1;
                }
                printf("%s\n", vpi.pvi_cdir.vip_path);
        }

        return 0;
}

こんな感じのコードでPIDを指定してそのプロセスのcwdを取得することができました。

これをMacで有効にしてくればいいのになと思っていたら、

github.com

そんなPRが二年前にすでに提出されていました。提出されていたのですが
libprocというファイルが存在するだけでは、実際に上記のMacのようなコードがサポートされているか不明瞭なので、autoreconfでの判定をもう少しちゃんとしないとねというところで止まっていました。

僕としてはぜひ取り込まれてほしいので、止まってるけどどんな感じとつついてみたところ、作者がさくっと取り込んでくれたようです。

これで、mac環境下でもrlwrapをつかっていて子プロセスのCWDが変わったときに適切にファイル名の補完が追従されるようになったようです。

rbenvをPlan9portのrcに対応させるPRを送った

Plan9

plan9の標準シェルrcを最近メインで使い始めたのですが、ヒストリー機能がないとかそういうのは自前でやるとしても rbenvなどの普段使っているツールが動かないのは辛いです。

rbenvなどは、rbenvからshell scriptを出力しそれをシェル側でevalする形で環境変数を設定したりPATHを設定したりしています。 例えばbashでrbenvを使うときに

eval $(rbenv init -)

のようにすると思いますが、これは

export PATH="/Users/pocket7878/.rbenv/shims:${PATH}"
export RBENV_SHELL=bash
source '/Users/pocket7878/.rbenv/libexec/../completions/rbenv.bash'
command rbenv rehash 2>/dev/null
rbenv() {
  local command
  command="$1"
  if [ "$#" -gt 0 ]; then
    shift
  fi

  case "$command" in
  rehash|shell)
    eval "$(rbenv "sh-$command" "$@")";;
  *)
    command rbenv "$command" "$@";;
  esac
}

というスクリプトがrbenvから出力されこれがevalされてrbenv関数をシェル内に定義する形で行われています. 幾つかのサブコマンドも同様の方法でシェルが操作されているのですが、rcシェルはshシェルとは文法が異なるので そのままでは動きません。

同様の問題を抱えていそうなfishシェルについては既にpatchが取り込まれていたので、ソースコードをざっくりとfishで検索して 対応する箇所を大まかに絞って幾つかのサブコマンドにrcへの分岐を組み込みました。

github.com

特にrcではシングルクオートとダブルクオートの扱いが変わっていたりするのでそのあたりの考慮が大変でした。

AirPodsが届いた

昨年末に注文していたAirPodsが今朝方届きました。

f:id:Pocket7878_dev:20170106131807j:plain

意外と思っているよりも箱が重たかったのが印象的でした。

f:id:Pocket7878_dev:20170106131954j:plain

箱を開けてみるとなかに小さなガムの容器のようにケースが入ってました。

このケースがしっかりした感覚の重さでした。(充電器として使えることを考えると当然かもしれませんが)

そのケースを開けると2秒ほどでiPhoneにペアリング用の画面が表示されて、ペアリングをしたら無事ペアリング完了です。

イヤホンの装着感ですが、元々EarPodsをつかっていて外れたことはまったくなかったのでAirPodsも同様
非常に安定した装着感で普通に作業していて外れる気配は全くありません。

ダブルタップでSiriを起動できるのも便利ですし、発信して、着信側で受話するのもこちらで切断するのもダブルタップでデキるようだったので使い所は色々ありそうです。

音量を上げるのにSiriに話しかけるのは少し面倒ですが、そもそも音量は基本変更することもないですし、
気にするほどではないかなと思います。

もう少し使ってみて充電などを確かめてみたいと思います。

競プロ勉強進捗

競プロ

f:id:Pocket7878_dev:20170105134541p:plain

競技プログラミングをちゃんと勉強しようというのが今年の目標でも合ったので とりあえず必ず1日一歩ずつでもいいから進めようと思う。

とりあえず、一昨日あたりからはじめてABCの残っていたA&B問を全問といておいた。 A&Bはすんなり解けるのを確認した。C++を最近触っていなかったので標準ライブラリで曖昧なところが最初は合ったが 後半になるに連れて基本的に使うライブラリは固定化されてきたので頭に入ったと思う。

計算量見積もって総当りでいいならそれでざっくり解いてしまうのも大事だなと思った。

2016年振り返り & 2017年のやってみたいこと

Github

f:id:Pocket7878_dev:20170104132758j:plain

比較的よく働いたと思う。 ただしこれはプライベートリポジトリも含めたもので、会社のプロダクトをGithubで管理しているので 思いに会社のプロダクトのRails, iOSあたりを毎日なにかしら書いているので、ほぼ埋まっているのは 当たり前かもしれない。

来年はオープンソース周りにも手を出していきたい

OSS

コントリビュート

the-rust-programming-language-ja

Rustの公式ドキュメントの翻訳プロジェクトκeenさんが始めてらして宣伝なさってたのでRustに丁度 興味を持っていた時期だったので参加した。そこそこ訳したように思う

f:id:Pocket7878_dev:20170104133326p:plain

コミット数では一応2位ということになった、行数とかで言うとそうでもない気がする。 一旦の1.6の翻訳が終わってからはあまり手を付けられていない.

github.com

Plan9PortはPlan9 OSのツールをモダンOSに移植したツール群であるが、 僕はこのPlan9Portのツールが大好きで、よく使っていた(Acme, Sam, 9Term)あたりだけだが、 日本語入力をサポートしていない(キーボードのコードをそのまま入力している)ので、それを つけようともがいていて、一応それらしいものがついた。

poketo7878-dev.hatenablog.com

今のところ余りにも日本語入力という目的のために非効率なことをしている部分もあるし、UseCaseとしては特殊だと思うのでPRは送っていない。

github.com

GooglerのサイドプロジェクトとしてRustをバックエンドにつかったエディタが開発されてるぞというのをみて 興味がある時に初期のプロジェクトが有るという機会はあまりないと思って使ってみたところこちらでもMacIMEをサポートしていなかったので plan9portにつけたときと同じ要領で日本語入力をサポートするPRを送った

github.com

こちらのほうが、plan9portとは違ってプログラムが直接文字を扱っているので実装しやすかった。 一方でやはり変換まわりはバックスペースを送りまくって置き換えるという怪しげな実装なので、 そのうち範囲リプレースをバックエンドでサポートしたら置き換えるべきだと思う。 Xcode8には事情により対応しないようなので、僕のローカルで環境を整えるのが大変でそれ移行あまりテストできていないのが悔しいが 人生初のGooglerとのPRのやりとりになった。丁寧にレビューしてくださってありがたかった。

その他は仕事でつかっている幾つかのライブラリにCarthageのアップデートやら、Cocoapod対応やらのPRを送った。

個人プロジェクト

github.com

上でも触れたがPlan9Portのエディタが大好きで使っていたのだが、 どうしても日本語入力をサポートしたとは言えネイティブのエディタには追いつけないので、 個人的に良いと思った機能を模倣したテキストエディタCocoaのNSDocumentをつかって実装している。 https://pocket7878.github.io/ec/

MouseChoordsの機能はサポートしていないが、右クリックに依るジャンプや、 中ボタンによるコマンドの実行などの機能は実装済みである。 また、ネット上でAcmeやSamにほしいと言われていた幾つかの機能も追加機能としてつけている (ドキュメントにはまだ乗せていないが、設定ファイルベースでインデントの仕様やカラーリングを設定でき、 ファイルパスベースでそれらのフラグを個別対応したりするのも検討している)

2016で一番作ってよかったのはこのエディタだともう。会社でRailsアプリケーションを書いているときも、個人でプログラムを書いているときも ドッグフーディングしている。いい面でも悪い面でもAcmeとSamの両方からいいとこ取りをしようと思っていて、 つけていない機能はある意味で「AcmeにはあるけどSamにはないし、あまり僕には必要ないので」と割り切っている。

読んだ本

めぼしいところはコレくらいだった。業務のほうが諸々忙しくあまり読書に時間をかけられなかったのが心残り。

今年は色々読むべきとされてる本の読めていないものたちをを読みたいとおもう。

学んだ言語・技術

  • Rust
  • Golang
  • RxSwift
  • Kotlin
  • Clean Architecture

業務の方は今のところ主にRails, Swift, Javaあたりで済むし、今年の頭あたりはプロダクトの忙しい時期でも合ったので とりあえず作らなければという形で技術というよりは、アーキテクチャをまっとうに組むという感じでやってきたが 年の後半になってプロダクトが落ち着いてきたので、色々と長期的なことを考える時間が取れた。

その中で関数型言語としてRustを学んで勉強会に顔を出したり、 すこしまだ様子見とおもっていたGolangもある程度導入事例が増えてきたので学んでみた。

肌感としてはGolangはまだ仕様に納得行かない部分もあるが(VendoringとかAliasとか)それでも、その納得行かない部分がコミュニティでも 確かに議論の対象にはなっているようだったので雲行きは怪しくないだろうと思って今後プロダクトに導入を進めることにした。 手始めに一部のサーバーサイドの機能をgRPCで分割して導入した。

RxSwiftやKotlin, Clean Architectureについては、 モバイルアプリケーションのリファクタリングをすすめるにあたって大きな方針として検討し、導入を開始した。

今年の目標

競技プログラミングの勉強

競技プログラミングはなかなか手を出しづらく、学生時代になんどか取り組んだがそのままにしていたが 今年はちゃんと取り組みたいと持っている。

ブログをちゃんと書く

ブログをちゃんと書こうともう、と言うかそもそもブログのネタがちゃんと見つかるように色々とやりたい

WebVR

A-Frameあたりをちゃんと触ってWebVR周りに触れていきたいと思う。

RustでWebプログラミング No.4 ~ MySQLに接続する~

振り返り

前回

poketo7878-dev.hatenablog.com

は、HandlebarでHTMLページをテンプレートから生成し、表示するというところまで作りました。今回は、MySQLに接続してデータを取得し、それをテンプレートに埋め込んで表示するということをやりました。

Iron-R2D2-Mysql-MiddlewareをCargo.tomlについか

Rust界隈はどうも主要なデータベースツール周りがPostgresをメインにサポートしていることが多いようで、MySQLをサポートしているツールがあまりない印象です。

そこで、拙作の

github.com

を利用します。これはIronのMiddlewareでR2D2-MySQLというコネクションプールライブラリを利用して、リクエストハンドラ中からMySQLへのコネクションを利用することを可能にするものです。

[dependencies]
iron = "0.4.0"
router = "0.4.0"
handlebars-iron = "0.18.0"
params = "0.4.1"
r2d2 = "0.7.0"
r2d2_mysql="*"
iron-r2d2-mysql-middleware = { git = "https://github.com/pocket7878/iron-r2d2-mysql-middleware.git" }


[dependencies.mysql]
version = "3.0.0"
default-features = false
features = ["socket"]

ChainへのMiddlewareの追加

extern crate iron_r2d2_mysql_middleware;

...

use iron_r2d2_mysql_middleware::{MysqlMiddleware, MysqlReqExt};

まず、iron_r2d2_mysql_middlewareを利用可能にして下さい。

そして、

let mysql_middleware = MysqlMiddleware::new("mysql://root:12345678@localhost/test").unwrap();
chain.link_before(mysql_middleware);

のように、MysqlMiddlewareを作成して前回同様, MiddlewareのChainに追加して下さい。 今回のこのMiddlewareはリクエストがメインのハンドラに落ちるに、 コネクションプールにつなぐための準備を設定するものなので、BeforeMiddlewareと呼ばれるものになっています

前回の、HandlebarのMiddlewareはハンドラが処理を行ったに、レスポンスの中身をみて、 Templateが設定されていたら処理するものなので、AfterMiddlewareになっています。

そのため、Chainへの追加の方法がlink_beforeであることに注意して下さい。

ハンドラ中でのMySQLへの接続

まず、コード全体は以下のとおりです:

    fn user_index(req: &mut Request) -> IronResult<Response> {
        let mut resp = Response::new();
        let mut data = HashMap::new();
        let mut con = req.db_conn();
        let mut users = vec![];
        for result in con.prep_exec("SELECT id, first_name, last_name FROM users", ()).unwrap() {
            let mut row = result.unwrap();
            let mut user_data: HashMap<String, String> = HashMap::new();
            let user_id: u64 = row.take("id").unwrap();
            let last_name: String = row.take("last_name").unwrap();
            let first_name: String = row.take("first_name").unwrap();
            user_data.insert(String::from("user_id"), format!("{}", user_id));
            user_data.insert(String::from("user_name"), format!("{} {}", last_name, first_name));
            users.push(user_data);
        }
        data.insert(String::from("users"), users);
        resp.set_mut(Template::new("user_index", data)).set_mut(status::Ok);
        return Ok(resp);
    }

肝となるのは

let mut con = req.db_conn();

の部分です、これで、コネクションプールからMySQLへの接続を1つ取り出してきています。 このインスタンス

outersky.github.io

インスタンスとなっています。 このインスタンスを利用して、クエリを発行し、データベースから取り出した値を利用してuser_indexテンプレートに流してやっています。

RustでWebプログラミング No.3 ~ HandlebarでHTMLテンプレート~

rust-lang

振り返り

前回 poketo7878-dev.hatenablog.com

までにRouterで複数URLに対応したHTTPサーバーを建てられるようになりました。 しかし、出力されるのは常に文字列でHTMLのページではありませんでした。

今回はHandlebarというテンプレートエンジンを利用してHTMLページをユーザーに表示できるようにしました。

handlebar-ironをCargo.tomlに追加

前回までと同様にCargo.tomlに以下のdependencyを追加します。

handlebars = "0.20.5"

また、今回はHTMLページでformを表示し、POSTしてみたいと思うので、 formやURLなどのパラメータをパース及び取得するためのcrateもdependencyに追加しておきます。

params = "0.4.1"

全体

まずはコードの全体です

extern crate iron;
extern crate router;
extern crate handlebars_iron as hbs;
extern crate params;

use std::collections::HashMap;
use std::error::Error;
use iron::prelude::*;
use iron::status;
use router::{Router, url_for};
use hbs::{Template, HandlebarsEngine, DirectorySource};

fn main() {

    fn top_handler(req: &mut Request) -> IronResult<Response> {
        let mut resp = Response::new();
        let mut data = HashMap::new();
        data.insert(String::from("greeting_path"),
                    format!("{}", url_for(req, "greeting", HashMap::new())));
        resp.set_mut(Template::new("index", data)).set_mut(status::Ok);
        return Ok(resp);
    }

    fn greet_handler(req: &mut Request) -> IronResult<Response> {
        use params::{Params, Value};
        let map = req.get_ref::<Params>().unwrap();
        return match map.find(&["name"]) {
            Some(&Value::String(ref name)) => {
                Ok(Response::with(
                    (status::Ok,
                     format!("Hello {}", name).as_str())))
            },
            _ => Ok(Response::with((status::Ok, "Hello world")))
        }
    }

    //Create Router
    let mut router = Router::new();
    router.get("/", top_handler, "index");
    router.post("/greet", greet_handler, "greeting");
    
    //Create Chain
    let mut chain = Chain::new(router);
    // Add HandlerbarsEngine to middleware Chain
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(
        DirectorySource::new("./src/templates/", ".hbs")));
    if let Err(r) = hbse.reload() {
        panic!("{}", r.description());
    }
    chain.link_after(hbse);
    
    println!("Listen on localhost:3000");
    Iron::new(chain).http("localhost:3000").unwrap();
}

細かく見ていきましょう

Router

    //Create Router
    let mut router = Router::new();
    router.get("/", top_handler, "index");
    router.post("/greet", greet_handler, "greeting");

前回同様Routerを作成しています。今回はgreetの名前はurlの一部ではなくpostのパラメータとして送信することにしました。

Chain

let mut chain = Chain::new(router);

前回は作成したRouterを直接HandlerとしてIronに渡していましたが、今回はChainというオブジェクトを作成しています。 これはMiddlewareの連鎖を表現したオブジェクトで、ここにミドルウェアをつなげていくことができます。

HandlebarEngine

    // Add HandlerbarsEngine to middleware Chain
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(
        DirectorySource::new("./src/templates/", ".hbs")));
    if let Err(r) = hbse.reload() {
        panic!("{}", r.description());
    }
    chain.link_after(hbse);

ここではHandlebarsEngineを作成し、 Handlebarのテンプレートファイルを設置するディレクトリのルートディレクトリ、 Handlebarのファイルとして扱うファイルの拡張子を指定しています

今回の場合は./src/templates/をルートディレクトリとして指定しています、また拡張子は.hbsとしています。

このようにディレクトリを指定したら

    if let Err(r) = hbse.reload() {
        panic!("{}", r.description());
    }

ディレクトリ中のファイルをロードしています。

そして最後に先ほどのChainの末尾にHandlebarsEngineを追加しています。

chain.link_after(hbse);

テンプレートの描画

    fn top_handler(req: &mut Request) -> IronResult<Response> {
        let mut resp = Response::new();
        let mut data = HashMap::new();
        data.insert(String::from("greeting_path"),
                    format!("{}", url_for(req, "greeting", HashMap::new())));
        resp.set_mut(Template::new("index", data)).set_mut(status::Ok);
        return Ok(resp);
    }

indexページのハンドラ中では、レスポンスのボディとしてTemplate::new("index", data)を指定しています。 このようにすることで、さきほどしたルートディレクトリからの相対パスで指定したhandlebarファイルを指定できます。

ファイル中で利用する変数のデータはJSONに変換可能なデータ構造、今回の場合はHashMap等で渡すことができます。 参考URL

トップページのテンプレートファイルは

<h1>Hello, world</h1>

<form action={{greeting_path}} method="POST">
      <input type="text" name="name"/>
      <input type="submit" value="Hello!"/>
</form>

このようにformのaction URLをgreetingのページのURLに設定しています。

このようにすると

https://gyazo.com/894654c0137504b7a682cfe47449c0a6

のようにformの表示されたHTMLページが描画されます。

POSTのパラメータの処理

さて、先ほどのページのformに値をいれてsubmitするとその値はnameというパラメータ名でPOSTされてきます。

この値をハンドラ中で利用するには

        let map = req.get_ref::<Params>().unwrap();
        return match map.find(&["name"]) {
            Some(&Value::String(ref name)) => {
                Ok(Response::with(
                    (status::Ok,
                     format!("Hello {}", name).as_str())))
            },
            _ => Ok(Response::with((status::Ok, "Hello world")))
        }

このように、まずリクエスト中からParams型の値を取り出し、 Paramsに対してfindメソッドで値を検索した結果のOptionalをmatchすることで利用できます。

今回の場合は取り出してきたnameを使ってHello 名前という形で表示しています。 https://gyazo.com/7464d8053c1c13b7262281b9c23c44fd

まとめ

今回は前回までテキストのみの表示だったページをHandlebarのテンプレートエンジンを用いることでHTMLの表示を可能にしました。 また、POST等のパラメータを取得するためのparams crateを利用しパラメータをハンドラ中で取得できるようになりました。

次回はHTMLだけでなくJSONなどのデータ型をユーザーからのリクエストに応じて返せるようにしたいと思います。

僕が働いているAzit.incでは一緒に働けるエンジニアを募集しています!
採用情報 — 株式会社アジット|Azit Inc.