mas9612's notes

プログラミングなどいろいろ.

はじめてのVimプラグインつくった

作成した経緯

私は普段から開発や文書作成にVimを愛用しているが,プレゼン用のスライドに関してはAppleKeynoteを使って作成していた.

しかし,スライドもテキストベースでVimで作れると楽だなと思っていろいろ調べていた. 過去にはPandocを使ってMarkdownからスライド用PDFを生成する ということも試したが,結局Keynoteに戻っていた.

いろいろ調べた結果,reveal.jsを使うとMarkdownで書いた文書をWebスライドとしてレンダリングできるということを知った.軽く使ってみた結果,使い勝手が良さそうだったので,これをVimからすぐに使えるようにプラグインとしてまとめた.

なお,今回が初のVimプラグイン開発なので,Vim Scriptの書き方がおかしいなどあるかもしれない.

プラグイン本体はGithubmas9612/mdslide.vimにて公開している.

プラグインの内容

現在はまだ必要最低限の機能しか実装していない.現時点ではローカルWebサーバの起動・停止,Webスライドのオープン,スライド内容を強制的に更新する,という4つの機能のみを使うことができる.

reveal.jsの仕様上,発表者ノートなどの機能を使うためにはローカルサーバを動かす必要があるため,その機能を盛り込んである.

基本的にreveal.jsのMarkdown表記に従ってMarkdownを作成し,ローカルサーバを起動するだけでWebスライドがレンダリングされて見れるようになる.

これから・所感

まだ欲しい機能など実装できていない部分も多いので,少しずつ実装をすすめていく. 英語難しい.

tmux コピーモードでのキーバインド

tmuxの設定を読み込み直す際に次のようなエラーが出現.

invalid or unknown command: bind-key -t vi-copy v begin-selection
invalid or unknown command: bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy"

そういえば周りで同じエラーが出てる人を見たことがある気がするなあと思いながら調べると, いつの間にか設定方法が変わっていたらしい.結構前からの変更だったようだが, 今までエラーが出なかったのはなぜかわからない.

というわけで,変更に合わせて次のように設定を書き換えた.

以前の設定

bind-key -t vi-copy v begin-selection
bind-key -t vi-copy y copy-pipe "reattach-to-user-namespace pbcopy"

新しい設定

bind-key -T copy-mode-vi v send-keys -X begin-selection
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "reattach-to-user-namespace pbcopy"

emacs-copyとvi-copyがそれぞれcopy-modeとcopy-mode-viに変更となったためにエラーが出ていたようです.

上記に限らず,emacs-copy,vi-copyに関しては次のように修正を加えれば良い.

  1. -t-T に変更する
  2. emacs-copyvi-copy は,それぞれ copy-modecopy-mode-vi に変更する
  3. コマンドの頭に send-keys -X をつける

これで今まで通りの操作をすることができるようになった.

SharePoint REST APIを使ってみた

SharePoint OnlineでサイトにアップロードしているExcelファイルを毎日自動でダウンロードしたいという要望をもらったので, 公開されているAPIを使ってやってみた.

環境

Office 365 APIを使う

認証情報を取得する

Office 365 APIを使用するには,まずアクセストークンと呼ばれる認証情報を取得する必要がある. 次の手順でアクセストークンを取得する.

