おんがえしの blog

作ったプログラムと調べた技術情報

ブログ執筆を加速させるコマンドラインインターフェースを作成した

このブログに素早く投稿するために、個人用のコマンドラインインターフェース(CLI)を作った。

github.com

  • 内部で blogsync を使っている。
  • Ruby + Thor で作成し、runablog という名前のコマンドとして、どこからでも実行できるようにしてある。

投稿: blog new

新規記事を作成するときは new コマンドを使う。引数として URL を指定するカスタムパスを渡す。

$ blog new path/to/article
     store /Users/ongaeshi/blog/ongaeshi.hatenablog.com/entry/path/to/article.md
  • 下書きとしてはてなブログ上に空の記事が投稿される。
  • URL は https://ongaeshi.hatenablog.com/entry/path/to/article になる。
  • 対応するファイルが /Users/ongaeshi/blog/ongaeshi.hatenablog.com/entry/path/to/article.md に作成される。

blog new 直後の path/to/article.md

---
Title:
URL: https://ongaeshi.hatenablog.com/entry/path/to/article
EditURL: ...
PreviewURL: ...
Draft: true
---

(ここに記事を書いていく)

執筆: blog edit

$ blog edit
  • ブログ記事が格納された git レポジトリをVSCode で開く。
  • blog new で作成したファイルを開いて記事を書いていく。
  • 記事の執筆が終わったら draft: true の行を削除して blog commit コマンドを実行する。

投稿: blog commit

$ blog commit

または

$ blog commit -m "/path/to/article を投稿"
  • 裏で blogsync push してから git commit を行う
  • blogsync push する記事は以下のどれか
    • まだ追加されていないファイル (git status --porcelain)
    • 編集済みのファイル (git diff --name-only)
    • main に pushしていないファイル (git diff --name-only origin/main..HEAD)
  • blogsync push と git commit を同時に行うことでどちらかの実行し忘れを防ぐ。(blog コマンドを作る前はこの操作ミスが本当に多かった)

感想

ここ1か月ほど、快適にブログ執筆するためのインターフェースはどうなっているのがよいかを試行錯誤していた。 個人的には特に以下の辺りが重要だった。

  1. ブログのカスタムパスは必ず指定する。
  2. blogsync post では標準入力を受け付けずに自動で空の記事を作成する。
  3. 空の記事がデフォルトになるので必ず下書きで作成する。(オプションからは指定できないようにする。)
  4. git commit と blogsync push を1つのコマンドできるようにする

自分用コマンドを作った効果は抜群で今月だけでだけですでに7本の記事を執筆できている。 今年6月までに書いた記事が10本なので1か月で半年分を追い抜きそうなペース。

よいインターフェースには利用者の力をブーストする、もしくは最大限に発揮させる力があると改め感じた。

まとめ

blogsync に薄いラッパーを被せて自分のブログ投稿ワークフローに最適なインターフェースを作った。 ユーザーにフィットしたインターフェースをデザインすることは、個人や組織の開発効率を飛躍的に向上させる。

今や、重要な内部パーツは多くがOSSとして既に存在している、あるいはLLMによって生成できる時代になった。 そのため、様々な状況に対応できるインターフェースを作成することが、これからのプログラマにとって一層重要なスキルとなりそうな予感がする。

ChatGPT に Ruby など他のプログラミング言語も実行させたい

ChatGPT はたくさんの種類のプログラミング言語のコードを出力できますが、直接実行できるのは現状 Python のみとなっています。Ruby など他の言語も実行することはできないかと考えていました。

1つのアイデアとして思いついたのは ChatGPT に生成したコードを Ruby on Browser に渡す URL を生成してもらい、それをクリックするだけで実行できるようにするという方法です。これなら他の言語でも URL 経由で実行できる処理系なら応用できそうです。(現状は ChatGPT 4 でのみ実行可能な模様)

下準備

まず Ruby on Browser に URL 経由でコードを渡す方法を用意する必要があります。もともとクエリーパラメータ q= で渡せるのですが、文字数を短くするために lz-string という Javascript のライブラリを使っておりそのエンコーディングPython 標準では提供されていないためパラメータ文字列が生成できないためです。そのためにシンプルな URL エンコーディンでコードを渡すことができる q2= を新規に用意することにしました。

github.com

これで準備は完了です。

プロンプト

色々調整して ChatGPT に渡すプロンプトはこんな感じになりました。◯◯◯の箇所を生成して欲しいスクリプト種類に変更して使います。

