write ahead log

ロールフォワード用

goのtemplateパッケージのblockアクションを使う

テンプレートが定義済みの場合と未定義の場合で処理を分けられる.

割と便利そう.

とりあえずコードを置いておく.

{{define "layout"}}

<html>
    <head></head>
    <div>
        {{block "contents" .}}
        not found
        {{end}}
    </div>
</html>

{{end}}
{{define "contents"}}
<h1>contents</h1>
{{end}}
package main

import (
    "html/template"
    "os"
)

func main() {
    if len(os.Args) > 1 && os.Args[1] == "no" {
        t, _ := template.ParseFiles("layout.html")
        t.ExecuteTemplate(os.Stdout, "layout", nil)
    } else {
        t, _ := template.ParseFiles("layout.html", "contents.html")
        t.ExecuteTemplate(os.Stdout, "layout", nil)
    }
}

実行結果

外部テンプレートを使う場合.

$ ./sample.exe


<html>
        <head></head>
        <div>

<h1>contents</h1>

        </div>
</html>

blockアクションで表示.

<html>
    <head></head>
    <div>
        
        not found
        
    </div>
</html>

goのhtml/templateでカスタム関数を追加する

golangのテンプレートエンジン(html/template)ではデフォルトで組み込み関数が用意されていますが, 物足りない場合は自作する事になります.

考え方

基本は以下です.

  • template.FuncMapを作る(関数名と関数のmap)
  • このmapをテンプレートのFuncs関数に渡して登録する

FuncMapに登録する関数は以下の規約があるようです.

  • 引数はいくつでもOK
  • 戻り値は1つだけ
  • ただし2つ目がerrorなら戻り値2つもOK

2つ目の戻り値errorがnilでなければ処理は中断し, Execute関数はtemplate.ExecErrorを返します.

サンプルコード

実用的とは言えない例ですが.

package main

import (
    "fmt"
    "html/template"
    "net/http"
    "time"
)

// 年-月-日にフォーマット
func formatDate(t time.Time) string {
    layout := "2006-01-02"
    return t.Format(layout)
}

// 引数の文字列を頭文字を大文字にして連結して返す.
// 合計文字数が15バイトを超えたらエラーにする
func CatAndUpper(args ...string) (string, error) {
    ret := ""
    for _, s := range args {
        ret += s
    }
    if len(ret) >= 15 {
        return "", fmt.Errorf("too long string")
    }
    return ret, nil
}

// 頭に$を付けるだけ
func formatDollar(c int) string {
    return fmt.Sprintf("$%d", c)
}

func testHandler(w http.ResponseWriter, r *http.Request) {
    // カスタム関数を登録
    fm := template.FuncMap{
        "fdate":   formatDate,
        "fdollar": formatDollar,
        "cat":     CatAndUpper,
    }
    t := template.New("tmpl.html").Funcs(fm)
    t, _ = t.ParseFiles("tmpl.html")

    // 流し込む適当なデータ
    data := &struct {
        Date   time.Time
        Dollar int
    }{}
    data.Date = time.Now()
    data.Dollar = 100

    // テンプレート実行
    fmt.Println(t.Execute(w, data))
}

func main() {
    http.HandleFunc("/", testHandler)
    http.ListenAndServe(":8080", nil)
}

サンプルテンプレート

<html>
    <head>
   </head>
    <body>
        <div>日付: {{fdate .Date}}</div>
        <div>ドル: {{fdollar .Dollar}}</div>
        <div>つなげる: {{cat "string" "string2"}}</div>
        <div>長いのつなげる: {{cat "string" "string2" "string3"}}</div>
    </body>
</html>

生成されるHTML

途中でエラーを返しているので途中まで生成して止まっています.

<html>
    <head>
   </head>
    <body>
        <div>日付: 2017-09-19</div>
        <div>ドル: $100</div>
        <div>つなげる: stringstring2</div>
        <div>長いのつなげる: 

コンソール

テンプレート内でエラーを起こしたのでExecuteメソッドのエラーが帰ってきています.

今回はfmt.Printlnで標準出力へ出してみました.

$ ./sample.exe
template: tmpl.html:8:32: executing "tmpl.html" at <cat "string" "string...>: error calling cat: too long string

