2019年12月23日月曜日

Vim で最高の HTML 開発環境 (+Bootstrap) を整えよう

ひさびさに Vim 活をして、Vim で最高の HTML 環境を整えました。 HTML は今までほぼ何も設定してなかったのですが、久々に設定を弄ってみたら、便利なプラグインがたくさんあることに気付きました。 軽くまとめてみたいと思います。

プラグインなしでのテクニックまとめ

ついでなのでプラグインを入れなくても使えるテクニックもまとめときます。
  • dit: タグ内を空にしてカーソルをタグの中へ移動
  • cit: タグ内を空にしてカーソルをタグの中へ移動 (+クリップボードへコピー)
  • vit: タグ内の文字列を選択
  • dat: タグを削除
  • 属性間のカーソル移動: W/B/E (正確には空白区切りで移動)

mattn/emmet-vim

あまりにも有名なので紹介だけ。使いこなせてませんが、特に便利なのは以下かなあ。
ul>li*    # 各行をリストに
ul>li*10  # リストをいっぱい作る
<C-y>d    # タグ選択
<C-y>k    # タグ削除

prabirshrestha/vim-lsp, neoclide/coc.nvim

LSP を利用した補完も、あまりにも有名なので紹介だけ。 prabirshrestha/vim-lsp, neoclide/coc.nvim なら HTML/CSS の補完もサポートしています。

alvan/vim-closetag

HTML の閉じ括弧を自動で入力してくれます。

andymass/vim-matchup

開始タグから終了タグへジャンプできるようになります。その逆ももちろん可能。様々な言語に利用できます。

sukima/xmledit

開始タグから終了タグへジャンプできるようになります。 上記と被ってますが、他にも色々な機能 (下) があるため、HTML に限って言えばこちらのほうが便利です。 一緒に入れても競合しない感じなので、そのまま入れてる。
選択状態で <Leader>x  # 選択タグをタグで囲む、属性も入力可能
<Leader>5  # 開始タグから終了タグへジャンプ
<Leader>%  # 開始タグから終了タグへジャンプ
<Leader>d  # タグ削除, etc.

othree/xml.vim

上記よりもっと高機能な編集機能があります。個人的イチオシはこれ。
<Leader>c  # タグ名を変更
<Leader>d  # タグを削除
選択状態で <Leader>v  # 選択範囲をタグで囲む

ArunSahadeo/Webval

W3C Validator API で Validation Check ができます。

vim-syntastic/syntastic

常にチェックするのであれば、Syntastic を使うと良いと思います。設定は以下のような感じ。チェックツールの設定 (htmlhint) 部分は 公式 を参考に。
Plug 'vim-syntastic/syntastic'
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
let g:syntastic_html_checkers = ['htmlhint']

skwp/vim-html-escape

HTML Escape は割とよく使うので入れてます。

tpope/vim-surround

文字列を " や ' で囲ったり削除したり、囲ったものを変更したりします。 他には machakann/vim-sandwich も有名。

rhysd/vim-operator-surround

vim-surround より直感的に、文字列を " や ' で囲ったり削除したり、囲ったものを変更したりします。 以下の設定で便利と思いますが、もっと高度な設定をしている人もいる
Plug 'rhysd/vim-operator-surround'
Plug 'rhysd/vim-textobj-anyblock'
Plug 'kana/vim-textobj-user'
Plug 'kana/vim-operator-user'
map <silent>sa <Plug>(operator-surround-append)
map <silent>sd <Plug>(operator-surround-delete)
map <silent>sr <Plug>(operator-surround-replace)
nmap <silent>sdd <Plug>(operator-surround-delete)<Plug>(textobj-anyblock-a)
nmap <silent>srr <Plug>(operator-surround-replace)<Plug>(textobj-anyblock-a)

jvanja/vim-bootstrap4-snippets

Twitter Bootstrap をスニペットで超簡単に入力できるようになります。 今までスニペットを使いこなせたことがなかったけど、このプラグインを入れて初めて、スニペットが便利だと思いました。 こうやって使うんだなあ。 公式の紹介画像 (下) のような自動補完の設定方法が最初よくわからなかったので、そこだけまとめておきます。



vim-lsp + asyncomplete.vim な人は以下のように設定できます。 coc.nvim な人も、ここには書きませんが簡単に設定できるみたい。 スニペットの補完入力が不要ならばもっとシンプルな設定になりますが、補完入力は欲しいですよね。

let g:python3_host_prog = $PYENV_ROOT . '/shims/python3'

:

Plug 'prabirshrestha/vim-lsp'
Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/asyncomplete.vim'
let g:asyncomplete_auto_popup = 0
Plug 'prabirshrestha/asyncomplete-lsp.vim'

Plug 'SirVer/ultisnips'
Plug 'honza/vim-snippets'
let g:UltiSnipsExpandTrigger=""
let g:UltiSnipsJumpForwardTrigger=""
let g:UltiSnipsJumpBackwardTrigger=""
let g:UltiSnipsEditSplit="vertical"
if has('python3')
  Plug 'SirVer/ultisnips'
  Plug 'honza/vim-snippets'
  Plug 'prabirshrestha/asyncomplete-ultisnips.vim'
