RubyVM::AbstractSyntaxTree で Node を辿る処理をシンプルに実装する
Ruby の Enumerator を使うと簡単にRubyのコードを処理できて便利だったので、ブログに書いておく。
ast = RubyVM::AbstractSyntaxTree.parse(<<~RUBY) class User < ApplicationRecord def say(text) puts text end def sum(x, y) x + y end end RUBY enum = Enumerator.new { |y| nodes = ast.children until nodes.empty? nodes.select! { _1.is_a?(RubyVM::AbstractSyntaxTree::Node) } nodes.each { y << _1 } nodes = nodes.flat_map(&:children) end } method_names = enum.select { _1.type == :DEFN }.map { _1.children[0] } pp method_names #=> [:say, :sum]
【追記】Rails v7.1.0 で `can't be blank` が `can’t be blank` に変わる(リバートされました)
既存アプリやライブラリへの影響が大きく、この変更に対してネガティブなフィードバックも多かったためリバートされました。 github.com
概要
表題の通り、Rails v7.1.0 で APOSTROPHE (U+0027) が SINGLE QUOTATION MARK (U+2019) に変わります。
既存のRailsアプリをアップグレードする際に影響が大きそうなので、記事を書きました。
影響範囲
テストでエラーメッセージを検証していた場合、Rails v7.1.0 のアップグレードによって検証に失敗するようになります。
Expected: "can't be blank" Actual: "can’t be blank"
今回の変更を知らない場合、このテストのエラーメッセージだけで ' と ’ の違いを見分けるのは厳しそう。
SINGLE QUOTATION MARK (U+2019) の入力方法
日本語入力モードで「Shift + 7」を押すと入力できました。*1
変更が戻る可能性
マージされるときも「I'll merge this one and see how people will react to them during the pre-releases.」と書いてあり、マージ後にrails/rails#45463でいくつかコメントが挙がっているので、もしかすると変更がリバートされる可能性はあります。
気になる方やこの変更に対して懸念がある方は v7.1.0.rc が出る前にGitHubで絵文字リアクションやコメントを投稿しておくと良さそうです。
*1:私はJIS配列キーボード使ってるので、英字キーボードだと入力方法が異なるかもしれない
プルリクで更新されたgemの一覧を出力する
Gemfile.lock
のdiffから更新されたgemを探す方法を調べたのでメモ
動作確認に使ったプルリク
GitHub APIでプルリクの情報を取得する
$ gh api '/repos/rails/rails/pulls/48955' --jq '.base.sha, .head.sha' 1e824aa8e0655b4717cf612e31010bdd808f4fdf 2d61a206f445ccd52645c8d47ce97de39a39ea9d
GitHub APIで変更前後のGemfileを取得する
$ gh api '/repos/rails/rails/contents/Gemfile.lock?ref=1e824aa8e0655b4717cf612e31010bdd808f4fdf' --jq '.content' | base64 --decode > Gemfile.lock.before $ gh api '/repos/rails/rails/contents/Gemfile.lock?ref=2d61a206f445ccd52645c8d47ce97de39a39ea9d' --jq '.content' | base64 --decode > Gemfile.lock.after
Gemfile.lock の差分を調べる
$ BUNDLE_GEMFILE=Gemfile.lock.before ruby -rbundler -e \ 'puts ["Gemfile.lock.after", "Gemfile.lock.before"].map { Bundler::LockfileParser.new(File.read(_1)).specs }.inject(:-)' nio4r (2.5.9) puma (6.3.0)
Gemfileがないディレクトリだと Could not locate Gemfile or .bundle/ directory
のエラーが出たので、 BUNDLE_GEMFILE
を指定することでエラーを回避している。
Rubyやgit-gsubを使って正規表現で複数行を置換する part2
の続き。
回答 3
Rubyで -0777
を指定するとファイル単位で読み込む方法を教えて頂きました。
$ cat foo.txt foo $ cat bar.txt bar $ cat buz.txt foo bar buz $ ruby -i -n0777 -e 'puts "hi, " + $_' foo.txt bar.txt buz.txt $ cat foo.txt hi, foo $ cat bar.txt hi, bar $ cat buz.txt hi, foo bar buz
詳細
-0777
のオプションは初めて知ったけど、 man ruby
に記載されていました。
-0[octal]. (The digit “zero”.) Specifies the input record separator ($/) as an octal number. If no digit is given, the null character is taken as the separator. Other switches may follow the digits. -00 turns Ruby into paragraph mode. -0777 makes Ruby read whole file at once as a single string since there is no legal character with that value.
まとめ
複数のファイルを一括で編集するときに -0777
は覚えておくと便利そう。
Rubyやgit-gsubを使って正規表現で複数行を置換する
150個を超える *.tf
をまとめて編集する方法を調べたので、備忘録としてまとめておく。
経緯
Terraformのコードを複数ファイルにすべきか、それとも1つのファイルにすべきか難しいな。hclをパースして、hclを書くプログラムを書いた方が良いのかなぁ...
— 神速 (@sinsoku_listy) 2023年6月2日
150ファイルくらいvimで1つずつ心を込めて編集している。
— 神速 (@sinsoku_listy) 2023年6月2日
30ファイル目くらいで正規表現で解決しようとしたけど、正規表現が難しくて時間が溶けてしまった...辛い... https://t.co/JRzCYN9g3x
— 神速 (@sinsoku_listy) 2023年6月2日
やりたかったこと
以下のようなコードが大量にあるとき、全てのファイルから指定のチームidを持つ team { ... }
を削除したい。
resource "github_repository_collaborators" "foo" { repository = github_repository.foo.name team { permission = "push" team_id = "rubyist" } team { permission = "admin" team_id = "ruby-committers" } }
ruby-jp に相談
Rubyが得意なのでRubyで置換する方法を調べていたが、うまくいかなかったので ruby-jp に相談した。
すると、何人かの方から回答を頂いた。
投稿した質問
Rubyでファイルの中身を置換する簡単な方法は何かあったりしないでしょうか?
ruby -e 'File.read("foo.txt").gsub(/def/, "!!").then { File.write("foo.txt", _1) }'
みたいな処理を簡単に書けないものかなと。
回答1
Rubyの -i -p
を使う方法を教えて頂きました。
$ ruby -i -p -e '$_.gsub!(/def/, "!!")' foo.txt
これらのオプションは初めて知って勉強になった。
-i[extension] edit ARGV files in place (make backup if extension supplied)
-p assume loop like -n but print line also like sed
あと、 $_
も初めてみた。
ただ、これだと team { ... }
のような複数行を処理できないため、別の方法が必要になった。
回答1-a
回答1 を参考に -i
を使いつつ、複数行を取得して正規表現で置換することで目的は達成できた。
$ ruby -i -e 'puts readlines.join.gsub(/ team {[\s\w="\.\[\]]+(ruby-committers)[\s\w="\.\[\]]+}\n/, "")'
回答2
Ruby ではないですが、git-gsub を使う方法も教えて頂きました。
$ git gsub "(?s)team {[^{]+?ruby-committers.+?}" ""
正規表現もシンプルですし、 git-gsub
は普段使いしやすそうなので、これも知ることができて勉強になった。
まとめ
ruby-jp に助けられたし、勉強になった。ありがとうございます。
手元にあるパッチの一覧を確認するシェルスクリプト
ブログに書いていなかったことに気づいたので、残しておく。
モチベーション
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
が発生してしまうため、使える文字種を調べた。
調査
- 環境変数のフォーマットは database.yml に記載されている
- 例:
DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
- 参考: railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
- 例:
- この文字列の解析には
URI.parse
が使用されてそう - RFCのUser Informationに文字種は書かれていない*1
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:他の箇所に記載あったかもしれないが、詳細は調べてない