golangでテンプレートエンジンを使う(その2)

以前, templateパッケージを利用する方法を調べたけど, 構成的な意味でもうちょっと実用的な例が欲しいので書き残しました.

コードはgithubに置いておきます.

https://github.com/twinbird/go-template-samplegithub.com

やりたいこと

  • どのページでも使うlayoutをまとめたい
  • 部品的なHTMLを使いまわしたい

作るもののイメージ

ブログシステムを考えてみます.

一般に2種類のページ枠があるでしょう.

  • 一般公開ページ
  • 管理者ページ

もう少し具体的に以下のページを考えます.

  • 一般公開ページ
    • 個別記事の編集
    • 個別記事の参照
  • 管理者ページ
    • 個別記事の参照

一般公開ページと管理者ページではメニューが異なるという事にします.

挙げるとキリがないのでこの辺で.

コード

長いですが, メモなのでそれぞれ全部載せます.

テンプレートは全体的に適当です.

ディレクトリ構成
$ tree .
.
├── main.go
└── templates
    ├── admin_menu.html    # 管理者用メニュー(define "menu")
    ├── entry_editor.html  # 記事エディタ(define "contents")
    ├── entry_view.html    # 記事ビュワー(define "contents")
    ├── layout.html        # 共通レイアウト(define "layout")
    └── public_menu.html   # 公開用メニュー(define "menu")

1 directory, 6 files
テンプレートコード
共通レイアウト

layout.html

ここでdefine "layout"としています.

{{define "layout"}}
<!DOCTYPE html>
<html>
    <head>
       <title>{{.Title}}</title>
       <meta charset="utf-8">
   </head>
    <body>
        <div class="menu">
            {{template "menu" .}}
        </div>

        <div class="container">
            {{template "contents" .}}
        </div>
    </body>
</html>
{{end}}
管理者用メニュー

admin_menu.html

ここと公開用メニューでdefine "menu"としています.

{{define "menu"}}
<!-- Admin menu -->
<ul>
    <li><a href="">New Entry</a></li>
    <li><a href="">Edit Entry</a></li>
</ul>
{{end}}
公開用メニュー

public_menu.html

{{define "menu"}}
<!-- Public menu -->
<ul>
    <li><a href="">About me</a></li>
    <li><a href="">Latest Entry</a></li>
</ul>
{{end}}
記事エディタ

以下2つはdefine "contents"です.

entry_editor.html

{{define "contents"}}
<textarea>{{.Entry.Text}}</textarea>
{{end}}
記事ビュワー

entry_view.html

{{define "contents"}}
<pre>
{{.Entry.Text}}
</pre>
{{end}}
goのコード
package main

import (
    "fmt"
    "html/template"
    "net/http"
)

// RenderData はテンプレートへ引き渡す表示用のデータです
type RenderData struct {
    Title string
    Entry *Entry
}

// Entry は1記事に相当します
type Entry struct {
    Text string
}

var (
    // サンプルデータ
    sampleEntry *Entry      = &Entry{Text: "This is a sample entry"}
    sampleData  *RenderData = &RenderData{Title: "sample", Entry: sampleEntry}
    // テンプレートディレクトリ
    templatesDir string = "templates"
)

func main() {
    // admin:編集ページ
    http.HandleFunc("/admin/edit/someEntry", editAdminSomeEntry)
    // admin:参照ページ
    http.HandleFunc("/admin/refer/someEntry", referAdminSomeEntry)
    // public:参照ページ
    http.HandleFunc("/refer/someEntry", referSomeEntry)

    http.ListenAndServe(":8080", nil)
}

// admin用:編集ページ
func editAdminSomeEntry(w http.ResponseWriter, r *http.Request) {
    execTemplate(w, sampleData, "layout", "admin_menu", "entry_editor")
}

// admin用:参照ページ
func referAdminSomeEntry(w http.ResponseWriter, r *http.Request) {
    execTemplate(w, sampleData, "layout", "admin_menu", "entry_view")
}

// public用:参照ページ
func referSomeEntry(w http.ResponseWriter, r *http.Request) {
    execTemplate(w, sampleData, "layout", "public_menu", "entry_view")
}

