手元にあるパッチの一覧を確認するシェルスクリプト

ブログに書いていなかったことに気づいたので、残しておく。

モチベーション

OSSや仕事のコードを修正してるときに「まだ動作確認ができていない」「コミットメッセージの説明が不十分」なコミットが手元に溜まることがある。

このパッチの一覧を確認するため、シェルスクリプトを使っている。

シェルスクリプト

このファイルを PATH の通ったディレクトリに git-patch というファイル名で置く。

$ cat << 'EOF' > ~/bin/git-patch
#!/bin/bash

ls ~/ghq/github.com/*/*/.git/refs/heads/patch/*
grep refs/heads/patch ~/ghq/github.com/*/*/.git/info/refs
EOF

$ chmod +x ~/bin/git-patch

Gitは git-*** という命名規則の実行可能ファイルをサブコマンドで実行できるので、以下のように使用できる。

$ git patch

このスクリプトを実行すると patch/ で始まるブランチの一覧を確認できる。

Railsの DATABASE_URL で指定できるパスワードの文字種

Railsアプリケーションを動かすAWSリソースをTerraformで作る場合、random_password を使ってDBパスワードを生成します。
しかし、 DBパスワードに一部の記号が含まれていると環境変数 DATABASE_URL で渡す際に URI::InvalidURIError が発生してしまうため、使える文字種を調べた。

調査

URI.parse を使ってるのは分かったので、実際に解析して調べた。

require 'uri'

specials = '!@#$%&*()-_=+[]{}<>:?'.chars
check = -> { URI.parse("postgres://myuser:#{_1}@localhost/somedatabase").password rescue nil }

specials.select(&check).join
#=> "!$&*()-_=+:"
specials.reject(&check).join
#=> "@#%[]{}<>?"

結果

使える文字種は "!$&*()-_=+:" なので、DBパスワードを生成するときの定義は以下の通り。

resource "random_password" "password" {
  special          = true
  override_special = "!$&*()-_=+:"
}

*1:他の箇所に記載あったかもしれないが、詳細は調べてない

GitHubのPull Requestでコミットメッセージの文字数を調べる

GitHubのPull Requestで各コミットメッセージの文字数や変更行数を取得する方法を調べたのでまとめておく。

GraphQL APIで取得できたので、雑にcsv形式にして出力した。

stat-commit-messages.sh

#!/bin/bash

set -eu

OWNER=${1:-rails}
NAME=${2:-rails}
NUMBER=${3:-20000}

echo 'author,subject,body,additions,deletions,changes'
gh api graphql -F owner="$OWNER" -F name="$NAME" -F number=$NUMBER -f query='
  query($name: String!, $owner: String!, $number: Int!) {
    repository(owner: $owner, name: $name) {
      pullRequest(number: $number) {
        commits(first: 100) {
          nodes {
            commit {
              author { name },
              messageHeadline,
              messageBody,
              additions,
              deletions
            }
          }
        }
      }
    }
  }
' --jq '.data.repository.pullRequest.commits.nodes[] | map("\"\(.author.name)\",\(.messageHeadline|length),\(.messageBody|length),\(.additions),\(.deletions),\(.additions + .deletions)") | join("\n")'

実行結果

$ ./stat-commit-messages.sh
author,subject,body,additions,deletions,changes
"Ryuta Kamizono",43,1714,4,4,8

$ ./stat-commit-messages.sh rails rails 46762
author,subject,body,additions,deletions,changes
"David Heinemeier Hansson",24,78,63,0,63
"David Heinemeier Hansson",47,465,105,22,127
"David Heinemeier Hansson",27,0,3,0,3
"David Heinemeier Hansson",35,0,18,4,22
"David Heinemeier Hansson",40,0,7,0,7
"David Heinemeier Hansson",41,0,2,2,4
"David Heinemeier Hansson",3,0,2,0,2
"David Heinemeier Hansson",36,0,9,4,13
"David Heinemeier Hansson",28,0,7,2,9
"David Heinemeier Hansson",9,0,0,2,2
"David Heinemeier Hansson",54,0,1,0,1
"David Heinemeier Hansson",35,0,5,0,5
"David Heinemeier Hansson",31,0,2,1,3
"David Heinemeier Hansson",9,0,18,0,18
"David Heinemeier Hansson",47,0,4,0,4
"David Heinemeier Hansson",13,0,3,0,3
"David Heinemeier Hansson",57,0,7,3,10
"David Heinemeier Hansson",29,0,2,1,3
"David Heinemeier Hansson",40,14,1,4,5
"David Heinemeier Hansson",45,0,2,1,3
"David Heinemeier Hansson",19,0,2,0,2
"David Heinemeier Hansson",13,0,28,0,28
"David Heinemeier Hansson",14,0,5,5,10

実際のGitHubのPull Requestは以下の2つで、取得した値もあってそう。

参考ページ

GraphQLのドキュメント docs.github.com

Rubyの定数の探索順序

Ruby技術者認定試験の勉強のため、この機会にちゃんと理解しておく。

