ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

DockerにてPythonの開発環境を作成するためのコマンド

タイトルにあるように、DockerにてPythonの開発環境を作成するためのコマンドのメモです。
ホストOSはWindows 10、Docker for Windowsはインストール済という状態からです。

ローカルイメージ確認~イメージダウンロード~起動

Python3.6のイメージをダウンロードして起動するまでです。

$ docker images

# イメージ名:バージョン番号でダウンロードするイメージを指定。
# /bin/bashを入れることでコンテナのシェルに入れる
$ docker run -it python:3.6 /bin/bash

$ python --version

コンテナ内からapt getが出来ない場合

「Temporary failure resolving・・・」というエラーメッセージが出た場合の対応方法です。
Docker Desktopの「Settings」「Network」にて「DNS Server」を「Fixed」を選択します。

vimのインストール

作業をするのに便利なようにvimをインストールしておきます。
コンテナ内で以下のコマンドを実行するか、もしくはDockerfileに記述してコンテナ作成時にインストールしてもいいかと思います。

$ apt-get update
$ apt-get install -y vim

Dockerfileによるイメージの作成~アプリのデプロイ

こちらについては以下のサイトの内容を一通りやってみたら理解できました。ありがとうございました。
https://qiita.com/ksh-fthr/items/e914ff213791b7150008

ザックリとした作業内容は以下となります。

  • Dockerfileにベースとなるコンテナ、デプロイ処理、アプリ起動処理などを記述し、Dockerfileからコンテナを作成する。
  • (Pythonの場合) requirements.txtに必要なモジュールは記述する。
  • Webアプリの場合は以下のようにコンテナを起動してコンテナのポートとホストのポートを紐づける
# ホストポート番号:コンテナポート番号 イメージ名 を指定
$ docker run -d -p 4080:80 image_name

cal-heatmapを使ってGithubの草を再現してみる

Githubの草、去年は結構話題だったかと思います(いや、Githubが出来てからずっとか?w)
遅ればせながら、あの「草」をどのようにすれば作れるのかを調べてみました。

結論としてはcal-heatmapというライブラリを使えば出来ることが分かりました。
https://cal-heatmap.com/

今回はこれを使い、以下のような草をhtmlファイル一つで作ってみました。
f:id:UnderSourceCode:20190103115740p:plain

htmlファイル一つで作ってみた理由としては

  • APIからデータを取得している例が多いが、cal-heatmapを試したいだけの場合はAPIは用意したくない
  • cal-heatmapをbowerなどでローカルに持ってきている例も多いが、これも試したいでだけの場合は面倒

といったところです。

今回作成したソース

今回作成したソースは以下になります。

<html>
    <head>
        <script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
        <script type="text/javascript" src="https://cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.min.js"></script>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/cal-heatmap/3.3.10/cal-heatmap.css" />
    </head>
    <body>
        <div id="sample-heatmap"></div>
    </body>
    <script type="text/javascript">
        var datas = {
            "1530370800" : 1, // 2018/07/01
            "1530457200" : 3, // 2018/07/02
            "1533049200" : 5, // 2018/08/01
            "1533135600" : 7, // 2018/08/02
            "1546268400" : 10 // 2019/01/01
        };
        var cal = new CalHeatMap();
        var now = new Date();
        cal.init({
            itemSelector: '#sample-heatmap',
            domain: "month",
            data: datas,
            domainLabelFormat: '%Y-%m',
            start: new Date(now.getFullYear(), now.getMonth() - 11),
            cellSize: 10,
            range: 12,
            legend: [1, 3, 5, 7, 10],
        });
    </script>
</html>

またGithubにも格納してあります。
https://github.com/SrcHndWng/cal-heatmap-sample

過去1年を表示するようになっているので、「datas」に格納しているデータの日付は任意の値に変えてください。
キーは日付のUnix Timestampですが、以下のサイトで算出できます。
https://url-c.com/tc/

少しソースについて解説すると

  • head部で必要なライブラリ、css等を参照
  • divタグで「草」を表示
  • JavaScriptの「datas」に表示するデータを固定値で設定
  • 「cal.init~」で草の設定

を行っています。

