オープンソースこねこね

Webプログラミングなどについてあれこれ。

Goについての雑感

最近Golangコマンドラインツールを書いているので、ちょっと思うところを書いておく。

気に入っているところ

シングルバイナリをクロスプラットフォームで生成できる

とくにコマンドラインツールを作ってみると、この特性がすごく気持ちよい。シングルバイナリはインストールが楽という利点があり、特に今はgithubのリリースページにバイナリを置いたり、https://bintray.com/とかを使うことでファイルをHTTPでダウンロードすることができ、導入の敷居がすごく下がる。アンインストールもファイルを消せばいいだけなので気楽だ。

ところで自分の場合、RubyPythonなど自分がメインで使っていない言語の処理系を必要とするツールだと、まずその処理系を「正しく」インストールする方法を調べるのに気を使ったりしてしまう。もちろん本当のところは目的のツールが単に動けばいいだけなので、Macにプリインストールされている処理系を使えばいいのだけど、どうせ使うんだったら、Linuxサーバ上のものと同じ環境にしたいとか、メジャーな方法を使いたいとかでrbenvを調べたりごにょごにょ環境周りをいじって時間を浪費することが多くて、しょんぼりする。

そのへんの事情から開放してくれて他に依存が無いのですぐに使えるし、すぐに捨てられるというシングルバイナリは思いの外、快適だったりする。

静的型付けのコンパイル言語

今はIntelliJでGoを書いているので静的型な言語だとIDEの補完や定義先へのジャンプがかなり強力に作用する。ちょっと作業環境が重いと感じることもあるが、許容できる。プライベートで書いているプログラムだと、ちょっと変数の名前の付け方やディレクトリ構成が気に食わないとか、気分でコード全体をもりっと変えることがよくあって、そういうときもコンパイラのチェックがあるので、ある程度のコードの正当性を担保してくれるのがよい。

また、型推論のためコードの見た目はLLっぽいのも気に入っている。

OSSですでに周辺ライブラリがいろいろある、導入も簡単

Githubで検索すれば必要なライブラリは大抵あるし、それを使ったサンプルコードも検索すれば大体出てくる。

困っているところ

GOPATHとimport

もういろんなところで言及されているので詳細は省くが、GoはGOPATHというパスを起点にプロジェクトのコードと依存する外部パッケージのコードを同列にフラットに管理するという独特の方式を採用している。これのせいで明確に困ることがあって、

GithubのGo言語プロジェクトにPull Requestを送るときのimport問題 | SOTA

のようにforkしたプロジェクトだと問題になる。また自分の作業環境だと、依存物をプロジェクトのディレクトリ配下に収められないのでctagsでタグ生成する範囲が広がりすぎる問題がある。で、この辺は以前書いたようにdirenvGomを使ってGOPATHをプロジェクトごとに複数、切り替えて対応している。

Goの開発環境 - オープンソースこねこね

幸いIntelliJもGOPATHを複数設定し、プロジェクトごとに切り替えることができるのでその機能を使っている。 とはいえちょっとややこしくて、非標準のツールに頼っているため悩ましいところではある。

プラグイン的なものが作れない

Goは動的に他のビルド済みのGoのコードをロードすることができないので、後からプロダクトに機能追加できるようにする、いわゆるプラグイン的なものはつくれない。

考えていること

単一で完結するコマンドラインツールは今後Goで書く。

普通のWebアプリだったら依然PHPを使うと思う。

MacでSSHログインと同時にターミナルの色を変える

いっぱいターミナルを開いていると間違って本番サーバで開発サーバ用のコマンドを実行しそうになってヒヤリとすることがあります。 そんなわけでSSH接続時、接続先ごとに自動的にターミナルの背景を変えるようにしてみました。以下のスクショのようになります。

f:id:kohkimakimoto:20150401165041g:plain

解説

背景色の選択はAppleScriptでやります。以下のような関数を~/.zshrcに書いておく

# ~/.zshrc
function terminal-color () {
  /usr/bin/osascript -e "tell application \"Terminal\" to set current settings of first window to settings set \"$1\""
}

これで、terminal-color 'Red Sands'のようにターミナルのテーマを指定して実行すると、背景色が変わります。 ついでに、踏み台サーバを経由するような場合も自動でそこまでたどり着けるように、コマンドを自動で連続してターミナルに流し込めるようにするAppleScriptも書いた。

# ~/terminal-exec.scpt
on run argv
  tell application "Terminal" to activate
  tell application "System Events" to tell process "Terminal" to keystroke "t" using command down

  tell application "Terminal"

    repeat with x in argv
      do script x in front window
    end repeat

  end tell

end run

これをコマンドで呼び出せるようにシェル関数でラップする。

function terminal-exec() {
  /usr/bin/osascript ~/terminal-exec.scpt $@
}

ここまでで

terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.1"

のようにコマンドを実行すれば、背景色を変えてSSH接続できるようになります。踏み台サーバがあるときはSSHコマンドを並べて書くだけでいいです。

terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.1" "ssh kohkimakimoto@192.168.56.2"

