paizaスキルチェック問題をローカルで自動テストできる環境のすゝめ（vim, python3使い向け）

【修正1. 02-24-2018(Sat)】parse.pyのform関数

# TL;DR
以下基本の３ステップでスキルチェック問題への迅速な解答手段を提案する．
> 1. make dl url=https://paiza.jp/path/to/problem # download problem
> 2. vim solve.py                                 # type code here ...
> 3. make test                                    # run all test cases

# 動機
- いちいち入力例をコピーしてinputに投げ込むのが面倒だった．
- web上でのコード作成とIO機能が搭載されたonlineツールの使い勝手が如何せん悪かった．
- vim中毒ゆえの決断ry

# 方針
- 解答までの時間を可能な限り短縮できる方法を実装すること．
- スキルチェック問題で提示されている全ての入出力例をテストできること．

# TODO
- [ ] cookie情報を自動で取得しcookie.txtを作成する => chromeの[cookie.txt export](https://chrome.google.com/webstore/detail/cookietxt-export/lopabhfecdfhgogdbojmaicoicjekelh)で代用．
- [x] 自動テスト環境
    - [x] problem.html（スキルチェック問題）をcurl(cookie利用）する．
    - [x] problem.htmlをparseし，問題タイトル及び入出力例を取得する．
    - [x] 取得した情報からsolve.py, test/sample-N.{in,out}を作成する．
    - [x] 自動テスト用のtest.shを作成．
- [ ] make testの出力結果の色付け．
- [ ] ユーザ定義のテストケースの追加．

# 導入
## プロジェクト構成
```sh
├── .system/
│   ├── cookie.txt
│   ├── parse.py
│   └── test.sh
├── Makefile
├── solve.py
└── test/
    ├── sample-0.ans
    ├── sample-0.in
    ├── sample-0.out
    ├── sample-1.ans
    ├── sample-1.in
    ├── sample-1.out
    ├── ...
```
## 必須ツール＆モジュール
```sh
brew install curl python3
pip3 install beautifulsoup4 lxml
```
## 使い方（例）
1. chromeの[cookie.txt export](https://chrome.google.com/webstore/detail/cookietxt-export/lopabhfecdfhgogdbojmaicoicjekelh)でcookie情報を取得し，.system/cookie.txtに上書きする．
2. vimを起動し，ビルドコマンドから**make dl url=https://paiza.jp/path/to/problem**を実行する．
3. make dl後に**solve.py**を開いてコードを記述し，**make test**でACを確認する．
4. solve.pyの内容をpaizaスキルチェックの解答欄にコピー＆ペーストしてコードを提出する．

# 主要コード
## Makefile
```shell
.PHONY: test
test:
	sh .system/test.sh

diff:
	diff --side-by-side test/sample-$(N).ans test/sample-$(N).out

dl:
	curl -b ./.system/cookie.txt -o ./problem.html $(url) -s
	rm -f ./solve.py ./test/*
	python3 .system/parse.py
	rm -f ./problem.html
```
## test.sh
```sh
#!/bin/sh
echo "\n-- * -- TEST -- * --\n"
for f in test/*.in
do
    cat $f | python3 solve.py 1>${f%.in}.ans 2>/dev/null
    if diff -q ${f%.in}.ans ${f%.in}.out >/dev/null; then
        echo "${f%.in}: Success"
    else
        echo "${f%.in}: Failure"
    fi
done
echo "\n-- * -- TEST -- * --\n"
```
## parse.py
```python
from bs4 import BeautifulSoup


def save(filename, body):
    with open(filename, 'w') as f:
        f.write(body)


def form(text):
    rn = map(lambda s: s.replace('\n', '').strip(), text.strings)
    fn = list(filter(lambda s: s != '', rn))[1:]
    return '\n'.join(fn) + '\n'


def get_soup(filename, parser):
    with open(filename, 'r') as f:
        soup = BeautifulSoup(f, parser)
    return soup


def parse(soup):
    # Problem Title
    prob_title = soup.h1.span.text
    # Test Cases
    box1 = soup.find_all("dl", class_="txt2")
    inputs = [b for b in box1 if "入力例" in b.text]
    outputs = [b for b in box1 if "出力例" in b.text]

    # solve.py
    save('solve.py', "# " + prob_title + "\n")

    # test/*.{in,out}
    path2t = 'test/sample-{}.{}'
    for n, [i, o] in enumerate(zip(inputs, outputs)):
        save(path2t.format(n, 'in'), form(i))
        save(path2t.format(n, 'out'), form(o))

    return soup


readfile = 'problem.html'
prob_soup = parse(get_soup(readfile, 'lxml'))
```

# 所感
動機の煩わしい問題を解決できたため，一応満足している．
その他の痒いところに手が届かない問題を少しずつ解決していこうと思った．

# おわりに
タイトルではvim, python3使い向けと銘打っていますが，その他の方にとってもpaizaスキルチェック問題を通したプログラミング学習の一助になれば幸いです．アドバイスやツッコミ，別なアプローチなど御座いましたらフィードバックよろしくお願いします．