endif
Plug 'thomasfaingnaert/vim-lsp-snippets'
Plug 'thomasfaingnaert/vim-lsp-ultisnips'

:

if has('python3')
  let g:UltiSnipsExpandTrigger=""
  call asyncomplete#register_source(asyncomplete#sources#ultisnips#get_source_options({
        \ 'name': 'ultisnips',
        \ 'whitelist': ['*'],
        \ 'completor': function('asyncomplete#sources#ultisnips#completor'),
        \ }))
endif


jvanja/vim-bootstrap4-snippets は欲を言えば、候補が多すぎて大変なのが玉に瑕だけど、よく使うところだけ覚えれば、かなり楽になります。 設定してみて「なにこれ凄い!」と久々に感じました。やっぱり Vim は最高ですね。

ところで coc.nvim は言語サポートが凄いことになってきてて、coc-angular とか coc-flutter とか出てきている。 LSP はメソッドには強そうだけど、HTML みたいなテンプレート形式にはどうなるのだろうか。今後が気になる。

2019年12月18日水曜日

フリゲ紹介: 引きこもりの錬金術師

引きこもりの錬金術師 は、タイトル通り引きこもりの錬金術師モニカが、様々なクエストをこなしていく育成+探索+錬金 RPG です。 RPG アツマール 上でもプレイできる。



仕送りを絶たれたモニカはお金を稼がなければいけなくなりました (左下)。 主人子モニカは引きこもりなので、当然自宅からは出られません。 素材集めや敵の討伐は仲間任せ。 モニカは常に自宅から様々な指示をすることになります (右下)。



仲間たちには電話で指示を出して、素材を集めてきてもらいます。 集めてきてもらった素材を自宅で合成し、装備を整えて、メインクエストを攻略していくようなゲーム進行となります (左下)。 シンプルなダンジョン探索ですが、割と強敵が色々なところに居て、少し気を使う。 探索し過ぎたり、装備が足りなかったりすると、結構歯ごたえがある。

一番良いなと思ったのが、戦闘バランス (右下)。 仲間のステータスを平均的に上げていくと、たぶん「詰む」ように設計されてると思います。 特に葉月さんがわかりやすいですが、きちんと特性を活かさないといけません。 葉月さんは割と壊れ性能のキャラですが、壊れ性能だからこそ楽しめるところがあり、よく考えられている気がする。



説明には5-10時間と書いてあったけど、私はラスボス踏破まで2時間30分でした。 サクサクプレイで楽しむには良い作品と思いました。

2019年12月17日火曜日

フリゲ紹介: もしも願いが叶うタワー

もしも願いが叶うタワー は、VIPRPG 2019 GW祭 で発表された、デフォ戦のダンジョン RPG です。 2-3時間でクリアできるので気軽に楽しめる作品です。



突如現れたという「願いが叶う塔」に挑戦する、キャロル、魔王、アンデッドナイ、カナエールというちょっと変わった面子で塔を踏破するRPGです (左下)。 いわゆるダンジョン探索 RPG になっています (右下)。



ダンジョン探索自体はシンプルなものですが、至るところにイベントがあって、イベントを楽しむゲという印象です (左下)。 雑魚戦は簡単めですが、ボス戦はレベルをきちんと上げないと難しい。 レベルを上げてスキルを覚えれば、それほど難しくはないけど、ギリギリ感が楽しめるくらいの、ちょうどいい難易度になってます (右下)。 攻撃すればするほどボスがギアが上げていくのは、緊張感があって良かったです。 特にラスボスは想像以上に強くて、白熱しました。 初見だと少し危なかった。



シンプル・イズ・ベストを感じられる作品。

フリゲ紹介: ぽんこつポン子の小冒険

ぽんこつポン子の小冒険 は 30分くらいでクリアできるミニアクションゲーム。 短い時間で楽しむには良い作品と思いました。



ロボットのポン子は頭を投げることができるのが特徴です (左下)。 敵にぶつければ倒すことができるし、頭を踏み台にすることができます。 最初、踏み台にできることに気付かなくて少し時間が掛かりましたが、わかってしまえば、サクサクプレイできる。

唐突に始まる系のゲームで、ストーリーも深くありません。単純にアクションを楽しむイメージ。 ダンジョン内に落ちている鍵を拾って、探索を深めていきます (右下)。



シンプルで面白かったです。あとポン子がかわいい。

2019年12月4日水曜日

PISA から学ぶ幼少期の学習方法

PISA 2018 の資料が公開されていました。 初めて要因分析付きでまとまっていたので参考になりました。 せっかくなので重要と言えそうな幼少期の学習方法をまとめてみました。

読書をしよう (ポイント 6p)