さて、この長ったらしいコマンドを毎回手打ちするわけもなく、pecoを利用したコマンドランチャーを利用します。これは以前のエントリ pecoをランチャーのようにして使う - オープンソースこねこね に書いたが当時のものから少し変わっているので、改めて以下にzshの関数を以下に貼り付けておきます。

# ~/.zshrc
function peco-snippets() {

    local line
    local snippet
    local cwd
    local snippet_file
    local local_snippet_file
    local direct_run

    # Get a snippets file in the current directory if it exists.
    cwd=`pwd`
    if [ -e "$cwd/.snippets" ]; then
      local_snippet_file="$cwd/.snippets"
    else
      local_snippet_file=""
    fi

    # Do not load home snippets as a local snippets
    if [ $HOME = $cwd ]; then
       local_snippet_file=""
    fi

    direct_run=0
    if [ $# -ge 1 ]; then
      # Load sub snippets file and selected line will be run directly.
      direct_run=1
      snippet_file=$1
      local_snippet_file=""
    else
      snippet_file=$ZSH_EXT_ROOT/snippets
    fi;

    if [ ! -e "$snippet_file" ]; then
        echo "$snippet_file is not found." >&2
        return 1
    fi

    line=$(cat $local_snippet_file $snippet_file | grep -v "^\s*#" | grep -v '^\s*$' | peco --query "$LBUFFER")
    if [ -z "$line" ]; then
        return 1
    fi

    snippet=$(echo "$line" | sed "s/^[ |\*]*\[[^]]*\] *//g")
    if [ -z "$snippet" ]; then
        return 1
    fi

    if [ $direct_run -eq 1 ]; then
      eval $snippet
    else
      BUFFER="$snippet"
      zle clear-screen
    fi
}
zle -N peco-snippets
bindkey '^x^x' peco-snippets

pecoがインストールされていない環境はインストールしておきます。

peco/peco · GitHub

そして、以下のようなコマンドを列挙したスニペットファイルを用意します。

# ~/.snippets
[ssh connect: hakoniwa-local1] terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.20 -p 22"
[ssh connect: hakoniwa-local2] terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.21 -p 22"
[ssh connect: hakoniwa-local3] terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.22 -p 22"
[ssh connect: hakoniwa-local4] terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.23 -p 22"
[ssh connect: hakoniwa-local5] terminal-exec "terminal-color 'Pro remote'" "ssh kohkimakimoto@192.168.56.24 -p 22"

これでcontrol+x control+xのショートカットでスクショにあるようにssh接続先一覧が出て、そこから選べるようになります。 ちなみに本番サーバはレッド、開発サーバやローカルの仮想環境はブルーで表示されるようにしています。

Codeから遷移したUIViewControllerにstoryboard上でナビゲーションバーを表示する

storyboad上のSegueで画面遷移をつないでいった場合、UINavigationControllerに含まれるViewControllerは自動でナビゲーションバーが表示されて、そこにタイトルとかバーボタンをとかを配置することができるのだけど、コードから遷移させた場合storyboad上ではどことも繋がっていないViewControllerとして表現されてしまって、実際はUINavigationControllerの中にいてナビゲーションバーとかがあるのにそれが表示されていない、ということが起こる。

そんなViewControllerの場合以下のようにしてやればstoryboard上にナビゲーションバーを表示でき、IB上でタイトルやボタンを配置することができます。

  • storyboard上でViewControllerを選択する。
  • Attributes inspectorでSimulated Metrics設定のTop BarTranslucent Navigation Barに設定する
  • storyboard上にナビゲーションバーの領域があらわれる
  • ナビゲーションバーの領域にNavigation Itemドラッグアンドドロップして配置する

以上。

UITextFieldやUITextViewでキーボードの外をタップしたらキーボードを閉じる

すでにいろいろやり方がネット上に書かれているが、自分の中では以下の方法に落ち着いたのでメモっておく。

まず、以下の記事で紹介されているようなFirstResponderを取得するメソッドをUIViewにカテゴリとして事前に実装しておく。

FirstResponderを探せ - Kazzzの日記

#import "UIView+UIUtil.h"
@implementation UIView (UIUtil)
- (UIView *)findFirstResponder
{
    if ([self isFirstResponder]) 
    {
        return self;
    }
    for (UIView *subView in [self subviews]) 
    {
        if ([subView isFirstResponder])
        {
            return subView;
        }
        if ([subView findFirstResponder])
        {
            return [subView findFirstResponder];
        }
    }
    return nil;
}
@end

あとはViewController上で以下のように実装する。

# XXHogeViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];

    // タップを検知するためのGestureRecognizer。デリゲートで処理するように設定
    UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] init];
    gestureRecognizer.delegate = self;
    gestureRecognizer.cancelsTouchesInView = NO;
    [self.view addGestureRecognizer:gestureRecognizer];
}

// タップ時のデリゲートメソッド
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    UIView *touchedView = touch.view;
    UIView *firstResponderView = [self.view findFirstResponder];
    
    if (touchedView != firstResponderView) {
        // 現在編集中のViewと違う場所がタップされたときのみキーボードを閉じる
        [self.view endEditing:YES];
    }
    return YES;
}