Perl install メモ的なもの(mac向け)

（年末に向けて）久しぶりにPerlを書くぜ！ってなった時の Perl install メモ的な物です。

# 対象読者 or 前提条件 or 環境

* macな人
* ちょー久しぶりにPerlを書こうと思って
* Perlの最新版をいい感じにインストールする方法って何？

# 必要なもの

* Homebrew
* plenv : Perl版のrbenv的なもの
* perl : 最新の安定版は perl 5.30.1 (2019-11-23)

# インストール

## Homebrew で plenv, perl-build をインストール

```bash
$ brew install plenv
$ brew search perl
==> Formulae
perl               perl-build         perl6              perl@5.18          perltidy           superlu
$ brew install perl-build
echo 'eval "$(plenv init -)"' >> ~/.bash_profile
$ plenv -v
plenv 2.2.0
```

## plenv で Perl5.30.1をインストール

おそらく何もしていなければ、macに最初から入っているPerlを使っているはず。

```bash
$ perl -e 'print $^V'
v5.18.2
$ plenv help
$ plenv commands
$ plenv root
$ plenv install --help
$ plenv install -l | more
$ plenv install 5.30.1

$ plenv versions
* system (set by /Users/kazoo/.plenv/version)
  5.30.1
$ plenv global 5.30.1
$ plenv versions
  system
* 5.30.1 (set by /Users/kazoo/.plenv/version)
$ which perl
/Users/kazoo/.plenv/shims/perl
$ perl -e 'print $^V'
v5.30.1
```

# CPANモジュールインストール

## cpanmでインストールした後に、インストールしたモジュールが not found な時
注意点です。

plenvやcpanmを使ってPerlやモジュールを新たにインストールした時は、
`plenv rehash` しましょう。
コマンドとか not found と言われますので。

### LWP::UserAgent

```bash
$ plenv list-modules
Crypt::RC4
Digest::Perl::MD5
ExtUtils::MakeMaker
OLE::Storage_Lite
Perl
Spreadsheet::ParseExcel
Spreadsheet::WriteExcel
local::lib
$ plenv install-cpanm

$ cpanm LWP::UserAgent

$ plenv list-modules
App::cpanminus
Crypt::RC4
Date::Parse
Digest::Perl::MD5
Encode::Locale
ExtUtils::Config
ExtUtils::Helpers
ExtUtils::InstallPaths
ExtUtils::MakeMaker
File::Listing
HTML::Parser
HTML::Tagset
HTTP::Cookies
HTTP::Daemon
HTTP::Date
HTTP::Message
HTTP::Negotiate
IO::HTML
LWP::MediaTypes
Module::Build::Tiny
Net::HTTP
OLE::Storage_Lite
Perl
Spreadsheet::ParseExcel
Spreadsheet::WriteExcel
Test::Fatal
Test::Needs
Test::RequiresInternet
Try::Tiny
URI
WWW::RobotRules
libwww::perl
local::lib
```
---
### LWP::Protocol::https

```bash
cpanm LWP::Protocol::https
--> Working on LWP::Protocol::https
Fetching http://www.cpan.org/authors/id/O/OA/OALDERS/LWP-Protocol-https-6.07.tar.gz ... OK
Configuring LWP-Protocol-https-6.07 ... OK
==> Found dependencies: Mozilla::CA, IO::Socket::SSL
--> Working on Mozilla::CA
Fetching http://www.cpan.org/authors/id/A/AB/ABH/Mozilla-CA-20180117.tar.gz ... OK
Configuring Mozilla-CA-20180117 ... OK
Building and testing Mozilla-CA-20180117 ... OK
Successfully installed Mozilla-CA-20180117
--> Working on IO::Socket::SSL
Fetching http://www.cpan.org/authors/id/S/SU/SULLR/IO-Socket-SSL-2.066.tar.gz ... OK
==> Found dependencies: Net::SSLeay
--> Working on Net::SSLeay
Fetching http://www.cpan.org/authors/id/C/CH/CHRISN/Net-SSLeay-1.88.tar.gz ... OK
Configuring Net-SSLeay-1.88 ... OK
Building and testing Net-SSLeay-1.88 ... OK
Successfully installed Net-SSLeay-1.88
Configuring IO-Socket-SSL-2.066 ... OK
Building and testing IO-Socket-SSL-2.066 ... OK
Successfully installed IO-Socket-SSL-2.066
Building and testing LWP-Protocol-https-6.07 ... OK
Successfully installed LWP-Protocol-https-6.07
4 distributions installed
```
---
### JSON