◯◯◯ Rubyスクリプトを作成してください。
このRubyスクリプトをrubyonbrowser上で実行させたいです。

- URL は https://rubyonbrowser.ongaeshi.me/
- 後ろにクエリ文字列 ?q2=
- コードテキストを URLエンコーディングした文字列を q2 のパラメータに設定

コードと生成したURLへのリンクの順に一回で出力してください。

実行例1: 文字列を大文字に変換する

あなた

文字列を大文字に変換するRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

以下に、文字列を大文字に変換するRubyスクリプトと、それを実行するためのrubyonbrowserへのリンクを提供します。

Rubyスクリプト:

puts gets.chomp.upcase

生成したURL: https://rubyonbrowser.ongaeshi.me/?q2=puts+gets.chomp.upcase

感想

実際の実行はモバイル上から行っており、リンクを生成することで特にモバイルからだと確認しやすくなります。Ruby on Browser で実行することで、コードをちょっと修正できるのも嬉しいですね。


www.youtube.com

実行例2: Hash#dig のサンプルコードを出力させる

あなた

Hash#dig の使い方を初心者プログラマに説明するRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

RubyHash#digメソッドは、ネストされたハッシュから安全に値を取得するために使用されます。例えば、あるハッシュが複数の階層を持っている場合、digメソッドを使って特定のキーの経路に沿って値を取得できます。もし指定したキーのいずれかが存在しない場合、nilが返されます。

初心者の方に向けた説明用のRubyスクリプトを以下に示します。このスクリプトでは、簡単なハッシュを作成し、digメソッドを使用して値を取得します。

# ネストされたハッシュの例
user_data = {
  name: "Alice",
  contact: {
    email: "alice@example.com",
    phone: {
      home: "123-456-7890",
      mobile: "987-654-3210"
    }
  }
}

# 'dig' メソッドを使用して、モバイル電話番号を取得
mobile_phone = user_data.dig(:contact, :phone, :mobile)

# 結果を出力
puts "Mobile phone number: #{mobile_phone}"

このスクリプトをRubyOnBrowserで実行するためのURLは以下の通りです。

実行用URL

感想

実行すると "987-654-3210" が返ってきました。引数を (:contact, :phone, :home) に変更すると "123-456-7890" になりました。途中のパラメータ名が間違っていても例外にならず nil で返ってくるのが便利なのだと理解できました。サンプルデータも含めてコードを作成してくれるのがありがたいです。

実行例3: 未知のアルゴリズムを教えてもらう

あなた

レーベンシュタイン距離を求めるRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

コードと生成したURLへのリンクの順に一回で出力してください。

ChatGPT

レーベンシュタイン距離を計算するためのRubyスクリプトと、そのスクリプトRuby on Browserで実行するためのURLを生成しました。

Rubyスクリプトは以下の通りです。

def levenshtein_distance(s, t)
  m = s.length
  n = t.length
  return m if n == 0
  return n if m == 0
  d = Array.new(m+1) {Array.new(n+1)}

  (0..m).each {|i| d[i][0] = i}
  (0..n).each {|j| d[0][j] = j}

  (1..n).each do |j|
    (1..m).each do |i|
      cost = (s[i-1] == t[j-1]) ? 0 : 1
      d[i][j] = [d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1] + cost].min
    end
  end
  d[m][n]
end

# 使用例
puts levenshtein_distance("kitten", "sitting")

このスクリプトRuby on Browserで実行するためのURLは以下のリンクからアクセスできます:Ruby on Browserでスクリプトを実行する.

感想

git のコマンドのスペルミスがあったときに近いコマンドを提示してくれるアルゴリズムが確かこんな名前だったという記憶だけで聞いてみました。ハルシネーションが怖いですが試した感じ正しく動いていそうな感じでした。実際に使いたいときはこれをベースに挙動を理解するとこから始めるもよさそうです。(そして調べたら git ではそれよりも少し複雑なダメラウ・レーベンシュタイン距離 を使っているそう)

実行例4: ゲームを作ってもらう

あなた

シンプルなゲームができるRubyスクリプトを作成してください。 このRubyスクリプトをrubyonbrowser上で実行させたいです。

ChatGPT

わかりました。シンプルなRubyスクリプトを作成して、それをrubyonbrowser上で実行するためのURLを生成します。ゲームの内容について、どのようなものを希望されますか?例えば、「数当てゲーム」や「じゃんけんゲーム」など、基本的なものであれば実装可能です。

あなた

数当てゲーム

ChatGPT