バージョン

$ ruby --version
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]

継承

子クラスに定数がない場合、親クラスの定数を参照できる。

class A
  FOO = "foo"
  BAR = "bar"
end

class B < A
  BAR = "bar-B"
end

puts A::FOO #=> foo
puts A::BAR #=> bar
puts B::FOO #=> foo
puts B::BAR #=> bar-B

include, prepend

モジュールも継承と同様に参照できる。

継承関係で探索するため、 includeprepend が混ざった場合は prepend で追加したモジュールの定数が優先される。

module M1
  FOO = "foo"
  BAR = "bar"
end

module M2
  FOO = "foo-M2"
end

class A
  include M1

  BAR = "bar-A"
end

class B
  prepend M1

  BAR = "bar-B"
end

class C
  include M1
  include M2
end

class D
  prepend M1
  include M2
end

puts M1::FOO #=> foo
puts M1::BAR #=> bar
puts A::FOO  #=> foo
puts A::BAR  #=> bar-A
puts B::FOO  #=> foo
puts B::BAR  #=> bar-B
puts C::FOO  #=> foo-M2
puts D::FOO  #=> foo

スコープ

継承関係よりもネスト関係の方が優先されるが、トップレベルの定数はネスト外部とは対象外になる。詳細は 変数と定数 (Ruby 3.1 リファレンスマニュアル) を参照。

CONST = "top-level"

module M
  CONST = "M"

  class A
    CONST = "A"
  end

  class B < A
    puts CONST #=> M
  end
end

class M::A
  puts CONST #=> "A"
end

class M::A::C
  puts CONST #=> "top-level"
end

Rubyのフリップフロップ (flip-flop) 構文

Ruby技術者認定試験の勉強をしていて、初めて知った機能だったのでまとめておく。

公式ドキュメント

挙動

基本

公式ドキュメントから引用。

5.times{|n|
  if (n==2)..(n==3)
    p n
  end
}
#=> 2
#   3

5.times{|n|
  if (n==2)...(n==3)
    p n
  end
}
#=> 2
#   3

テキストの処理

複数行のテキストから範囲を取得する際に便利らしい。
以下の例ではマークダウン記法でRubyのコード部分を取得するために使用している。

input = <<-EOF
The reproduction code is as follows.

### pattern 1

  ```rb
  puts "hello"
  ```

### pattern 2

  ```rb
  puts "world"
  ```
EOF

input.each_line do |line|
  puts line if (line =~ /```rb$/)..(line =~ /```$/)
end
#=>  ```rb
#  puts "hello"
#  ```
#  ```rb
#  puts "world"
#  ```

アルファベットの1, 2, 10, 11, 20, 21文字目を表示

('a'..'z').each_with_index { |x, i| print x if (i%10==0)..(i%10==1) }
#=> abkluv

「..」と「...」の違い

公式ドキュメントから引用。

5.times{|n|
  if (n==2)..(n==2)
    p n
  end
}
#=> 2

5.times{|n|
  if (n==2)...(n==2)
    p n
  end
}
#=> 2
#   3
#   4

違いは分かるけど、有効な使い道がよく分からん...。

Ruby 2.6で非推奨になるも、Ruby 2.7で復活

クックパッドの記事は読んだはずなのに、フリップフロップ構文を使う機会が無さ過ぎて記憶に全く残っていなかった。

フリップフロップ構文を覚えて

資格試験のためだけに覚えるけど、仕事で使うことはないかな...

Ruby 3.1.xだと継承ツリーに後からincludeでモジュールを追加できる

Ruby技術者認定試験Goldの勉強をしていてRubyのバージョンによって変わっている挙動を知ったのでメモ。

検証コード

module M1
  def method_1
    __method__
  end
end

class C
  include M1
end

p C.new.method_1

module M2
  def method_2
    __method__
  end
end

module M1
  include M2
end

p C.new.method_2

Ruby 2.7.6

:method_1
Traceback (most recent call last):
a.rb:23:in `<main>': undefined method `method_2' for #<C:0x0000000148184c48> (NoMethodError)
Did you mean?  method
               method_1
               methods

Ruby 3.1.2

:method_1
:method_2

Rubyのensure節の挙動

Ruby技術者認定試験の対象バージョンがRuby 3.1.xになったので、試験勉強していて気づいた挙動をブログにメモっておく。

ensure節の値は無視される

ensure 節が存在する時は begin 式を終了する直前に必ず ensure 節の本体を評価します。
begin式全体の評価値は、本体/rescue節/else節のうち最後に評価された文の値です。また各節において文が存在しなかったときの値はnilです。いずれにしてもensure節の値は無視されます。

引用: https://docs.ruby-lang.org/ja/latest/doc/spec=2fcontrol.html

def foo
  "foo"
ensure
  puts "ensure"

  "bar"
end

puts foo
#=> ensure
#=> foo

ensure節にreturnがあると無視されない

def foo
  "foo"
ensure
  puts "ensure"

  return "bar"
end

puts foo
#=> ensure
#=> bar