```bash
$ cpanm JSON
--> Working on JSON
Fetching http://www.cpan.org/authors/id/I/IS/ISHIGAKI/JSON-4.02.tar.gz ... OK
Configuring JSON-4.02 ... OK
Building and testing JSON-4.02 ... OK
Successfully installed JSON-4.02
1 distribution installed
```

### Dotenv

```bash
$ cpanm Dotenv
--> Working on Dotenv
Fetching http://www.cpan.org/authors/id/B/BO/BOOK/Dotenv-0.002.tar.gz ... OK
Configuring Dotenv-0.002 ... OK
==> Found dependencies: Path::Tiny, Test::Pod::Coverage, Test::CPAN::Meta, Test::Pod
--> Working on Path::Tiny
Fetching http://www.cpan.org/authors/id/D/DA/DAGOLDEN/Path-Tiny-0.108.tar.gz ... OK
Configuring Path-Tiny-0.108 ... OK
Building and testing Path-Tiny-0.108 ... OK
Successfully installed Path-Tiny-0.108
--> Working on Test::Pod::Coverage
Fetching http://www.cpan.org/authors/id/N/NE/NEILB/Test-Pod-Coverage-1.10.tar.gz ... OK
Configuring Test-Pod-Coverage-1.10 ... OK
==> Found dependencies: Pod::Coverage
--> Working on Pod::Coverage
Fetching http://www.cpan.org/authors/id/R/RC/RCLAMP/Pod-Coverage-0.23.tar.gz ... OK
Configuring Pod-Coverage-0.23 ... OK
==> Found dependencies: Devel::Symdump
--> Working on Devel::Symdump
Fetching http://www.cpan.org/authors/id/A/AN/ANDK/Devel-Symdump-2.18.tar.gz ... OK
Configuring Devel-Symdump-2.18 ... OK
Building and testing Devel-Symdump-2.18 ... OK
Successfully installed Devel-Symdump-2.18
Building and testing Pod-Coverage-0.23 ... OK
Successfully installed Pod-Coverage-0.23
Building and testing Test-Pod-Coverage-1.10 ... OK
Successfully installed Test-Pod-Coverage-1.10
--> Working on Test::CPAN::Meta
Fetching http://www.cpan.org/authors/id/B/BA/BARBIE/Test-CPAN-Meta-0.25.tar.gz ... OK
Configuring Test-CPAN-Meta-0.25 ... OK
Building and testing Test-CPAN-Meta-0.25 ... OK
Successfully installed Test-CPAN-Meta-0.25
--> Working on Test::Pod
Fetching http://www.cpan.org/authors/id/E/ET/ETHER/Test-Pod-1.52.tar.gz ... OK
Configuring Test-Pod-1.52 ... OK
Building and testing Test-Pod-1.52 ... OK
Successfully installed Test-Pod-1.52
Building and testing Dotenv-0.002 ... OK
Successfully installed Dotenv-0.002
7 distributions installed
```
#### Dotenv チートシート

```
.env
SUBDOMAIN='subdomain'

use Dotenv -load;
ENV{'SUBDOMAIN'} # 'subdomain'
```

## jq

- https://stedolan.github.io/jq/

```bash
$ brew install jq
```


# kintone REST API

## record.json GET