// execTemplate はfilesのテンプレートからHTMLを構築して,
// wに対して書き込みます.
// HTML構築の際にはdataを利用します.
// filesで指定するテンプレートには必ず{{define "layout"}}された
// ものを1つだけ含む必要があります.
func execTemplate(w http.ResponseWriter, data interface{}, files ...string) {
    // 渡された引数からテンプレートパスの集合を作る
    var pathes []string
    for _, f := range files {
        p := fmt.Sprintf("%s/%s.html", templatesDir, f)
        pathes = append(pathes, p)
    }
    //上記で作ったパスの一覧を使ってテンプレートを作る
    template := template.Must(template.ParseFiles(pathes...))
    // layoutが必ず基点になるという事にする
    template.ExecuteTemplate(w, "layout", data)
}

テンプレートをキャッシュするか

init時に全てのテンプレートをParseしてmapなどに保存しておくことが出来るはずです.

今回のexecTemplate関数をmapからテンプレートを取得し, パッケージ標準のExecuteTemplateを呼び出す仕組みに変えるだけで高速化もできそうです.
(どの程度早くなるかはわかりませんが)

vimのnetrwを使う

vimには標準プラグインとしてnetrwというファイラが付属している.

あんまり触ってなかったんだけど,ふと思い立って使い方を調べてみた.

起動

コマンド 機能
:Ex カレントバッファでnetrwを開く
:Tex タブを開いてnetrwを開く

ディレクトリ移動

操作 機能
Enter 選択中のディレクトリを開く
o バッファを水平分割して開く
v バッファを垂直分割して開く
t 新しいタブで開く
- 1つ上の階層へ戻る
u 以前にいたディレクトリへ戻る
U uで戻る前にいたディレクトリへ戻る
c 開いているバッファのカレントをvimのカレントに変更

CRUD操作

操作 機能
Enter 選択中のファイルを開く
% 新規ファイルを作成
d ディレクトリを作成
D カーソル下orマークしたファイル/ディレクトリを削除
R ファイル/ディレクトリをリネーム

もうちょい複雑な操作

操作 機能
mf ファイルのマーク/アンマーク
mu 全てのアンマーク
mr ファイル名指定でのマーク(*でのワイルドカード可)
mt コピー/移動先にカーソル下のディレクトリを設定
mc マークしたファイルをコピー
mm マークしたファイルを移動
md マークしたファイルを差分を見る
mz マークしたファイルを圧縮/展開

複雑な操作の手順

複雑な操作は手順がややこしいのでメモっておく.

  1. mtで移動/コピー先を指定(カーソル下でmtを入力)
  2. mfなどで移動/コピーするファイルをマーク
  3. mc/mmなどでコピーや移動を実行

その他

操作 機能
i 表示を切り替え
s 表示をソート

シェルスクリプトで外部コマンドを組み立てて実行結果を得る

何度も調べたり考えたりしている気がする...

他にももっといい方法があるのかもしれない.

問題

シェルスクリプト内で引数をsha256化した結果を使いまわしたかった.

コマンドラインで素直に書くとこんな感じ.

echo -n 'pass'| shasum -a 256 | tr -d ' *-'

これをスクリプト内で実行して, 結果を変数に設定したい.

解決したスクリプト

こんな感じになった.

password='pass'
hashingCommand="echo -n "$password" | shasum -a 256 | tr -d ' *-'"
sha256Password=$(eval $hashingCommand)

echo $sha256Password  # デバッグ表示
  1. まずpasswordという変数に文字列を設定
  2. 次に変数展開してコマンドを構成
  3. 2を展開してevalで実行
  4. $()の外部コマンド実行で結果をsha256Passwordへ設定

感想

これでいいの?

gitで日本語ファイル名が文字化けする

なんだこれと思いつつ放置していたけど, いい加減不便なので解消した.

メモらないとどうせまた忘れる.

対処

現在のリポジトリのみの場合.

$ git config --local core.quotepath false

ずっと適用したいとき.

$ git config --global core.quotepath false

なんでこんなことになんのよ

どうもgitはデフォルトではUTF-8の文字は処理しないらしい.

この業界は英語圏で回っているのだ.

Source Tree Support