sironekotoroの日記

Perl で楽をしたい

Perl入学式 2018 in東京 秋開講 第1回 落穂ひろい

msys2・ターミナル(黒い画面)編

スライドの入力例の先頭にある $ マーク

この $ は一般ユーザー権限でコマンド入力待ちになっていることを示すものです。
UNIXLinuxには権限といった考え方があり、一般ユーザーとスーパーユーザー(管理者,root)とで実行できるコマンドに差があります。
書籍やスライドにある先頭の $ は入力する必要はないです(というか、手元のターミナルに既に表示されているはず

スーパーユーザーの場合には、先頭に # と表示されます。

whoamiはユーザー名を表示するコマンド、su -は一般ユーザーから管理者ユーザーになる時のコマンドです。
先頭の $# に変わっています。

f:id:sironekotoro:20181104141155g:plain

以前に入力したコマンドをもう一度使いたい

キーボードの キーを押すことで、過去に入力したコマンドが出てきます。

長いファイル名を入力するのが大変

キーボードの TAB キーを押すことで、ファイル名が補完されます。

長いファイル名のファイルを作成し、それを削除する時に先頭のファイル名先頭の a のみを入力してTABキーでそれ以降を保管しています。

f:id:sironekotoro:20181104135513g:plain

ターミナルの画面が入力した文字や、実行結果で埋まってきた・・・

clear コマンドで画面がきれいになります。

CUIは寡黙

コマンドが成功した場合には特に応答を返さないことが多いです。
エラー時にはエラーメッセージを表示します。

OSの歴史編

プログラミング言語の系統図

www.levenez.com

Plotter ってのをクリックすると一枚のPDFで表示されます。重いので注意。
Perlは1987年に誕生しています。

書籍だと、この本が簡潔にまとまってて良いです。もちろんPerlも載ってます!

www.socym.co.jp

詳説! Hello world

!/usr/bin/env perl
use strict;
use warnings;

print "Hello world!";

1行目:#!/usr/bin/env perl

いきなりわかんないですよね・・・ということで定番書籍にご登場願います。

#!/usr/bin/perl
(中略)

Unixシステムでは、テキストファイルの最初の2文字が #!shebang:シバンまたはシェバンと発音します)だったら、その後ろに続くものが、ファイルの残りの部分を実際に実行するプログラムの名前になります。このケースでは、そのプログラムは usr/bin/perl に格納されています。

はじめてのPerl 第7版 電子版 14p

この上記の例だと、「このファイルは /usr/bin/perl で実行してくださいねー」とシステム(Unix)に伝える行、と思ってください。

ですので、この行を適当に書き換えると、Unixはどのプログラムで実行して良いか分からず、エラーになったり異なる応答をします。
macOSで実行してみました。

  • 1行目を存在しないプログラム名(SuperDuperMiracleLanguage)にした場合
    #!/usr/bin/SuperDuperMiracleLanguage
  $ perl hello.pl  
  Can't exec SuperDuperMiracleLanguage at hello.pl line 1.

そんなプログラム無いから実行できないよ、とエラーが出ます。

  • 1行目を別のプログラム言語にした場合 #!/usr/bin/ruby
  $ perl hello.pl  
  hello.pl:2:in `<main>': undefined local variable or method `strict' for main:Object   (NameError)

rubyとしてファイルを実行したけど、rubyが2行目の use strict の解釈でエラー、と出ます。

では/usr/bin/env perl って?

再度定番書籍にご登場願います。

Unixシステムの中には、 #! 行に次のように書くと、perlを探してくれるものもあります。

#! /usr/bin/env perl

はじめてのPerl 第7版 電子版 14p

ということで、Unix環境の中からPerlを探してくれることを期待して書く行なのでした。

2行目:use strict;

厳密な書式を定めたり, 未定義の変数を警告するといった効果があります

変数については第2回でやるので、そこで説明します。

3行目:use warnings;

望ましくない記述を警告してくれる効果があります。

例えば、use warnings の無い、このようなプログラムで実験してみます。

#!/usr/bin/env perl
use strict;

print;

実行します。

$ perl hello.pl
$ 

エラーは出ません。
print 文ですが、なにをprint、表示するのかの指定が無いプログラムです。

しかし、これは正しいprint文でしょうか?何も表示しないなら、不要な行なのでは?

こういったあやしい記述を警告してくれるのが use warnings です。

#!/usr/bin/env perl
use strict;
use warnings;

print;

実行します。

$ perl hello.pl
Use of uninitialized value $_ in print at hello.pl line 5.

エラーが出ます。
エラーの内容は「未定義の暗黙の変数使ったな!」ってやつなのですが、「エラーが出たんでどっか直さないとダメなんだな」くらいでokです。

4行目:(空白行)

Perlでは空白文字であるスペースやタブは無視されます。
そのため、これらの空白文字を使ってプログラムを読みやすくすることができます。

5行目:print "Hello world"

・・・古来、というか1989年の「プログラミング言語C 第2版(訳書訂正版)によるとこうです。

https://pbs.twimg.com/media/DrID3ibUcAAWXbK.jpg

ちなみに、すごい驚いたんですが、Perlちょっとやってると、Cのプログラムが読めます。書けないけど。びっくり。

練習問題(1), 練習問題(2)

TMTOWTDIのモットー通り、いろいろな書き方があります。

  • 普通の書き方
print "Perl-Entrance\n";
print "2018/10/27\n";
print "Gotanda\n";
print "Tokyo\n";
  • 1行で書いてみる
print "Perl-Entrance\n2018/10/27\nGotanda\nTokyo\n";
  • 偏屈な書き方
print "Per";
print "l-Entrance\n20";
print "18/10/27\nGot";
print "anda\nTokyo\n";

上記3つとも同じ出力になります。

  • 練習問題(2)の引き算
print 2018 - 1987;
  • 練習問題(1)と練習問題(2)の引き算も含めて1行で・・・ってのは第1回の範囲では無理かなー?
    第2回でやる文字列連結演算子 . (ドット) と計算部分をカッコで囲む方法を使います。
print "Perl-Entrance\n2018/10/27\nGotanda\nTokyo\n" . (2018 - 1987) . "\n";

Perl入学式 2018 in東京 秋開講 第1回 お疲れ様でした

Perl入学式 2018 in東京 秋開講 第1回

受講された方、サポーターの方、お疲れ様でした。
講師をやったジャージの人です。

今期からの新要素は以下となります。

  • Windowsユーザ向けにmsys2でUNIX環境を構築し、その上でPerlを実行
  • スライド共有

不慣れなところが多く、お手数をおかけしました。

今回ですが、Perlの学習内容としては print "Hello! World"; のみなので特に復習問題はないです。
また来月お会いしましょう!

Perl入学式 2018 in東京 秋開講 第1回 ピザ会の課題

Perl入学式 in東京 では講義終了後、希望者で集まりピザ会と称した懇親会を行っています。
(ジュース・ピザ代は実費)
昨年からそこで余興として xtetsuji さんが課題を出し、そこにチャレンジャーが回答して公開する、というようなことをやってます。

今回の出題は、シミュレーションでした。
過去の出題傾向からてっきりWebからスクレイピングってネタが来ると思ってたので完全に不意打ち。

ルール

  • プレイヤーA、プレイヤーBに手札が3枚配られる
    • 今回は配られる手札は固定
    • プレイヤーA:1, 5, 10
    • プレイヤーB:2, 2, 2
  • 1 〜 40 の目がランダムに出るルーレットを3回まわし、手札と同じ数字が出たら手札を捨てる
  • プレイヤーBのように同じ手札が複数枚ある場合には、同じ目が出ても捨てられるカードは1枚だけ
  • 先にカードがなくなった方が勝ち

ヒント

  • 1 〜 40 のランダムな数字を出すには int( rand(40) + 1 ) を使う

うちの予想

  • 3枚のカードが来て、ルーレットも3回まわすんだから、プレイヤーAもプレイヤーBも勝率は同じなのではないか?

コード

ピザ会で書いたものを少し改変して、コメントを入れてアップしています。

Perl入学式でやる範囲の内容のみ、かつリファレンスを使わない、という縛りで書いてみました。
結果、めっちゃ煩雑になりましたわ・・・

Perl入学式 2018 in東京 秋開講 第1回 ピザ会でのお題 — Bitbucket

なお、コードを実行すると勝利回数は6〜7倍プレイヤー1の方が高いですが、これは確率的に正しいとのこと。
うちはサイコロに置き換えて考えて納得した気になってるけど、正しいかどうか不安・・・

サイコロに置き換えると

  • プレイヤーAの手札(1, 3, 5)
    • 出た目が 1, 3, 5 どれかに合致すれば札を捨てられる
      • 最初に 1 が来ても 3 が来ても 5 が来ても手札を捨てることができる
    • 3/6 * 2/6 * 1/6 = 6/216
  • プレイヤーBの手札(2, 2, 2)
    • 3回とも同じ目が出なくてはいけない
    • 1/6 * 1/6 * 1/6 = 1/216

これで約6倍の差、って言ってたのもわかる気がする・・・合ってるかな?

そういえば

確率の面白問題として「モンティホール問題」ってのがあるんですが、それをPerlで解いたことあったなぁ、ってことで。

sironekotoro.hateblo.jp

Perl入学式 第5回の作例をDockerにした & オススメ本 #マンガでわかるDocker

Perl入学式 第5回の作例をDockerにした

これの一番最後に自分で書いた宿題の回収です。

sironekotoro.hateblo.jp

Mojoliciousアプリをお手軽セキュアに展開する方法も探していかんとなぁ

一つの回答として、Dockerイメージを用意しました。

https://hub.docker.com/r/sironekotoro/bbs_custom/

本当はどこかwebにあげて、そこで好きに触ってもらおうかなぁ、とも思ったんですが、なんせ掲示板アプリなので脅迫とか殺人予告が書き込まれないとも限らない・・・ので、Dockerイメージにしてみました。

dockerコマンドが動くなら以下の2行でokのはず。

$ docker pull sironekotoro/bbs_custom
$ docker run --rm -p 3000:3000 --name bbs_custom -e PORT=3000 sironekotoro/bbs_custom

このあと、ブラウザで http://localhost:3000 を開くと掲示板が開きます。

ソースはこちらです。

sironekotoro / bbs_custom / source / — Bitbucket

Perl の Web Application Framework である Mojolicious のバージョンが上がったので、前回から少し手直しをしています。

ナマナマしい JavaScript のところを Vue で書き換えるって目標もあるんですが、全然進んでない。

オススメ本 #マンガでわかるDocker

Dockerイメージを自分で作ってあげてみよう!ってモチベーションになったのは通称「わかばちゃん本」2巻でした。

本当にオススメです。

booth.pm

booth.pm

うちは bitbucket と Docker Hub を連携させましたが、手元で git push したのがスルスルと Docker Hub に上がった時は感動しました。
( git も「わかばちゃん本」にお世話になったんだった)

とにかく、この本でハードル低くしといて「あれはどうやるんだろ、こういう風にやってみたい!」ってな欲望が出てきたら、一般書店で最近出た Docker 本を購入して勉強するのがいいんじゃないかなぁと思います。

Docker実践活用ガイド Chapter 10 DockerとJavaScriptでウェブサービスを作る(簡易オンラインジャッジシステム)

book.mynavi.jp

ずっと気になっていたDockerなのですが、本屋で見かけてこれなら(うちみたいな初級者でも)理解できそう、ということで購入。
厚みはある本なのですが256pとIT技術書にしては少なめ。
細かい仕様よりも、とりあえず動かしてみよう!ってところが気に入りました。

AmazonKindle Unlimited対象なのも良い。

Docker実戦活用ガイド

Docker実戦活用ガイド

すでに業務とかで触っている人向けではないですね。
この本を参考書にハンズオン形式とかやるといいかもしれないなぁ、と思いましたわ。

蒙が啓かれる

うちはDockerを「本番環境をそのまま手元に再現できる、開発環境のツール」としか思ってなかったんですね。
でも、この本に掲載されている「任意のコードをDocker環境上で動かし、その結果をホストに持ってくる」ってのに衝撃を受けましたわ。
それってつまり、普通のソフトと同じように使えるということ。すごい。

2つの不満点・・・

Chapter 8 「複数のDocker を使う(Docker MAchine, Docker Swarm, Docker Compose)」

最後のDocker Composeで docker-compose scale web=3 でスケールアップできなかったこと。
前節のDocker Swarm のところから続けて試してたんだけど、暗黙の何かの前提条件があったのか、うちの見落としか、スケールアップすることができず。

動かせなかったDocker Composeは次のDocker本で改めて学習するつもり。

コードがWeb上にない!

目gerpで目を酷使してつらい。
そんで、いまからこの2016年の本を読む人がいるかどうかはともかく、うちはきっと戻ってくるだろう、ってことで10章のコードを載っけときます。

Docker実践活用ガイド Chapter 10 DockerとJavaScriptでウェブサービスを作る(簡易オンラインジャッジシステム) — Bitbucket

本に書いてあるものを少し拡張して、Perlのコードも試せるようにしています。Perl好きなんだもん仕方ないね。
Perl追加したことと、エディタの自動整形の影響で、本に掲載されている行数と異なってますのでご注意を(主に将来の自分へ)

お手軽に動かしてみたい!という将来の自分へ

環境

  • macOS 10.12.6

  • Docker for Mac で実行

      $ docker version
      Client:
       Version:           18.06.1-ce
       API version:       1.38
       Go version:        go1.10.3
       Git commit:        e68fc7a
       Built:             Tue Aug 21 17:21:31 2018
       OS/Arch:           darwin/amd64
       Experimental:      false
    
      Server:
       Engine:
        Version:          18.06.1-ce
        API version:      1.38 (minimum version 1.12)
        Go version:       go1.10.3
        Git commit:       e68fc7a
        Built:            Tue Aug 21 17:29:02 2018
        OS/Arch:          linux/amd64
        Experimental:     true
    

準備

  • コード実行環境用コンテナを用意する

      # イメージ作成時に ubuntu-devの名前をつける
      # docker build -t ubuntu-dev .
      FROM ubuntu
      RUN apt-get update
      RUN apt-get install -y perl
      RUN apt-get install -y ruby
      RUN apt-get install -y python
      RUN apt-get install -y clang
      RUN apt-get install -y time
      RUN apt-get install -y binutils
    
  • コード実行環境のフロントエンド用コンテナをDocker Hubから持ってくる

    https://hub.docker.com/u/sironekotoro/

      docker pull sironekotoro/code-runner
    
  • イメージサイズはこんな感じ。大きい

      docker images
      REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
      ubuntu-dev                 latest              5cbc5fafea4f        17 minutes ago      674MB
      sironekotoro/code-runner   latest              73722453f41f        About an hour ago   678MB
    

起動

docker run -v /usr/local/bin/docker:/usr/local/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -p 3000:3000 --rm sironekotoro/code-runner
  • http://localhost:3000 にブラウザでアクセスする

遊ぶ

後片付け

    docker stop $(docker ps -a -q)
    docker rmi ubuntu-dev sironekotoro/code-runner

Mojolicious::Liteで作ったWebアプリをDockerに入れてHerokuで動かす

昔語り

書いてたら止まらなくなってしまったので全部カット

参考・影響を受けた記事&スライド

www.nqou.net

環境

  • macOS 10.12.6
  • heroku CLI(https://devcenter.heroku.com/articles/heroku-cli)

      $ heroku -v
      heroku/7.14.2 darwin-x64 node-v10.9.0
    
  • Docker for Mac(https://store.docker.com/editions/community/docker-ce-desktop-mac)

      $ docker version
      Client:
       Version:           18.06.1-ce
       API version:       1.38
       Go version:        go1.10.3
       Git commit:        e68fc7a
       Built:             Tue Aug 21 17:21:31 2018
       OS/Arch:           darwin/amd64
       Experimental:      false
    
      Server:
       Engine:
        Version:          18.06.1-ce
        API version:      1.38 (minimum version 1.12)
        Go version:       go1.10.3
        Git commit:       e68fc7a
        Built:            Tue Aug 21 17:29:02 2018
        OS/Arch:          linux/amd64
        Experimental:     true
    
  • Perl

      $ perl -v | grep This
      This is perl 5, version 28, subversion 0 (v5.28.0) built for darwin-2level
    
  • cpanm, MojoliciousについてはLocal::Libを利用せず、普通にインストールしてます

    • cpanm

            $ cpanm --version | head -1
            cpanm (App::cpanminus) version 1.7044 (/Users/sironekotoro/.plenv/versions/5.28.0/bin/cpanm)
      
    • mojolicious

            $ mojo version
            CORE
              Perl        (v5.28.0, darwin)
              Mojolicious (7.94, Doughnut)
      
            OPTIONAL
              Cpanel::JSON::XS 4.04+  (4.04)
              EV 4.0+                 (n/a)
              IO::Socket::Socks 0.64+ (n/a)
              IO::Socket::SSL 2.009+  (2.056)
              Net::DNS::Native 0.15+  (n/a)
              Role::Tiny 2.000001+    (2.000006)
      
            This version is up to date, have fun!
      
  • macOSにtreeコマンドなかったんで急遽入れたtree brew install tree

作成手順

Mojolicious::Liteを利用したWebアプリを作る(Perl入学式の範囲)

  1. 今回の作業場所になるフォルダを作成します

    $ mkdir heroku

  2. フォルダの中に移動します

    $ cd heroku/

  3. Dockerfileを作成します(作成だけ)

    $ touch Dockerfile

  4. Webアプリ用のフォルダを作成します

    $ mkdir webapp/

    ここまでのフォルダ構成です。Dockerfileはファイル、webappはフォルダです

      $ tree  
      .
      ├── Dockerfile
      └── webapp
    
      1 directory, 1 file
    
  5. Webアプリ用のフォルダに移動します

    $ cd webapp/

  6. Mojolicious::Liteの雛形を作成します。myapp.plファイルが作成され、実行権限がつきます

    $ mojo generate lite_app

  7. この時点で一度Webアプリを起動して確認しておきます

    $ morbo -l http://*:3000 myapp.pl

  8. ブラウザでhttp://localhost:3000にアクセスし、いつものMojoliciousの画面が出るかどうか確認します

  9. ターミナルをCtrl + cで抜けます

  10. 1つ上の階層に戻ります

    $ cd ../

  11. この時点でのフォルダ構成です

      $ tree
      .
      ├── Dockerfile
      └── webapp
          └── myapp.pl
    
      1 directory, 2 files
    

Dockerイメージの中に、先ほど作ったWebアプリを入れる

  1. Dockerfileを下記の内容で編集します

     # ベースイメージ
     FROM perl:5.28
    
     # Mojolicious::Liteのインストール
     RUN cpanm Mojolicious::Lite
    
     # Dockerイメージ内にウェブアプリケーション用のフォルダを用意して移動
     WORKDIR webapp/
    
     # ホストのwebapp/myapp.plをDockerイメージ内の現在位置(webapp/)にコピー
     # 行末にあるドット . に注意
     COPY webapp/myapp.pl .
    
     # Mojoliciousを起動
     CMD morbo -l http://*:3000 myapp.pl
    
  2. Dockerfileを元にDockerイメージをbuildします
    最後の.を忘れないように

    $ docker build -t mojolicious -f Dockerfile .

  3. Dockerイメージが作成されたか確認します

      $ docker images
      REPOSITORY          TAG                IMAGE ID            CREATED              SIZE
      mojolicious         latest              9241214e107f        About a minute ago   902MB
      perl                5.28                c58a7ea6dfc4        28 hours ago         885MB
    
  4. DockerイメージからDockerコンテナを起動します
    オプションにより、コンテナは実行後破棄されます。また、ホストのポート3000番とDockerコンテナのポート3000番を結びつけ、コンテナにはmojoという名前をつけています

    $ docker run --rm -i -t -p 3000:3000 --name mojo mojolicious

  5. ブラウザでhttp://localhost:3000へアクセスし、いつもの画面が出ることを確認します

    • この時点で実行中のコンテナに入る場合には、別途ターミナルを立ち上げて以下のコマンドを入力します

      $ docker exec -it mojo bash

    • コンテナから抜けるにはexitコマンドで抜けます

  6. ブラウザでの確認が終わったらCtrl + cで終了します

Herokuに先ほど作ったDockerイメージをアップする

  1. Dockerfileの最後の行であるCMD行を修正します

     # Mojoliciousを起動
     # Herokuではポート番号の指定ができない。3000 を $PORT に書き換える
     CMD morbo -l http://*:$PORT myapp.pl
    
  2. Herokuにログインします

      heroku login
      heroku: Enter your login credentials
      Email:heroku-test@sironekotoro.com
      Password: **************************
      Logged in as heroku-test@sironekotoro.com
    
  3. Heroku上でwebアプリ用のスペースを用意します
    ⬢ 以降の文字列がアプリ名、その下の行がアプリ用のURLとgitのリモートリポジトリです

      $ heroku create
      Creating app... done, ⬢ floating-hamlet-68207
      https://floating-hamlet-68207.herokuapp.com/ | https://git.heroku.com/floating-hamlet-68207.git
    
  4. 先ほど用意されたURLにブラウザでアクセスして画面を確認します

    f:id:sironekotoro:20180909145341p:plain

  5. ターミナルに戻り、HerokuのContainer Registryにログインします

      $ heroku container:login
      Login Succeeded
    
  6. HerokuのContainer RegistryにDockerイメージをpushします。-aの後のアプリ名はheroku createで発行されたものを利用します

      $ heroku container:push web -a floating-hamlet-68207
      === Building web (/Users/sironekotoro/Desktop/heroku/Dockerfile)
      (以下略)[f:id:sironekotoro:20180909150557p:plain]
      latest: digest: sha256:08cc85e39e5ab8a6faf0653737d5e9fa658935faab9c30df13f5d3653a327423 size: 2632
      Your image has been successfully pushed. You can now release it with the 'container:release' command.
    
  7. Heroku上でwebアプリをリリースします

      $ heroku container:release web -a floating-hamlet-68207
      Releasing images web to floating-hamlet-68207... done
    
  8. ブラウザでアプリのURL(この例では https://floating-hamlet-68207.herokuapp.com/ )を入力し、アクセスします。なお、初回の表示は少し時間がかかるようです

  9. 表示を確かめてニヤリとします

後片付け

  1. アプリに割り当てられているwebのプロセスを0にします

     $ heroku ps:scale web=0 -a floating-hamlet-68207
     Scaling dynos... done, now running web at 0:Free
    
  2. この時点でWeb上ではエラーが表示されます

    f:id:sironekotoro:20180909152321p:plain

  3. Herokuで作成したアプリを削除します
    本当に消して良いのか再確認を求められるので、アプリ名を入力します

      $ heroku apps:destoroy floating-hamlet-68207
       ▸    WARNING: This will delete ⬢ floating-hamlet-68207 including all add-ons.
       ▸    To proceed, type floating-hamlet-68207 or re-run this command with
       ▸    --confirm floating-hamlet-68207
    
      > floating-hamlet-68207
    
  4. Herokuからログアウトします

      $ heroku logout
      Logging out... done
    
  5. Dockerイメージを削除する場合には、docker imagesを利用してIMAGE IDを確認します
    表示が見切れていると思うので、右側にスクロールしてください

      $ docker images
      REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
      registry.heroku.com/floating-hamlet-68207/web   latest              282624a4c7c5        11 minutes ago      891MB
      mojolicious                                      latest              60486db357d4        23 minutes ago      891MB
      perl                                             5.28                c58a7ea6dfc4        4 days ago          885MB
    
  6. 確認したIMAGE IDを引数にしてdocker rmiコマンドを実行します
    なお、Perlのイメージファイルは大きいので、今後もPerlを利用したコンテナを作る場合には残しておいた方がよいでしょう

      $ docker rmi 282624a4c7c5 60486db357d4
      Untagged: registry.heroku.com/floating-hamlet-68207/web:latest
      Untagged: registry.heroku.com/floating-hamlet-68207/web@sha256:406a6002c3657824f5e79689c7d4b9083ec50cf64c16c9c5764d451a5567c6dc
      Deleted: sha256:282624a4c7c58dd90c385eb3e1607275bda542db1d278bc3c3c0687846a26ad0
      Untagged: mojolicious:latest
      (中略)
      Deleted: sha256:c42cc493a9c425ca897aaac856b3ae547840b28d34678d19aefcca98b759599e
    

Perl入学式 in 東京 第5回 ピザ会での課題

Perl入学式 in 東京では講義の後にピザ会を実施し、ピザとジュース飲んで雑談する時間を設けています。
興が乗ってくると、id:xtetsujiさんが課題を出し、会場にあるプロジェクターを使って、ライブコーディングを行ったりしています。

昨日の課題は、NHKの首相動静のCSVPerlでパースしてみる、というものでした。

www.nhk.or.jp

まず、このファイルの文字コードですが、UTF-8ではありません。cp932でもありません。

$ file 201808.csv
201808.csv: Little-endian UTF-16 Unicode text, with CRLF, LF line terminators

UTF-16、初めて遭遇。

そしてこのファイル、拡張子こそcsvですが、タブ区切りです。だったら拡張子はtsvにするべきでは。

さらに、複数行にまたがる、日付が違う、月が違う、など、酷いファイルという他ない出来です。

とりあえず、できるところまで書いてみたものがこちらです。

NHKの首相動静 自称CSVファイルをPerlでパースしようとするも、元のファイルが間違ってるので出力も間違ってるという悲しいオチ — Bitbucket

NHKのご意見フォームにどうにかしろ!って意見あげるつもりです。

Perl入学式 in 東京 第5回 〜Webアプリ編〜 の最終問題で色々やってみた

Perl入学式 in 東京 第5回 〜Webアプリ編〜 の最終問題で色々やってみた

これは、2018年現在のPerl入学式カリキュラムにある最終問題をベースに色々とやってみたものです。

今回の講義でMojoliciousがインストールされていれば、ソースをコピペして来てmorboで実行するだけでローカル環境で試すことができます。

Perl入学式 2018 第5回 最終問題で色々やってみる — Bitbucket

本来、この行数(500行超)になる前に適切に分割するもんなんですが、今回は以下のこだわりのもとでやってみました。

  • 1枚スクリプトでやってみる(コピペしやすい)
  • HTMLのタグを極力排してMojoliciousのタグヘルパーを利用する
  • 書き込み内容の永続化
  • インストールが必要な外部モジュールは利用しない(コピペしてすぐ動かせる)
  • CSSフレームワーク Bootstrap4の利用
  • JavaScriptを利用した入力補助、ボタンon/off制御
  • とりあえずの管理者ログイン実装(危険)

HTMLのタグを極力排してMojoliciousのタグヘルパーを利用する

Mojoliciousのタグヘルパーについては木本先生が書いてくれたGitHubのページを参考にしました

Mojolicious::Plugin::TagHelpers · yuki-kimoto/mojolicious-guides-japanese Wiki · GitHub

先に言っておきますが、全部をタグヘルパーで記述する必要はないです。
まぁ、修行と興味本位です。

例えば、ページの表題にあたるH1タグをHTMLではこのように書きます。

<!-- html -->
<h1>ページタイトルだよ</h1>

Mojoliciousのタグヘルパーを使うと、以下のように書くことで上記と同じHTMLが出力されます

# Perl, Mojolicious
%= t h1 => 'ページタイトルだよ'

HTMLタグの中に複数の属性が設定される場合、HTMLは以下のように記述します

<!-- html -->
<h1 id='red-h1' style='color:red'>赤いタイトルだよ</h1>

Mojoliciousのタグヘルパーを使った場合です。( ) 内部の書き方がハッシュですよね

# Perl, Mojolicious
%= t h1 => (id => 'red-h1', style => 'color:red') => '赤いタイトルだよ'

フォームタグ<form></form>など、タグとタグの間に、他のタグが挟まる場合

<!-- html -->
<form action="/logout" method="POST">
  <input class="btn btn-warning float-right mx-auto" type="submit" value="ログアウト">
</form>

Mojoliciousでは1行で収まらない場合には begin と end で挟む必要があります

# Perl, Mojolicious
%= form_for '/logout' =>( method => 'POST') => begin
  %= submit_button 'ログアウト' => (class => 'btn btn-warning float-right mx-auto')
% end

無理くり1行に納めることもできますが、可読性が厳しくなります。ファットカンマ => でつなげるのがメソッドチェーンぽくはあってカッコいい気はするんですが

# Perl, Mojolicious
%= form_for '/logout' => ( method => 'POST') => submit_button 'ログアウト' => (class => 'btn btn-warning float-right mx-auto')

書き込み内容の永続化

Perl標準モジュールのStorableを利用しました。

perldoc.jp

StorableはPerl標準モジュールなので、use Storable; と1行追加することで利用することが可能です。
いつから標準モジュールなんかな?とおもったらPerl5.7の頃からってことで、2000年頃からみたいですね。

$ corelist Storable

Data for 2018-06-26
Storable was first released with perl v5.7.3

Storableのnstore関数を利用して、変数をファイルに出力して保存するスクリプトです。

use strict;
use warnings;
use Storable qw(nstore);

# 配列を設定
my @perl_entrance = ( 'Sapporo', 'Tokyo', 'Osaka', 'Fukuoka', 'Okinawa' );

# 配列をリファレンス化してStorableモジュールのnstore関数でファイルに保存する
# スクリプトと同じ場所にarchive.fileが作成される
nstore( \@perl_entrance, 'archive.file' );

出力したファイルarchive.fileから変数に戻すスクリプトです

use strict;
use warnings;
use Storable qw(retrieve);

# nstoreで保存したファイルをretrieveで変数に戻す
my $array_ref = retrieve('archive.file');

# 変数の中身は配列リファレンスなので、デリファレンスして中身を表示する
for my $i (@{$array_ref}){
    print "$i\n";
}

ただし、このStorableで保存したファイルは中身を単純に見ることが可能です。

$ cat archive.file
pst0

Sapporo
Tokyo
Osaka
Fukuoka
Okinawa

特定の行だけ取り出す、保存する際にも一旦全て変数に展開し直す必要があります。
可用性やセキュリティを考えた場合、データベースなどを使う必要があります。
が、まぁ自分で使うぶんにはこんな感じでお手軽に保存が可能です。

今回は投稿がPOSTされた時点でファイルから既存の投稿を全てretrieveで配列リファレンスに戻してpush、そのあとに再度ファイルに書き出しています。
GETでのアクセス時には同じく、既存の投稿を全てretrieveで配列リファレンスに戻してコントローラーのetnriesに渡しています。

ファイルの読み込み処理がGETとPOSTで発生するのですが、全く同じ処理なので、サブルーチンload_fileを作って対応しています。

# ファイルから書き込み内容を取り出す
sub load_file {

    my $entries;

    # ファイルが存在するとき
    if ( -e $data_file ) {
        return retrieve($data_file);
    }
}


get '/' => sub {

    # 引数のコントローラーオブジェクトを受け取る
    my $c = shift;

    # サブルーチンload_fileから書き込み内容を
    # 配列リファレンスとして取得
    my $entries = load_file();

    # 配列リファレンスをテンプレート部で利用するために
    # stashに保存する
    $c->stash( entries => $entries, admin => $c->session->{admin} );

    # indexテンプレートを描画する
    $c->render('index');
};

CSSフレームワーク Bootstrap4の利用

Mojoliciousはwebアプリケーションフレームワークですが、Webデザインに関わるCSSフレームワークとしてTwitter Bootstrapというものがあります。
今回はこれを利用しました。

getbootstrap.com

ボタンのデザイン、タイトルや入力欄の位置、既存エントリなど、ページの全てに利用しています。
1枚スクリプトで収めるため、BootstrapのプログラムはWeb上から取得しています。レイアウト部に書いてあるリンクがそれです。
Bootstrapのページにあるリンクの書き方を、Mojoliciousのタグヘルパーを利用した書き方にしました(意地

    %# bootstrapに必要となるな外部リンク
    <%= stylesheet 'https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css' => (
      integrity => 'sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO',
      crossorigin => 'anonymous'
      ) %>

JavaScriptを利用した入力補助、ボタンon/off制御

ホームページではGET/POST通信を利用してデータの送受信を行い、その内容でHTMLを変化させます。
しかし、ページを移動せず、今見えているHTMLの内容だけを変更したい・・・そういう時にはJavaScriptの独壇場です。

JavaScriptにもフレームワークがあります。JQueryやReact、AngularにVueなどなど。
しかし、よくわからないので生のJavaScriptを書いてみました。
結果、この1枚スクリプト500行超のうち100行近くがJavaScriptという大惨事になりました・・・
大体は入力欄の状態とボタンの状態を結びつけるところで悲惨な感じになってますね(他人事
この反省を踏まえ、Vue.jsというフレームワークを勉強中です。スッキリ書きたい。

百人一首APIAjax

また、Ajaxという方法を用いて、百人一首のデータをダミーデータとして入力欄に記入するスクリプトも追加しています。
これは自分で投稿のテストをやっている時にいちいちダミーデータ入れるのが面倒だった、というのからスタートしてます。

百人一首APIは下記サイトのものを利用させてもらいました。おかげで雅な掲示板になりました。

クジラ 百人一首 API

投稿する都度、百人一首APIに聞きに行くのは負荷になると思い、このアプリの起動時に一度だけ全ての短歌を取得しています。

# 百人一首取得関数から百人一首を取得する
# APIの負荷を避けるため、一度だけ全短歌を取得しておく
my $POEMS = get_hundred_poems();

# 中略

# 百人一首をAPIから取得する
sub get_hundred_poems {

    # MojoliciousのWebユーザーエージェントを使う
    my $ua = Mojo::UserAgent->new;

    my $tx
        = $ua->build_tx(
        GET => 'http://api.aoikujira.com/hyakunin/get.php?fmt=json' );

    $tx = $ua->start($tx);

    if ( my $res = $tx->success ) {
        return $res->json();
    }
}

Perlの変数に保存して、そこからランダムに返して満足していたのですが、せっかくなのでMojoliciousのコントローラに短歌を一つだけJSONで返すものを作ってみました。
これでJSONでデータをやり取りすることができます。

Ajaxによるデータの取得、それを利用したHTMLの更新っての、確か2000年代に一度JavaScriptを勉強してやろうとして挫折したところでした。
当時はWeb2.0とか言われた時代で、いろいろなサイトのデータを自分で組み合わせてサービスを実現する、マッシュアップというものが流行った時代です。
それを指をくわえて見ていた自分が、穴だらけとはいえ(なんせエラー処理書いてない)曲がりなりにも実装できたってのは本当に嬉しいことでした。

# 百人一首の中から、ランダムに一つをJSON返す
get '/select_poem' => sub {
    my $c = shift;

    # 0〜99のランダムな数字を求める
    my $index = int( rand(100) );

    # 百人一首から一つ選ぶ
    my $select_poem = $POEMS->[$index];

    # JSON形式で返す(返した先ではajaxで解釈する)
    $c->render(
        json => {
            number => $select_poem->{no},
            name   => $select_poem->{sakusya},
            mail   => '@example.com',
            body   => $select_poem->{kami} . ' ' . $select_poem->{simo},
        }
    );
};

とりあえずの管理者ログイン実装(危険)

実はここが最もよくわからないところでした。

IDとパスワードをアプリに送り、認証がokであればその旨をCookieに残す。
その後の通信はCookieの中に認証(した証)があるかどうかで管理者として扱って良いか、都度判断する。 原理は知っていたつもりなのですが、いざそれを実装しようとすると全く訳が分からなくなってしまいました。

ただ、MojoliciousでCookieを受け取り、フィールドを設定し値を更新する、ということを経験できたことは良かったです。

という訳で、穴だらけの管理者ログイン実装です。どんだけ穴かっていうと、Cookieをデコードすると普通に「お、管理者ログインしてんな」ってのが見えます。

CookieValueをコピペして・・・ f:id:sironekotoro:20180826124923p:plain

JavaScriptコンソールでBASE64でデコードすると、なんかそれっぽいフィールドが f:id:sironekotoro:20180826124925p:plain

じゃぁどう実装するのが正解なんだよ!って話ですが、今後にご期待ください。
あ、管理者IDとパスワードはソースにベタガキしてあります。

色々やって見た感想

意外だったのはBootstrapで画面をちょっと小綺麗にしたら、やる気と改善アイデアがどっと出て来たこと。
いままではデザインにはそれほど興味がなかったんですが、自分自身でも意外な変化でした。
デザインが及ぼす影響って大きいなぁって思い知りました。

それと世の中に数多あるWebサイトなんですが、いろいろな仕組みの上で作られているんだなぁ、と実感することができました。
情報処理試験で知ってはいたCookieなんかを自分が実装できなくて、知っていることと作ることは別よな、と痛感しております。

本当はピザ会中にこのスクリプトを自分のVPSで実行して参加者さんに触って欲しいと思ってたんですが、準備不足に知識不足で叶わなかったことが残念です。
Mojoliciousアプリをお手軽セキュアに展開する方法も探していかんとなぁ、というところです。