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

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

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"

EchoとVue.jsでCRUDを、、、、作りかけた

EchoとVue.jsを使いTodoをCRUDするサンプルを作り始めました。
「始めました」と書いているのは、サーバ側のCRUDは実装したのですが
クライアント側は一覧の表示だけしか実装していないからです。

まあ、EchoとVue.jsを組み合わせたサンプルの一つにはなるかと思います。

アプリについて

サーバ側はCRUDAPIを作成してあります。クライアント側はReadのAPIを叩き、取得した一覧を表示しています。
以下、APIcurlと、画面です。

curl

create
$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d "{\"name\":\"programming\"}"  http://localhost:8080/todos
update
$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X PUT -d "{\"name\":\"trainning\"}"  http://localhost:8080/todos/1
select
$ curl http://localhost:8080/todos
$ curl http://localhost:8080/todos/1
delete
$ curl -v -H "Accept: application/json" -H "Content-type: application/json" -X DELETE http://localhost:8080/todos/1

HTML

ブラウザで「http://localhost:8080/」を表示。

「Show Todos!」ボタンを押下。

ソースについて

以下のmainが一番特徴を表しているかと思います。

package main

import (
	"github.com/SrcHndWng/go-todo-echo-sample/handlers"
	"github.com/labstack/echo"
)

func main() {
	e := echo.New()
	e.File("/", "templates/index.html")
	e.POST("/todos", handlers.CreateTodo)
	e.GET("/todos", handlers.GetTodos)
	e.GET("/todos/:id", handlers.GetTodo)
	e.PUT("/todos/:id", handlers.UpdateTodo)
	e.DELETE("/todos/:id", handlers.DeleteTodo)
	e.Logger.Fatal(e.Start(":8080"))
}

URL毎の動きがここに書かれています。
ルートURLでアクセスが来るとtemplates/index.htmlを返却しています。
/todos配下にGet・Postなどの要求が来ると、それぞれに応じたメソッドを実行しており、これがAPIとしての動きとなります。

今回作成したソースについては以下に格納してあります。
GitHub - SrcHndWng/go-todo-echo-sample

Windows10 ドライブを削除できない?

Windows10 でCドライブを拡張するために、一旦隣のDドライブを削除しようとしました。

「管理ツール」-「コンピュータの管理」- 「記憶域」配下の「ディスクの管理」からDドライブを選択し
右クリックから「ドライブの削除」を使用とすると選べません。

結局、Dドライブが仮想メモリの領域として使われていたのが原因でした。
おそらくCドライブが一杯になったので、Dドライブに退避したのでしょう。。。

「コントロール パネル」-「システムとセキュリティ」-「システム」を選び
左の「システムの詳細設定」を押します。

「詳細設定」タブ - パフォーマンスの「設定」ボタンを押下 - 仮想メモリの「変更」ボタンを押下し
出てくるダイアログでDドライブの使用を0にしました。
再起動しないとドライブの削除が出来なかったので注意してください。

以上、Windowsを使うときの備忘録として残しておきます。

「現場で役立つシステム設計の原則」を読んで - その2

「現場で役立つシステム設計の原則」を読んで - ソースコードから理解する技術-UnderSourceCode
前回の記事の続きとなります。

こちらの本を読み終えました。
最後まで読んだ感想としては、オブジェクト指向ドメイン駆動開発を如何に実践的に使うかについて
非常に分かりやすく書かれていると思います。

前回にも書きましたが非常に読みやすい本だと思うので、気になる方は本書を読んでみることをお勧めします。

以下、前回からの続きのチラシの裏書レベルのメモ書きです。

  • データベースについて
    • テーブルを分けることで、全てのカラムをNot Nullにする。
    • 「記録のタイミングが異なるデータはテーブルを分ける」
    • 取り消し、更新もすべてInsertで表現する。
    • 「コト」を記録する(Insertする)テーブルと、状態を保持するテーブルを分ける。
      • 「コト」を記録は即時、状態は非同期も検討。
  • 画面について
    • 内容ごとに画面を分けることを推奨しているが、特に受託開発などで「大きな」管理画面を作る必要が出たときはどうするのだろうか?
      • 「内部の設計はタスクベースに分けておくべき」
    • とはいえ画面を「タスクベースに分ける」のが今後の主流となることについては同意である。
    • 「画面はドメインオブジェクトを視覚的に表現したもの」
    • パッケージ内の配置や、クラス内の変数の位置まで、画面の表示位置と一致させるべき。
  • APIについて
    • APIの基本的なこと(RESTやレスポンスの種類など)についても書かれている。
    • ドメインオブジェクトとAPIが返す or 受け取る形式が異なる場合、レスポンス層にオブジェクトを作って違いを吸収する。
    • 「共通部分と個別対応部分を明確にする」
      • コアとなる基本API
      • 拡張API
      • 個別対応API
  • CHAPTER9
    • 多分、この一文が重要。
    • 契約についても言及されている。


私が今回写経・改変したプログラムは以下となります。
GitHub - SrcHndWng/learningPrinciplesSystemArchitecture

libgdx をVisual Studio Codeで開発してみる

libgdxをVisual Studio Codeで開発できるかを試してみました。

Visual Studio Codeには(プラグインを入れることで)Javaの入力補完やコンパイルエラーの表示があるので

  • Javaの入力補完や定義元へのジャンプができる
  • コンパイルエラーが表示される

などコーディングに支障がない状態になることを確認しました。

デバッグ実行は行わず、コマンドラインでの起動としました。
以下、Visual Studio Codeでlibgdxのプロジェクトを読み込んだ時に出たエラーと対応法です。
いづれもgradleなどプロジェクト設定で解決したので、他のエディタを使うときにも参考になるかと思います。

前提条件

libgdxのプロジェクトの実行対象は、デスクトップとAndroidの2つです。

ローカルのJavaのバージョンは11、build.gradleのsourceCompatibility、targetCompatibilityは1.8に変更(最低限Java8にしたいから)した状態です。
sourceCompatibility、targetCompatibilityは以下のように記述しました。

        sourceCompatibility = 1.8
        targetCompatibility = 1.8

エラーメッセージと対応法

Could not determine java version from '11'

デスクトップアプリの起動のために「gradlew desktop:run」を実行すると上記のエラーメッセージとなりました。
これには、gradle\wrapper\gradle-wrapper.propertiesの「distributionUrl」を以下のように変更することで対応しました。

distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip

The import android.os.Bundle cannot be resolved

android\src\com\sample\prj\AndroidLauncher.javaで「import android.os.Bundle」のインポートができず、上記のエラーが表示されました。
Android SDKのパスはlocal.propertiesや、ローカルのシステム環境変数ANDROID_HOME」に設定したのですが解決しないので
以下の記述をプロジェクト直下のbuild.gradleに追加することで解決しました。(android.os.Bundleを含むJarをコンパイル対象に含めた)

project(":android") {
    apply plugin: "android"

    configurations { natives }

    dependencies {
        compile project(":core")
        compile "com.google.android:android:2.3.1" # この行を追加

コンパイル対象に含めてしまっているので、Androidアプリのビルドには外すなどの考慮が必要かと思います。
あくまでエディタでコーディングを行うための設定ということで。。。