2020年8月31日月曜日

Ruby で macOS アプリが作れる Shoes を試してみた

概要

Ruby で macOS アプリが開発できる shoes を試してみました
かなり簡単に GUI アプリが作成できます

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • shoes 3.3

インストール

公式だとインストーラを使う方法が推奨されています
macOS の場合はここからダウンロードできます

執筆時点では shoes-3.3.7-osx-10.10.tgz というファイルがダウンロードできました
ダウンロードした圧縮ファイルを解凍すると Shoes.app というファイルが解凍されるのでそれを Applications 配下に移動すればインストールは完了です

起動すると以下のような管理 GUI が立ち上がります

サンプルアプリを作成してみる

とりあえず動作するアプリを作成してみます

  • vim app.rb
Shoes.app do
  background "#DFA"
  para "Welcome to Shoes"
end

あとは Shoes.app から「Run on App」で app.rb を選択すれば OK です

ボタンを設置する

トライ&エラーできる環境が整ったら開発していきます
まずはボタンを設置してみましょう
先程のサンプルに button を追加するだけです

Shoes.app do
  background "#DFA"
  para "Welcome to Shoes"
  button "Push me"
end

これで起動するとボタンが追加されているのが確認できます

ボタンが押されたらテキストを表示する

次にボタンのイベントハンドリングをしてみます
やり方は簡単で button によって作成されたオブジェクトに対して .click メソッドをコールするだけです

Shoes.app do
  background "#DFA"
  @t1 = para "Welcome to Shoes"
  b1 = button "Push me"
  b1.click {
    @t1.replace "Aha! Click!"
  }
end

また para で作成したテキストフィールドも @t1 でインスタンス変数化しておきそれを click のブロック内で .replace することで文字を変更しています

テキストボックスを置いてみる

テキストボックスは edit_line を使います

Shoes.app do
  background "#DFA"
  stack do
    @t1 = para "Welcome to Shoes"
    @el1 = edit_line
    b1 = button "Push me"
    b1.click {
      @t1.replace @el1.text
    }
  end
end

edlt_line に対して text メソッドを使うことで入力中のテキストを取得できます

その他コンポーネントも充実

他にも図形を描いたり図を挿入できたりリンクを使えたりといろいろと機能があります
興味があればチュートリアルの続きをやれば使い方の基本はだいたい学べるかなと思います

配布用のアプリとしてビルドするには

Shoes.app のトップに「Package an App with Shoes」があるので選択します
そして作成した Ruby ファイルを選択しビルドするプラットフォームを選択すれば OK です
Select Architecture」をクリックするとプラットフォームの一覧が表示されます
I want advanced install options」を選択すると配布時のアプリのアイコンなども設定できます

ビルドが完了すると tgz の圧縮ファイルが作成されるのでそれを解凍すると .app ファイルがあるのでこれをクリックするだけでアプリが起動するようになります

最後に

macOS アプリを Ruby で開発できる Shoes を使ってみました
かなり簡単に開発できるので Xcode や Swift の使い方を知らない人でも簡単に macOS アプリが開発できるかなと思います
ドキュメントもかなり見やすく充実しているのでわからないことがあったらドキュメントを検索すれば解決するかなと思います

参考サイト

2020年8月29日土曜日

Plyr 超入門

概要

plyr は HTML5 の audio タグや video タグを簡単にスタイリッシュに見せることができる Javascript のライブラリです
今回は簡単な使い方を紹介したいと思います

環境

  • macOS 10.15.6
  • Chrome 85.0.4183.83

Getting Started

とりあえず動かしてみましょう
まずは audio タグを使って HTML5 を書きます

<html>
<head>
  <title>Test Plyr</title>
</head>
<body>
  <audio id="player" controls>
    <source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3" type="audio/mp3" />
  </audio>
</body>
</html>

これだけだと以下のようにブラウザデフォルトのプレイヤーが表示されます

これに Plyr を適用してみます
方法は簡単で Plyr の js と css を設定するだけです

<html>
<head>
  <title>Test Plyr</title>
  <link rel="stylesheet" href="https://cdn.plyr.io/3.6.2/plyr.css" />
</head>
<body bgcolor="#504B4A">
  <audio id="player" controls>
    <source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3" type="audio/mp3" />
  </audio>
  <script src="https://cdn.plyr.io/3.6.2/plyr.js"></script>
  <script>
    const player = new Plyr('#player');
  </script>
</body>
</html>

これだけで以下のように Plyr が適用されてスタイリッシュなプレイヤーが表示されます

スタイルを適用する

plyr にはスタイル用のオプションがいくつか用意されています
例えば --plyr-color-main を使うと再生された部分やボリュームの色を変更することができます
スタイルのオプションは css を記載するだけです

<html>
<head>
  <title>Test Plyr</title>
  <link rel="stylesheet" href="https://cdn.plyr.io/3.6.2/plyr.css" />
  <style type="text/css">
  .plyr {
    --plyr-color-main:#000000;
  }
  </style>
</head>
<body bgcolor="#504B4A">
  <audio id="player" controls>
    <source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3" type="audio/mp3" />
  </audio>
  <script src="https://cdn.plyr.io/3.6.2/plyr.js"></script>
  <script>
    const player = new Plyr('#player');
  </script>
</body>
</html>

.plyr クラスは自動で生成される HTML のクラスになります
以下のような感じで色が変わっているのが確認できると思います

プレイヤーのオプションを適用する

Javascript を使ってプレイヤーのオプションを指定することでカスタマイズすることができます
例えばデフォルトでミュートの状態にする場合は muted オプションを使います
生成した Plyr オブジェクトに対してオプションを指定します

<html>
<head>
  <title>Test Plyr</title>
  <link rel="stylesheet" href="https://cdn.plyr.io/3.6.2/plyr.css" />
  <style type="text/css">
  .plyr {
    --plyr-color-main:#000000;
  }
  </style>
