2014/03/14


ruby のアプリを動かす時にいちいち bundle exec って書くのがダルい。書きたくない。でもシステムに入ってたり違うバージョンの物が動いて変な動作をされても困る。
どうにかしてこのダルさを解消できないかと考えてみた。

まず rbenv を使ってるなら gem でインストールされるコマンドは必ずシェルのラッパとして生成され、そこから本物が起動する様になっている。例えば rails であれば以下の様なシェルになっている。
#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x

program="${0##*/}"
if [ "$program" = "ruby" ]then
  for arg; do
    case "$arg" in
    -e* | -- ) break ;;
    */* )
      if [ -f "$arg" ]then
        export RBENV_DIR="${arg%/*}"
        break
      fi
      ;;
    esac
  done
fi

export RBENV_ROOT="/home/mattn/.rbenv"
exec "/home/mattn/.rbenv/libexec/rbenv" exec "$program" "$@"
おっと、運良く /usr/bin/env を使ってくれている。つまりはパスを書き換えれば rails とタイプしたコマンドを自前のコマンドに置き換えられるという事だ。
って事で direnv を入れよう。
direnv - unclutter your .profile

Usage Use direnv edit . to open an ".envrc" in your 1EDITOR. This script is going to be executed onc...

http://direnv.net/
$ go get github.com/zimbatm/direnv
bash をインタラクティブシェルに使っているなら以下を .bashrc に追加する。
eval "$(direnv hook bash)"
これでこのディレクトリ特有のフックが実行出来る。次に rails や middleman 等を起動しているディレクトリに .envrc というファイルを作ろう。
中身はパスを書き換えるだけ。
export PATH=$PWD/.bin:$PATH
シェルを起動し直すか source ~/.bashrc した後でこのディレクトリに移動すると
direnv: error .envrc is blocked. Run `direnv allow` to approve its content.
と怒られる。すかさず
$ direnv allow
を実行しよう。さて、後は .bin というディレクトリを作り、その中に bash というファイルを作る。勘のいい方であればそろそろ気付いたはず。
#!/bin/bash

PATH=`echo $PATH sed 's/^[^:]*://g'` bundle exec "$@"
追加したパスを元に戻して bundle exec "$@" しています。
これでいちいち bundle exec middleman とかしなくても middleman とするだけで同じコマンドが実行出来る様になります。

さようなら、bundle exec、いい思い出をありがとう。

ちなみに shebang が bash じゃなくて perl な物を起動しようとしているなら同様に .bin/perl を書いてしまえば、carton exec にも対応出来るかもしれませんね。
(だらだら書いてますが bundle install --binstubs してそこにパス通すのでいいです)

2014/03/12


C言語で可変子引数扱う場合は va_start/va_arg/va_end を使うのだけど...
#include <stdio.h>
#include <stdarg.h>

void
foo(int n, ...) {
  va_list list;
  int i;
  va_start(list, n);
  for(i = 0; i < n; i++)
    puts(va_arg(list, char*));
  va_end(list);
}

int
main(int argc, char* argv[]) {
  foo(3"foo""bar""baz");  
  return 0;
}
型が拘束出来ない。なので
foo(31"bar""baz");  
こんな呼び出し方が出来てしまうし、コンパイルエラーや警告も出ない。実行時にクラッシュする。
なんか出来ないかなーと思ってマクロの海へ...
#include <stdio.h>
#include <stdarg.h>

void
foo(int n, ...) {
  va_list list;
  int i;
  va_start(list, n);
  for(i = 0; i < n; i++)
    puts(va_arg(list, char*));
  va_end(list);
}

#define bar(...) foo((sizeof((char*[]){__VA_ARGS__})/sizeof(char*)), __VA_ARGS__)

int
main(int argc, char* argv[]) {
  bar("foo""bar""baz");
  return 0;
}
これだと引数の個数指定も要らなくなる。エラーにはならないものの、警告は出るので少しはメリットあるかも。
vaarg.c: In function 'main':
vaarg.c:18:3: warning: initialization makes pointer from integer without a cast [enabled by default]
vaarg.c:18:3: warning: (near initialization for '(anonymous)[0]') [enabled by default]

2014/03/10


Twitter / ymmt2005: むう、mime/multipart の ...

むう、mime/multipart の CreateFormFile の Content-Type は application/octet-stream 固定になっている。泣ける。。#golang https://code.google.com/p/go/source/browse/src/pkg/mime/multipart/writer.go#128

https://twitter.com/ymmt2005/status/431143170659717120
ファイルから multipart.CreateFormFile を呼ぶと io.Writer が返ります。この writer は隠ぺいされているのでパートのヘッダを書き換える事は出来ません。
この場合は multipart.CreatePart を使います。
package main

import (
    "bytes"
    "io"
    "log"
    "mime/multipart"
    "net/textproto"
    "os"
)

func main() {
    var b bytes.Buffer
    w := multipart.NewWriter(&b)
    part := make(textproto.MIMEHeader)
    part.Set("Content-Type""application/vnd.ms-excel")
    part.Set("Content-Disposition"`form-data; name="file"; filename="Foo.xlsx"`)
    pw, err := w.CreatePart(part)
    if err != nil {
        log.Fatal(err)
    }
    f, err := os.Open("Foo.xlsx")
    if err != nil {
        log.Fatal(err)
    }
    io.Copy(pw, f)
    w.Close()

    b.WriteTo(os.Stdout)
}