「datas」については先に書いたように、

  • キー = 日付のUnix Timestamp
  • 値 = commit(か何かの)回数

となります。
この値は、実運用ではAPIから取得することが多いかと思います。

「cal.init」で草のグラフの書式を設定します。

  • domainで月毎の表示
  • rangeで表示する期間(ここでは12カ月を設定)
  • legentで草の色の濃さを変えるレベルを指定

という感じです。

上記のソースで単純な「草」は実現できましたが、その他の機能を知りたければ
以下の公式ドキュメントを参考にしてみてください。
https://cal-heatmap.com/

Microsoft To-Do のタスクをGolangで取得してみる

前回の記事で、Microsoft To-Doのタスクをcurlで取得してみました。
Microsoft To-Do のタスクをAPIで取得してみる - ソースコードから理解する技術-UnderSourceCode

今回はGolangでタスクを取得するコンソールアプリを作ってみました。
ビルドしてバイナリを起動すると、10秒ごとにタスクを取得し、以下のようにターミナルに出力します。

※今回もベータ版のAPIをコールしています。なので仕様変更などあったら動かなくなるかもしれません。

token unauthorized.
print tasks status, name...
completed : タスク名
notStarted : タスク名
print tasks status, name...
completed : タスク名
notStarted : タスク名

以下、このアプリの作成手順についてです。
実行環境はWindows 10 になります。(多分他のOSでも動くとは思いますが・・・)

事前準備

アプリの登録~アクセストークンの取得

前回の記事の「1. アプリの登録」「2. コードの取得」「3. アクセストークンの取得」を参考に
APIを叩くのに必要な

を取得しておきます。

環境変数の設定

取得した値を環境変数に設定します。以下のキーで登録します。

  • MS_TO-DO_CLIENT_ID ・・・ アプリケーションID
  • MS_TO-DO_ACCESS_TOKEN ・・・ アクセストーク
  • MS_TO-DO_REFRESH_TOKEN ・・・ リフレッシュトーク

プログラムの作成

今回作成したプログラムは以下になります。
https://github.com/SrcHndWng/ms-todo-sample

詳細な実装はプログラムを見てほしいのですが、流れを簡単に説明すると
1. 環境変数よりアクセストークン、リフレッシュトークンを取得する
2. アクセストークンを使ってAPIを叩いてタスクを取得する
3. 2.のレスポンスのステータスコードがUnAuthorized(401)だった場合、リフレッシュトークンを使って新しいアクセストークン、リフレッシュトークンを取得する
4. 3. で取得したトークンを環境変数に設定する
5. 新しいアクセストークンを使ってタスクを再度取得する
6. 取得したタスクをターミナルに出力する
となり、これを10秒ごとに繰り返しています。
(Ctrl + Cなどでの停止処理は今回は実装していません)

この流れがmain()に実装されています。

ちなみに、今回APIからの戻り値のJSONからGolangのstructを作成するのに、以下のサイトを使いました。
非常に使いやすく、助かりました。
https://mholt.github.io/json-to-go/

最後に

こんな感じでGolangMicrosoft To-Do のタスクを取得することができました。
以上です。

Microsoft To-Do のタスクをAPIで取得してみる

MicrosoftのTo-Doというアプリを使って、普段は日々のタスクを管理しています。
このTo-DoのタスクはOutlookAPIを使って操作できるなので、やってみました。

まずはお試しなので、curlでタスクを取得するところまでです。

手順について

1. アプリの登録

APIでタスクを取得するためには、APIを叩くためのアプリを登録しておく必要があります。
https://apps.dev.microsoft.com/#/appList

こちらよりアプリを登録します。
以下の項目を登録しておきました。

  • アプリケーションID
  • アプリケーションシークレット
  • プラットフォームとしてネイディブアプリケーション
  • Microsoft Graph のアクセス許可として「Tasks.Read」

これらの値は以降の手順で使うので、参照できるようにしておいてください。

2. コードの取得

以降はブラウザやcurlでの操作となります。
まずコードを取得するため、以下の値をブラウザのURL欄に入れて起動します。

