sironekotoroの日記

Perl で楽をしたい

Perl入学式 オンライン 2020 第4回お疲れ様でした

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

講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。

appslideshare.tugougaii.site

問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。

Perl入学式 オンライン 2020 第4回

今回は以下の項目について学習しました。

  • 配列を操作する関数
  • ハッシュ

Perl のイベント紹介

来月、Perl のオンラインイベントが開催されます。

コロナ禍によりオフラインイベントである YAPC::Japan は開催できない状況が続いていますが、オンラインで Perl についての話が聞ける貴重な機会です。楽しみですね〜

yapcjapan.connpass.com

次回オンライン第 5 回!

2021 年の 2 月 27 日開催予定です。

次回はハッシュの操作関数とサブルーチン・正規表現をやります。

過去の動画や講義資料は Perl 入学式の公式ページに掲載しております。

ぜひ閲覧いただいた上で参加ください!お待ちしております!!

Mac で Ansible によるインストール時にエラーが出るようになった

エラーが出る前にやったこと

多分これが原因かなぁ。

$ brew update

ここで、

git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow

するようにメッセージが出たで素直に従う。ただ、これはエラーには関係ないかな、多分。

ついでに $ brew upgrade したり、 Docker のアップデートなどを行った。

エラー内容

Mac のアプリケーションを ansible で管理しているのだけど、実行したところ、エラー

TASK [homebrew cask packages install] ******************************************
failed: [localhost] (item={'name': 'aerial'}) => {"ansible_loop_var": "item", "changed": false, "item": {"name": "aerial"}, "msg": "Warning: Cask 'aerial' is already installed.\n\nTo re-install aerial, run:\n  brew reinstall aerial"}

こんなのが、Ansible で設定している item の数だけ出てくる・・・

Warning: Cask 'aerial' is already installed

ううん?

こっからが長い旅路だった、2時間くらい?

ちなみに aerial ってのは綺麗なスクリーンセーバーアプリ。

github.com

設定を抜粋するとこんな感じ

    homebrew_cask_packages:
      - name: aerial
      - name: alfred
      - name: appcleaner

    - name: homebrew cask packages install
      homebrew_cask: name={{ item.name }} state=installed
      # ※1 GUIツールをインストールする場所を `/Application` に固定
      environment:
        HOMEBREW_CASK_OPTS: "--appdir=/Applications"
      with_items: '{{ homebrew_cask_packages }}'

調べて分かったこと

  • brewGUI アプリをインストールするときのコマンド brew cask install Foo ってのは deprecated 、非推奨だった

  • 現在は brew install --cask Foo 、cask はオプション引数みたいな感じになった

  • Ansible で brew cask する plugin である homebrew_caskbrew cask install Foo の形式に対応していない

  • brew cask install Foo の形式に対応しているプラグインcommunity.general.homebrew_cask

  • このプラグインをインストールするコマンド

    $ ansible-galaxy collection install community.general

プラグインインストール後の設定

    - name: homebrew cask packages install
      community.general.homebrew_cask:
        name: "{{ item.name }}"
        state: 'installed'
        install_options: 'appdir=/Applications'
      with_items: '{{ homebrew_cask_packages }}'

ということでした。

参考

github.com

docs.ansible.com

github.com

github.com

Perl5 で Exercise に挑戦してみる(追記あり)

ってことで、巷で話題の Exercise に挑戦してみました。もちろん Perl5 で。

anatofuz.hatenablog.com

うちは手元にある Macでやってみます。まだ Big Sur にあげておらず、Catalina です。

Exercism への登録は GitHub の ID と連携しました。

ecercism コマンドはいつも通り、homebrew でインストールします。

$ brew install exercism

手元の環境では久々に brew するので色々と警告が出たりしましたが、本題と関係薄いので飛ばします。

exercism コマンドが無事入りました。

この辺りは登録直後のページ右側にあります。モダンな感じのページですね。

f:id:sironekotoro:20210108182310p:plain

早速、最初の課題に取り組みます・・・が、API を設定しなよ!って出てくるので書いてある通りに設定します。親切。

$ exercism download --exercise=hello-world --track=perl5
Error:

    Welcome to Exercism!

    To get started, you need to configure the tool with your API token.
    Find your token at

        https://exercism.io/my/settings

    Then run the configure command:

        exercism configure --token=YOUR_TOKEN

API を登録します。

$ exercism configure --token=YOUR_EXERCISM_TOKEN

You have configured the Exercism command-line client:

Config dir:                       /Users/sironekotoro/.config/exercism
Token:         (-t, --token)      YOUR_EXERCISM_TOKEN
Workspace:     (-w, --workspace)  /Users/sironekotoro/Exercism
API Base URL:  (-a, --api)        https://api.exercism.io/v1