数当てゲームのRubyスクリプトを作成します。このゲームでは、コンピュータが1から100の間でランダムに数を選び、ユーザーがその数を当てるまで推測を続けます。ユーザーが数を入力すると、コンピュータはその数が正解より高いか低いかを教えます。正解を当てるとゲームが終了します。

数当てゲームのRubyスクリプトをURLエンコーディングしました。以下のURLを使用してrubyonbrowserでゲームを実行できます。

https://rubyonbrowser.ongaeshi.me/?q2=

リンクをクリックすると、ゲームが開始されます。楽しんでください!

あなた

gets が使えないのでコンピュータが自動で入力するような方式にできますか?

ChatGPT

了解しました。getsを使用しないで、コンピュータが自動で数を推測する方式に変更します。この場合、コンピュータは一定のルールに基づいて数を推測し、それに応じて次の推測を行います。

コンピュータが自動で数を推測する方式の数当てゲームのRubyスクリプトをURLエンコーディングしました。以下のURLを使用してrubyonbrowserでゲームを実行できます。

https://rubyonbrowser.ongaeshi.me/?q2=

リンクをクリックすると、ゲームが開始されます。楽しんでください!

感想

最後は簡単なゲームを作ってもらいました。答える側の AI 部分もこちらが要求すれば作ってくれるのはすごいですね。

まとめ

プロンプト側に URL も含めて生成させておくと後からチャットログを再確認するときや、今回のようにブログに貼り付けるときにすぐに確認できるので結構便利です。その場でコードを変更して確認できるのもよいです。是非みなさんも色々な Ruby コードを生成させて実行してみてください。

Runa: Ruby で中規模アプリケーションを書くためのフレームワーク

Runa という Ruby で Gem を使ったり複数ファイルで構成された中規模のアプリケーションを簡単に書くためのフレームワークを作っています。

Runa を作った経緯

  • Ruby は単独のスクリプトファイルとして実行するときは取り回しも簡単で大変使いやすい(小規模アプリケーション)
  • が、特定の gem に依存したり複数ファイルで構成されるようなアプリケーションを作ろうとするとスタンダードな方法が用意されておらず(特に配布や共有のことを考えると)敷居が高くなってしまう(中規模アプリケーション)
  • これが今まで余り問題にならなかったのは、Web アプリであれば Rails がその辺りも面倒をみてくれたり、コンソールアプリケーションなら gem で配布するみたいな方法でやりくりしてきた経緯がある。しかし gem で配布するには RubyGems のアカウントが必要だったり、昨今のセキュリティ問題などを考えると思いつきのアプリケーションを気軽に置くような感じではすでに無くなっている気がする(大規模アプリケーション)
  • 私が普段作りたいのは個人的な問題を思い付きで解決したちょっとしたアプリケーションであり配布はレポジトリ作ったから使いたい人はどうぞ位にしたい

そこで普段使いのアプリケーションを気軽に作れるようにするために Runa を作りました。Ruby には Bundler という優れた依存関係管理ツールがすでに存在しており、実際のところ Runa は Bundler のラストワンマイルを埋める薄いラッパーです。

Runa のインストール

gem でインストールします。runaコマンドが使えるようになるので以後はこれを使います。

$ gem install runa

runa new アプリケーション名 でプロジェクトを作成します。ディレクトリができるので下に移動します。

$ runa new hello
Created 'hello' application.
$ cd hello

Gem の追加や複数ファイルのアプリケーション作成に必要なものが自動で追加されています。

$ ls
.bundle/
.runa/
lib/
.gitignore
Gemfile
hello.rb

runa run スクリプト名で実行します。runa runで実行すると追加した Gem や lib/ 以下へのパスが通った状態で実行されます。

$ runa run .\hello.rb
Hello, world!

Launchy Gem を追加してみます。Gem の追加にはruna gem_add Gem名を使います。(bundle install した後に runa install でも OK)

$ runa gem_add launchy
.
.
Installing launchy 2.5.2
Generate '.runa/runa_load_path.rb'

これで hello.rb から require "launchy" が可能になります。

require "hello"
require "launchy"

url = "https://www.ruby-lang.org"
Launchy.open(url)
puts "Hello, #{url}"

実行するとブラウザで https://www.ruby-lang.org/ が開けるようになります。

Runa アプリケーションをデプロイする

ここまでは bundler だけでも頑張ればできると思います。

Runa を使うと作成したスクリプトを起動するための実行ファイル(シェルスクリプトWindowsならバッチファイル)を簡単に作成することができます。これが一番の特徴です。私はこの起動スクリプトを PATH の通った場所に配置しています。