https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=アプリケーションID&response_type=code&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&response_mode=query&scope=offline_access%20user.read%20tasks.read&state=12345

上記を実行するとアクセス許可を求めるダイアログが出ます。許可をすると空白のページにリダイレクトされます。
リダイレクトされたページのURLにコードが書かれているので、それをメモしておきます。以下のようなURLになっているはずです。

https://login.microsoftonline.com/common/oauth2/nativeclient?code=コード&state=12345

3. アクセストークンの取得

アクセストークンを取得します。curlで以下のコマンドを実行します。

curl -X POST https://login.microsoftonline.com/common/oauth2/v2.0/token -H "HTTP/1.1" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=アプリケーションID&scope=offline_access%20user.read%20tasks.read&code=コード&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&grant_type=authorization_code"

正常に実行できると、json形式でアクセストークン、リフレッシュトークンが取得できます。
タスクの取得や更新などの操作にはアクセストークンを、アクセストークンがタイムアウトした場合にはリフレッシュトークンを使って再取得します。

4. タスクの取得

タスクを取得します。curlで以下のコマンドを実行します。

curl -i https://graph.microsoft.com/beta/me/outlook/tasks -H "Authorization: Bearer アクセストークン"

今回はbeta版のAPIを使って取得しました。バージョン1.0のAPIもあるのですが、この記事を書いている時点では個人用のMicrosoftアカウントでタスクは取得できないようです。
正常に実行できると、json形式でタスクが返ってきます。「subject」欄にあるのがタスク名ですが、日本語のタスク名はUnicode形式となっています。
今回は以下のサイトでデコードして確認しました。

Unicodeエスケープシーケンス変換|コードをホームページに載せる時に便利 | すぐに使える便利なWEBツール | Tech-Unlimited

5. アクセストークンの再取得

アクセストークンがタイムアウトした場合は、以下のコマンドで再取得します。

curl -i -X POST https://login.microsoftonline.com/common/oauth2/v2.0/token -H "HTTP/1.1" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=アプリケーションID&scope=offline_access%20user.read%20tasks.read&refresh_token=リフレッシュトークン&redirect_uri=https%3A%2F%2Flogin.microsoftonline.com%2Fcommon%2Foauth2%2Fnativeclient&grant_type=refresh_token"

まとめ

以上の手順でTo-Doに登録したタスクを取得することができました。
単純なcurlでの取得でしたが、ここまでできればアプリに組み込むこともできるかと思います。

goqueryでスクレイピングする

Golangスクレイピングしようと思い、いくつかのライブラリを検討しました。
それらの中でjQueryライクに扱うことができるgoqueryを使ってみたので、メモ代わりに書いておきます。

GitHub - PuerkitoBio/goquery: A little like that j-thing, only in Go.

とはいっても、ほぼ公式のREADMEにある通りです。

例えば以下のようなテーブルからリンクのURLと表示名を取得したい場合

<table id="sample_table">
    <tr>
        <td><a href="xxxx1.html">Name1<a></td>
    </tr>
 <tr>
        <td><a href="xxxx2.html">Name2<a></td>
    </tr>
</table>

このようにgoqueryを使って取得しました。

    doc, err := goquery.NewDocument("対象ページのURL")
    if err != nil {
        return
    }

    doc.Find("#sample_table").Children().Find("a").Each(func(_ int, s *goquery.Selection) {
        url, _ := s.Attr("href")
        name, _ := s.Html()
        fmt.Printf("url = %s, name = %s\n", url, name)
    })

jQueryライクというだけあり、なんとなくjQueryっぽく書けばスクレイピングできる印象です。
いじょ。

go-wkhtmltopdfでPDFを出力してみる

GolangでPDFを出力するためのライブラリとして
GitHub - SebastiaanKlippert/go-wkhtmltopdf: Golang commandline wrapper for wkhtmltopdf
があります。

単純な使用例は上記のGithub内にもあるのですが
今回は実案件で使用することを想定し

  • HTMLをPDFに変換してくれるが、そのHTMLの定義にテンプレートを使った
  • ロゴなどを印字することを想定してimgタグにて読み込んだ画像がPDFに出した

をやってみました。