```3.pl
use strict;
use LWP::UserAgent;
use JSON;
use utf8;
use Encode;
use Data::Dumper; # debug
use Dotenv -load;
use MIME::Base64;

# ssl_opts https://metacpan.org/pod/LWP::UserAgent#ssl_opts
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $url = "https://${ENV{'SUBDOMAIN'}}.cybozu.com/k/v1/record.json";
my $req = HTTP::Request->new(GET => $url);
$req->header("Host" => "${ENV{'SUBDOMAIN'}}.cybozu.com:443");
$req->header("X-Cybozu-Authorization" => encode_base64("${ENV{'UID'}}:${ENV{'PASS'}}", ''));
$req->header("Content-Type" => "application/json");
$req->content('{"app":1,"id":1}');
my $res = $ua->request($req);
my $content = $res->content;

if ($res->is_success) {
    my $json = JSON->new->utf8->decode($content);
    print $json->{'record'}->{'$id'}->{'value'}, "\n";
    print $json->{'record'}->{'レコード番号'}->{'value'}, "\n";
    print encode('UTF-8' ,$json->{'record'}->{'文字列__1行_'}->{'value'}), "\n";
}else{
    print $res->status_line, "\n";
}
```

 **メモ：** 
外からの入力に対しては基本decodeして、Perl内部文字列に変換する。
`decode($content)`

## record.json POST

```record-post.pl
use strict;
use LWP::UserAgent;
use JSON;
use utf8;
use Encode;
use Data::Dumper; # debug
use Dotenv -load;
use MIME::Base64;

my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $url = "https://${ENV{'SUBDOMAIN'}}.cybozu.com/k/v1/record.json";
my $req = HTTP::Request->new(POST => $url);
$req->header("Host" => "${ENV{'SUBDOMAIN'}}.cybozu.com:443");
$req->header("X-Cybozu-Authorization" => encode_base64("${ENV{'UID'}}:${ENV{'PASS'}}", ''));
$req->header("Content-Type" => "application/json");
my $body = <<'EOS';
{
    "app":1,
    "record": {
        "文字列__1行_": {
            "value": "テスト株式会社3"
        },
        "ラジオボタン": {
            "value": "C"
        }
    }
}
EOS

$req->content(encode('UTF-8', $body));
my $res = $ua->request($req);
my $content = $res->content;

if ($res->is_success) {
    print 'success:', $res->status_line, "\n";
    print $res->content, "\n";
    my $json = JSON->new->utf8->decode($content);
    print 'recordId:', $json->{'id'}, "\n";
    print 'revision:', $json->{'revision'}, "\n";
}else{
    print 'error:', $res->status_line, "\n";
    print $res->content, "\n";
}
```

 **メモ：** 
外部に渡す場合はencodeする。`encode('UTF-8', $body)`

## record.json GET with Basic Auth

```record-get-basic-auth.pl
use strict;
use LWP::UserAgent;
use JSON;
use utf8;
use Encode;
use Data::Dumper; # debug
use Dotenv -load;
use MIME::Base64;
use base 'HTTP::Message';

my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $url = "https://${ENV{'SUBDOMAIN2'}}.cybozu.com/k/v1/record.json";
my $req = HTTP::Request->new(GET => $url);
$req->header("Host" => "${ENV{'SUBDOMAIN2'}}.cybozu.com:443");
$req->header("X-Cybozu-Authorization" => encode_base64("${ENV{'UID2'}}:${ENV{'PASS2'}}", ''));
$req->header("Authorization" => 'Basic ' . encode_base64("${ENV{'BASICAUTHID'}}:${ENV{'BASICAUTHPASS'}}", ''));
$req->header("Content-Type" => "application/json");
$req->content('{"app":2,"id":1}');
# print Dumper $req;
my $res = $ua->request($req);
my $content = $res->content;

if ($res->is_success) {
    print $res->is_success, "\n";
    print $res->status_line, "\n";
    # print $res->code, "\n";
    print $content, "\n";
    # print Dumper $res->content;
    my $json = JSON->new->utf8->decode($content);
    print $json->{'record'}->{'$id'}->{'value'}, "\n";
    print $json->{'record'}->{'レコード番号'}->{'value'}, "\n";
    print encode('UTF-8' ,$json->{'record'}->{'attend'}->{'value'}), "\n";
}else{
    print $res->status_line, "\n";
}
```