なお,今回はOffice 365 Enterprise E3を使用し,すでにSharePointでサイトを作成していることとする.

  1. 新Azure PortalにOffice 365の管理者アカウントでログインする.
  2. 左側のメニューから「Azure Active Direcotory」を選択する.
  3. メニューから「アプリの登録」を選択する.
  4. ページ上部にある「追加」を選択して新規アプリケーションを作成する.
    • 名前: 好きなものを
    • アプリケーションの種類: Webアプリ/API
    • サインオンURL: 作成したアプリケーションを動かすURL
  5. アプリケーションの作成後,作成したアプリケーションを選択して,以下の情報をメモしておく.
    • アプリケーションID
    • ホームページ(サインオンURLで指定したものになっているはず)
  6. 右側の設定メニューから「キー」を選択して新しくキーを作成する. キーの説明を入力して期間をドロップダウンリストから選択し,保存をクリックするとキーが生成される. この時キーを 必ず メモしておくこと(この画面から移動した後はキーを再確認できない).
  7. 設定メニューから「必要なアクセス許可」を選択してアプリケーションに対して必要な権限を付与する. 今回は,SharePointの機能を使いたいので,上部にある「追加」をクリック後,表示されるサービス名の中から 「Office 365 SharePoint Online」を選択し,必要な権限を追加する.
  8. アクセストークンを生成するために必要な code を取得する.次のURLにGETでリクエストを送信する.
    • URL: https://login.windows.net/common/oauth2/authorize?response_type=code&client_id=<client_id>&resource=<resource>&redirect_uri=<redirect_uri>
    • パラメータ
      • client_id : アプリケーションID
      • resource : https://<テナント名>.sharepoint.com/ (例: テナント名が testtenanthttps://testtenant.sharepoint.com/
      • redirect_uri : サインオンURL
  9. 正しくリクエストを送れていればログイン画面が表示され,ログインが成功すると redirect_uri で指定したURIにリダイレクトされ,パラメータに code がセットされる.
  10. 取得した code を用いてアクセストークンを取得する.次のURLにPOSTでリクエストを送信する.
    • URL: https://login.windows.net/common/oauth2/token
    • HTTPヘッダに追加: Content-Type: application/x-www-form-urlencoded
    • リクエストボディ: grant_type=authorization_code&code=<code>&client_id=<client_id>&client_secret=<client_secret>&redirect_uri=<redirect_uri>
    • パラメータ
      • code : 先程取得したもの
      • client_secret : アプリケーション作成時に生成したキー
  11. 正しくリクエストを送れていれば, access_token を含むJSONが返却される.

APIを叩く

上記の手順で取得したアクセストークンを使用して実際にAPIを使用した. APIを叩くときには,HTTPヘッダに取得したアクセストークンを以下のようなフォーマットで加える必要がある.

Authorization: Bearer <access_token>

今回はSharePointのサイト上にアップロードしているExcelファイルをダウンロードするのが目的なので, それっぽいAPIをリファレンスから探し出して叩いてみた.

SharePoint 2013 REST API リファレンスによれば, Fileというリソースがサイト内のファイルを表していて,そのファイルを取得するには次のようなURIを指定すれば良い.

http://<site_url>/_api/web/getfilebyserverrelativeurl('/<folder>/<file>')

また,ファイル自体をダウンロードするためには, $value というODataのクエリオプションを付加する.

というわけで,Pythonで簡単にAPIを叩く.なお,以下のプログラムではRequestsという外部ライブラリを使用しているので注意.

import requests
import urllib.parse


def main():
    # ファイル自体を取得するため,$valueを付加
    uri = "https://<tenant>.sharepoint.com/_api/web/getfilebyserverrelativeurl('<file-path>')/$value"
    access_token = sys.argv[1]
    headers = {
        'accept': 'application/json;odata=verbose',
        'Content-Type': 'application/json;odata=verbose',
        'Authorization': 'Bearer ' + access_token,
    }
    res = requests.get(urllib.parse.quote(uri, safe=':/'), headers=headers, stream=True)
    with open('file.xlsx', 'wb') as f:
        f.write(res.raw.read())


if __name__ == '__main__':
    main()

これを実行すると,URIに指定したファイルが file.xlsx という名前でダウンロードされる.

まだ未完成なところ

  • 現時点では code を取得する際にブラウザを使う必要がある
    • Requestsモジュールの Session を使えばできるようなので後で修正する

参考にしたところ

Pandocを使用してMarkdownからプレゼン用PDFを作成する

タイトルの通り.エディタで完結するならそっちのほうが良いので試してみた.

環境はOS X 10.11.6.

pandocのインストールなど

必要なもののインストールなどは以下の記事を参考にさせて頂きました.ただし,TeXはMacTeXをインストールしました.

markdownの原稿を、pandocを使って、Texのbeamerを利用して、プレゼンスライドPDFに変換

スタイルファイルの導入

設定などを済ませた後に,Keynote風のスタイルファイルを導入した.beamerthemeKeynoteLikeGradient.styというスタイルファイルを使うとできるみたいなので,それをダウンロードしてきてインストールする.

beamerthemeKeynoteLikeGradient.styはここからお借りしました.

$ mkdir -p /usr/local/texlive/texmf-local/tex/latex/
$ mv beamerthemeKeynoteLikeGradient.sty /usr/local/texlive/texmf-local/tex/latex/
$ sudo texhash

