かまたま日記3

プログラミングメイン、たまに日常

メソッドの引数にメソッドの返り値を渡す場合、blockの渡し方に注意する

たとえば、 こんなメソッドがあるとします。

def foo(arg)
  puts "#{arg}, #{block_given?}"
end

def var
  if block_given?
    yield
  else
    'no block'
  end
end

下記のようにブロックの渡し方でfooメソッド側のblockとなったりvarメソッド側になったりします。想定した渡し方になってない場合があるので注意です。

foo var do
  'block'
end
# => no block, true

foo var {
  'block'
}
# => block, false

foo(var do
  'block'
end)
# => block, false

GitHubのPull Requestで変更されたファイル一覧を取得する

結論から言うと、あるPull Requestがmasterにマージコミットされると、 直前のコミット*1と、Pull Requestの最後のコミットの二つが親コミットになっているので、最初の親をHEAD^で取得しHEADとのdiffを見る。

たとえばこのPRがマージされた場合の、masterの履歴がこんな感じ

f:id:kamatama_41:20161008234549p:plain

ここで以下のコマンドを打つ。HEAD^18c4d8aなので、差分である今回のPRで変更されたファイル一覧が取得できる。

% git diff HEAD^..HEAD --name-only
scripts/1.txt
scripts/2.txt
scripts/3.txt

ちなみに、二つ目の親81c4663HEAD^2で取れるが、上記のPRの場合、これとマージコミットは差分が無いので、何も出ない。

% git diff HEAD^2..HEAD --name-only

参考

*1:PRベースでmasterにコミットしている場合は前回マージされたPRのマージコミットのはず

Little Glee Monsterにハマっている件

TL;DR

最近 Little Glee Monster (以下リトグリ)というボーカルグループにハマっておりまして、その良さを共有したいというポストです。

リトグリとは?

このブログにたどり着くであろう想定読者(Web系エンジニア)の方は知らない人が大半だと思うので軽く紹介すると、2014年10月デビューの女子高生6人組のボーカルグループで、ソニー損保のCMの人たちと言われたら分かる人もいるかもしれません *1

https://www.youtube.com/watch?v=wKhPF894kVUwww.youtube.com

(2016.12.1追記) ↑の動画がprivateになったので、代わりにCMじゃないバージョンで https://www.youtube.com/watch?v=TWX1v7BKnUIwww.youtube.com

(2017.2.14追記) ↑の動画もprivateになりました..orz

リトグリの魅力

1にも2にも圧倒的な歌唱力です*2。6人全員が年齢離れした歌唱力を持っていて、その6人が合わさった時のハーモニーに非常に圧倒される毎日です。とりあえず3曲挙げておきますので、興味があったらYouTubeにたくさん動画あるので公式チャンネルとか他の動画とか見てみて下さい。

オリジナル曲「空は見ている」のアカペラver(音量注意)

www.youtube.com

オリジナル曲「Girls be Free!

www.youtube.com

ドリカム「朝がまた来る」のカバー

www.youtube.com

ライブ

今月9/3に日比谷野外音楽堂に、9/22に山梨県東京エレクトロン韮崎文化ホールの2回行ってきました。 小並感ですが非常に良かったです、CD音源より生歌の方が迫力あるし、ライブ中にメンバーがステージ降りてファンと交流したり、写真撮影OKな曲があったりでファンサービス満点ですし、ライブに行くのが非常に価値のあるアーティストだと思いました。

9/3 日比谷野外音楽堂

9/22 東京エレクトロン韮崎文化ホール

まとめ

リトグリの良さを語れるエンジニア募集!

*1:ちなみに動画の真ん中の人はリトグリメンバーではなく女優の唐田えりかさんです

*2:3以降はまた説明したくなったらします

GoogleのAPIトークンをS3に保存する

GoogleAPI Clientには Google::Auth::TokenStore というトークンを保存するためのインターフェースが存在していて、デフォルトでは

  • Google::Auth::Stores::FileTokenStore
  • Google::Auth::Stores::RedisTokenStore