</head>
<body bgcolor="#504B4A">
  <audio id="player" controls>
    <source src="https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_700KB.mp3" type="audio/mp3" />
  </audio>
  <script src="https://cdn.plyr.io/3.6.2/plyr.js"></script>
  <script>
    const player = new Plyr('#player');
    player.on('ready', () => { 
      player.muted = true
    });
  </script>
</body>
</html>

またプレイヤーが ready の状態になったときに muted = true にする必要があるので注意が必要です

video を使う方法

基本的には audio と同じです
代わりに video タグを使うだけです
スタイルやオプションの指定方法も同じです

<html>
<head>
  <title>Test Plyr</title>
  <link rel="stylesheet" href="https://cdn.plyr.io/3.6.2/plyr.css" />
</head>
<body bgcolor="#504B4A">
  <video id="player" playsinline controls data-poster="/path/to/poster.jpg">
    <source src="https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4" type="video/mp4" />
    <track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default />
  </video>
  <script>
    const player = new Plyr('#player');
  </script>
</body>
</html>

最後に

Plyr を使ってみました
かなり簡単に導入できるのとデフォルトのスタイルでも十分いい感じに表示されるのでとりあえず適用するだけでもいい感じになると思います
レスポンシブデザインにもなっているのでスマホでもきれいに表示されると思います

参考サイト

2020年8月28日金曜日

Chrome でテストなどで自己証明書を設定しているサイトアクセスする方法

概要

https では証明書が必須ですが証明書が正しく発行されていない場合には Chrome では NET::ERR_CERT_INVALID となりアクセスできません

環境

  • macOS 10.15.6
  • Chrome 84.0.4147.135

thisisunsafe とタイプする

警告画面が表示された状態でブラウザにフォーカスを合わせて「thiisunsafe」とキーボードでタイプしましょう
URL バーなどにフォーカスがいかないので文字は表示されていませんがちゃんとタイプできてればサイトにサクセスできるようになります

2020年8月27日木曜日

Gitlab で自分で取得した証明書を設定する方法

概要

デフォルトでは gitlab は Let’sEncrypt から SSL 証明書を取得します
Let’sEncrypt ではなく自分ですでに証明書を持っている場合はそれを設定したほうが良い場合があります
今回は自己証明書を Gitlab に設定する方法を紹介します

環境

  • GitLab Enterprise Edition 13.2.2-ee

証明書の準備

何でも OK です
今回は LetsEncrypt で取得した想定で進めます
LetsEncrypt での証明書の取得方法はこちらで紹介しています

gitlab.rb の編集

external_url を設定している場合は Gitlab は自動で Let’sEncrypt に問い合わせて証明書を取得します
なのでそれを OFF にする必要があります

  • vim /etc/gitlab/gitlab.rb
letsencrypt['enable'] = false

また参照する証明書がデフォルトだと external_url に設定した URL 名の証明書を見ることになっているのでそこも変更します

nginx['ssl_certificate'] = "/etc/gitlab/ssl/fullchain.pem"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/privkey.pem"

証明書の配置