ソース、テンプレートは以下のようになりました。
テンプレートのimgタグのsrcには、好きな画像のURLなどを入れてみてください。

package main

import (
	"bytes"
	"fmt"
	"html/template"
	"log"
	"strings"

	wkhtmltopdf "github.com/SebastiaanKlippert/go-wkhtmltopdf"
)

type templateData struct {
	Datas []data
}

type data struct {
	Name string
	Cal  string
}

func main() {
	datas := make([]data, 0)
	datas = append(datas, data{Name: "M16", Cal: "5.56"})
	datas = append(datas, data{Name: "AK47", Cal: "7.62"})
	datas = append(datas, data{Name: "MP5", Cal: "9.00"})
	tmplData := templateData{Datas: datas}

	t := template.Must(template.ParseFiles("sample.tpl"))

	var tpl bytes.Buffer
	if err := t.Execute(&tpl, tmplData); err != nil {
		log.Fatal(err)
	}
	html := tpl.String()

	pdfg := wkhtmltopdf.NewPDFPreparer()
	pdfg.AddPage(wkhtmltopdf.NewPageReader(strings.NewReader(html)))
	pdfg.Dpi.Set(600)

	jsonBytes, err := pdfg.ToJSON()
	if err != nil {
		log.Fatal(err)
	}
	pdfgFromJSON, err := wkhtmltopdf.NewPDFGeneratorFromJSON(bytes.NewReader(jsonBytes))
	if err != nil {
		log.Fatal(err)
	}

	err = pdfgFromJSON.Create()
	if err != nil {
		log.Fatal(err)
	}

	err = pdfgFromJSON.WriteFile("./sample.pdf")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("print done, pdf size %d bytes", pdfgFromJSON.Buffer().Len())
}
<!doctype html>
<style>
.table {
    border-collapse:collapse;
    border-spacing:0px; 
    border:1px solid #FF0000;
}

.table tr td:nth-child(1) {
    width:300px;
}

.table tr td {
    border:1px solid #000000;
}
</style>
<html>
    <head>
        <title>PRINT PDF TEST</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
    <table class="table">
        <tr>
            <td>品名</td>
            <td>口径</td>
        </tr>
        {{range $i, $data := .Datas}}
            <tr>
                <td>{{$data.Name}}</td>
                <td>{{$data.Cal}}</td>
            </tr>
        {{end}}
    </table>
    <br>
    <img src="your favorite img" />
    </body>
</html>

テンプレートと出力したいデータを定義し
tpl.String()」でテンプレートからhtml形式の文字列を取得しています。
このhtml形式の文字列をgo-wkhtmltopdfに渡すことで、PDFに出力しています。

今回作成したソースは以下となります。
GitHub - SrcHndWng/go-wkhtmltopdf-sample

gb環境下で定義ジャンプができない?

普段はVisualStudioCodeを使ってGolangの開発を行っているのですが
定義へジャンプができないことがありました。

gbを使ってビルドしており、ライブラリなどはプロジェクト直下のvendor/に配置されます。
このvendor内のソースへジャンプできないようです。。。

ググったところ以下の記事を見つけたので、やっていたところ上手くジャンプできるようになりました。
https://github.com/Microsoft/vscode-go/issues/249
https://github.com/Microsoft/vscode-go/issues/650

やったことは、VisualStudioCodeのGopathの指定を見直し、vendorもGopathに追加しました。
やり方としては、[Preferences] - [Settings] - [Extensions] - [Go configurations] - [Gopath]で
[Edit in settings]をクリックします。

設定のためのjsonフォーマットが表示され、右の[USER SETTINGS]か[WORKSPACE SETTINGS]に

"go.gopath": 対象のプロジェクトのパス/vendor"

を追記します。

gbを使っているのが一つのプロジェクトだけなら、プロジェクト毎の設定となる[WORKSPACE SETTINGS]でいいでしょう。
逆にgbを複数のプロジェクトで使っているのなら、ユーザで共通の設定となる[USER SETTINGS]に書けばいいと思います。

この場合はプロジェクトに動的に適用されるよう、以下のように記述します。

"go.gopath": "${workspaceRoot}:${workspaceRoot}/vendor"