読書を肯定的にとらえる生徒や本を読む頻度が高い生徒の方が、読解力の得点が高い。 中でも、フィクション、ノンフィクション、新聞をよく読む生徒の読解力の得点が高い。
最近の子は YouTube などの動画が大好きですが、読解能力の養成はできません。 読解の基礎力を高めるには、ジャンルは何でも良いので活字を読むべきのようです。 読解のできない子は実際、そもそも本を読む機会が少な過ぎることが多いです。

インターネットは1日30分未満でいい (ポイント 9p)

日本、OECD平均ともに、学校外でのインターネットの利用時間が4時間以上になると、3分野ともに平均得点が低下。
OECD では情報を手に入れることが学力差に繋がる可能性は十分に考えられます。 一方の日本人は情報量が多すぎるので、30分以上見ても効果がゼロに近い。 30分未満でも構わないように見えますし、2時間以上はやらせないほうが良さそう。


まとまってるのはこの 2 つだけですが、日本より安定して学力の高い香港・マカオ・シンガポールなどのアンケートを見ていると、結構面白いです。
  • ネット上でチャットをしている国は学力が高い? (p39)
    • ただし、SNSオンラインゲームは微妙 (p41)
  • インターネットを見て楽しんでいる国は学力が高い? (p42)
子どもの頃からインターネットを見て楽しんだり、コミュニケーションを取っている国の学力が高いように見えます。 「インターネットを見て楽しむ」と「インターネットは30分未満でいい」を合わせ、さらに「学校外での平日のインターネットの利用時間 (p2)」も合わせて考えると、学力の高い国ではインターネットを自制して使っていることがわかります。 それに対してチリの子はインタネットジャンキー過ぎるような…。

休憩時間などの短い利用は許しつつも、きちんと勉強させている日本の家庭教育は優秀ということなんでしょうね。 でもシンガポールの子はかなりインターネットしてるなあ。 インターネットで実用的な情報を調べる (p44) が高いので、プログラミングなどできちんと使いこなすことも重要ということでしょう。

凄く普通の結論にしかならなかったけど、読書の比率はどんどん下がってきているので、そこが割と大きな差になるかも知れません。


追記: もっと詳細な記事が出てきた。良かった。 日本のレベル 1 の層をどうするかですが、私は進度を戻してでも再学習するべきと思ってます。

2019年12月1日日曜日

CGI + Python で画像を添付したメールを送る

CGI で画像を添付して Python でメールを送るサンプルを置いておきます。

なんだかんだモックを作るときに CGI はとても便利です。 ただメール送信も CGI も自発的には使わなくなってきてサンプルを見つけるのが面倒になってきているので、残しておきます。 昔だったらこういうのは Ruby で書いてたけど、最近は Python のほうが楽かも。

# -*- coding: utf-8 -*-
import cgi, ssl, textwrap, base64, smtplib
from email import encoders
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.utils import formatdate


ACCOUNT = 'foo@bar.com'
PASSWORD = 'hogefuga'

def create_message(name, img, to):
    message = textwrap.dedent('''\
        {name}さん、こんにちは。
        ''').format(name=name).strip()
    msg = MIMEMultipart()
    msg['Subject'] = "タイトル"
    msg['From'] = ACCOUNT
    msg['To'] = to
    msg['Date'] = formatdate()
    body = MIMEText(message)
    msg.attach(body)
    attachment = MIMEBase('image', 'png')

    # https://www.lisz-works.com/entry/python-base64-incorrect-padding
    # base64 不足文字を補完
    img += '=' * (len(img) % 4)

    attachment.set_payload(base64.b64decode(img))
    encoders.encode_base64(attachment)
    attachment.add_header(
        "Content-Disposition", "attachment", filename='capture.png')
    msg.attach(attachment)
    return msg

def send_email(msg, to):
    smtp = smtplib.SMTP('smtp.gmail.com', 587)
    smtp.ehlo()
    smtp.starttls()
    smtp.login(ACCOUNT, PASSWORD)
    smtp.mail(ACCOUNT)
    smtp.rcpt(to)
    smtp.data(msg.as_string())
    smtp.quit()

def check_parameters(form):
    if 'name' not in form:
        return False
    if 'to' not in form:
        return False
    if 'img' not in form:
        return False
    return True



# name = '山田 太郎'
# to = 'hello@world.com'
# img = ''  # base64に変換したものを適当に
# msg = create_message(name, img, to)
# send_email(msg, to)

form = cgi.FieldStorage()
if check_parameters(form):
    name = form.getvalue('name')
    img = form.getvalue('img')
    to = form.getvalue('email')

    msg = create_message(name, img, to)
    send_email(msg, to)
    print("Content-Type: text/html\n")
    print("OK!")
else:
    print("Status: 400 Bad Request\n")


クエリパラメータで、 name = [氏名] & to = [送信先] & img = [画像データ] を CGI 経由で POST で受け取り、Gmail でメール送信する例です。 画像データは、<input type="file"> や canvas.toBlob() を FormData.append() すると良いでしょう。 画像は昔なら base64 化して送ってましたが、今は canvas.toBlob() が良いです。

ちなみに Gmail でこのスクリプトを動かすには、Google アカウントの セキュリティ設定 から、アプリパスワードの設定が必要です。