実行ファイルの作成には runa deploy コマンドを使います。(ここでは ../bin にパスが通っていると仮定しています)

$ runa deploy hello.rb ../bin
Generate '.runa/runa_load_path.rb'
Deploy execution script to '../bin/hello.bat'.

デプロイしたバッチファイル(もしくはシェルスクリプト)は runa コマンドを経由せずにシェルから実行するができます。(つまり .exe と同じ)

$ ../bin/hello
$ hello                # (注) PATH が通っていれば
Hello, https://www.ruby-lang.org
(ブラウザ起動)

Win+R からも直接実行できます。

デプロイしたアプリケーションの挙動を変更する

実行用ファイルの実体は元々作った runa レポジトリなので、hello.rb を変更すればアプリケーションの挙動を変更することができます。このとき「再デプロイは不要」です。これは gem を追加したときも同様です。

例として hello アプリケーションに clipboard gem を追加して、特定のページを開きつつ URL をクリップボードにコピーしてみます。まずは clipboard gem をインストールします。

$ runa gem_add clipboard
.
.
Generate '.runa/runa_load_path.rb'

hello.rb を書き換えます。

require "hello"
require "launchy"
+ require "clipboard"

url = "https://www.ruby-lang.org"
Launchy.open(url)
puts "Hello, #{url}"
+ Clipboard.copy(url)

再デプロイは不要なのでそのままコマンドラインや Win+R から hello を実行します。

