Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Jekyllで遊んでみる

自分のパソコンのホームディレクトリには、これまでいじったいろんなソフトウェアの自作チートシートや有用なリンク集を集めたメモがある。

これらは古くはRDで書いてあって、どこかのタイミングでMarkdownで書き直した。ちまちまと書きためていて、rakeコマンドでHTMLファイルを生成出来るようにしてある。しかし、見たいタイミングでrakeで最新化するのは面倒くさいので、ついついMarkdownのまま見てしまったりする。

こういうもののHTML生成は、現代的には静的サイトジェネレーターを使うのが正しいらしい。というわけで、Rubyで作られた静的サイトジェネレーターでGithub Pagesの中の人であるJekyllをいじってみることにする。

いつもの通り、gemとbundlerでインストールは出来てしまう。

> gem install bundler jekyll
Fetching bundler-2.4.13.gem
Successfully installed bundler-2.4.13
Parsing documentation for bundler-2.4.13
Installing ri documentation for bundler-2.4.13
Done installing documentation for bundler after 0 seconds
Fetching forwardable-extended-2.6.0.gem
Fetching safe_yaml-1.0.5.gem
Fetching terminal-table-3.0.2.gem
Fetching pathutil-0.16.2.gem
Fetching liquid-4.0.4.gem
Fetching kramdown-2.4.0.gem
Fetching unicode-display_width-2.4.2.gem
Fetching mercenary-0.4.0.gem
Fetching kramdown-parser-gfm-1.1.0.gem
Fetching ffi-1.15.5.gem
Fetching rb-inotify-0.10.1.gem
Fetching rb-fsevent-0.11.2.gem
Fetching listen-3.8.0.gem
Fetching jekyll-watch-2.2.1.gem
Fetching google-protobuf-3.23.2-arm64-darwin.gem
Fetching sass-embedded-1.62.1-arm64-darwin.gem
Fetching jekyll-sass-converter-3.0.0.gem
Fetching concurrent-ruby-1.2.2.gem
Fetching i18n-1.13.0.gem
Fetching http_parser.rb-0.8.0.gem
Fetching eventmachine-1.2.7.gem
Fetching em-websocket-0.5.3.gem
Fetching colorator-1.1.0.gem
Fetching public_suffix-5.0.1.gem
Fetching jekyll-4.3.2.gem
Fetching addressable-2.8.4.gem
Successfully installed unicode-display_width-2.4.2
Successfully installed terminal-table-3.0.2
Successfully installed safe_yaml-1.0.5
Successfully installed forwardable-extended-2.6.0
Successfully installed pathutil-0.16.2
Successfully installed mercenary-0.4.0
Successfully installed liquid-4.0.4
Successfully installed kramdown-2.4.0
Successfully installed kramdown-parser-gfm-1.1.0
Building native extensions. This could take a while...
Successfully installed ffi-1.15.5
Successfully installed rb-inotify-0.10.1
Successfully installed rb-fsevent-0.11.2
Successfully installed listen-3.8.0
Successfully installed jekyll-watch-2.2.1
Successfully installed google-protobuf-3.23.2-arm64-darwin
Successfully installed sass-embedded-1.62.1-arm64-darwin
Successfully installed jekyll-sass-converter-3.0.0
Successfully installed concurrent-ruby-1.2.2
Successfully installed i18n-1.13.0
Building native extensions. This could take a while...
Successfully installed http_parser.rb-0.8.0
Building native extensions. This could take a while...
Successfully installed eventmachine-1.2.7
Successfully installed em-websocket-0.5.3
Successfully installed colorator-1.1.0
Successfully installed public_suffix-5.0.1
Successfully installed addressable-2.8.4
Successfully installed jekyll-4.3.2
Parsing documentation for unicode-display_width-2.4.2
Installing ri documentation for unicode-display_width-2.4.2
Parsing documentation for terminal-table-3.0.2
Installing ri documentation for terminal-table-3.0.2
Parsing documentation for safe_yaml-1.0.5
Installing ri documentation for safe_yaml-1.0.5
Parsing documentation for forwardable-extended-2.6.0
Installing ri documentation for forwardable-extended-2.6.0
Parsing documentation for pathutil-0.16.2
Installing ri documentation for pathutil-0.16.2
Parsing documentation for mercenary-0.4.0
Installing ri documentation for mercenary-0.4.0
Parsing documentation for liquid-4.0.4
Installing ri documentation for liquid-4.0.4
Parsing documentation for kramdown-2.4.0
Installing ri documentation for kramdown-2.4.0
Parsing documentation for kramdown-parser-gfm-1.1.0
Installing ri documentation for kramdown-parser-gfm-1.1.0
Parsing documentation for ffi-1.15.5
Installing ri documentation for ffi-1.15.5
Parsing documentation for rb-inotify-0.10.1
Installing ri documentation for rb-inotify-0.10.1
Parsing documentation for rb-fsevent-0.11.2
Installing ri documentation for rb-fsevent-0.11.2
Parsing documentation for listen-3.8.0
Installing ri documentation for listen-3.8.0
Parsing documentation for jekyll-watch-2.2.1
Installing ri documentation for jekyll-watch-2.2.1
Parsing documentation for google-protobuf-3.23.2-arm64-darwin
Installing ri documentation for google-protobuf-3.23.2-arm64-darwin
Parsing documentation for sass-embedded-1.62.1-arm64-darwin
Installing ri documentation for sass-embedded-1.62.1-arm64-darwin
Parsing documentation for jekyll-sass-converter-3.0.0
Installing ri documentation for jekyll-sass-converter-3.0.0
Parsing documentation for concurrent-ruby-1.2.2
Installing ri documentation for concurrent-ruby-1.2.2
Parsing documentation for i18n-1.13.0
Installing ri documentation for i18n-1.13.0
Parsing documentation for http_parser.rb-0.8.0
unknown encoding name "chunked\r\n\r\n25" for ext/ruby_http_parser/vendor/http-parser-java/tools/parse_tests.rb, skipping
Installing ri documentation for http_parser.rb-0.8.0
Parsing documentation for eventmachine-1.2.7
Installing ri documentation for eventmachine-1.2.7
Parsing documentation for em-websocket-0.5.3
Installing ri documentation for em-websocket-0.5.3
Parsing documentation for colorator-1.1.0
Installing ri documentation for colorator-1.1.0
Parsing documentation for public_suffix-5.0.1
Installing ri documentation for public_suffix-5.0.1
Parsing documentation for addressable-2.8.4
Installing ri documentation for addressable-2.8.4
Parsing documentation for jekyll-4.3.2
Installing ri documentation for jekyll-4.3.2
Done installing documentation for unicode-display_width, terminal-table, safe_yaml, forwardable-extended, pathutil, mercenary, liquid, kramdown, kramdown-parser-gfm, ffi, rb-inotify, rb-fsevent, listen, jekyll-watch, google-protobuf, sass-embedded, jekyll-sass-converter, concurrent-ruby, i18n, http_parser.rb, eventmachine, em-websocket, colorator, public_suffix, addressable, jekyll after 6 seconds
27 gems installed

> jekyll new manuals
Running bundle install in /Users/tambara/manuals... 
  Bundler: Fetching gem metadata from https://rubygems.org/............
  Bundler: Resolving dependencies...
  Bundler: Using bundler 2.4.13
  Bundler: Using eventmachine 1.2.7
  Bundler: Using http_parser.rb 0.8.0
  Bundler: Using ffi 1.15.5
  Bundler: Using forwardable-extended 2.6.0
  Bundler: Using rexml 3.2.5
  Bundler: Using liquid 4.0.4
  Bundler: Using mercenary 0.4.0
  Bundler: Using unicode-display_width 2.4.2
  Bundler: Using public_suffix 5.0.1
  Bundler: Using colorator 1.1.0
  Bundler: Using addressable 2.8.4
  Bundler: Using em-websocket 0.5.3
  Bundler: Using rb-inotify 0.10.1
  Bundler: Using kramdown 2.4.0
  Bundler: Using concurrent-ruby 1.2.2
  Bundler: Using terminal-table 3.0.2
  Bundler: Using google-protobuf 3.23.2 (arm64-darwin)
  Bundler: Using rb-fsevent 0.11.2
  Bundler: Using rouge 4.1.1
  Bundler: Using safe_yaml 1.0.5
  Bundler: Using webrick 1.8.1
  Bundler: Using pathutil 0.16.2
  Bundler: Using i18n 1.13.0
  Bundler: Using sass-embedded 1.62.1 (arm64-darwin)
  Bundler: Using listen 3.8.0
  Bundler: Using kramdown-parser-gfm 1.1.0
  Bundler: Using jekyll-sass-converter 3.0.0
  Bundler: Using jekyll-watch 2.2.1
  Bundler: Using jekyll 4.3.2
  Bundler: Fetching jekyll-feed 0.17.0
  Bundler: Fetching jekyll-seo-tag 2.8.0
  Bundler: Installing jekyll-feed 0.17.0
  Bundler: Installing jekyll-seo-tag 2.8.0
  Bundler: Fetching minima 2.5.1
  Bundler: Installing minima 2.5.1
  Bundler: Bundle complete! 7 Gemfile dependencies, 33 gems now installed.
  Bundler: Use `bundle info [gemname]` to see where a bundled gem is installed.