今度こそ最初の問題です。

$ exercism download --exercise=hello-world --track=perl5

Downloaded to
/Users/sironekotoro/Exercism/perl5/hello-world

おなじみ Hello World から。ダウンロードされたものを確認します。

  • HelloWorld.pm
  • README.md
  • hello-world.t

ふむふむ。まずは README.md から

$ cat README.md
# Hello World

The classical introductory exercise. Just say "Hello, World!".

["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is
the traditional first program for beginning programming in a new language
or environment.

The objectives are simple:

- Write a function that returns the string "Hello, World!".
- Run the test suite and make sure that it succeeds.
- Submit your solution and check it at the website.

If everything goes well, you will be ready to fetch your first real exercise.
## Source

This is an exercise to introduce users to using Exercism [http://en.wikipedia.org/wiki/%22Hello,_world!%22_program](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program)

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise

次はテスト。おぉ、 Test2 使ってる。このテストに通るのを書けばいいんだな〜

$ cat hello-world.t
#!/usr/bin/env perl
use Test2::V0;

use FindBin qw<$Bin>;
use lib $Bin, "$Bin/local/lib/perl5";    # Find modules in the same dir as this file.

use HelloWorld qw(hello);

plan 2;                                  # This is how many tests we expect to run.

imported_ok qw<hello> or bail_out;

# Run the 'is' sub from 'Test2::V0' with three arguments.
is(
  hello(),                               # Run the 'hello' sub imported from the module.
  'Hello, World!',                       # The expected result to compare with 'hello'.
  'Say Hi!'                              # The test description.
);

で、手を入れる必要があるのはこのファイル HelloWorld.pm と。

おー、モジュールだ。

$ cat HelloWorld.pm
# Declare package 'HelloWorld'
package HelloWorld;
use strict;
use warnings;
use Exporter qw<import>;
our @EXPORT_OK = qw<hello>;

sub hello {

# Remove the comments and write some code here to pass the test suite.
}

1;

って感じです。

この HelloWorld.pm を書いたら、Perl ではおなじみの prove コマンドでテストを実行します。

おおっとテスト失敗!

どんなコード書いてテスト失敗したかはご想像にお任せします・・・

$ prove hello-world.t
hello-world.t .. 1/2
# Failed test 'Say Hi!'
# at hello-world.t line 14.
# +-----+----+---------------+
# | GOT | OP | CHECK         |
# +-----+----+---------------+
# | 1   | eq | Hello, World! |
# +-----+----+---------------+
# Seeded srand with seed '20210108' from local date.
hello-world.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/2 subtests

Test Summary Report
-------------------
hello-world.t (Wstat: 256 Tests: 1 Failed: 0)
  Non-zero exit status: 1
  Parse errors: Bad plan.  You planned 2 tests but ran 1.
Files=1, Tests=1,  1 wallclock secs ( 0.02 usr  0.01 sys +  0.11 cusr  0.04 csys =  0.18 CPU)
Result: FAIL

改めて書き直し。今度は合格点をもらえました

$ prove hello-world.t
hello-world.t .. ok
All tests successful.
Files=1, Tests=2,  0 wallclock secs ( 0.02 usr  0.01 sys +  0.11 cusr  0.04 csys =  0.18 CPU)
Result: PASS

さて、課題提出です。

$ exercism submit HelloWorld.pm


    Your solution has been submitted successfully.
    You can complete the exercise and unlock the next core exercise at:

    https://exercism.io/my/solutions/fc5c6ec1b3d7471fbc037e65d970a417

ということで、最後に発行された URL に行くと・・・合格っぽい感じです。

解答を公開することもできるようなので置いておきます。いや、Hello, World! しただけですが・・・あ、多分 Exercism へのログインが必要です。

exercism.io

課題のページには次の問題へ!とあるので、暇な時に進めてみようと思います。

追記

この最初の課題をやるのに必要な知識は

  • Perlでのテスト

  • サブルーチンとその返り値

です。

テスト、特に Perl の Test2 については Gihyo さんに記事があります。

実は、Test2 に触れたの今回が初めてでした。

gihyo.jp

サブルーチンについては 1 月 23 日のPerl入学式でやる予定です。

やれるといいな。

時間が押さなければできるんじゃないだろうか、多分。

perl-entrance.connpass.com

ということで、興味のある方の参加をお待ちしております!

Perl入学式 オンライン 2020 第3回お疲れ様でした

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

講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。

www.perl-entrance.org

問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitterハッシュタグ #Perl入学式 をつけて聞いてみてください。

Perl入学式 オンライン 2020 第3回

今回は以下の項目について学習しました。

  • if 文
  • 配列
  • for 文

そして最後にまとめとして「fizzbuzz」をやりました。第 1 回 〜 第 3 回で学習した内容を全て含む良問です。

fizzbuzz についてはスライドでも紹介していますが、こちらの記事が有名です。

www.aoky.net

Perl のイベント紹介

この時期になると、各エンジニアコミュニティがアドベントカレンダーというものを始めます。

12 月 1 日から 12 月 25 日にかけて 1 日ずつ持ち寄りで記事を投稿していくという企画です。

2020 年の Perlアドベントカレンダーはこちらです。

見て分からなくても 3 年後、4 年後困った時に「昔、何かで見たな・・・?」と繋がることがありますので目を通しておくこと良いと思います。

qiita.com

また、来年 2 月に Perl のオンラインイベントが開催されることになりました。

楽しみですね〜

次回オンライン第 4 回!

2021 年の 1 月開催予定です。

次回は配列に要素を加えたり取り出したりといった配列の操作関数をやります。

そして最後の変数であるハッシュをやります。

第 4 回ともなると、まったくの初心者の方が途中から入るのは厳しいです。

とはいえ、過去の動画や講義資料は Perl 入学式の公式ページに掲載しております。

ぜひ閲覧いただいた上で参加ください!お待ちしております!!

Perl で Chatwork API を使って添付ファイルをダウンロードする

チャットツール変更

お仕事で使うチャットツールを Chatwork から Slack に変更することになりました。

ユーザーも作り、運用ルールはメルカリさんの物をちょっと変えて・・・メルカリさんありがとうございます。

mercan.mercari.com

さて、これでめでたしめでたし。

で・・・終わることもなく。Chatwork のログの抽出を行うことになりました。

いろいろな手段があり、いろいろと情報もあります。スクレイピングツールとか、Ruby の Gem とか。

が、今回は正攻法で Chatwork のプランをエンタープライズプランに変更した上でログのエクスポートを行いました。

やはり、正攻法ならではの安心感がありますね。

エンタープライズプランにするにあたって最低限の課金にするため、必要最低限のメンバー以外は一気に退会させました。

あれ?添付ファイルは・・・?

エンタープライズプランの管理者(1名)は、メニューからログのエクスポートが可能です。

翌朝、ダウンロードリンクが記されたメールが届きます。早速ダウンロード・・・あれ?圧縮済みとはいえ全ログのサイズが 10MB 以下?

そう、Chatwork のログエクスポートは csv ファイルのみ。添付ファイルはダウンロードリンクが files1.csv ってな csv ファイルにまとめられています。

面倒くさいことは Perl

この files1.csv に記載されている 内容 はこんな感じです。

アップロード日時 ルームID ルーム名 アカウントID アカウント名 ファイル名 ダウンロードURL ファイルサイズ

肝心のダウンロード URL はこんな感じ

https://www.chatwork.com/service/packages/chatwork/subpackages/archive/download_file.php?fid=?????????

なお、ファイルサイズはテキストで 18.70 KB とか 3.27 MB って文字列で書いてあるので、サイズの比較できないですね・・・

このダウンロードリンクからダウンロードできるのはエンタープライズプランの管理者(1名)のみです。

これをフォルダに分けつつ、心を込めて 1 つ 1 つ保存していくの? 無理無理ー

添付ファイル保存依頼のものだけで 10000 ファイルを超えます。 人力では絶対無理ー

ここはダウンローダーで一気に処理でしょ!と思うも、このダウンロードのパスにどうエンタープライズプランの管理者(1名)の認証情報を加えればいいかわかりません。

basic 認証、ってこともないはずですが、ダメ元でやってダメでした。

Perl のモジュールで Chatwork 扱えないかなぁ、と思ったものの、API のバージョンが古いものしかないみたいです。残念。

ってことで、モジュール作ることにしました。

API の概要はここです。

developer.chatwork.com

これを見ると、ダウンロードしたいファイルの room_idfile_id がわかればいけそう。

ただし、この API でできるのはダウンロードするリンクを生成するまで。そのダウンロードリンクの制限時間 30 秒というちょっと捻った感じです。

ということで、files1.csv から ダウンロードリンク を抜き出し、そのリンクから room_idfile_id を抽出。

それをもとに URL を組み立てて、API にアクセス、返ってきたダウンロードリンクからダウンロードします。

API の利用制限は 5 分で 300 回、1 秒に 1 回ですね・・・ってことで、ダウンロードごとに 1 秒の sleep 入れると良さそうです。

モジュールにしたものの

この勢いで Chatwork の API 網羅するモジュール作ったろ!って思ったのですが、自分がエンタープライズプランの管理者でいられる時間は短く、あと 1 ヶ月未満です。

だって Chatwork を解約前提なのだもの・・・

ということで、モジュールは作ってみたものの、自分が使う最低限だけ実装したのでした。

github.com

もっと早く管理者権限を手にしていればなー、と思ったのでした。

Perl で Google Sheet API をつかってセルに値を書き込む

さくっとやりました!と言いたいところなのですが

3 ヶ月じゃないな 2 ヶ月くらいだな。

Google Sheet API でセルの内容を書き換えるメソッドは 3 つあるんですが、自分の用途に合致した values/batchUpdate を使いました。

developers.google.com

後、Qiita のこの記事がとても参考になりました。

qiita.com

こんな感じでセルを埋めることができます。

f:id:sironekotoro:20201123121228p:plain

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

use Data::Dumper;
use HTTP::Tiny;
use JSON;
use URI;
use utf8;

my $ACCESS_TOKEN = '';

my $SPREADSHEET_ID = '1sVNSpvtWPPkv5Qnb0tifYHpKMxaehxj5ChAeQ1G1kgA';

my $GOOGLE_SHEET_API = "https://sheets.googleapis.com/v4/spreadsheets/";

my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN );

# Method: spreadsheets.values.batchUpdate  |  Sheets API
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate
values_batchUpdate();

sub values_batchUpdate {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $SHEET_URI
        = $GOOGLE_SHEET_API . $SPREADSHEET_ID . '/values:batchUpdate';
    my $URI = URI->new($SHEET_URI);

    my $response = $ht->request(
        'POST', $URI,
        {   content => encode_json(
                {   valueInputOption => 'USER_ENTERED',
                    data             => [
                        {   range  => 'シート1!A1',
                            values => [
                                [ 200,    100, '=A1+B1' ],
                                [ 'hoge', '',  'fuga' ]
                                ]
                        }
                    ],
                    includeValuesInResponse      => 'true',
                    responseValueRenderOption    => 'UNFORMATTED_VALUE',
                    responseDateTimeRenderOption => 'FORMATTED_STRING',
                }
            )
        },
    );
    # print Dumper $response;
}

Perl で Google Sheet API をつかってセルの値を取得する

これも GET だけでできるのでお手軽ー

説明よりもコードを読んでもらったほうが早い気がする。

配列リファレンスの中の配列リファレンス、てな形でデータが取得できるので、csv にするなりなんなり後は自由自在ですね(力量による)

ちょっと詰まったところ

この API は取得するセルの範囲を指定する必要があります。

シート1!A1:C2 こんな感じで。

もう一段楽をするためには、値が入っているセルの範囲を取得する必要があります。

値が入っているセルの行と列の最大値が欲しい・・・!

Google Apps Script だと getLastRow() とか getLastColumn() みたいなやつ。

developers.google.com

となると、API でアクセスして、情報が入っている最後の Row(行)と Column(列)を把握して・・・あれー、そんな関数見当たらないぞー

困ったときは Google 先生、そして stack overflow。

stackoverflow.com

You can set the range to "A2:D" and this would fetch as far as the last data row in your sheet.

あ、そういう・・・列さえ把握しておけばいい的な・・・

で解決したのでした。

Perl で Gogle Sheet API をつかってセルの値を取得する

以下がコードです。

情報をとるときには 2 つの方法 values.get, values.batchGet があるので、太っ腹に両方やってみました。

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

use Data::Dumper;
use HTTP::Tiny;
use JSON;
use URI::QueryParam;
use URI;
use utf8;

my $SPREADSHEET_ID = '1sVNSpvtWPPkv5Qnb0tifYHpKMxaehxj5ChAeQ1G1kgA';

my $ACCESS_TOKEN  = '';

my $GOOGLE_SHEET_API = "https://sheets.googleapis.com/v4/spreadsheets/";

my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN );

values_get();
values_batchGet();


# Method: spreadsheets.values.get
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get
sub values_get {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $SHEET_URI
        = $GOOGLE_SHEET_API . $SPREADSHEET_ID . '/values/シート1!A1:C';
    my $URI = URI->new($SHEET_URI);

    my $res  = $ht->get($URI);
    my $data = decode_json( $res->{content} );

    print Dumper $data->{values};

}

# Method: spreadsheets.values.batchGet
# https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet
sub values_batchGet {
    my $ht
        = HTTP::Tiny->new( default_headers => { Authorization => $bearer, } );

    my $URI
        = URI->new( 'https://sheets.googleapis.com/v4/spreadsheets/'
            . $SPREADSHEET_ID
            . '/values:batchGet' );
    $URI->query_param( ranges => 'シート1!A1:C');

    my $res = $ht->get($URI);
    my $data = decode_json( $res->{content} );

    print Dumper $data->{valueRanges}->[0]->{values};

}