のふたつがある。これのS3バージョン。 ひとつ下の記事FileTokenStore を置き換えられる。

GCSじゃないの?というツッコミは無しで...w cacheは厳密にはいらないけど何回も呼ぶときがあるかなと思って用意しました。

require 'googleauth/token_store'
require 'aws-sdk'

class S3TokenStore < Google::Auth::TokenStore
  def initialize(bucket_name:, path_prefix:)
    @bucket = Aws::S3::Resource.new.bucket(bucket_name)
    raise "Bucket(#{bucket_name}) not exists" unless @bucket.exists?
    @path_prefix = path_prefix
    @cache = {}
  end

  def load(id)
    @cache[id] ||= begin
      object = object(id)
      if object.exists?
        object.get.body
      end
    end
  end

  def store(id, token)
    object(id).put(body: token)
    @cache[id] = token
  end

  def delete(id)
    object(id).delete
    @cache.delete(id)
  end

  private
  def object(key)
    @bucket.object("#{@path_prefix}#{key}")
  end
end

GmailをAPI経由で取得する

GmailAPI経由で未読のメールを1件検索して既読にするサンプル。OAuthのトークンがない場合はコンソールに出てくるURLをブラウザで開いて認証後に出てくるコードを入力する。

require 'google/apis/gmail_v1'
require 'googleauth/stores/file_token_store'

Gmail = Google::Apis::GmailV1
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'

def credentials(email)
  @credentials ||= begin
    client_id = Google::Auth::ClientId.new(
      'xxxxx.apps.googleusercontent.com',
      'xxxxx'
    )
    token_store = Google::Auth::Stores::FileTokenStore.new(file: "#{ENV['HOME']}/google_credentials.yaml")
    authorizer = Google::Auth::UserAuthorizer.new(client_id, Gmail::AUTH_SCOPE, token_store)
    user_id = email
    credentials = authorizer.get_credentials(user_id)
    if credentials.nil?
      url = authorizer.get_authorization_url(base_url: OOB_URI)
      puts "Open the following URL in your browser and authorize the application."
      puts url
      puts "Enter the authorization code:"
      code = gets
      credentials = authorizer.get_and_store_credentials_from_code(
        user_id: user_id, code: code, base_url: OOB_URI
      )
    end
    credentials
  end
end

email = 'foo@gmail.com'
gmail = Gmail::GmailService.new
gmail.authorization = credentials(email)

result = gmail.list_user_messages(email, max_results: 1, q: 'label:inbox label:unread')
result.messages.each do |m|
  puts m.id
  puts gmail.get_user_message(email, m.id).snippet
  r = Gmail::ModifyMessageRequest.new
  r.remove_label_ids = ["UNREAD"]
  result = gmail.modify_message(email, m.id, r)
end

BashのPS4でデバッグが捗る

Bashset -xとするとデバッグモードとして処理内容が逐次出力されますが、PS4という環境変数で出力内容を調整できます。


test.sh

#!/usr/bin/env bash

PS4='+ [${BASH_SOURCE}:${LINENO}] ${FUNCNAME:+$FUNCNAME(): }'
set -x

hello() {
  name=$1
  echo "Hello $name"
}

hello 'World'

これを実行すると、以下のようにソース名(BASH_SOURCE)と行数(LINENO)、関数の場合は関数名(FUNCNAME)が出力されます。

% bash test.sh
+ [test.sh:11] hello World
+ [test.sh:7] hello(): name=World
+ [test.sh:8] hello(): echo 'Hello World'

シェルスクリプトで再実行するための関数

retry() {
  command="$@"
  local try_count=0
  local retry_limit=3
  local wait_seconds=300
  until sh -c "$command"; do
     [ $try_count -eq $retry_limit ] && return 1
     sleep $wait_seconds
     try_count=$(expr $try_count \+ 1)
  done
}

# example
retry ls -ltra hoge