$ hello
Hello, https://www.ruby-lang.org
(ブラウザ起動&クリップボードに https://www.ruby-lang.org がコピーされる)

再ビルド不要でアプリケーションの挙動を変更することができるのはかなり便利です。

他の人の使った Runa アプリケーションを使う

Runa で作られたアプリケーションは他の人の環境でも簡単に動かすことができます。ここでは先ほど作った hello アプリケーションを動かしてみます。

$ git clone https://github.com/ongaeshi/hello.git
cd hello
$ runa install
$ runa run hello.rb

とても簡単ですね。デプロイも簡単です。

$ runa deploy hello.rb /path/to/bin

最後に gosu を使ったゲームを runa で動かして終わりにします。

$ git clone https://github.com/ongaeshi/minesweeper.git
cd minesweeper
$ runa install
$ runa run hello.rb

おわりに

私が自分用に作ったものはongaeshi/toolbox に置いてあります。src2urlとかお気に入りでよく使っています。

みなさんもお気に入りのアプリケーションを runa で作って気軽に公開してください!

Ruby on Browser 1.0 リリース

窓の杜で取り上げていただいた後も実装は少しずつ続けていて、ひとまずブラウザ上で最新のRubyを試すのに必要な機能は一通り実装できたんじゃないかと思う。リファレンスマニュアルへのリンクを貼ってシンタックスハイライトを入れたりCtrl+Enterで実行できるようにした。(自分が書いたサンプルコードはScrapboxにあるのでコピペして試せます)

https://rubyonbrowser.ongaeshi.me/

f:id:tuto0621:20220417001111p:plain

モバイルでも簡単なコードだったら書けるように色々工夫したのでちょっとしたコードを書きたいときにぜひ試してみてほしい。(Select Allボタンは結構こだわった)

f:id:tuto0621:20220417001229p:plain

他のブラウザ言語処理系と大きく違うこととして「ファイルを読み書きするAPIも使える」ということがある。元々WASIがWASMにファイルIOや通信を持せたることを目的にしたものなのでRuby WASM/WASI自体がファイルIOを持っており、そこにWasmerFSというブラウザ上に仮想ファイルシステムを持たせることができるライブラリと組み合わせて実現している。

おかげで以下のようなサンプルコードをブラウザ上で実行できる!PC上で実行するよりもはるかに安全に色々試せるのはとてもよい。(ファイルはブラウザリロードで消える)

https://docs.ruby-lang.org/ja/latest/class/File.html#I_TRUNCATE

f:id:tuto0621:20220417001133g:plain

まだまだ伸びしろを感じているのでもうしばらくRuby WASIで遊ぶ予定。

WindowsでWSL2を使ってRuby3.0.0-preview2を動かす

動きました。

f:id:tuto0621:20201216010609p:plain

WSL2のインストール

https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#manual-installation-steps

プレビュー版のWindows10を使って簡単にやる方法と、現状のWindows10に手動インストールする方法が用意されているが、今回は手動インストールの方で進めた。とはいえ手動インストールでも十分に簡単。

を入れた。

Windows ターミナルをインストール

https://docs.microsoft.com/ja-jp/windows/wsl/install-win10#install-windows-terminal-optional

普段DvorakJを使って親指シフト入力をしているのだが、DOSプロンプトとの相性が悪く入力がもたつくバグがある。デフォルトのWSLのシェルはDOSプロンプトのため同じ問題があるのがずっと不満だった。(そのときだけDvorakJを無効にしてしのいでいた) Windowsターミナルだとそれが起きない!

それ以外でもきびきびと動作してとても印象がよい。

aptを最新に更新

$ sudo apt update

rbenvをインストール

https://qiita.com/ma2shita/items/5c41aa8a4908c919ba78

途中までは同じで最後に3.0.0-preview2を選択する。

$ rbenv install 3.0.0-preview2
$ rbenv global 3.0.0-preview2

Ruby3で遊ぶ

https://qiita.com/jnchito/items/bf8c6c2e1dd6cff05f4e

と同じことが一通りできることを確認。

WSL2上でcode rubytest/とかやると自動でRemove - SSHがインストールされて開くことができる。VSCode Steepも動いているようだ。

注意: /mnt/c/Users/..のようなWindowsフォルダ上でVSCode Steepを使おうとすると動作がもたついたり挙動が怪しくなるので注意。~/ruby3-testとかに置いて試してみよう。

感想

小さなファイルだからかもしれないが補完はきびきびと動くし型エラーが発生したときにすぐに赤線がついてエラー箇所も分かる。

後はF12で定義ジャンプや一括リネームなどができるようになれば型を書こうという気持ちになるかもしれない。

RubyMineのような有料の優れたツールを使えば現在でも補完やリファクタリングツールが使えるのかもしれないが、無料でもある程度の補完ができるようになると裾野が広がってとてもよいと思いました。

f:id:tuto0621:20201216010704p:plain

fizz_buzzの戻り値に対してmapをかけてもちゃんとその引数がStringになることも追跡できている

シングルバイナリで動くゲームエンジンの fude というのを作っている

ongaeshi/fude: A single binary video game programming environment.

インストール不要でRubyスクリプトを書くだけで動くゲームエンジンを作ってみたくて作った。raylibというC言語で書かれたゲームエンジンを内部で使っている。とても軽量で raylib、mruby、raylib APIのmrubyへのバインディング全てでバイナリサイズが1.8MB! これならアプリケーション単位でfude.exeバイナリをgitレポジトリに置いても十分に運用できると思う。

自分がWindowsなのでWindowsバイナリを置いてしまっているけどraylibもmrubyも様々なプラットフォームで動くのでMacLinux, Androidで動かすのもそこまで難しくないと思う。後raylibはemscriptenによるHTMLビルドもサポートしているようなので機会があれば試してみたい。

インストールと使い方

git clone して exe 実行すればすぐにサンプルが動く。

ホットリロードモードもあってfude main.rbのようにファイル名指定、またはドラッグアンドドロップして起動した状態でファイルを編集すれば自動でホットリロードする。

raylibのAPIは大体移植したので https://github.com/raysan5/raylib/tree/master/examples にあるサンプルコードはある程度移植できるようになっているはず。 https://github.com/ongaeshi/fude/tree/master/examples にちょっと移植してある。

GitHubからスクリプトを読み込んでnippで実行する

経緯

nippは便利なのだが、一度URLを公開した後にスクリプトを修正すると再度URLを公開しないといけないのが面倒なときがある。

そこでgithubに置いたスクリプトを読み込んでnippにロードする仕組みを作った。(ほとんどnipp作者の @nwtgck さんが作ってくれた)

使い方

で、そのロード用のURLを作るためのnippを作った。例によってawesome-nippに置いてあるが、

感想

スクリプトGitHubから読み込んでnipp上に展開するためにES2017を使っているのだが、

Opal.load('opal');
Opal.load('opal-parser');
(async ()=>{
  const gh = location.search.split("?")[1].split("=")[1];

  const res = await fetch(`https://raw.githubusercontent.com/${gh}`);
  const rbScript = await res.text();

 const e = document.querySelector("textarea[ng-model='inputText']");
  const $scope = angular.element(e).scope();
  $scope.script = await rbScript;
  $scope.transpiler = RubyTranspiler;
  $scope.enablePromiseWait = false;
  $scope.enableClickRun = false;
  $scope.enablePromiseWait = false;
  $scope.setLocationHash();
  $scope.transpile();
})();

こんな短いコードで書けて感動した。async await の便利さがよく分かった。