Perl入学式 オンライン 2020 第4回お疲れ様でした
受講された方、サポーターの方、お疲れ様でした。 講師をやったパーカーの人です。
講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。
問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitter でハッシュタグ #Perl入学式 をつけて聞いてみてください。
Perl入学式 オンライン 2020 第4回
今回は以下の項目について学習しました。
- 配列を操作する関数
- ハッシュ
Perl のイベント紹介
来月、Perl のオンラインイベントが開催されます。
コロナ禍によりオフラインイベントである YAPC::Japan は開催できない状況が続いていますが、オンラインで Perl についての話が聞ける貴重な機会です。楽しみですね〜
次回オンライン第 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 ってのは綺麗なスクリーンセーバーアプリ。
設定を抜粋するとこんな感じ
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 }}'
調べて分かったこと
brew で GUI アプリをインストールするときのコマンド
brew cask install Foo
ってのは deprecated 、非推奨だった現在は
brew install --cask Foo
、cask はオプション引数みたいな感じになったAnsible で
brew cask
する plugin であるhomebrew_cask
はbrew 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 }}'
ということでした。
参考
Perl5 で Exercise に挑戦してみる(追記あり)
ってことで、巷で話題の Exercise に挑戦してみました。もちろん Perl5 で。
うちは手元にある Macでやってみます。まだ Big Sur にあげておらず、Catalina です。
Exercism への登録は GitHub の ID と連携しました。
ecercism
コマンドはいつも通り、homebrew でインストールします。
$ brew install exercism
手元の環境では久々に brew するので色々と警告が出たりしましたが、本題と関係薄いので飛ばします。
exercism
コマンドが無事入りました。
この辺りは登録直後のページ右側にあります。モダンな感じのページですね。
早速、最初の課題に取り組みます・・・が、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 へのログインが必要です。
課題のページには次の問題へ!とあるので、暇な時に進めてみようと思います。
追記
この最初の課題をやるのに必要な知識は
Perlでのテスト
サブルーチンとその返り値
です。
テスト、特に Perl の Test2 については Gihyo さんに記事があります。
実は、Test2 に触れたの今回が初めてでした。
サブルーチンについては 1 月 23 日のPerl入学式でやる予定です。
やれるといいな。
時間が押さなければできるんじゃないだろうか、多分。
ということで、興味のある方の参加をお待ちしております!
Perl入学式 オンライン 2020 第3回お疲れ様でした
受講された方、サポーターの方、お疲れ様でした。 講師をやったジャージの人です。
講義に利用したスライド・動画類は以下 Perl 入学式の公式サイトで公開しています。参加された方も、参加できなかった方も、ぜひ復習に使ってください。
問題の意味がわからない、とか、このような解答例はどうだろう?という方は Discord の Perl 入学式チャンネル(招待コード)や、twitter でハッシュタグ #Perl入学式 をつけて聞いてみてください。
Perl入学式 オンライン 2020 第3回
今回は以下の項目について学習しました。
- if 文
- 配列
- for 文
そして最後にまとめとして「fizzbuzz」をやりました。第 1 回 〜 第 3 回で学習した内容を全て含む良問です。
fizzbuzz についてはスライドでも紹介していますが、こちらの記事が有名です。
Perl のイベント紹介
この時期になると、各エンジニアコミュニティがアドベントカレンダーというものを始めます。
12 月 1 日から 12 月 25 日にかけて 1 日ずつ持ち寄りで記事を投稿していくという企画です。
2020 年の Perl のアドベントカレンダーはこちらです。
見て分からなくても 3 年後、4 年後困った時に「昔、何かで見たな・・・?」と繋がることがありますので目を通しておくこと良いと思います。
また、来年 2 月に Perl のオンラインイベントが開催されることになりました。
楽しみですね〜
おまたせしました!オンラインPerlカンファレンス、https://t.co/zuK0JlFovB の開催が決定しました!2/18(木) 2/19(金)で開催する予定です。詳細につきましては続報をお待ち下さい! #yapcjapan #perl
— yapcjapan (@yapcjapan) 2020年12月2日
次回オンライン第 4 回!
2021 年の 1 月開催予定です。
次回は配列に要素を加えたり取り出したりといった配列の操作関数をやります。
そして最後の変数であるハッシュをやります。
第 4 回ともなると、まったくの初心者の方が途中から入るのは厳しいです。
とはいえ、過去の動画や講義資料は Perl 入学式の公式ページに掲載しております。
ぜひ閲覧いただいた上で参加ください!お待ちしております!!
Perl で Chatwork API を使って添付ファイルをダウンロードする
チャットツール変更
お仕事で使うチャットツールを Chatwork から Slack に変更することになりました。
ユーザーも作り、運用ルールはメルカリさんの物をちょっと変えて・・・メルカリさんありがとうございます。
さて、これでめでたしめでたし。
で・・・終わることもなく。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 の概要はここです。
これを見ると、ダウンロードしたいファイルの room_id
と file_id
がわかればいけそう。
ただし、この API でできるのはダウンロードするリンクを生成するまで。そのダウンロードリンクの制限時間 30 秒というちょっと捻った感じです。
ということで、files1.csv から ダウンロードリンク を抜き出し、そのリンクから room_id
と file_id
を抽出。
それをもとに URL を組み立てて、API にアクセス、返ってきたダウンロードリンクからダウンロードします。
API の利用制限は 5 分で 300 回、1 秒に 1 回ですね・・・ってことで、ダウンロードごとに 1 秒の sleep
入れると良さそうです。
モジュールにしたものの
この勢いで Chatwork の API 網羅するモジュール作ったろ!って思ったのですが、自分がエンタープライズプランの管理者でいられる時間は短く、あと 1 ヶ月未満です。
だって Chatwork を解約前提なのだもの・・・
ということで、モジュールは作ってみたものの、自分が使う最低限だけ実装したのでした。
もっと早く管理者権限を手にしていればなー、と思ったのでした。
Perl で Google Sheet API をつかってセルに値を書き込む
さくっとやりました!と言いたいところなのですが
Google Sheet API 叩き職人の朝は早い
— sironekotoro (@sironekotoro) 2020年11月23日
「'status' => '400',
'reason' => 'Bad Request',
これが出てからが本番ってことさ」
この道3ヶ月の sironekotoro はそう言って笑う
3 ヶ月じゃないな 2 ヶ月くらいだな。
Google Sheet API でセルの内容を書き換えるメソッドは 3 つあるんですが、自分の用途に合致した values/batchUpdate
を使いました。
後、Qiita のこの記事がとても参考になりました。
こんな感じでセルを埋めることができます。
#!/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()
みたいなやつ。
となると、API でアクセスして、情報が入っている最後の Row(行)と Column(列)を把握して・・・あれー、そんな関数見当たらないぞー
困ったときは Google 先生、そして stack overflow。
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}; }