最後の texhash を実行しないと,スタイルファイルを配置してもコンパイル時にスタイルファイルが見つからないというエラーになるので注意.

プレゼン用PDFの作成

次のコマンドを実行するだけ. OUTFILEINFILE は適当に変えてください.

$ pandoc --latex-engine=lualatex \
       -t beamer \
       -V theme:KeynoteLikeGradient \
       -H h-luatexja.tex \
       -o OUTFILE \
       INFILE

しかし,毎回これを打つのはめんどくさいため,ラッパースクリプトを書いた.

ソースは以下.

#!/bin/sh

CMDNAME=`basename $0`
USAGE="Usage: $CMDNAME [-o OUTFILE] INFILE"

while getopts o: OPT
do
    case $OPT in
        "o" )
            OUTFILE=$OPTARG
            ;;
        * ) echo $USAGE
            exit 1;;
    esac
done

shift `expr $OPTIND - 1`

if [ $# == 0 -o $# -gt 1 ]; then
    echo $USAGE
    exit 1
fi

INFILE=$1

if [ -n "$OUTFILE" ]; then
    pandoc --latex-engine=lualatex \
           -t beamer \
           -V theme:KeynoteLikeGradient \
           -H h-luatexja.tex \
           -o $OUTFILE \
           $INFILE
else
    pandoc --latex-engine=lualatex \
           -t beamer \
           -V theme:KeynoteLikeGradient \
           -H h-luatexja.tex \
           $INFILE
fi

このスクリプトを適当な所に配置することで,次のようにすればPDFを作成できるようになる.

$ pandoc_wrapper -o OUTFILE INFILE

スクリプトを書くまでもなかった気がするが,気にしないことにする.

なお,pandocに --listings オプションを指定し, -H オプションで指定するヘッダファイル内でlistingsの設定をすることでソースコード部分にlistingsを使ったシンタックスハイライトが可能になる.

Lilypondのインストールと環境設定

今まで楽譜を書くときにはFinaleを使用していたが,いつも使用していたFinale2011はYosemite以降,プレイバック関係でちゃんと使えない.仕事で使っているわけではないのでわざわざ新しいバージョンを購入するのはどうかと思い,前から気になっていたLilypondを導入した.

環境はOS X v10.11.6 El Capitan.エディタにはVimを使用する.

Lilypondのインストール

homebrewで簡単にインストールできる. 事前に homebrew/tex をtapする必要がある.

$ brew tap homebrew/tex
$ brew install lilypond

Vimの設定

runtimepathの設定

Lilypondをインストールすると,一緒にVimのftpluinなどがついてくる.これを有効にするために, ~/.vimrc に以下を追記する.

filetype off
set runtimepath+=/usr/local/share/lilypond/2.18.2/vim/
filetype on

2行目で runtimepath に追加するパスは,インストールされたバージョンに応じて変更する必要があるかも.

quickrunの設定

vim-quickrun を使用して,Vimから直接コンパイルしてPDFを確認できるように設定する.

~/.vim/ftplugin/ 以下にLilypond用の設定ファイルを追加する.今回は lilypond_quickrun.vim というファイル名で作成した. ( ~/.vim/ftplugin/lilypond_quickrun.vim )

let g:quickrun_config = {}

let g:quickrun_config['lilypond'] = {
\   'command' : 'lilypond',
\   'outputter' : 'error',
\   'outputter/error/success' : 'null',
\   'outputter/error/error' : 'quickfix',
\   'srcfile' : expand("%"),
\   'exec': '%c %o %a %s',
\ }

let s:hook = {
\   'name': 'open_pdf',
\   'kind': 'hook',
\   'config': {
\     'enable': 1,
\   },
\ }

function! s:hook.on_success(...)
    let l:fileName = expand("%")
    let l:fileName = substitute(l:fileName, "ly", "pdf", "")
    " if not store retval to variable, E492 error occur
    let l:result = system("open " . l:fileName)
endfunction

call quickrun#module#register(s:hook, 1)

もっと良い設定方法があるかもしれないが,まだquickrunの設定をきちんと理解していないのでとりあえずこんな感じで.

s:hook.on_success の部分でコンパイル後に生成されたPDFを開く処理を定義している.無理矢理感はあるが良い方法が思いつかなかった.

これで :QuickRun を実行すると,自動的にPDFが開くようになった.

9/10追記

出力ファイル名を指定しない時はこの方法で上手くPDFが開くが,複数ファイルで楽譜を書いている時など,出力ファイル名を指定している時は上手くPDFを開けない. とりあえず解決策が思いつかないのでMakefile作ってVimから :make することにした.

テンプレートファイルの作成

Lilypondは,ファイルの頭にバージョンを指定しなければいけないらしい.毎回書くのは面倒なので,Vimのテンプレート機能を使用する.

~/.vim/template/lilypond.txt に,自動的に挿入してほしい内容を記述する.とりあえずバージョンの宣言だけ書いた.

\version "2.18.2"

次に,FiletypeがLilypondの時にこのテンプレートを読み込むように設定する. ~/.vimrc に以下を追加する.

autocmd BufNewFile *.ly 0r $HOME/.vim/template/lilypond.txt

終わりに

これでとりあえずVim+Lilypondでの楽譜作成環境の構築が完了したので,Lilypondのチュートリアルでもやって勉強しようと思います. MusicXMLへの変換もできるようにしたいので,これから調べます.

C++のstd::vectorについて

std::vectorについて勉強しなおした.

STLコンテナの一種.実行時に動的にサイズを変更できる.vectorを使用するためには, #include <vector> を記述する必要がある.

vectorの生成

vectorには複数のコンストラクタが存在する.

const int data[] = {1, 2, 3, 4, 5};

std::vector<int> empty_vector;                  // 空のvector
std::vector<int> int_vector(10);                // 要素数10のvector
std::vector<double> double_vector(10, 3.2);     // 要素数10,各要素は3.2で初期化されたvector
std::vector<double> copy_vector(double_vector); // double_vectorのコピー
std::vector<int> iter_vector(data, data + 5);   // dataからdata+5の要素をもつvector

vectorのサイズ・容量

vectorでは,実際に確保されている動的配列の要素数と,実際に使用されている動的配列の要素数は異なる.これらの値を調べたい時には,それぞれ capacity() 関数, size() 関数を使用する.これらの関数の戻り値は, std::vector::size_type 型である.

vectorで使用できるサイズの最大値は max_size() 関数で得ることができる.

std::vector<int> v;

std::cout << v.capacity()   // 確保されている要素数
          << v.size()       // 使用されている要素数
          << v.max_size()   // std::vector<int>で使用できる最大要素数
          << std::endl;

vectorの容量を拡張したい時には, reserve() 関数を使用する.

std::vector<int> v;

v.reserve(100);

vectorの操作

要素の追加・代入

vectorの末尾に値を追加するには, push_back() 関数を使用する.要素追加時には,容量の拡張は自動的に行われる. また,任意の位置に値を挿入するには, insert() 関数を使用する.

std::vector<int> v(5, 1);

v.push_back(3);     // vの末尾に3を追加
v.(v.begin(), 10);  // vの先頭に10を挿入

vectorへの値の代入には, = 演算子が使用できる.さらに, assign() 関数を使用することもできる.

std::vector<int> v(10);

v.assign(10, 1);    // 10個の1を代入
v[5] = 0;           // v[5]に0を代入

const int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
v.assign(a, a + 10);    // aからa+10の要素を代入

要素の取得

要素にアクセスするには,通常の配列のように [] を使用することができる.添字アクセスの際には容量の拡張は行われないため,サイズ以上の値を指定した場合は範囲外アクセスになる.

範囲外アクセスに対応したい場合には, at() 関数を使用する.この関数を使用している際に範囲外アクセスが起こると, std::out_of_range 例外が送出される.

std::vector<int> v(10, 3);

try {
    cout << v[3] << '\n';   // 3が表示される
    v.at(15) = 13;          // std::out_of_range例外の送出
} catch (const std::out_of_range& e) {
    std::cerr << e.what() << std::endl;
}

また,先頭の要素は front() 関数,末尾の要素は back() 関数で取得できる.

std::vector<int> v(10);

std::cout << v.front() << '\n';
std::cout << v.back()  << '\n';

要素の削除

要素の削除には, pop_back() 関数,もしくは erase() 関数を使用する.なお,要素を削除しても,その領域は残ったままになる.そのため, new で確保した領域を削除した場合でも,自動的に delete されない.

std::vector<int> v(10, 1);

v.pop_back();                       // 末尾の要素を削除
v.erase(v.begin());                 // 先頭要素を削除
v.erase(v.begin() + 2, v.end());    // 3番めの要素から最後まで削除

要素をすべて削除したい場合は, clear() 関数を使用する.

std::vector<int> v(5, 5);

v.clear();

PythonからMySQLを使う

DjangoでデータベースにMySQLを使用するときはmysqlclientを使用することが推奨されている.Djangoが勝手にデータベースに接続などの処理をしてくれるのでモジュールの使い方は知らなくても使うことは可能だが,せっかくなら使い方もわかるほうが良いので調べてみた.

基本的に,Python標準ライブラリのsqlite3と使い方は同じ. まずコネクションオブジェクトを作成し,そこからカーソルオブジェクトを作る.できたカーソルオブジェクトを使って様々なクエリを実行する.

インストール

$ pip install mysqlclient

MySQLに接続する

conn = MySQLdb.connect(
    user='username',
    passwd='password',
    host='host',
    db='dbname'
)

返り値はコネクションオブジェクト.userpasswd は名前の通り.MySQLに登録されているユーザー情報を記述する. host はデータベースの置いてある場所を指定する.ローカルのMySQLに接続する場合は localhost を指定する. db には使用するデータベース名を指定する.

カーソルオブジェクトの作成

c = conn.cursor()

MySQLdb.connect で作成したオブジェクトを使ってカーソルオブジェクトを作成する.

クエリの実行

c.execute(query)

query に指定したクエリを実行する.

プレースホルダ

クエリ中に %s を記述すると,プレースホルダとして扱える.ここに値を埋め込む場合は,与えたい値を execute() の第2引数にタプルで渡す.

c.execute('select * from test where id = %s', (2,))

レコードの取得

execute() でselect文を実行した後,レコードを得るためには以下のいずれかを使用する.

  • fetchone() : レコードを1件取得
  • fetchmany(n) : レコードをn件取得
  • fetchall() : レコードをすべて取得

データベースへの変更を保存

conn.commit()

このメソッドを呼び出すことで,変更を保存できる. これを呼び出し忘れると,追加・削除などの変更が破棄される ので注意.

このメソッドはカーソルオブジェクトではなく,コネクションオブジェクトが持っていることにも注意.

サンプルコード

# coding: utf-8

import MySQLdb


def main():
    conn = MySQLdb.connect(
        user='testuser',
        passwd='testuser',
        host='192.168.33.3',
        db='testdb'
    )
    c = conn.cursor()

    # テーブルの作成
    sql = 'create table test (id int, content varchar(32))'
    c.execute(sql)
    print('* testテーブルを作成\n')

    # テーブル一覧の取得
    sql = 'show tables'
    c.execute(sql)
    print('===== テーブル一覧 =====')
    print(c.fetchone())

    # レコードの登録
    sql = 'insert into test values (%s, %s)'
    c.execute(sql, (1, 'hoge'))  # 1件のみ
    datas = [
        (2, 'foo'),
        (3, 'bar')
    ]
    c.executemany(sql, datas)    # 複数件
    print('\n* レコードを3件登録\n')

    # レコードの取得
    sql = 'select * from test'
    c.execute(sql)
    print('===== レコード =====')
    for row in c.fetchall():
        print('Id:', row[0], 'Content:', row[1])

    # レコードの削除
    sql = 'delete from test where id=%s'
    c.execute(sql, (2,))
    print('\n* idが2のレコードを削除\n')

    # レコードの取得
    sql = 'select * from test'
    c.execute(sql)
    print('===== レコード =====')
    for row in c.fetchall():
        print('Id:', row[0], 'Content:', row[1])

    # データベースへの変更を保存
    conn.commit()

    c.close()
    conn.close()


if __name__ == '__main__':
    main()

実行結果

* testテーブルを作成

===== テーブル一覧 =====
('test',)

* レコードを3件登録

===== レコード =====
Id: 1 Content: hoge
Id: 2 Content: foo
Id: 3 Content: bar

* idが2のレコードを削除

===== レコード =====
Id: 1 Content: hoge
Id: 3 Content: bar