New jekyll site installed in /Users/tambara/manuals. 

> cd manuals

> bundle exec jekyll serve
Configuration file: /Users/tambara/manuals/_config.yml
            Source: /Users/tambara/manuals
       Destination: /Users/tambara/manuals/_site
 Incremental build: disabled. Enable with --incremental
      Generating... 
       Jekyll Feed: Generating feed for posts
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($spacing-unit, 2) or calc($spacing-unit / 2)

More info and automated migrator: https://sass-lang.com/d/slash-div

   ╷
40 │   margin-bottom: $spacing-unit / 2;
   │                  ^^^^^^^^^^^^^^^^^
   ╵
    ../../../../minima-2.5.1/_sass/minima/_base.scss 40:18  @import
    minima.scss 48:3                                        @import
    /Users/tambara/manuals/assets/main.scss 1:9             root stylesheet
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($spacing-unit, 2) or calc($spacing-unit / 2)

More info and automated migrator: https://sass-lang.com/d/slash-div

    ╷
134 │   padding-left: $spacing-unit / 2;
    │                 ^^^^^^^^^^^^^^^^^
    ╵
    ../../../../minima-2.5.1/_sass/minima/_base.scss 134:17  @import
    minima.scss 48:3                                         @import
    /Users/tambara/manuals/assets/main.scss 1:9              root stylesheet
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($spacing-unit, 2) or calc($spacing-unit / 2)

More info and automated migrator: https://sass-lang.com/d/slash-div

    ╷
189 │     padding-right: $spacing-unit / 2;
    │                    ^^^^^^^^^^^^^^^^^
    ╵
    ../../../../minima-2.5.1/_sass/minima/_base.scss 189:20  @content
    minima.scss 38:5                                         media-query()
    ../../../../minima-2.5.1/_sass/minima/_base.scss 186:3   @import
    minima.scss 48:3                                         @import
    /Users/tambara/manuals/assets/main.scss 1:9              root stylesheet
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($spacing-unit, 2) or calc($spacing-unit / 2)

More info and automated migrator: https://sass-lang.com/d/slash-div

    ╷
190 │     padding-left: $spacing-unit / 2;
    │                   ^^^^^^^^^^^^^^^^^
    ╵
    ../../../../minima-2.5.1/_sass/minima/_base.scss 190:19  @content
    minima.scss 38:5                                         media-query()
    ../../../../minima-2.5.1/_sass/minima/_base.scss 186:3   @import
    minima.scss 48:3                                         @import
    /Users/tambara/manuals/assets/main.scss 1:9              root stylesheet
Deprecation Warning: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($spacing-unit, 3) or calc($spacing-unit / 3)

More info and automated migrator: https://sass-lang.com/d/slash-div

    ╷
244 │     padding: ($spacing-unit / 3) ($spacing-unit / 2);
    │               ^^^^^^^^^^^^^^^^^
    ╵
    ../../../../minima-2.5.1/_sass/minima/_base.scss 244:15  @import
    minima.scss 48:3                                         @import
    /Users/tambara/manuals/assets/main.scss 1:9              root stylesheet
Warning: 6 repetitive deprecation warnings omitted.
Run in verbose mode to see all warnings.
                    done in 7.24 seconds.
 Auto-regeneration: enabled for '/Users/tambara/manuals'
    Server address: http://127.0.0.1:4000/
  Server running... press ctrl-c to stop.

なんかいっぱいWarningは出ているのだが、画面は出るのでインストールはこれで良いらしい。

作られたディレクトリの中を見ると、すごくシンプルだ。

> ls -l
total 48
-rw-r--r--@ 1 tambara  staff   419  5 27 21:28 404.html
-rw-r--r--@ 1 tambara  staff  1309  5 27 21:28 Gemfile
-rw-r--r--  1 tambara  staff  2052  5 27 21:29 Gemfile.lock
-rw-r--r--@ 1 tambara  staff  2079  5 27 21:28 _config.yml
drwxr-xr-x  3 tambara  staff    96  5 27 21:28 _posts/
drwxr-xr-x  9 tambara  staff   288  6 22 23:13 _site/
-rw-r--r--@ 1 tambara  staff   539  5 27 21:28 about.markdown
-rw-r--r--@ 1 tambara  staff   175  5 27 21:28 index.markdown

_siteに作られたhtmlファイルが格納される。さっき表示されたページの元ネタはindex.markdownだが、このファイルの中身はこれしかない。

---
# Feel free to add content and custom Front Matter to this file.
# To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults

layout: home
---

このファイルに書いてあるURLをみると、どうやらこういうことらしい。

  • 普通はテンプレートファイルを作って、それを元に表示をする。
  • しかし、今のJekyllはnewした段階でminimaというgemで提供されているデフォルトテーマを使う。これがGemfileに指定されている。
  • 本来は_layoutというディレクトリを作ってそこにテンプレートを入れて作っていくんだけど、出来合のものがgemに入っているので何にもないのに動いているように見える
  • このテーマを使い続けるなら、コンテンツを_postsというディレクトリに置いていけばブログ風のサイトが出来る

なるほど。しかし、作りたいのはblogじゃないんだわ。

というわけで、このドキュメントを読んでいく。

ローカルに久しぶりにtDiaryをインストールしてみる

tdiary-core/INSTALL-rack.md at master · tdiary/tdiary-core · GitHub を読んで、そのままやってみる。

> gem install tdiary
Fetching webrick-1.8.1.gem
Fetching thor-1.2.2.gem
Fetching rack-3.0.7.gem
Fetching mail-2.8.1.gem
Fetching hikidoc-0.1.0.gem
Fetching mini_mime-1.1.2.gem
Fetching emot-0.0.4.gem
Fetching fastimage-2.2.6.gem
Fetching tdiary-5.2.4.gem
Successfully installed webrick-1.8.1
Successfully installed thor-1.2.2
Successfully installed rack-3.0.7
Successfully installed mini_mime-1.1.2
Successfully installed mail-2.8.1
Successfully installed hikidoc-0.1.0
Successfully installed fastimage-2.2.6
Successfully installed emot-0.0.4
Successfully installed tdiary-5.2.4
Parsing documentation for webrick-1.8.1
Installing ri documentation for webrick-1.8.1
Parsing documentation for thor-1.2.2
Installing ri documentation for thor-1.2.2
Parsing documentation for rack-3.0.7
Installing ri documentation for rack-3.0.7
Parsing documentation for mini_mime-1.1.2
Installing ri documentation for mini_mime-1.1.2
Parsing documentation for mail-2.8.1
Installing ri documentation for mail-2.8.1
Parsing documentation for hikidoc-0.1.0
Installing ri documentation for hikidoc-0.1.0
Parsing documentation for fastimage-2.2.6
Installing ri documentation for fastimage-2.2.6
Parsing documentation for emot-0.0.4
Installing ri documentation for emot-0.0.4
Parsing documentation for tdiary-5.2.4
Installing ri documentation for tdiary-5.2.4
Done installing documentation for webrick, thor, rack, mini_mime, mail, hikidoc, fastimage, emot, tdiary after 6 seconds
9 gems installed

しんぷる

> tdiary version
tdiary 5.2.4

大昔のバージョンしか知らないので、CLIがあるのが驚き。

> tdiary new diary
      create  diary
      create  diary/public
      create  diary/misc/plugin
      create  diary/lib/tdiary/filter
      create  diary/lib/tdiary/style
      create  diary/js
      create  diary/theme
      create  diary/README.md
      create  diary/Gemfile
      create  diary/Gemfile.lock
      create  diary/config.ru
      create  diary/tdiary.conf.beginner
      create  diary/tdiary.conf.sample
      create  diary/tdiary.conf.sample-en
      create  diary/doc
      create  diary/doc/HOWTO-authenticate-in-rack.md
      create  diary/doc/HOWTO-make-io.md
      create  diary/doc/HOWTO-make-plugin.md
      create  diary/doc/HOWTO-make-theme.md
      create  diary/doc/HOWTO-testing-tDiary.md
      create  diary/doc/HOWTO-use-plugin.md
      create  diary/doc/HOWTO-write-tDiary.en.md
      create  diary/doc/HOWTO-write-tDiary.md
      create  diary/doc/INSTALL-cgi.md
      create  diary/doc/INSTALL-paas.md
      create  diary/doc/INSTALL-rack.md
      create  diary/doc/INSTALL.md
      create  diary/doc/README.en.md
      create  diary/doc/README.md
      create  diary/doc/UPGRADE.md
      create  diary/doc/doc.css
      create  diary/tdiary.conf
      create  diary/Gemfile.local
[DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` (called at /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/tdiary-5.2.4/lib/tdiary/cli.rb:23)
         run  bundle install --without test development from "./diary"
[DEPRECATED] The `--without` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set --local without 'test development'`, and stop using this flag
Bundler 2.4.13 is running, but your lockfile was generated with 2.3.7. Installing Bundler 2.3.7 and restarting using that version.
Fetching gem metadata from https://rubygems.org/.
Fetching bundler 2.3.7
Installing bundler 2.3.7
[DEPRECATED] The `--without` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set --local without 'test development'`, and stop using this flag
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...
Using rake 13.0.6
Using bundler 2.3.7
Using rexml 3.2.5
Fetching thor 1.2.1
Using hikidoc 0.1.0
Using mini_mime 1.1.2
Fetching timeout 0.3.0
Fetching rack 3.0.1
Using fastimage 2.2.6
Fetching webrick 1.7.0
Fetching mail 2.7.1
Installing timeout 0.3.0
Fetching net-protocol 0.1.3
Installing thor 1.2.1
Installing webrick 1.7.0
Using emot 0.0.4
Installing net-protocol 0.1.3
Installing rack 3.0.1
Using net-smtp 0.3.3
Installing mail 2.7.1
Fetching rackup 0.2.3
Using tdiary 5.2.4
Installing rackup 0.2.3
Bundle complete! 28 Gemfile dependencies, 16 gems now installed.
Gems in the groups 'test' and 'development' were not installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
         run  bundle exec tdiary htpasswd from "./diary"
Input your username/password
Username: tambara
New password: 
Re-type new password: 
install finished
run `tdiary server` in diary directory to start server

サーバを起動してみる

> bundle exec tdiary server
/Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/file.rb:5: warning: Rack::File is deprecated and will be removed in Rack 3.1
[2023-05-20 09:51:17] INFO  WEBrick 1.7.0
[2023-05-20 09:51:17] INFO  ruby 3.2.2 (2023-03-30) [arm64-darwin22]
[2023-05-20 09:51:17] INFO  WEBrick::HTTPServer#start: pid=12696 port=19292

あがった。localhost:19292にアクセスしてみよう。

Rack::Lint::LintError: uppercase character in header name: Content-Type (Rack::Lint::LintError)
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/lint.rb:653:in `block in check_headers'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/lint.rb:637:in `each'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/lint.rb:637:in `check_headers'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/lint.rb:73:in `response'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/lint.rb:35:in `call'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/show_exceptions.rb:27:in `call'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/common_logger.rb:43:in `call'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/content_length.rb:20:in `call'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rackup-0.2.3/lib/rackup/handler/webrick.rb:94:in `service'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb:140:in `service'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/webrick-1.7.0/lib/webrick/httpserver.rb:96:in `run'
    /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/webrick-1.7.0/lib/webrick/server.rb:310:in `block in start_thread'
127.0.0.1 - - [20/May/2023:09:53:01 +0900] "GET / HTTP/1.1" 500 40600 0.0290
127.0.0.1 - - [20/May/2023:09:53:01 JST] "GET / HTTP/1.1" 500 40600
- -> /

あら?

というわけで、動かない。調べるか・・・

とりあえず、この設定だとマシンの外部からアクセス出来てしまう。ローカルでだけ使いたい。

cli.rbをみると、serverコマンドにオプションを付ければ良さそうだ。

(前略)
        desc "server", "Start tDiary server"
        method_option "rack", type: :string, banner:
            "start server with rack interface (default)"
        method_option "cgi", type: :string, banner:
            "start server with cgi interface"
        method_option "bind", aliases: "b", type: :string, default: "0.0.0.0", banner:
            "bind to the IP"
        method_option "port", aliases: "p", type: :numeric, default: 19292, banner:
            "use PORT"
        method_option "log", aliases: "l", type: :string, banner:
            "File to redirect output"
        def server
(後略)

オプションbらしい。-bの形で指定すればいいのかな?

> bundle exec tdiary server -b localhost
/Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/rack-3.0.1/lib/rack/file.rb:5: warning: Rack::File is deprecated and will be removed in Rack 3.1
[2023-05-20 11:42:10] INFO  WEBrick 1.7.0
[2023-05-20 11:42:10] INFO  ruby 3.2.2 (2023-03-30) [arm64-darwin22]
[2023-05-20 11:42:10] INFO  WEBrick::HTTPServer#start: pid=14501 port=19292

見た目変わらないが、これでLAN上の別のマシンからはアクセス出来なくなったのでOKっぽい。

さて、エラーの原因だけども、

Rack::Lint::LintError: uppercase character in header name: Content-Type (Rack::Lint::LintError)

と言われていて、ググった感じだとRackの3.0.0からレスポンスヘッダは小文字じゃないとダメみたい。HTTP ヘッダの大文字小文字がはどちらでもいいという認識だったけど、最近、変わったのだろうか。

さらに検索してみると、そのもののPRが見つかった。

Rack 3 で動かない箇所を直しました by hsbt · Pull Request #1068 · tdiary/tdiary-core · GitHub

これは5.2.4に含まれている問題みたい。 tDiary-5.2.4 リリース - tDiary.org

にも、動かなかったら5.2.3を使ってくれと書いてある。はーい。

> gem uninstall tdiary
Remove executables:
    tdiary

in addition to the gem? [Yn]  Y
Removing tdiary
Successfully uninstalled tdiary-5.2.4

> gem install tdiary -v 5.2.3
Fetching tdiary-5.2.3.gem
Successfully installed tdiary-5.2.3
Parsing documentation for tdiary-5.2.3
Installing ri documentation for tdiary-5.2.3
Done installing documentation for tdiary after 0 seconds
1 gem installed

今度は~lib/tdiaryで作ることにする。

> tdiary new lib/tdiary
      create  lib/tdiary
      create  lib/tdiary/public
      create  lib/tdiary/misc/plugin
      create  lib/tdiary/lib/tdiary/filter
      create  lib/tdiary/lib/tdiary/style
      create  lib/tdiary/js
      create  lib/tdiary/theme
      create  lib/tdiary/README.md
      create  lib/tdiary/Gemfile
      create  lib/tdiary/Gemfile.lock
      create  lib/tdiary/config.ru
      create  lib/tdiary/tdiary.conf.beginner
      create  lib/tdiary/tdiary.conf.sample
      create  lib/tdiary/tdiary.conf.sample-en
      create  lib/tdiary/doc
      create  lib/tdiary/doc/HOWTO-authenticate-in-rack.md
      create  lib/tdiary/doc/HOWTO-make-io.md
      create  lib/tdiary/doc/HOWTO-make-plugin.md
      create  lib/tdiary/doc/HOWTO-make-theme.md
      create  lib/tdiary/doc/HOWTO-testing-tDiary.md
      create  lib/tdiary/doc/HOWTO-use-plugin.md
      create  lib/tdiary/doc/HOWTO-write-tDiary.en.md
      create  lib/tdiary/doc/HOWTO-write-tDiary.md
      create  lib/tdiary/doc/INSTALL-cgi.md
      create  lib/tdiary/doc/INSTALL-paas.md
      create  lib/tdiary/doc/INSTALL-rack.md
      create  lib/tdiary/doc/INSTALL.md
      create  lib/tdiary/doc/README.en.md
      create  lib/tdiary/doc/README.md
      create  lib/tdiary/doc/UPGRADE.md
      create  lib/tdiary/doc/doc.css
      create  lib/tdiary/tdiary.conf
      create  lib/tdiary/Gemfile.local
[DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` (called at /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/tdiary-5.2.3/lib/tdiary/cli.rb:23)
         run  bundle install --without test development from "./lib/tdiary"
[DEPRECATED] The `--without` flag is deprecated because it relies on being remembered across bundler invocations, which bundler will no longer do in future versions. Instead please use `bundle config set --local without 'test development'`, and stop using this flag
Fetching gem metadata from https://rubygems.org/...........
Resolving dependencies...
Using rake 13.0.6
Using bundler 2.3.7
Fetching digest 3.1.0
Using thor 1.2.1
Using fastimage 2.2.6
Using hikidoc 0.1.0
Using mini_mime 1.1.2
Using timeout 0.3.0
Using webrick 1.7.0
Using rexml 3.2.5
Fetching rack 2.2.4
Using emot 0.0.4
Using mail 2.7.1
Using net-protocol 0.1.3
Installing digest 3.1.0 with native extensions
Installing rack 2.2.4
Fetching tdiary 5.2.4
Installing tdiary 5.2.4
Fetching net-smtp 0.3.1
Installing net-smtp 0.3.1
Bundle complete! 27 Gemfile dependencies, 16 gems now installed.
Gems in the groups 'test' and 'development' were not installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
         run  bundle exec tdiary htpasswd from "./lib/tdiary"
Input your username/password
Username: tambara
New password: 
Re-type new password: 
install finished
run `tdiary server` in lib/tdiary directory to start server

あ、あれ?tdiary 5.2.4がインストールされちゃった。

> bundle exec tdiary server -b localhost
bundler: failed to load command: tdiary (/Users/tambara/.rbenv/versions/3.2.2/bin/tdiary)
/Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/tdiary-5.2.4/lib/tdiary/cli.rb:129:in `server': uninitialized constant Rackup (NameError)

                ::Rackup::Server.start( opts )
                        ^^^^^^^^
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/tdiary-5.2.4/bin/tdiary:7:in `<top (required)>'
    from /Users/tambara/.rbenv/versions/3.2.2/bin/tdiary:25:in `load'
    from /Users/tambara/.rbenv/versions/3.2.2/bin/tdiary:25:in `<top (required)>'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli/exec.rb:58:in `load'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli/exec.rb:58:in `kernel_load'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli/exec.rb:23:in `run'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli.rb:484:in `exec'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli.rb:31:in `dispatch'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/cli.rb:25:in `start'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/exe/bundle:48:in `block in <top (required)>'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/lib/bundler/friendly_errors.rb:103:in `with_friendly_errors'
    from /Users/tambara/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/bundler-2.3.7/exe/bundle:36:in `<top (required)>'
    from /Users/tambara/.rbenv/versions/3.2.2/bin/bundle:25:in `load'
    from /Users/tambara/.rbenv/versions/3.2.2/bin/bundle:25:in `<main>'

案の定動かない。よくわからんけど、Gemfile.lockのtdiaryのバージョンを5.2.3に直してうごかしちゃった(笑)

あと、RDスタイルがないと困ってしまうので、探して入れた。

RubyのLinda実装であるrindaを触ってみる

部門の勉強会でオライリーから出ている「ソフトウェアアーキテクチャの基礎」という本を読んでいる。アーキテクチャパターンランゲージを手に入れることを主眼にしているので、第2部から読んでる。

www.oreilly.co.jp

内容的に同意できないところもあるし、「そのネーミングはどうなのか?」と思うこともしばしばだけども知識の整理にはなるし、何より「うーん、どうなの?」という内容の方が勉強会が盛り上がるというのはあるので、楽しくやっているところ。

で、次回は15章の「スペースベースアーキテクチャ」である。「すぺーす?宇宙?」という感じでまったく聞いたことがない名前なんだけれども、インメモリDBグリッドでデータ共有したたくさんのアプリケーションサーバでリクエスト数の変動幅が大きい(ときどきバーストした大量のリクエストが飛んでくるような)システムを作ろうというようなアーキテクチャパターンのことをそう呼んでいるらしい。まあ、確かに固まった名前がないといえばないような気がする。

しかし、それがなんで「スペースベース」なのかというと、これは以下のように説明されている。

スペースベースアーキテクチャの名前は、タプルスペースの概念に由来する。タプルスペースとは、複数の並列プロセッサーが共有メモリを介して通信する技術だ。

タプルスペースというのは聞いたことがない。調べてみると、Lindaというものが昔、考えられていたらしい。

ja.wikipedia.org

Wikipediaには「プログラミング言語」だと書いてあるが、これは並列プログラミングのモデルの一種のようだ。コルーチンとかアクターとかスレッドとかミューテックスとか、そういうものの仲間な気がする。いや、それらの仲間が「タプルスペース」でタプルスペースの実装をLindaと呼ぶのかもしれない。よくわからない。

よくわからないけども、並列プログラミングを行う上での問題というのは、並列で動作しているプロシージャ同士で共有するリソースの競合をどうやって避けるかということに尽きる。共有しているリソースが何にもなければそれぞれが勝手に動いていればいい。けど、それを並列とはいわないわけで、共有するリソースへのアクセスをどうするかということが問題になる。ロックして他のプロシージャを待たせればいいという考え方もあるし、共有リソースをプロシージャ間で通信に載せてやりとりすればいいという考え方もある。

タプルスペースというのはシンプルなリクエストキューのようなもので、どんなスキーマでも格納できるストレージにputする動作と、ある特定のスキーマだけをgetする動作だけが出来て、リクエスタとコンシューマはスキーマを取り決めておくことでメッセージをやりとり出来るというような仕組み。getする側は欲しい情報がなければブロッキングするので、単純にタプルスペースからgetするよというコードだけを書いておけば、それでリクエストの待ち受け状態に出来るというわけ。

Wikipediaによると、これが提案された1986年頃には注目されたそうだが、90年代には忘れられたらしい。まったく聞いたことがなかった。

で、dRubyの関さんがLindaのRuby実装であるrindaというライブラリを書いていて、大昔からRubyの標準ライブラリに入っていて、Railsの内部でも使われていたりするらしい。まったく知らなかった・・・というか、ここまでくると聞いたことがないわけはない気もするんだけども、関心を持っていないとそんなものである。というわけで、ちょろっと触ってみることにする。

まず、1つ目のターミナルでタプルスペースを作る。

irb(main):001:0> require 'rinda/tuplespace'
=> true
irb(main):002:0> ts = Rinda::TupleSpace.new
=> 
#<Rinda::TupleSpace:0x0000000108141eb0                                                                    
...                                                                                                       
irb(main):003:0> DRb.start_service('druby://localhost:12345',ts)
=> 
#<DRb::DRbServer:0x000000010818e030                                                                       
 @config=                                                                                                 
(途中略)
 @uri="druby://localhost:12345">
irb(main):004:0> 

3行だけである。簡単だ。requireして、タプルスペースを作って、それをdRubyでURLを付けて公開する。それだけ。

では、2つ目のターミナルで、待ち受けをしてみる。

> irb
irb(main):001:0> require 'drb/drb'
=> true
irb(main):002:0> DRb.start_service
=> 
#<DRb::DRbServer:0x0000000106dc7038                                                             
 @config=                                                                                       
(途中略)
 @uri="druby://192.168.0.125:59142">
irb(main):003:0> ts = DRbObject.new_with_uri('druby://localhost:12345')
=> #<DRb::DRbObject:0x00000001067db2c8 @ref=nil, @uri="druby://localhost:12345">        
irb(main):004:0> mes = ts.take([:message, nil])

今度は4行だ。requireして、dRubyを開始して、タプルスペースに接続して、メッセージを取得する。

2行目のDRb.start_serviceはなぜ必要なのかというと、dRubyでやりとりするときに暗黙のオブジェクト公開が行われることがあるからなんだそうだ。なので、オブジェクトを公開しない場合も、おまじないで引数なしのDRb.start_serviceをやっておく。

3行目が典型的なdRubyの使い方で、DRbObject.new_with_uriするとそのURLで公開されているオブジェクトに対するDRbObjectが作られる。そのDRbObjectに対するメソッド呼び出しをすると、それがネットワークを通じて元のオブジェクトへ送られることになる。ありがちなRMI(Remote Method Invocation)だと、そのメソッドの呼び出しのためのIDL(Interface Definition Language)での記述がめんどっちかったりするわけだが、なんせRubyなのでそういうものはなく、いきなりメソッド呼び出しが出来てしまう。

なので、4行目でいきなりDRbObjectであるtsに対してtakeを実行する。こちらのプロセスはたぶんDRbObjectの実体がTapleSpaceであることは知らないまま、takeを向こうに送りつけている。で、takeは引数に渡したものをパターンとしてマッチするものを返す。見つからなければ待ち続ける。nilワイルドカードとして扱われるので、要するに2要素で1要素目に:messageが入っているものが来るのを待ち続ける。 最後の行でプロンプトが戻ってきていないのがポイントね。

では、3つ目のターミナルを開いて、メッセージを送りつけてみよう。

> irb
irb(main):001:0> require 'drb/drb'
=> true
irb(main):002:0> DRb.start_service
=> 
#<DRb::DRbServer:0x000000010134f010
 @config=                
(途中略)
 @uri="druby://192.168.0.125:59454">
irb(main):003:0> ts = DRbObject.new_with_uri('druby://localhost:12345')
=> #<DRb::DRbObject:0x00000001013fd5e8 @ref=nil, @uri="druby://localhost:12345">
irb(main):004:0> ts.write([:message, "hoge"])
=> #<DRb::DRbObject:0x00000001014a4028 @ref=78840, @uri="druby://localhost:12345">
irb(main):005:0> 

さっきとの違いは、takeじゃなくてwriteでメッセージを送っていること。これをwriteした瞬間に2つ目のターミナルに以下が表示されることになる。

irb(main):004:0> mes = ts.take([:message, nil])    ## <== さっきはここで止まってた
=> [:message, "hoge"]
irb(main):005:0> 

めちゃめちゃ簡単だ。

1つのリクエスタと1つのコンシューマしかいないと何がうれしいのかわからないが、これの素敵なところはいくらでもコンシューマを待ち受けさせてよいし、いくらでも同時のプロセスからメッセージを投げ込んでよいということだ。時間がかかるIOを伴う処理(例えば、何かのリストにもとづいてインターネットのいろんな場所から何かを集めてくるとか)を並列化したり、時間のかかる計算をコア数分だけ並列化したりしたいことはちょいちょいあるが、それがものすごく簡単に実行出来るし、まったく同じプログラムでマルチプロセスにもマルチマシンにも出来る。これはとっても便利じゃないか。なんで今まで知らなかったんだろう。覚えておこう。

個人のドメインを取得した

20年この業界で仕事をし、いろいろなWebベースのシステムのインフラ部分に関わって、おおよそのシステムの勘所は掴んでいるつもりだ。だけども、仕事が社内向けシステムに偏りがちだということもあって、SEOなんかはまったくわからない。特に、最近不勉強で良くないなと思っているのがDNSである。出入りの業者がお客様のDNSを触ることはないので、ドメインの管理の仕組みやDNSのレコードについてはさっぱりわからない。この点に関しては、「レンタルサーバWordpressを建ててブログを書こう!あなたもすぐに収益化!」というようなサイトを見て見よう見まねでWebサーバを立てている業界外の皆さんより数段劣っている自覚がある。プロとして恥ずかしい。

WordpressのようなCMSについてもそうである。CMSでやれるようなWebシステムを汎用フレームワークを使ったり、なんならフレームワークを使わなかったりして自力で実装することは出来るんだけども、Wordpressを使ってお手軽Webサーバを立てることはやったことがない。なんなら、何が出来るのか理解をしていない。Wordpressでちゃちゃっとできるようなことをするのに、Spring bootを持ってきがちなのである。プロとして恥ずかしい。

さらに、長年ブログを書いてきたココログが、社内のDNSから「怪しいサイト」扱いをされて名前解決出来なくなるという悲しい事件が起きた。もちろん、「ココログ怪しくないよ」と社内の部門に連絡してもいいのだが、本当に怪しくないのか別に私が保証できるわけでもない。ともかく、自分のブログに書いたことを社内で連携できなくなったのはつらい。引っ越しを考えるべきである。HerokuのF1ラップタイムPDFをCSVに変換するツールの移行先も考えたい。

いろいろ考えると、これは自分でサイトを立ち上げるべきである。やっていることがざっと25年遅れな気がして恥ずかしいが、今回の記事はここまでに恥ずかしい点はたくさんあったのでもうしょうがないのである。

まず、第1歩として、ドメインを取得することにした。

勝手がわからないんだけども、ぐぐるととにかくいろんなレジストラの名前が出てくる。お名前.comとかがとにかくめちゃめちゃ推されている。それはGoogleに大量のお金を払っているからなのかもしれないが、とりあえず、有名どころを触ってみるのは経験としてよいことなので、とりあえず目に付いたところから買えばいいかなと思う。後の引っ越しのことも考えて、今回はサーバを置くところとドメインを管理するところはとりあえず別にしてみることにした。

が、ググりまくっていると「とにかく、お名前.comはメールが死ぬほどくるのでむかつく」という評判をみた。ああー・・・、いわゆる楽天状態かー。なんとなく、日本のスーパーのチラシみたいなWebサイトを作っているような業者はおしなべてメールを死ぬほど送ってきて迷惑である。関わりたくない。どうしよう。でも、このへん、何にも知らないので日本語が通じるところでやりたい・・・。

という葛藤のあげく、Google Domainsから買うことにした。お値段はそこそこだが、とりあえず年3000円ぐらいは勉強代で構わない。あっという間に買えた。あっという間に買えたけど、これをどうやってレンタルサーバにつくるサイトと紐付けるのは未だによくわかっていない。転びながらやっていくことにする。次はサーバを建てないと。

Herokuのしょぼいアプリが動かなくなっていたので対応した

何年か前の話だが、某所で某F1関係者がFIAが提供しているラップチャートのPDFから、グラフを書くためにExcelに各ドライバーのラップタイムをコピペするのがめんどいと愚痴っているのを聞き、PDFを読んでCSVを返すRubyスクリプトを作って提供することにした。とはいえ、Rubyスクリプトを実行してねというのも乱暴で、どこかでWebツールとして提供するのが良かろうということになり、CSSもなんにもないWeb画面で各グランプリをリストし、クリックするとCSVがダウンロードされるだけの手抜きアプリを作り、Herokuの無料枠で提供していた(事実上、ユーザーが1名しかいないアプリなので、それで十分だった)。Herokuのガイドに従って、sinatraで制御してた。

PDFの入手リンク先を毎戦確認したり、FIAが気分で変えたPDFのフォーマットに対応したりで、ちょいちょいメンテナンスが発生したりはするものの概ね機嫌良く動いてはいた。だが、Rubyのバージョンが古くなって更新しろよと警告がでていたりしたのだが、放置していた。というのも、Herokuは無料枠がなくなってしまうということがあって、どこかに引っ越さなくてはいけないなと思っていたから。しかし、ここのところ仕事に追いまくられて対応出来ず、ついに今週は2023年の開幕戦である。Herokuのまましばらく運用するほかない。

というわけで、去年の最終戦以来、アプリをビルドしてみたところ、動かない。ごちゃごちゃといろいろ対応する必要があったので、メモしておく。

> git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 378 bytes | 378.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: !   Your app is using the Heroku-18 stack, which reaches end-of-life on April 30th, 2023.
remote: !   
remote: !   Builds on Heroku-18 will not be possible from May 1st, 2023.
remote: !   
remote: !   Please upgrade the stack of your app as soon as possible:
remote: !   https://devcenter.heroku.com/articles/upgrading-to-the-latest-stack
remote: !   
remote: !   Heroku-18 EOL FAQ: https://help.heroku.com/X5OE6BCA/heroku-18-end-of-life-faq
remote: !   
remote: !   THIS IS A ONE-OFF NOTICE. THE NEXT DEPLOY WILL FUNCTION WITHOUT INTERRUPTION.
remote: !   
remote: !   The next warning of this kind will occur in: 7 day(s).
remote: 
To https://git.heroku.com/lit-taiga-21087.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/lit-taiga-21087.git'

Heroku-18スタックはもう使えないから移行しろということらしい。エラーメッセージに出ているFAQを読むと新しいディレクトリに新しいスタック用にアプリを組み立てて・・・となっていて「おおぅ」となった。

が、HerokuのWeb管理画面にいくと以下のようになっていて、ボタンを押せば更新が走ってしまうようだ。私のしょぼいアプリはこれで問題なかった。

> git push
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 378 bytes | 378.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Building on the Heroku-22 stack
remote: -----> Using buildpack: heroku/ruby
remote: -----> Ruby app detected
remote: -----> Installing bundler 2.3.25
remote: -----> Removing BUNDLED WITH version in the Gemfile.lock
remote: -----> Compiling Ruby/Rack
remote:        Command: 'set -o pipefail; curl -L --fail --retry 5 --retry-delay 1 --connect-timeout 3 --max-time 30 https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-22/ruby-2.7.1.tgz -s -o - | tar zxf - ' failed on attempt 1 of 3.
remote:        Command: 'set -o pipefail; curl -L --fail --retry 5 --retry-delay 1 --connect-timeout 3 --max-time 30 https://heroku-buildpack-ruby.s3.us-east-1.amazonaws.com/heroku-22/ruby-2.7.1.tgz -s -o - | tar zxf - ' failed on attempt 2 of 3.
remote: 
remote:  !
remote:  !     The Ruby version you are trying to install does not exist on this stack.
remote:  !     
remote:  !     You are trying to install ruby-2.7.1 on heroku-22.
remote:  !     
remote:  !     Ruby ruby-2.7.1 is present on the following stacks:
remote:  !     
remote:  !     - heroku-18
remote:  !     - heroku-20
remote:  !     
remote:  !     Heroku recommends you use the latest supported Ruby version listed here:
remote:  !     https://devcenter.heroku.com/articles/ruby-support#supported-runtimes
remote:  !     
remote:  !     For more information on syntax for declaring a Ruby version see:
remote:  !     https://devcenter.heroku.com/articles/ruby-versions
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote: 
remote:  !     Push failed
remote: Verifying deploy...
remote: 
remote: !   Push rejected to lit-taiga-21087.
remote: 
To https://git.heroku.com/lit-taiga-21087.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/lit-taiga-21087.git'

次はRubyが古いと言われている。単純にGemfileのバージョンを3.1.3に書き換えて、Gemfile.lockを消して(する必要があるのか理解出来てない)、bundle installする。

> bundle install
rbenv: version `2.7.1' is not installed (set by /Users/tambara/F1_lap_analysis/.ruby-version)

rbenvでRubyバージョンを固定してあった。パソコンの移行後は手元に2.7.1が入ってない状態。普段、PDF入手先を書き換えてPUSHするだけなのでローカルにRubyが入ってなくてもメンテ出来ていたようだ。

> rbenv local 3.1.3

を実行しておいて、

> bundle install
Fetching https://github.com/sinatra/sinatra.git
Fetching gem metadata from https://rubygems.org/.............
Resolving dependencies...
Using rake 13.0.6
Fetching afm 0.2.2
Using bundler 2.3.26
Fetching ffi 1.15.5
Fetching Ascii85 1.1.0
Fetching diff-lcs 1.5.0
Fetching coderay 1.1.3
Fetching formatador 1.1.0
Fetching rb-fsevent 0.11.2
Fetching lumberjack 1.2.8
Installing formatador 1.1.0
Installing Ascii85 1.1.0
Installing afm 0.2.2
Installing lumberjack 1.2.8
Installing diff-lcs 1.5.0
Installing rb-fsevent 0.11.2
Installing coderay 1.1.3
Fetching nenv 0.3.0
Fetching shellany 0.0.1
Fetching method_source 1.0.0
Fetching thor 1.2.1
Fetching guard-compat 1.2.1
Fetching minitest 5.17.0
Fetching hashery 2.1.2
Installing shellany 0.0.1
Fetching multi_json 1.15.0
Installing nenv 0.3.0
Installing method_source 1.0.0
Using ruby2_keywords 0.0.5
Fetching ruby-rc4 0.1.5
Installing guard-compat 1.2.1
Fetching ttfunk 1.7.0
Fetching rack 2.2.6.2
Installing ffi 1.15.5 with native extensions
Installing thor 1.2.1
Fetching rspec-support 3.12.0
Installing hashery 2.1.2
Fetching tilt 2.1.0
Installing minitest 5.17.0
Fetching notiffany 0.1.3
Installing ruby-rc4 0.1.5
Fetching mustermann 3.0.0
Installing multi_json 1.15.0
Fetching pry 0.14.2
Installing ttfunk 1.7.0
Installing tilt 2.1.0
Fetching guard-minitest 2.4.6
Installing notiffany 0.1.3
Fetching pdf-reader 2.11.0
Installing rack 2.2.6.2
Fetching rack-test 2.0.2
Using rack-protection 3.0.5 from https://github.com/sinatra/sinatra.git (at master@4fc73c3)
Installing rspec-support 3.12.0
Fetching rspec-core 3.12.1
Fetching rspec-expectations 3.12.2
Installing mustermann 3.0.0
Fetching rspec-mocks 3.12.3
Installing guard-minitest 2.4.6
Using sinatra 3.0.5 from https://github.com/sinatra/sinatra.git (at master@4fc73c3)
Fetching sinatra-contrib 3.0.5
Installing pry 0.14.2
Installing rack-test 2.0.2
Installing rspec-expectations 3.12.2
Installing pdf-reader 2.11.0
Installing rspec-core 3.12.1
Installing sinatra-contrib 3.0.5
Installing rspec-mocks 3.12.3
Fetching rspec 3.12.0
Installing rspec 3.12.0
Fetching rb-inotify 0.10.1
Installing rb-inotify 0.10.1
Fetching listen 3.8.0
Installing listen 3.8.0
Fetching guard 2.18.0
Installing guard 2.18.0
Bundle complete! 9 Gemfile dependencies, 40 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

改めて、ビルドするためにgit pushする。

> git push
Enumerating objects: 14, done.
Counting objects: 100% (14/14), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 1.23 KiB | 1.23 MiB/s, done.
Total 9 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Building on the Heroku-22 stack
remote: -----> Using buildpack: heroku/ruby
remote: -----> Ruby app detected
remote: -----> Installing bundler 2.3.25
remote: -----> Removing BUNDLED WITH version in the Gemfile.lock
remote: -----> Compiling Ruby/Rack
remote: -----> Using Ruby version: ruby-3.1.3
remote:        Purging Cache. Changing stack from heroku-18 to heroku-22
remote: -----> Installing dependencies using bundler 2.3.25
remote:        Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
remote:        Your bundle only supports platforms ["arm64-darwin-22"] but your local platform
remote:        is x86_64-linux. Add the current platform to the lockfile with
remote:        `bundle lock --add-platform x86_64-linux` and try again.
remote:        Bundler Output: Your bundle only supports platforms ["arm64-darwin-22"] but your local platform
remote:        is x86_64-linux. Add the current platform to the lockfile with
remote:        `bundle lock --add-platform x86_64-linux` and try again.
remote: 
remote:  !
remote:  !     Failed to install gems via Bundler.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote: 
remote:  !     Push failed
remote: Verifying deploy...
remote: 
remote: !   Push rejected to lit-taiga-21087.
remote: 
To https://git.heroku.com/lit-taiga-21087.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/lit-taiga-21087.git'

プラットフォーム設定がおかしいと。親切にコマンドまで提示されているので、その通りやる。

> bundle lock --add-platform x86_64-linux
Fetching gem metadata from https://rubygems.org/............
Resolving dependencies...
Writing lockfile to /Users/tambara/F1_lap_analysis/lit-taiga-21087/Gemfile.lock

Gemfile.lockをコミットして、再ビルド

> git push
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 8 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (14/14), 1.66 KiB | 1.66 MiB/s, done.
Total 14 (delta 8), reused 0 (delta 0), pack-reused 0
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Building on the Heroku-22 stack
remote: -----> Using buildpack: heroku/ruby
remote: -----> Ruby app detected
remote: -----> Installing bundler 2.3.25
remote: -----> Removing BUNDLED WITH version in the Gemfile.lock
remote: -----> Compiling Ruby/Rack
remote: -----> Using Ruby version: ruby-3.1.3
remote:        Purging Cache. Changing stack from heroku-18 to heroku-22
remote: -----> Installing dependencies using bundler 2.3.25
remote:        Running: BUNDLE_WITHOUT='development:test' BUNDLE_PATH=vendor/bundle BUNDLE_BIN=vendor/bundle/bin BUNDLE_DEPLOYMENT=1 bundle install -j4
remote:        Fetching https://github.com/sinatra/sinatra.git
remote:        Fetching gem metadata from https://rubygems.org/............
remote:        Fetching rake 13.0.6
remote:        Installing rake 13.0.6
remote:        Using bundler 2.3.26
remote:        Fetching hashery 2.1.2
remote:        Fetching Ascii85 1.1.0
remote:        Fetching afm 0.2.2
remote:        Fetching multi_json 1.15.0
remote:        Installing Ascii85 1.1.0
remote:        Installing hashery 2.1.2
remote:        Installing multi_json 1.15.0
remote:        Using ruby2_keywords 0.0.5
remote:        Fetching ruby-rc4 0.1.5
remote:        Installing afm 0.2.2
remote:        Fetching ttfunk 1.7.0
remote:        Installing ruby-rc4 0.1.5
remote:        Fetching rack 2.2.6.2
remote:        Fetching tilt 2.1.0
remote:        Installing ttfunk 1.7.0
remote:        Fetching mustermann 3.0.0
remote:        Installing mustermann 3.0.0
remote:        Installing tilt 2.1.0
remote:        Installing rack 2.2.6.2
remote:        Fetching pdf-reader 2.11.0
remote:        Using rack-protection 3.0.5 from https://github.com/sinatra/sinatra.git (at master@4fc73c3)
remote:        Using sinatra 3.0.5 from https://github.com/sinatra/sinatra.git (at master@4fc73c3)
remote:        Fetching sinatra-contrib 3.0.5
remote:        Installing pdf-reader 2.11.0
remote:        Installing sinatra-contrib 3.0.5
remote:        Bundle complete! 9 Gemfile dependencies, 16 gems now installed.
remote:        Gems in the groups 'development' and 'test' were not installed.
remote:        Bundled gems are installed into `./vendor/bundle`
remote:        Bundle completed (3.57s)
remote:        Cleaning up the bundler cache.
remote:        Removing bundler (2.3.25)
remote: -----> Detecting rake tasks
remote: 
remote: 
remote: -----> Discovering process types
remote:        Procfile declares types     -> web
remote:        Default types for buildpack -> console, rake
remote: 
remote: -----> Compressing...
remote:        Done: 57.2M
remote: -----> Launching...
remote:        Released v127
remote:        https://lit-taiga-21087.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/lit-taiga-21087.git
   6cef8e7..515d352  master -> master

とりあえず、これでビルドは通ったっぽい。しかしながらアクセスするとエラー画面がでる。ログを見てみる。

> heroku logs --tail
 (略)
2023-02-28T02:55:40.608799+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/" host=lit-taiga-21087.herokuapp.com request_id=2273cf84-1e3a-48e1-a980-24304729662d fwd="129.41.57.0" dyno= connect= service= status=503 bytes= protocol=https

H14はdynoが起動してないぞということらしい。このコマンドを打てとガイドされているものを素直にやる。

> heroku ps:scale web=1
 ›   Warning: heroku update available from 7.66.4 to 7.68.2.
Scaling dynos... !
 ▸    Item Could not be Updated: The app owner has to subscribe to Eco to scale your dynos. Learn more at https://blog.heroku.com/new-low-cost-plans

金を払えということのようだ。やっとここまでたどり着いた。満を持して、Herokuにクレジットカードを登録して、金を払う。そして再実行。

> heroku ps:scale web=1
 ›   Warning: heroku update available from 7.66.4 to 7.68.2.
Scaling dynos... done, now running web at 1:Eco

これでよさげ。再度、アクセスしてみるがまだエラー。

> heroku logs --tail
(略)
2023-02-28T03:01:54.590932+00:00 app[api]: Scaled to console@0:Eco rake@0:Eco web@1:Eco
2023-02-28T03:01:57.043327+00:00 heroku[web.1]: Starting process with command `bundle exec ruby main.rb -p 7213`
2023-02-28T03:01:58.289070+00:00 app[web.1]: /app/vendor/bundle/ruby/3.1.0/gems/rack-2.2.6.2/lib/rack/handler.rb:45:in `pick': Couldn't find handler for: thin, falcon, puma, HTTP, webrick. (LoadError)
(略)

rackがエラーを出している。ググると、Ruby3.0でwebrickが標準gemじゃなくなったことが原因とのこと。Gemfileにpumaを足して再チャレンジ。

これで復活した。いろいろあったけど、全部だいたいググったら先人がちゃんと記録を残してくれていて、英語をほとんど読むこともなく対応できた。感謝しかない。

ImbaのQuick Startをたしなむ(1)

https://imba.io/start

npx imba createしろとのことなので、する

> npx imba create
✔ Enter a project name or . for current dir … imba-project
✔ Choose a template or find more at https://imba.io/templates › Default
✔ Create Default project named imba-project in ./imba-project? … yes

Created <Default> project named 'imba-project' in ./imba-project

Installing dependencies
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead

added 234 packages, and audited 235 packages in 20s

69 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Install the vscode extension for an optimal experience:
  https://marketplace.visualstudio.com/items?itemName=scrimba.vsimba

Join the Imba community on discord for help and friendly discussions:
  https://discord.gg/mkcbkRw

Get started:

  ➜ cd imba-project
  ➜ npm run dev

ふむ

> cd imba-project/
> ls
node_modules/      package-lock.json  package.json       src/
> cat package.json 
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: package.json
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ {
   2   │     "name": "imba-project",
   3   │     "scripts": {
   4   │         "dev": "imba -w src/index.html",
   5   │         "start": "npm run dev",
   6   │         "build": "imba build src/index.html",
   7   │         "preview": "npx http-server dist"
   8   │     },
   9   │     "devDependencies": {
  10   │         "imba": "^2.0.0-alpha.227"
  11   │     }
  12   │ }
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> ls src
index.html  main.imba

さて、早速この2ファイルを見てみるわけだけども、最近のお約束としてはVS codeプラグインまで作っておくわけで、VSCで開いてマーケットプレースでImba拡張を入れる。シンタックスハイライトがちゃんと出る。現代のプログラミング言語はここまで出来てなければならない。

さて、実行してみよう。

> npm run dev

> dev
> imba -w src/index.html

cache dir does not exist - create /Users/tambara/study/imba_study/imba-project/node_modules/imba/.imba-cache
ℹ starting to build in /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/index.js.map 10.2kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/index.js 17.4kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/main.DSF2C6TO.js.map 30.0kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/main.DSF2C6TO.js 55.2kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/main.6WRCZDOK.css.map 0.9kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/main.6WRCZDOK.css 2.6kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/index.RWH5L9H6.css 2.6kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/public/assets/index.RWH5L9H6.css.map 0.8kb
✔ /private/var/folders/kn/44qnqkwx1qq1dy9qr4jtfm3c0000gn/T/tmp-74212-dBNL7cXFjX9F/manifest.json 0.5kb
ℹ built src/index.html in 673ms - 13.57mb
✔ src/index.html: listening on http://127.0.0.1:3000

アクセスしてみよう

動いているのはわかるけど、さっぱりわからない

「Head First Ruby」を読む(1)

ほぼ新入社員といっていい会社の後輩から、「Excelの先生を探しています」と相談された。最近は学生時代にしっかりCSを学んでいたり、趣味でプログラミングをやっている新人君も多いが、彼女はそういうバックグラウンドを持っていないタイプだ。うちの会社ではそういう人材は普通である。実際、ギークばかりを採ってもうちの仕事がはかどるってわけでもないのだ。

2022年になっても、Excelの需要は高い。バグの傾向分析をしたり、プロジェクトのメンバーリストを元に所属会社別の人数を数えたり、いかにも新入社員に回ってきそうな雑用というのはたくさんあるものだ。そういうものはだいたいがみんなExcelを使ってやっている。彼らはプログラミングというのは納品するためのアプリケーションをつくるためにやるもので、その他の日常の細かなことはExcelの関数でやるものだと考えている。どうしようもなくなるとExcel VBAを担ぎ出すが、それを「プログラミング」だとは思っていない。なぜなら、Excel VBAの抽象化されたRangeオブジェクトの操作は、彼らが思っているプログラミングとどうも似ていないからだ。

私は一時期かなりExcel VBAを書いたし(プロジェクトで雑用に使える許可されたプログラミング環境がそれしかない時代があったのだ)、WindowsのCOMは偉大な発明であり、あんな複雑なものがちゃんと動いて使えているのはマイクロソフトが成し遂げた一種の奇跡みたいなものだと思っているので、割と好きでもある。ただ、さすがにベースとなる言語であるVBAで文字列操作をするのは大分泣きそうだし、2022年にはもっとよい言語がたくさんあるのでアレで仕事をするのはだいぶキツい。

そのようなExcel VBAをプログラミングのバックグラウンドがない新入社員が最初のプログラミング環境として学ぶとそれは大混乱をするだろうし、不幸だろうと思う。もうちっと下地をつくってからであれば、Excel VBAのリファレンスも読みやすくなるに違いない。それに、バグの傾向分析をやるならちょっとしたスクリプト言語の方が何倍も楽ちんだ。というわけで、世界で一番学習しやすい言語であるRubyを真面目に教えてみることにした。

Rubyのことなら内部の仕組みまで含めてある程度わかっている自信があるのだが、2022年の新入社員にRubyを学ぶと何に困難を覚えるのかはさっぱりわからない。私が教材を作るよりは、定評のある本を選ぼう。幸い、彼女は英語に不自由しないので(最近の新人君は間違いなく我々より優秀だ)、会社が金を払ってくれているOreilly Safariで読み放題の中から「Head First Ruby」をやってみることにした。「Head First xxx」シリーズは翻訳をいくつか読んだことがあって信頼がおけるブランドだし、Amazonの★も4以上だ。きっと初心者をはじめてのプログラミングの世界に誘ってくれるに違いない。たぶんな。まあ、ちょっと今年はトラブルプロジェクトに巻き込まれていて、教材を作る時間がないんだ。

というわけで、これを週に1章ずつやるぞということにした。これが新人君にとってキツいのかどうかもわからん。やってみながら考える。電子書籍はページ数がないから分量もよくわからないんだなあ。それにしたって、自分も一応読んでおく必要があるので、いまから1章を読む。そして、ハンズアウト部分をやった結果をこの後に貼る。ただ、まあ、1章は見るべきものはなんもないかな(笑)

Get Ruby

> ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
> ruby
^Cruby: Interrupt

うん、Rubyインタプリタはちゃんといる。本ではバージョンが2.0.0だ。結構古いぞ。

Use Ruby

> echo 'puts "hello world"' > hello.rb
> cat hello.rb
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: hello.rb
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ puts "hello world"
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> ruby hello.rb 
hello world

伝統を重んじている。

Use Ruby - interactively

irbは大事だ。

> irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> "Hello".upcase
=> "HELLO"
irb(main):003:0> exit
> 

Math operations and comparisons

Rubyを電卓として使おう

> irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> 5.4 - 2.2
=> 3.2
irb(main):003:0> 3 * 4
=> 12
irb(main):004:0> 7 / 3.5
=> 2.0
irb(main):005:0> 2 ** 3
=> 8
irb(main):006:0> 4 < 6
=> true
irb(main):007:0> 4 > 6
=> false
irb(main):008:0> 2 + 2 == 5
=> false

Strings

文字列だ。

irb(main):009:0> "Hello"
=> "Hello"
irb(main):010:0> 'world'
=> "world"

Variables

+=はいきなり理解出来るものだろうか・・・?

irb(main):011:0> small = 8
=> 8
irb(main):012:0> medium = 12
=> 12
irb(main):014:0> small + medium
=> 20
irb(main):015:0> pie = "Lemon"
=> "Lemon"
irb(main):016:0> pie = 3.14
=> 3.14
irb(main):017:0> number = 3
=> 3
irb(main):018:0> number += 1
=> 4
irb(main):019:0> number
=> 4
irb(main):020:0> string = "ab"
=> "ab"
irb(main):021:0> string += "cd"
=> "abcd"
irb(main):022:0> string
=> "abcd"

Everything is an object!

Rubyを最初に学ぶと「すべてがオブジェクト!」と言われても、驚きがあるわけでもないよね

irb(main):023:0> "Hello".upcase
=> "HELLO"
irb(main):024:0> "Hello".reverse
=> "olleH"
irb(main):025:0> 42.even?
=> true
irb(main):026:0> -32.abs
=> 32

Calling a method on an object

EXERCISEがある。やっとこう

irb(main):001:0> 42 / 6
=> 7
irb(main):002:0> 5 > 4
=> true
irb(main):003:0> name = "Zaphod"
=> "Zaphod"
irb(main):004:0> number = -32
=> -32
irb(main):005:0> name.upcase
=> "ZAPHOD"
irb(main):006:0> number.abs
=> 32
irb(main):007:0> "Zaphod".upcase
=> "ZAPHOD"
irb(main):008:0> -32.abs
=> 32
irb(main):009:0> name.reverse
=> "dohpaZ"
irb(main):010:0> number += 10
=> -22
irb(main):011:0> name.upcase.reverse
=> "DOHPAZ"
irb(main):012:0> rand(25)
=> 0
irb(main):013:0> rand(25)
=> 9
irb(main):014:0> rand(25)
=> 6
irb(main):015:0> name.class
=> String
irb(main):016:0> number.class
=> Integer
irb(main):017:0> name * 3
=> "ZaphodZaphodZaphod"

これだけでも語るべきことはいくつかある気がする。

Rubyではほとんどすべてがメソッド呼び出しだ。

irb(main):018:0> 42./(6)
=> 7

変数への代入は違うけど。

irb(main):019:0> number.=(-32)
/Users/tambara/.rbenv/versions/3.1.2/lib/ruby/3.1.0/irb/workspace.rb:119:in `eval': (irb):19: syntax error, unexpected '=' (SyntaxError)                                        
number.=(-32)                                           
       ^                                                
        from /Users/tambara/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `load'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'

まあ、難しいことは後で出てくるだろう・・・

Let’s build a game

伝統に則って、数当てゲームを作るようだ。ゲームの仕様を英語で読み取るのが難しいが・・・この8つを満たすプログラムをつくるぞと。

  1. 名前を入力してもらって、挨拶する
  2. 1から100までの数字をランダムで決める
  3. プレイヤーの挑戦回数を記録する。10回で当てなきゃいけない
  4. プレイヤーに数字を入力させる
  5. プレイヤーの入力した数字と正解を比較して、高いとか、低いとか伝える
  6. 一致してたら正解だと伝え、挑戦回数も教える
  7. 挑戦回数を使い果たしたら、正解の番号を教える
  8. 正解するか、挑戦回数を使い果たすまで繰り返す

伝統に則ってるなあ・・・

Running scripts

ここからはスクリプトを書かせる。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

input = gets

puts "#{input}さん、いらっしゃい"
> ruby get_number.rb 
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: たんばりん
たんばりん
さん、いらっしゃい

いらんところに改行はいったー。chompしておかなければならないな。 まあ、それは後で。

Comment

コメントというものがあるねと。

"puts" and "print"

なんで2つあるのか不思議に思うかもしれない。

そして、なんでputsはレシーバーを取らないのだと書いてある。でもそれは2章でやるそうだ。

Method arguments

メソッドには引数をつけられる。 putsはいくつでも引数をつけられるのだ。

first line
=> nil                                           
irb(main):002:0> puts "second line", "third line", "fourth line"
second line
third line                                                                
fourth line                                                               
=> nil                                    

"gets"

後ろの改行も入っちゃうぜー

hoge
=> "hoge\n"    

Parentheses are optional on method calls

メソッドに括弧つけるか問題

irb(main):004:0> puts("one", "two")
one
two                                              
=> nil                                           
irb(main):005:0> puts "one", "two"
one
two                                              
=> nil                                           
irb(main):006:0> gets
hoge
=> "hoge\n"                                      
irb(main):007:0> gets()
hoge
=> "hoge\n"           

しかし、Rubyistはメソッドが引数を取らない場合には「断固として(adamant)」括弧はつけないのだと書いてある。うむ。

String interpolation

interpolateは「補間」らしいが、日本語にしてもわからない。るりまはこれを「式展開」と呼んでいる。

irb(main):008:0> puts "The answer is #{6 * 7}"
The answer is 42
=> nil             

シングルクォートでくくると展開されない。

irb(main):009:0> puts 'The answer is #{6 * 7}'
The answer is #{6 * 7}
=> nil                   

ここの節のコラムで

  1. 行末のセミコロンとかいらんの?
  2. mainメソッドとかいらんの?

という質問が取り上げられていて、答えは両方、「めんどくさいからいらん」である。うむ。

What’s in that string?

あ、さっき私がひっかかった入力してもらった名前の最後に改行が入ってる問題がここで取り上げられる

Inspecting objects with the “inspect” and “p” methods

pメソッドは私がRuby初心者のときに不思議に思ったものの1つ。なぜなら、当時(まだ20世紀の話だ)出たばっかりのバイブルでpの説明はなかなか出てこないのに、みんなruby-list(Rubyメーリングリスト)では使われまくりだったので。

というわけで、ここでpとセットでinspectも紹介される。良いね。

Escape sequences in strings

そして、pを説明したら、エスケープシーケンスの話もする必要があるということ。構成が考えられてる。 そして、シングルクォートとダブルクオートの文字列の使い分けが説明されている。

Calling “chomp” on the string object

ここでchompの説明がくると。

What methods are available on an object?

呼べるメソッドの確認をしてる。どんなオブジェクトにもmethodsメソッドがある。

「るりま」の場所はここで教えた方がいいかもしれない。

Generating a random number

次はランダムな数を得るところを作る。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

puts "target = #{target}"

実行するとこんな感じ

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: だるま
だるまさん、いらっしゃい
---
1から100までの数字を用意します
当てられるかなー?
target = 6

Converting to strings

文字列への変換について

irb(main):001:0> num_guesses = 0
=> 0
irb(main):002:0> remaining_guesses = 10 - num_guesses
=> 10
irb(main):003:0> puts remaining_guesses + " guesses left"
(irb):3:in `+': String can't be coerced into Integer (TypeError)
        from (irb):3:in `<main>'                                        
        from /Users/tambara/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `load'  
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'

エラーメッセージの意味がわかりづらいというのはあるだろうなと思う。これを強制的に変換してしまう言語もあるがRubyはそうしない。

Pythonはどうだっけな。

> python
Python 3.10.6 (main, Aug 14 2022, 16:05:52) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> num_guesses = 0
>>> remaining_guesses = 10 - num_guesses
>>> print(remaining_guesses + " guesses left")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

しないね。

というわけで、to_sする

irb(main):004:0> puts remaining_guesses.to_s + " guesses left"
10 guesses left
=> nil                

ただし、putsprintは渡したら変換してくれるから、任せたらいいのでは・・・あ、改行が・・・

irb(main):005:0> puts remaining_guesses, " guesses left"
10
 guesses left                                                           
=> nil                                           

こうする方法はある(もちろん2行にわけて書いていい)。

irb(main):009:0> print remaining_guesses; puts " guesses left"
10 guesses left
=> nil  

Ruby makes working with strings easy

つか、式展開つかえやと。違いない

irb(main):010:0> puts "#{remaining_guesses} guesses left"
10 guesses left
=> nil

Converting strings to numbers

入力された文字列を数字にする必要がある。to_i使うだけ。

Conditionals

if文が出てくる。truefalseが出てくる。&&||も出てくる。

というわけで、ここまでのところを含めてコードはどうなったかというとこんな感じと。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

num_guess = 0

guessed_it = false

puts "あなたには、#{10 - num_guess}回のチャンスが残っています"
print "正解だと思う数字をどうぞ: "
guess = gets.to_i

if guess < target
  puts "うーん、あなたの答えは小さすぎるようです"
elsif guess > target
  puts "うーん、あなたの答えは大きすぎるようです"
elsif guess == target
  puts "#{name}さん、大正解!"
  puts "あなたは#{num_guess}回目で正解出来ました"
  guessed_it = true
end

if not guessed_it
  puts "残念。あなたは正解出来ませんでした(答えは#{target}でした)"
end

実行する。まだ1回しか応えられないので、クソゲーである。

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ                  
お名前をどうぞ: あらた                              
あらたさん、いらっしゃい                            
---                                                 
1から100までの数字を用意します                      
当てられるかなー?                                  
あなたには、10回のチャンスが残っています         
正解だと思う数字をどうぞ: 50                     
うーん、あなたの答えは大きすぎるようです
残念。あなたは正解出来ませんでした(答えは42でした)

The opposite of “if” is “unless”

if not guessed_it

はダサいのでunlessの説明。unlessはあんまりほかの言語で見たことがない気がするけど、思っているより頻繁に使う。特にガード節とかで。

Loops

whileの説明。untilも説明してくれるけど、unlessと違ってこっちはまず書かない。

しかし、この例では使うのである。おおお。

というわけで、コードはこうなった。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

num_guesses = 0

guessed_it = false

until num_guesses == 10 || guessed_it
  puts "あなたには、#{10 - num_guesses}回のチャンスが残っています"
  print "正解だと思う数字をどうぞ: "
  guess = gets.to_i

  num_guesses += 1

  if guess < target
    puts "うーん、あなたの答えは小さすぎるようです"
  elsif guess > target
    puts "うーん、あなたの答えは大きすぎるようです"
  elsif guess == target
    puts "#{name}さん、大正解!"
    puts "あなたは#{num_guesses}回目で正解出来ました"
    guessed_it = true
  end
end

unless guessed_it
  puts "残念。あなたは正解出来ませんでした(答えは#{target}でした)"
end

Let’s try running our game!

動かしてみようってことで、この章は終わり。

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: たんばりん
たんばりんさん、いらっしゃい
---
1から100までの数字を用意します
当てられるかなー?
あなたには、10回のチャンスが残っています
正解だと思う数字をどうぞ: 50
うーん、あなたの答えは大きすぎるようです
あなたには、9回のチャンスが残っています
正解だと思う数字をどうぞ: 30
うーん、あなたの答えは大きすぎるようです
あなたには、8回のチャンスが残っています
正解だと思う数字をどうぞ: 20
うーん、あなたの答えは小さすぎるようです
あなたには、7回のチャンスが残っています
正解だと思う数字をどうぞ: 25
うーん、あなたの答えは大きすぎるようです
あなたには、6回のチャンスが残っています
正解だと思う数字をどうぞ: 23
うーん、あなたの答えは大きすぎるようです
あなたには、5回のチャンスが残っています
正解だと思う数字をどうぞ: 22
たんばりんさん、大正解!
あなたは6回目で正解出来ました