次に Gitlab 上の /etc/gitlab/ssl/ 配下に取得した証明書を配置しましょう
中間証明書がある場合はそれらも配置します

  • ls -ltr /etc/gitlab/ssl/*.pem
total 8
-rw-r--r-- 1 root root 3558 Aug 27 09:06 fullchain.pem
-rw-r--r-- 1 root root 1704 Aug 27 09:06 privkey.pem`

LetsEncrypt の場合は上記の 2 つが取得できるのでそれらを配置しましょう
またここで配置した証明書やキーのパスが先程設定した gitlab.rb と同一か確認しておきましょう

reconfigure

あとは reconfigure をかけます

  • gitlab-ctl reconfigure

動作確認

これでサイトに確認するとちゃんと自分が設定した証明書になっていることが確認できると思います
502 になる場合は reconfigure 後に少し待ってからアクセスしてください

最後に

Gitlab でカスタム証明書を設定する方法を紹介しました
基本は LetsEncrypt で問題ないと思いますが EV 証明書などを使いたい場合はこの方法で設定しましょう
なお証明書が更新した場合は pem ファイルを入れ替えて reconfigure すれば OK です

参考サイト

2020年8月26日水曜日

ruby で gepub を使って EPUB ファイルを作成してみた

概要

ePUB ファイルを ruby で作成してみました
gepub という gem があったのでそれを使っています

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • gepub 1.0.11

インストール

  • bundle init
  • vim Gemfile
gem "gepub"
  • bundle config path vendor
  • bundle install

サンプルコード

ちょっと長いですがやっていることは単純で本のコンテンツを作成して ePUB オブジェクトにコンテンツを追加しているです
後述で詳細を説明しています

  • vim app.rb
# coding: utf-8
require "gepub"

# 最初に本のコンテンツ情報を定義します
cover_image_file = "cover.png"
book_title = "自分の本のタイトル"
book_creator = "hawksnowlog"

cover_title = "Cover Page"
cover_content = StringIO.new(<<-EOF)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>#{cover_title}</title>
</head>
<body>
<h1>#{book_title}</h1>
<img src="../img/#{cover_image_file}" />
</body>
</html>
EOF

capter1_name = "チャプタ 1"
c1_s1_title = "Section1"
c1_s1_content = StringIO.new(<<-EOF)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>#{c1_s1_title}</title>
</head>
<body>
<p>capter1 section1 page</p>
</body>
</html>
EOF
c1_s2_title = "Section2"
c1_s2_content = StringIO.new(<<-EOF)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>#{c1_s2_title}</title>
</head>
<body>
<p>capter1 section2 page</p>
</body>
</html>
EOF

capter2_name = "チャプタ 2"
c2_s1_title = "Section3"
c2_s1_content = StringIO.new(<<-EOF)
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>#{c2_s1_title}</title>
</head>
<body>
<p>capter2 section1 page</p>
</body>
</html>
EOF

nav_content = StringIO.new(<<-EOF)
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
  <title>Table of contents</title>
</head>
<body>
<nav epub:type="toc" id="toc">
  <h1>Table of contents</h1>
  <ol>
    <li><a href="chap1-1.xhtml">#{capter1_name}</a></li>
    <li><a href="chap2-1.xhtml">#{capter2_name}</a></li>
  </ol>
</nav>
</body>
</html>
EOF

# ここから ePUB 情報を生成していきます 
book = GEPUB::Book.new
book.primary_identifier('https://hawksnowlog.blogspot.com/', 'BookID', 'URL')
book.language = 'ja'

book.add_title(
  book_title,
  title_type: GEPUB::TITLE_TYPE::MAIN,
  lang: 'ja',
  display_seq: 1
)
book.add_creator(
  book_creator, 
  display_seq: 1
)

File.open("./#{cover_image_file}") do |io|
  book.add_item("img/#{cover_image_file}", content: io).cover_image
end

# 本のコンテンツ部分を生成します
book.ordered {
  book.add_item(
    'text/cover.xhtml',
    content: cover_content).landmark(type: "cover", title: cover_title)
  book.add_item(
    'text/nav.xhtml',
    content:nav_content).add_property('nav')
  book.add_item(
    'text/chap1-1.xhtml',
    content: c1_s1_content).toc_text(capter1_name).landmark(type: "bodymatter", title: c1_s1_title)
  book.add_item(
    'text/chap1-2.xhtml',
    content: c1_s2_content)
  book.add_item(
    'text/chap2-1.xhtml',
    content: c2_s1_content).toc_text(capter2_name)
}

# ePUB ファイルの書き出し
epubname = File.join(File.dirname(__FILE__), 'my_book.epub')
book.generate_epub(epubname)
  • bundle exec ruby app.rb

解説

わかりやすいように本のコンテンツは最初にすべて定義しました
基本は xhtml で作成できるので装飾文字やリストなどは好きなように定義できます
作成したコンテンツは GEPUB::Book のオブジェクトにセットしていきます
add_title, add_creator で本のタイトルと著者を設定します
add_item でコンテンツを追加していきます
追加する際にはチャプタを設定しましょう
またコンテンツは IO を渡すのでコンテンツごとに別ファイルで管理しても OK です
いわゆる目次を作成するには add_item をした上で add_property('nav') を設定しましょう
nav_content の xhtml を見るとわかりますが単純にチャプタ先頭のファイルにリンクを貼ることで目次からチャプタに飛ぶことができます

また .epub ファイルはただの圧縮ファイルです
中身を確認すると xhtml やメタ情報のファイルが確認されています

M Filemode      Length  Date         Time      File
- ----------  --------  -----------  --------  ------------------------
  -rw-r--r--        20  26-Aug-2020  01:22:02  mimetype
  -rw-r--r--       252  26-Aug-2020  01:22:02  META-INF/container.xml
  -rw-r--r--    145170  26-Aug-2020  01:22:02  OEBPS/img/cover.png
  -rw-r--r--      1742  26-Aug-2020  01:22:02  OEBPS/package.opf
  -rw-r--r--       137  26-Aug-2020  01:22:02  OEBPS/text/chap1-1.xhtml
  -rw-r--r--       137  26-Aug-2020  01:22:02  OEBPS/text/chap1-2.xhtml
  -rw-r--r--       137  26-Aug-2020  01:22:02  OEBPS/text/chap2-1.xhtml
  -rw-r--r--       178  26-Aug-2020  01:22:02  OEBPS/text/cover.xhtml
  -rw-r--r--       353  26-Aug-2020  01:22:02  OEBPS/text/nav.xhtml
  -rw-r--r--       793  26-Aug-2020  01:22:02  OEBPS/toc.ncx
- ----------  --------  -----------  --------  ------------------------
                148919                         10 files

ePUB にはルールがあり gepub はそれにそって必要なファイルを自動的に生成してくれる gem になります
もし gepub で操作できないタグなどがあった場合には自分でこれらのファイルを編集してアーカイブし直しても OK です

ePUB Reader のインストール

何でも OK です
Mac の場合は Apple Books のアプリでも epub ファイルを確認できるのでそれでも OK です

もしない場合には Chrome のエクステンションなどもあるのでそれでも OK です
こちらからインストールしてください

動作確認

  • open my_book.epub

Apple Book アプリだと以下のように確認できました

最後に

gepub を使って Ruby から ePUB ファイルを作成してみました
手動で作成することもできますが面倒なのでこういったジェネレータを使うことをオススメします
プログラムから操作できることで例えばブログの情報を取得して ePUB で出版するなんてことも簡単にできるようになるかなと思います
あとはデータベースなんかにある情報も ePUB にできるかなと思います

参考サイト

2020年8月25日火曜日

Capistrano 3.14 を試してみた

概要

執筆時点では最新の Capistrano3.14 を試してみました
タスクの作成方法から実行までの簡単な流れをまとめています

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • capistrano 3.14.1

インストール

  • bundle init
  • vim Gemfile
gem "capistrano"
  • bundle config path vendor
  • bundle install

初期化

まずは capistrano が動作する環境を作成します

  • bundle exec cap install
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

必要最低限の config ファイルや Capfile と呼ばれる capistrano の管理ファイルが作成されます
またステージ (環境) は STAGES 変数にセットすると必要なファイルを自動で作成してくれます

  • bundle exec cap install STAGES=local,sandbox,qa,production

として実行すると config/deploy/local.rb や sandbox.rb, qa.rb なども作成してくれます
なおこの時点で自動である程度のタスクを作成してくれているので

  • bundle exec cap -T

と実行すると実行可能なタスクの一覧が表示されます
例えば doctor:environment タスクを実行すると capistrano の実行環境が確認できます

  • bundle exec cap production doctor:environment

デプロイ対象のサーバを定義する

今回はロールベースでデプロイ対象のサーバを定義します

  • vim config/deploy/production.rb
role :local_mac, %w{192.168.100.1}, my_property: :hello

とりあえず実行しているローカルの Mac をデプロイ対象のサーバにしています
「ロール名」「対象のサーバ」「プロパティ」という順番で定義します
他にも server という単位でデプロイ対象のサーバを定義できますが基本は 1 台ずつしか定義できないのでロールベースのほうがよく使うかなと思います

独自タスク定義

ではタスクを定義してみます
タスクは rake ファイル形式で記述する必要があるので .rake で作成します
とりあえず今回はテストなので role で定義したプロパティの値と実行中のホスト名でも表示してみたいと思います

  • vim lib/capistrano/tasks/test_tasks.rake
namespace :test do
  desc 'Show the hostname'
  task :echo do
    on roles(:local_mac) do |host|
      info "Host => #{host.hostname}"
      info "Properties => #{host.properties.fetch(:my_property)}"
    end
  end
end

この時点でタスクは capistrano に登録されているので cap -T で確認するとタスクの一覧に表示されるのが確認できると思います

  • bundle exec cap -T | grep echo
cap test:echo                      # Show the hostname

ちなみに roles + on でロールに含まれているサーバ分ループすることができます
引数の host は Capistrano::Configuration::Server クラスのオブジェクトで SSHKit::Host というクラスを継承しているため .username.password などで属性の参照ができます

ssh して実行してみる

実は上記のタスクは単純に config/deploy/production.rb で定義した値を参照しているだけなので実際に ssh はしていません
本来は対象のサーバに ssh してコマンドを実行します
その場合は (execute)[https://github.com/capistrano/sshkit#the-command-map] というリソースを使います
新たに execute するタスクを追加してみます

  • vim lib/capistrano/tasks/test_tasks.rake
namespace :test do
  desc 'Show the hostname'
  task :echo do
    on roles(:local_mac) do |host|
      info "Host => #{host.hostname}"
      info "Properties => #{host.properties.fetch(:my_property)}"
    end
  end

  desc 'Show macOS version'
  task :vers do
    on roles(:local_mac) do |host|
      execute :sw_vers
    end
  end
end

これで試しに test:vers タスクを実行してみると ssh できないというエラーになると思います

  • bundle exec cap production test:vers

Errno::ECONNREFUSED: Connection refused - connect(2) for 192.168.100.1:22
これは単純に実行しているローカルの Mac で sshd が有効になっていないだけです
ちゃんと sshd を有効にしてあげれば成功するのでこちらを参考に mac の sshd を有効にして再度動作確認してみましょう

cap console

便利なので紹介しておきます
'capistrano/console' という gem を使うことで実現できます
簡単に言うと任意のコマンドを role や server で定義したサーバに一括で ssh 経由で実行することができます
Capfile に一行追加しましょう

  • vim Capfile
require "capistrano/console"

そして cap production console とするとインタラクティブモードになり今回 role で指定したローカルの Mac に ssh してコマンドを実行することができます

  • bundle exec cap production console
capistrano console - enter command to execute on production
production> hostname
00:00 console
      01 hostname
username@192.168.100.1's password:
      01 mac01
    ✔ 01 192.168.100.1 6.271s

これが嬉しいのは例えば role に 100 台のサーバを登録したら 100 台のサーバに対して一斉に同じコマンドを実行したりすることができるようになります
ちなみに有効にするとちゃんと cap -T のタスクの一覧にも表示されます

最後に

最新版の Capistrano3.14 に触ってみました
基本はタスクを追加するだけなので表示に簡単にタスクはかけるかなと思います
バージョン管理の機能があるので切り戻しも簡単にできるかなと思います
ただ冪等性はなく基本は execute でコマンドをリモートで実行していくだけなので chef や ansible のような便利なリソースはないのでゴリゴリ自分でデプロイコードを書いていく必要はあります
一応 capistrano はプラグインの機能があるので Capistrano::Dockerbuild
のようにプラグインとして作成してリソースとして使用することはできるかなと思います

参考サイト

2020年8月24日月曜日

Ruby 標準の Web サーバ Webrick を使ってサーバを構築してみる

概要

開発などでは大抵の場合 Rails や Sinatra などのフレームワークを使います
ただベースになっている技術は Ruby に標準で実装されている Webrick を使っている場合がほとんどです
今回は Webrick の学習も含めて Webrick だけを使ってサーバを構築してみました

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83

とりあえずサーバを立ててみる

まずはサーバを立ててみましょう
単純な Web サーバでカレントにあるファイルを配信するだけですが以下でサーバが作れます

  • vim app.rb
require 'webrick'
srv = WEBrick::HTTPServer.new({
                                :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080
                              })
trap("INT"){ srv.shutdown }
srv.start
  • ruby app.rb

trap("INT"){ srv.shutdown } をしないと Ctrl+c で停止できないので記載しましょう
これで localhost:20080 にアクセスすれば以下のようにファイルインデックスが確認できます

Index files を見せない

公開するサーバはセキュリティ的にもファイルインデックスをオフにする場合が多いです
Webrick の場合は以下のようにします

  • vim app.rb
require 'webrick'
srv = WEBrick::HTTPServer.new({
                                :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080
})
srv.mount('/', WEBrick::HTTPServlet::FileHandler, Dir.pwd, { :FancyIndexing => false })
trap("INT"){ srv.shutdown }
srv.start

これで 403 が表示されるようになります

cgi を使う

CGI を使って ruby のスクリプトの結果を返すこともできます
WEBrick::HTTPServlet::CGIHandler を使います
また Ruby スクリプト内で cgi を使うようにしましょう

  • vim view.rb
#!/usr/local/opt/ruby/bin/ruby
require 'cgi'

cgi = CGI.new
html = <<-EOF
<html>
<head>
<title>cgi test</title>
</head>
<body>
<h1>Hello</h1>
</body>
</html>
EOF
puts cgi.header
puts html
  • chmod 755 view.rb

またシェバングと権限の変更を忘れないように行いましょう

  • vim app.rb
require 'webrick'
srv = WEBrick::HTTPServer.new({
                                :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080
})
srv.mount('/view.cgi', WEBrick::HTTPServlet::CGIHandler, 'view.rb')
trap("INT"){ srv.shutdown }
srv.start

これで localhost:20080/view.cgi を確認すると「hello」が返ってくることが確認できると思います

HTML を表示する

単純に HTML を表示することもできます

  • vim test.html
<html>
<head>
<title>test html</title>
</head>
<body>
<h1>Test html</h1>
</body>
</html>
  • vim app.rb
require 'webrick'
srv = WEBrick::HTTPServer.new({
                                :DocumentRoot => './',
                                :BindAddress => '127.0.0.1',
                                :Port => 20080
})
srv.mount('/hoge.html', WEBrick::HTTPServlet::FileHandler, 'test.html')
trap("INT"){ srv.shutdown }
srv.start

http://localhost:20080/hoge.html にアクセスすると test.html の内容が表示されるのが確認できると思います

最後に

素の Webrick を使ってみました
最低限の Web サーバを構築するための機能が実装されているのでこれらを使ってフレームワークなどが作られている感じかなと思います

参考サイト

2020年8月22日土曜日

middleman 入門

概要

middleman は jekyll, nanoc の静的サイトが作成できるツールです
今回はインストールから簡単な使い方を紹介します

環境

  • macOS 10.15.6
  • Ruby 2.6.5p114
    • middleman 4.3.8

最初に注意

Ruby 2.7 だと動作しません
undefined method new for BigDecimal:Class (NoMethodError) が発生し middleman コマンドが使えません

なので Ruby2.6 系で試しましょう
以下では rbenv でインストールした ruby を使っています

  • rbenv install 2.6.5
  • $(rbenv which ruby) -v
  • $(rbenv which gem) -v
  • rbenv init - >> ~/.zshrc
  • source ~/.zshrc

インストール

グローバルインストールしましょう
bundle install でもできると思うんですが面倒なのでグローバルインストールにしました

  • gem install middleman

Mac の rbenv 場合は /Users/username/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0 に gem はインストールされます
適当に PATH 配下において実行できるようにしましょう

  • ln -s /Users/username/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/middleman-cli-4.3.8/bin/middleman /usr/local/bin

プロジェクト作成

まずはプロジェクトを作成しましょう

  • middleman init test

init のあとにプロジェクト名を指定することもできます
指定しないとカレントがプロジェクトのルートになります

とりあえず動かしてみる

サンプルプロジェクトの内容でとりあえず動かしてみましょう

  • cd test
  • bundle exec middleman server

で起動できます
localhost:4567 でサンプルサイトにアクセスできます

トラブルシューティング

bundle exec middleman server するとなぜか「undefined method show_deprecation_message! for AutoprefixerRails:Module」というエラーが出てしまいうまく表示されませんでした
ちゃんとメソッドが定義されているのに動作しない状態だったのでもしかすると LOAD_PATH あたりがおかしいのかなと思います

  • vim /Users/username/Documents/work/try_middleman/test/vendor/gems/autoprefixer-rails-9.8.6.2/lib/autoprefixer-rails/processor.rb

で process メソッドでコメントアウトすれば OK です

# AutoprefixerRails.show_deprecation_message!

ページを追加する

新しいページを追加してみましょう
erb ファイルを追加すれば OK です

  • touch source/about.html.erb
  • vim source/about.html.erb
---
title: About page
---

<h1>
  About me
</h1>

<p>name・・・hawksnowlog</p>
<p>age・・・10</p>

これで localhost:4567/about.html にアクセスすると新しく追加したページを確認できます

スタイルを変更する

仕組みから説明するとページのレイアウトを管理している erb ファイルがまずあります

  • vim soruce/layouts/layout.erb

ここで <%= stylesheet_link_tag "site" %> というメソッドがコールされています
これは middleman の機能で source/stylesheets/site.css.scss を自動で読み込んで適用してくれます
なのでスタイルを変更する場合は source/stylesheets/site.css.scss を変更すれば OK です
例えば背景の色を変更する場合は

  • vim source/stylesheets/site.css.scss
background-color: #CCFFFF;

とかに変更すると以下のように背景の色が変わるのが確認できると思います

YAML Frontmatter

例のごとく YAML Frontmatter があります
ページのタイトルの設定や固定値の設定もできます

  • vim source/about.html.erb
---
layout: layout
title: About page
favorites:
  - ruby
  - swift
  - python
---

<h1>
  About me
</h1>

<p>name・・・hawksnowlog</p>
<p>age・・・10</p>
<% current_page.data.favorites.each do |f| %>
<p>I like <%= f %></p>
<% end %>

layout は source/layouts/ 配下で作成したレイアウトのテンプレートファイル名を指定します
デフォルトは layout ですが上記では明記しています
宣言した変数の値は current_page.data で参照できます
ちなみにコロンで区切れば JSON Frontmatter も使えるようです

;;;
"layout": "layout",
"title": "About page",
"favorites": [
  "ruby",
  "swift",
  "python"
]
;;;

<h1>
  About me
</h1>

<p>name・・・hawksnowlog</p>
<p>age・・・10</p>
<% current_page.data.favorites.each do |f| %>
<p>I like <%= f %></p>
<% end %>

ヘルパーメソッドの定義

link_tostylesheet_link_tag は middleman がデフォルトで用意してくれているヘルパーメソッドです
自分でヘルパーメソッドを定義することもできます
その場合は lib/custom_helpers.rb という感じでモジュールを追加してあげます

  • mkdir lib
  • vim lib/custom_helpers.rb
module CustomHelpers
  def hello
    "hello"
  end
end

そして作成したヘルパーメソッドは config.rb で読み込みます

  • vim config.rb
require "lib/custom_helpers"
helpers CustomHelpers

あとは about.html.erb で参照するだけです

<p><%= hello %></p>

ブログ化する

今までは erb ファイルを追加することで新しいページを作成しました
middleman-blog を追加することで markdown を追加するだけでページを追加できます

  • vim Gemfile
gem "middleman-blog", "~> 4.0"
  • bundle install

そして config.rb でブログモードを有効にします
ブロック内でいろいろと設定できますが今回はデフォルトのまま使います

activate :blog do |blog|
end

追加する記事のファイル名には命名規則があり {year}-{month}-{day}-{title}.html という形式で追加します
例えば以下のように追加できます
source ディレクトリ配下に追加します

  • touch source/2020-08-21-test.html.md

あとは markdown と Frontmatter を使って普通に記事をかけば OK です

---
title: Test article
tags:
  - test
  - diary
---
# This is test article
Hello

アクセスする際は localhsot:4567/2020/08/21/test.html という感じで year, month, day をスラッシュで区切ったパスにアクセスします

最後に

middleman を使って静的ページの作成を行ってみました
セットアップに躓きましたが使い方としては jekyll, nanoc と同じように使えると思います
erb がデフォルトで使えるので ruby を使ってゴリゴリ書きたい人には向いているかもしれません

今回は試していませんが動的ページの作成やローカライズの機能もあるのでっ興味があれば調べてみてください

参考サイト

2020年8月21日金曜日

Ruby で QR コードを生成してみる

概要

Ruby で QR コードを作成できる rqrcode を使ってみました
簡単な使い方を紹介したいと思います

環境

  • macOS 10.15.6
  • Ruby 2.7.1p83
    • rmagick 4.1.2
  • imagemagick 7.0.10

rqrcode インストール

  • bundle init
  • vim Gemfile
gem "rqrcode"
gem "rmagick"
  • bundle config path vendor
  • bundle install

また今回は ImageMagick を使って svg ファイルを画像ファイルにするので ImageMagick 自体のインストールができていない場合はインストールしましょう

  • brew install imagemagick

文字列として作成

そもそも QR コードは英数字や漢字などの文字列をあるルールに則って小さなコードに変換する技術です
コードはパターンに沿って 2 値で表現されるのでコンソールなどに表示することも可能です

require 'rqrcode'

qr = RQRCode::QRCode.new('http://github.com')
result = ''

qr.qrcode.modules.each do |row|
  row.each do |col|
    result << (col ? 'X' : 'O')
  end

  result << "\n"
end

puts result

svg として作成

次に画像として保存してみます
svg ファイルをそのまま保存する方法と svg ファイルの情報から png 形式保存する方法を紹介します

require 'rqrcode'
require 'RMagick'

qrcode = RQRCode::QRCode.new("http://github.com/")

svg = qrcode.as_svg(
  offset: 0,
  color: '000',
  shape_rendering: 'crispEdges',
  module_size: 6,
  standalone: true
)

File.open("qr.svg", mode = "w"){|f|
  f.write(svg)
}

img = Magick::Image.from_blob(svg) {
  self.format = 'SVG'
  self.background_color = 'transparent'
}
img[0].write "qr.png"

これで svg ファイルと png ファイルが作成されています

png イメージとして作成

先程は imagemagick を使って svg から png ファイルを作成しました
rqrcode 自体にも png 形式のバイナルデータを作成する方法があるのでそれを使っても OK です

require 'rqrcode'

qrcode = RQRCode::QRCode.new("http://github.com/")

png = qrcode.as_png(
  bit_depth: 1,
  border_modules: 4,
  color_mode: ChunkyPNG::COLOR_GRAYSCALE,
  color: 'black',
  file: nil,
  fill: 'white',
  module_px_size: 6,
  resize_exactly_to: false,
  resize_gte_to: false,
  size: 120
)

IO.binwrite("qr.png", png.to_s)

ansi カラーコードとして作成

コンソールに出力する用です
先程は単純なバイト文字でしたがカラーコードで出力できるのでターミナルでも見やすくなります

require 'rqrcode'

qrcode = RQRCode::QRCode.new("http://github.com/")

svg = qrcode.as_ansi(
  light: "\033[47m", dark: "\033[40m",
  fill_character: '  ',
  quiet_zone_size: 4
)

print svg

ruby の場合 print を使うと自動的にカラーコードを展開してくれます

その他オプション

qrcode = RQRCode::QRCode.new("http://github.com/", size: 4)
  • size・・・QR コードの大きさを指定できます、数が大きいほどコードも大きくなります
  • level・・・「l」「m」「q」「h」が指定できます、コードが汚れて見えない部分が多くなっても補完できるレベルを指定します、h がより補完できますがコードが複雑になります

最後に

Ruby で QR コードを生成してみました
プログラマブルに QR コードを生成するようなサービスを作りたい場合に使えるかなと思います

2020年8月20日木曜日

Mac で rsvg を扱える imagemagick をインストールする方法

概要

デフォルトでインストールできる ImageMagick では rsvg を扱えないのでビルドオプションを変更することで svg も扱えるようにします
brew install 時に --with-librsvg オプションは指定できなくなっているようです

環境

  • macOS 10.15.6
  • ImageMagick 7.0.10
  • librsvg 2.48.8

librsvg のインストール

まずは svg を扱えるライブラリをインストールします

  • brew install librsvg

imagemagick のビルドオプションを変更する

直接 formula を変更します

  • brew edit imagemagick

で編集状態になるので install メソッドに with 系のオプションがたくさんあるのでそこに --with-rsvg を付与しましょう
※–with-librsvg オプションではないので注意が必要です
diff を見ると以下のようになっています

  • cd $(brew --repository homebrew/core)
  • git diff
diff --git a/Formula/imagemagick.rb b/Formula/imagemagick.rb
index a6bf7ff3b3..23623a737c 100644
--- a/Formula/imagemagick.rb
+++ b/Formula/imagemagick.rb
@@ -60,6 +60,7 @@ class Imagemagick < Formula
       --without-pango
       --without-x
       --without-wmf
+      --with-rsvg
       --enable-openmp
       ac_cv_prog_c_openmp=-Xpreprocessor\ -fopenmp
       ac_cv_prog_cxx_openmp=-Xpreprocessor\ -fopenmp

これで imagemagick をインストールしましょう

  • brew reinstall --build-from-source $(brew --repository homebrew/core)/Formula/imagemagick.rb

参考サイト

2020年8月19日水曜日

pytest でテストのカバレッジを表示する方法とカバレッジを上げるコツ

概要

過去に pytest で monkeypatch を当てるテクニックを紹介しました
今回は pytest でテストのカバレッジを表示する方法を初回します

環境

  • macOS 10.15.5
  • Python 3.8.3
    • pytest 6.0.1
    • pytest-cov 2.10.1

使用するコード

  • vim user.py
from http.client import HTTPSConnection


class User():
    FAVORITE_FRAMEWORKS = {
        "ruby": "sinatra",
        "swift": "spritekit",
        "python": "flask"
    }

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def hello(self):
        return("%s,%i" % (self.name, self.age))

    def access(self):
        con = HTTPSConnection("kaka-request-dumper.herokuapp.com")
        con.request('GET', '/')
        res = con.getresponse()
        return res.read().decode()
  • vim test_user.py
import sys
from user import User

def test_hello(monkeypatch):
    monkeypatch.setattr(User, "hello", lambda self: "hawksnowlog,5")
    u = User("hawksnowlog", 10)
    assert(u.hello() == "hawksnowlog,5")

def test_access(monkeypatch):
    def mock_return(self):
        return '{"key":"value"}'

    monkeypatch.setattr(User, "access", mock_return)
    u = User("hawksnowlog", 10)
    assert(u.access() == '{"key":"value"}')

def test_langs(monkeypatch):
    monkeypatch.setitem(User.FAVORITE_FRAMEWORKS, "ruby", "rails")
    assert(User.FAVORITE_FRAMEWORKS["ruby"] == "rails")

def test_syspath(monkeypatch):
    monkeypatch.syspath_prepend("hoge")
    assert(sys.path[0] == "hoge")

pytest-cov のインストール

pytest-cov という pytest のプラグインがあるのでこれを使います

  • pipenv install pytest-cov

テスト実行

とりあえずコンソールにカバレッジを表示するだけであれば --cov を付与するだけです
値はソースがあるプロジェクトのパスになります

  • pipenv run pytest --cov=.

結果は .coverage という SQLite 形式のファイルが出力されます

テスト結果を HTML で出力する

HTML ファイルで出力する場合には --cov-report=html を使います

  • pipenv run pytest --cov=. --cov-report=html
  • open htmlcov/index.html

htmlcov/index.html というファイルを開くと結果が確認できます
以下のような感じでまだテストしていないところを確認するのに便利です

テスト用のコンフィグファイルを作成する

オプションで指定せず設定ファイルにテストのルールなどをあらかじめ記載しておくこともできます
例えば HTML 結果の出力先を変更したい場合には以下のようにします

  • vim .coveragerc
[html]
directory = coverage_html_report
  • pipenv run pytest --cov=. --cov-report=html

カバレッジを上げるコツ

monkeypatch を使ったテストを書いているとその部分のコードは通らないことになるのでカバレッジは上がりません
monkeypatch を使ってカバレッジを上げるポイントとしては monkeypatch する関数などは pypi からインストールしたライブラリの関数にしましょう

例えばサンプルコードで access 関数のカバレッジを上げたい場合は access 関数をパッチするのではなく HTTPSConnection をパッチします

def test_access(monkeypatch):
    class DummyHTTPResponse:
        def __init__(self):
            pass

        def read(self):
            return b'{"key":"value"}'

    class DummyHTTPSconnection:
        def __init__(self):
            pass

        def request(self, method, path):
            return None

        def getresponse(self):
            return DummyHTTPResponse()

    monkeypatch.setattr(HTTPSConnection, "request", DummyHTTPSconnection.request)
    monkeypatch.setattr(HTTPSConnection, "getresponse", DummyHTTPSconnection.getresponse)
    u = User("hawksnowlog", 10)
    assert(u.access() == '{"key":"value"}')

HTTPSConnection で使用している関数だけにパッチを当てれば OK です
return がオブジェクトの場合などはダミー用のクラスを作成してそのダミー用クラスのオブジェクトを返すようにします
そうすることで http.client 側のライブラリの実体ではなくダミー用の関数がコールされるためテストもちゃんと通ることになりカバレッジも下がらずに済みます

最後に

pytest-cov を使ってカバレッジを表示する方法を紹介しました
monkeypatch と組み合わせて使うことが多いと思いますがその場合はちゃんとパッチを当てる箇所を考えましょう
闇雲にパッチを当ててもカバレッジが上がらないので少しコツと経験感的なものが必要になりそうです

参考サイト

2020年8月18日火曜日

python の環境変数を文字列以外の型で受け取るコツ

概要

python の環境変数の読み込みはいくつかの方法があります
どの方法でも読み込み後は文字列として処理されます
一度読み込んだあとにキャストすれば良いのですが面倒です
今回は読み込み時に型変換して使う方法を紹介します

環境

  • macOS 10.15.6
  • Python 3.8.3

サンプルコード

import os

str_val = os.environ.get("STR_VAL", "hoge")
print(str_val)
print(str_val.__class__)

int_val = int(os.environ.get("INT_VAL", 5))
print(int_val)
print(int_val.__class__)

from distutils.util import strtobool

bool_val = strtobool(os.environ.get("BOOL_VAL", "True"))
print(bool_val)
print(bool_val.__class__)

list_val = os.environ.get("LIST_VAL", "1,2,3").split(",")
print(list_val)
print(list_val.__class__)

import json

dict_val = json.loads(os.environ.get("DICT_VAL", '{"key":"value"}'))
print(dict_val)
print(dict_val.__class__)

list_val2 = json.loads(os.environ.get("LIST_VAL2", '["1", "2", "3"]'))
print(list_val2)
print(list_val2.__class__)

動作確認

  • STR_VAL=fuga python3 test.py
  • INT_VAL=100 python3 test.py
  • BOOL_VAL=False python3 test.py
  • LIST_VAL="3,4,5" python3 test.py
  • DICT_VAL='{"key2":"value2"}' python3 test.py
  • LIST_VAL2='["a",4,5]' python3 test.py

解説

基本は各型にキャストすれば OK です
bool や list, dict は少し特殊です
list は split を使っていますが dict 同様に json.loads でも OK です
bool は True ならば「1」Falseならば「0」として扱われます

今回の場合の問題点はキャストできない型の値が環境変数にセットされていると Exception が発生する点です

2020年8月13日木曜日

docker-compose でデータベースなどの起動を待つには wait-for-it スクリプトを使おう

概要

公式にもあるのですが wait-for-it を使います
内容はただのシェルスクリプトでポートが LISTEN しているかをチェックしてくれます

環境

  • macOS 10.15.6
  • docker for mac 19.03.12
  • docker-compose 1.26.2

wait-for-it の取得

  • wget 'https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh'
  • chmod 755 wait-for-it.sh

docker-compose.yml

今回は version3 フォーマットを使います
version2 であれば healthcheck + condition の組み合わせが使えますが version3 では使えません

  • vim docker-compose.yml
version: '3'
services:
  check:
    image: mysql:5
    environment:
      MYSQL_ROOT_PASSWORD: hogefuga
      MYSQL_DATABASE: test
      TZ: 'Asia/Tokyo'
    volumes:
      - ./:/home
    command: /home/wait-for-it.sh db:3306 --timeout=30 --strict -- mysqladmin ping -h db -u root -phogefuga
  db:
    image: mysql:5
    environment:
      MYSQL_ROOT_PASSWORD: hogefuga
      MYSQL_DATABASE: test
      TZ: 'Asia/Tokyo'
    ports:
      - 3306:3306

ポイントは check 側の command です
wait-for-it.sh db:3306 とすることで db コンテナの 3306 ポートが LISTEN するまで待ちます
ポートが ready になったあとは -- 以降のコマンド mysqladmin ping -h db -u root -phogefuga が実行されます
check 側のコンテナも mysql イメージを使っていますが適当なアプリでも OK です
今回は簡易的な動作確認のために mysql イメージを使っています

動作確認

  • docker-compose up -d

check コンテナのログを確認しましょう

  • docker-compose logs -f check
Attaching to downloads_check_1 check_1 | wait-for-it.sh: waiting 30 seconds for db:3306 check_1 | wait-for-it.sh: db:3306 is available after 12 seconds check_1 | mysqladmin: [Warning] Using a password on the command line interface can be insecure. check_1 | mysqld is alive downloads_check_1 exited with code 0

ちゃんと waiting してポートの監視をしていることがわかります
最終的に「mysqld is alive」になれば OK です

2020年8月12日水曜日

zsh で docker の CLI コマンドを補完する方法

概要

docker コマンドを実行した際にコンテナ名やイメージ名を補完してくれないと面倒です
今回は Mac 上の zsh を使っている場合に補完する方法を紹介します

環境

  • macOS 10.15.6
  • zsh 5.7.1

zsh-completions のインストール

  • brew install zsh-completions

.zshrc への追記

  • vim ~/.zshrc
if type brew &>/dev/null; then                                                                                        
  FPATH=$(brew --prefix)/share/zsh-completions:$FPATH
  autoload -Uz compinit
  compinit                                                                                               
fi

mac の場合 zsh-completions のスクリプトを管理するパスは /usr/local/share/zsh-completions/ になっています

ディレクトリの権限変更

  • chmod go-w '/usr/local/share'

これを実行しないと zsh compinit: insecure directories, run compaudit for list. で怒られます

docker-compose 用の補完スクリプトを配置

  • curl -L https://raw.githubusercontent.com/docker/compose/1.26.2/contrib/completion/zsh/_docker-compose > $(brew --prefix)/share/zsh-completions/_docker-compose

Mac の場合は Docker for Mac に含まれているのでそれを zsh-completions ディレクトリにシンボリックリンクすれば OK です

またシンボリックを貼るディレクトリは /usr/local/share/zsh-completions ではなく usr/local/share/zsh/site-functions/ なので注意しましょう

  • ln -s /Applications/Docker.app/Contents/Resources/etc/docker-compose.zsh-completion /usr/local/share/zsh/site-functions/_docker-compose

docker コマンドおよびイメージ名やコンテナ名の補完スクリプトを配置

  • ln -s /Applications/Docker.app/Contents/Resources/etc/docker.zsh-completion /usr/local/share/zsh/site-functions/_docker

zcompdump の再生成

  • rm -f ~/.zcompdump; compinit

シェルの再ログイン

これでターミナルを一度 exit して再度ログインすれば補完してくれます

参考サイト