### jqを使ってJSONを整形

```pl
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 1 });
my $url = "https://${ENV{'SUBDOMAIN'}}.cybozu.com/k/v1/record.json";
my $req = HTTP::Request->new(GET => $url);
$req->header("Host" => "${ENV{'SUBDOMAIN'}}.cybozu.com:443");
$req->header("X-Cybozu-Authorization" => encode_base64("${ENV{'UID'}}:${ENV{'PASS'}}", ''));
$req->header("Content-Type" => "application/json");
$req->content('{"app":1,"id":1}');
my $res = $ua->request($req);
my $content = $res->content;

if ($res->is_success) {
    my $json = JSON->new->utf8->decode($content);
    open(CMD, "| jq '.'");
    print CMD $content;
    close(CMD);
}else{
    print $res->status_line, "\n";
}
```

出力

```json
{
  "record": {
    "レコード番号": {
      "type": "RECORD_NUMBER",
      "value": "1"
    },
    "Table": {
      "type": "SUBTABLE",
      "value": [
        {
          "id": "2",
（略）
    "$revision": {
      "type": "__REVISION__",
      "value": "3"
    },
    "更新日時": {
      "type": "UPDATED_TIME",
      "value": "2017-06-25T11:41:00Z"
    },
    "作成日時": {
      "type": "CREATED_TIME",
      "value": "2017-06-24T09:04:00Z"
    },
    "$id": {
      "type": "__ID__",
      "value": "1"
    }
  }
}
```

# 参考

- [plenv](https://github.com/tokuhirom/plenv)
- [plenvを使ったPerl環境構築 〜2018年度版〜](https://papix.hatenablog.com/entry/2018/03/10/160838)
- [Perl](https://www.perl.org/)
- https://perldoc.jp/
- [他言語ユーザから見たPerlのおもしろさ / YAPC::Nagoya::Tiny 2019](https://speakerdeck.com/utgwkk/yapc-nagoya-tiny-2019)
- https://tutorial.perlzemi.com/
- [plenvの基本メモ書き](https://qiita.com/laikuaut/items/82c9b402920e30c2c4eb)
- [cpanとperldocの歩き方](https://qiita.com/mackee_w/items/30cf46a5654ae1bfb4cf)
- [JSON - JSONデータを解析](https://tutorial.perlzemi.com/blog/20100427127089.html)

## ライブラリ

- [JSON](https://metacpan.org/pod/JSON)
- [Dotenv](https://metacpan.org/pod/Dotenv)
- [LWP](https://metacpan.org/pod/LWP)
- [LWP::Protocol::https](https://metacpan.org/pod/LWP::Protocol::https)
- [LWP::UserAgent](https://metacpan.org/pod/LWP::UserAgent)
- [HTTP::Response](https://metacpan.org/pod/HTTP::Response)
- [HTTP::Status](https://metacpan.org/pod/HTTP::Status)
- [MIME::Base64](https://perldoc.jp/docs/modules/MIME-Base64-2.12/Base64.pod)

## ツールなど

- [JSON Editor Online](https://jsoneditoronline.org/)
- [jq コマンドを使う日常のご紹介](https://qiita.com/takeshinoda@github/items/2dec7a72930ec1f658af)

## モジュール

- [Minillaを使ったモダンなCPANモジュール開発（2）](http://gihyo.jp/dev/serial/01/perl-hackers-hub/005002)

## 読み物的な
（技術的よりもどちらかというと読み物的な記事。結構大事）

- [Perl: WEB API を利用する](https://bayashi.net/wiki/perl/cpan_modules/call_web_api)

## テスト
所謂、Unit Test

- [Perl のテストに関するモジュールをまとめてみる](https://qiita.com/key-amb/items/43795c44e78d904f7a72)
- [Test::More](https://metacpan.org/pod/Test::More)

## Perl Mongers（Perlユーザーコミュニティ）

- [Perl Mongers](https://www.pm.org/)

---

ToDo

2019-11-24

- cpanで良さげなライブラリを探して、REST API のラッパーを作る。