Nanocで記事ごとにUUIDを付けてリンク作ってPermalinkとする
静的ファイルジェネレータの弱点(と僕が勝手に思っている)は、URL=ファイルのパスというところです。 何が困るかと言うと、移動した場合にURLが変わってしまう。
これを解消するために、記事ごとにUUIDをつけて、実際のパスへのシンボリックリンクを作り、それをPermalinkとして使えば良いということです。
メタデータを利用する
Nanocのドキュメントの上部には、メタデータを入れることができます。
--- KEY: VALUE ---
のような感じです。ここに、UUID用に
--- NANOC_UUID: "....." ---
というのを、挿入します。こうすると、コンパイル時に attributeとして使えるようになります。layoutをhamlで書いていたとすると、
@item[:NANOC_UUID]
で、参照できます。filter内であれば、下記のように参照できます。
@item.attributes[:NANOC_UUID]
こうしておけば、permalinkようのURLとして、上記のUUIDを使ったURLを作るとか、 index tree を作るような処理のなかで、通常のパスの代わりに、UUIDのリンクの方を使うようにすればよいかと思います。
コード
最初 filter
で作っていたのですが、こういうのはRules
内のpreprocess
でやるべきでした。filter
内だと、attribute
の変更はできませんが、preprocess
であれば、attribute
の変更ができます。
preprocess do @items.each do |item| path = item.raw_filename.sub(/^.+\/content\//, "content/") # markdownのみ対象 next if path != nil && !path.match(/\.md$/); content = '' File.open(path, 'r') do |f| content += f.read end if content.match(/^NANOC_UUID: (.+)$/) # すでにある場合それを作る(とり方がちょっと雑ですが) uuid = $1 else # 新しく作成して先頭に書き込む uuid = SecureRandom.uuid if (content.match(/\A---/)) content = content.sub(/\A---/, "---\nNANOC_UUID: " + uuid + "\n") else content = "---\nNANOC_UUID: " + uuid + "\n---\n" + content end File.open(path, "w") do |f| f.puts(content) end # attribute に追加する item.attributes[:NANOC_UUID] = uuid end if (uuid) link = 'output/' + uuid + '.html' if !File.exist?(link) # output のルートに、実際のパスへのリンクを作る File.symlink(path.sub(/^content\/(.+)\.md$/, "\\1.html"), link) end end end end
結果
こんな感じのリンクが出来上がるので、permlink として、b247771c-a07a-4cb6-835d-ee8b2d461e49.html
の方を使えばいい感じです。
lrwxrwxrwx 1 ktat ktat 10 Apr 29 16:01 b247771c-a07a-4cb6-835d-ee8b2d461e49.html -> index.html -rw-r--r-- 1 ktat ktat 12169 Apr 29 16:02 index.html
元のファイルにメタデータを追加しているので、nanoc コマンドのラッパーのshell scriptかなんかをかいて、git commit
するようにしとくのが良いのではないかなと思います。
元のパスをURL欄からコピーされるのを防ぎたければ、パスにUUIDが含まれていない場合は、JavaScriptで強制的に移動させれば良いんじゃないかなと思います。
注意
filter :relativize_path
を使っていると、相対パスになってしまうので、元ファイルの場所がルートに存在しない場合、シンボリックリンクをルートに置いている関係上、相性が悪いです。
cssやjsは、絶対パスで指定するようにする必要があります。
Nanocで絵文字を表示する
前回の続きでNanocの話です。
honkitでは、:name:
という表記で、絵文字を表記できたりしますが、Nanocではできません。
これも、自分でフィルターを簡単に書くことができます。
gemojione を使って、絵文字を表示するフィルターを作る
lib/nanoc/filters/gemojione.rb とか適当にファイルを作ればOKです。
module Nanoc::Filters class ChangeCodeBlock < Nanoc::Filter identifier :gemojione require 'gemojione' def run(content, params = {}) index = Gemojione::Index.new content = content.gsub(/:([a-z0-9 _-]+?):/) {|s| emoji = index.find_by_name($1) || index.find_by_shortname(":" + $1 + ":") moji = s if (emoji != nil) moji = emoji['moji'] end moji } content end end end
Rulesで、下記のように使うだけです。
filter :gemojione
余談
しかし、gemojioneなんですが、こんな感じになっていて、
emoji_hash["description"] = emoji_hash["name"] emoji_hash["name"] = key @emoji_by_name[key] = emoji_hash if key
なぜか、@emoji_by_name
なのに、key
をキーとしていて、定義上、実質、key
と shortname
が同じなので、
- find_by_name
- find_by_shortname
にほぼ違いがないんですよね。謎い。絵文字はこんな感じで定義されています。
※ masterは調べてませんが、v3.3.0に関しては、key と shortnameは一致してそうでした。
{ "100": { "unicode": "1F4AF", "unicode_alternates": [ ], "name": "hundred points symbol", "shortname": ":100:", "category": "symbols", "aliases": [ ], "aliases_ascii": [ ], "keywords": [ "numbers", "perfect", "score", "100", "percent", "a", "plus", "school", "quiz", "test", "exam", "symbol", "wow", "win", "parties" ], "moji": "💯" }, // ... }
Nanoc で PlantUMLを表示する
Nanoc とは?
静的サイトジェネレーターです。jekyllryやhonkitとかの仲間です。
GitLabで静的ページを作るのに、honkitを使っていたのですが、最近、Nanocを試していました。
Nanocの利点
- ビルドが速い(honkitと比較すると爆速といえる)
- filterを書くのが簡単
デメリットは、honkitだと、色々プラグインが存在して楽なんですが、あんまりなさそう?というのと、僕がRubyに慣れてないくらいですね。
お題の件の通り、honkitだと、PlantUMLもプラグインで表示ができるのですが、nanoc ではできません。
Nanocのフィルターを書いてPlantUMLに対応する
ですが、Nanocは、自分で簡単にフィルターを書くことができます。
フィルターは、lib/nanoc/filters/
の下に、.rb
なファイルを作り、identifier
に名前をつけて、それを、Rules
ファイルの中で使うだけです。
もともと、markdownをhtmlに変えるのは、kramdownというフィルターがありますが、このソースコードをコピーしてきて、下記のパッチを当てて、適当なファイル名で保存すればOKです。
@@ -2,10 +2,10 @@ module Nanoc::Filters # @api private - class Kramdown < Nanoc::Filter - identifier :kramdown + class KramdownPlantUML < Nanoc::Filter + identifier :kramdownplantuml - requires 'kramdown' + requires 'kramdown-plantuml' # Runs the content through [Kramdown](https://kramdown.gettalong.org/). # Parameters passed to this filter will be passed on to Kramdown.
単に、名前を変えているのと、require しているものをkramdown
からkramdown-plantuml
に変えるだけですね。
そして、Rules
のほうで、
filter :kramdownplantuml, :input => "GFM"
のようにすれば、PlantUMLに対応することができます。
GraphViz::DBI で ER図を吐き出す
この記事は, Perl Advent Calendar 2019 の20日目の記事です。 19日は、doikojiさんの、「WINI & cal: perlベースの新しい簡易マークアップ言語WINIで来年のカレンダーを作りましょう! 」でした。
この記事は、もともとWanoグループのAdvent Calendar に書くつもりでしたが、Perl の Advent Calendarに空きがあったので、 会社の方はRustの記事を載せました。よろしければ、そちらもどうぞ。
閑話休題。
最近、DocBase、Growi 、Gitlab の Wiki (設定すれば)などPlantUMLでの表示をサポートするものが増えてきています。 自分の手でGraphVizのER図を書くのも可能ですが、正直、だいぶ面倒ですし、テーブル数が多いとやる気が出ません。
PerlのGraphViz::DBIでさくっと書いてみましょう。
ですが、僕の担当している、とあるシステムはテーブル数が1000近くある関係で、はきだされたものをそのままPlantUMLに渡すと、長すぎて壊れてしまいました。 そらそうですね。
これは同種のテーブルが負荷対策の関係で分けたりしているのが原因で非常に多くなっちゃってるのですが、もし同様なケースでしたら、似たようなテーブルはグルーピングするなりの処理をしたり、 ER図をいくつかのグループに分割するなどしたほうが良いですね(例えば、カテゴリだけをグルーピングするとか、特定機能に関わるテーブルだけをグルーピングするなど)。
下記のようにグルーピングの定義をしてやると良いと思います。 この時に、似たようなテーブルがいくつかある場合は、1つのテーブルのみをグループの中に入れてやれば良いでしょう。
tie my %GROUP_TABLES, 'Tie::IxHash'; %GROUP_TABLES = ( # グルーピング => 表示したいテーブルの正規表現を書く(※正規表現は、`schema`.`table_name` にマッチするようにする) 'グループ1' => qr{}, 'グループ2' => qr{}, );
出力時に下記のようにすれば、各グループ毎にER図を出力できます。
foreach my $group (keys %GROUP_TABLES) { my $filter = $GROUP_TABLES{$group}; my $g = __PACKAGE__->new($dbh); $g->{tables} = [ grep {$_ =~ $filter} $g->get_dbh->tables]; my $txt = $g->graph_tables->as_text; # これは、表示を簡略化するためなので、グルーピングとは関係ないです。 # `schema`.`table_name` => table_name だけにしています $txt =~ s{`$SCHEMA`\.`(.+?)`}{$1}g; print "## $group\n\n"; print "```plantuml\n\@startuml\n", $txt, "\n\@enduml\n```\n\n"; }
他にも、削除フラグとか更新日時とか、ほとんどのテーブルにあるけど、特に表示したくないものもあるでしょうから、
my %IGNORE_COLUMNS = ( updated_at => 1, delete_flag => 1, created_at => 1, );
のように定義しておいて、graph_tables
をオーバーライドして、調整するとよいかもしれません(後のコードを参照)。
また、GraphViz::DBI の外部キーの実装は、下記のようになっています。
sub is_foreign_key { # if the field name is of the form "<table>_id" and # "<table>" is an actual table in the database, treat # this as a foreign key. # This is my convention; override it to suit your needs. my ($self, $table, $field) = @_; return if $field =~ /$table[_-]id/i; return unless $field =~ /^(.*)[_-]id$/i; my $candidate = $1; return unless $self->is_table($candidate); return $candidate; }
コメントを訳すと、
フィールド名が、"<table>_id"で、"<table>"が実際にDBに存在する テーブルの場合、これを外部キーとします。 これは、自分の慣例なので、必要に応じてオーバーライドしてください。
とした、ざっくりな感じなので、コメントの通り継承してオーバーライドしました。
my %SPECIAL_FOREIGN_KEY = ( '特殊な命名の外部キー1' => "`$SCHEMA`.`テーブル`", # 外部キーの参照しているテーブル '特殊な命名の外部キー2' => "`$SCHEMA`.`テーブル`", # ... ); sub is_foreign_key { my ($self, $table, $field) = @_; my $candidate; if (not $candidate = $SPECIAL_FOREIGN_KEY{$field}) { # TABLE_NAME_ID は TABLE_NAME の外部キーとみなす return unless $field =~ /^(.*)[_-]id$/i; $candidate = "`$SCHEMA`.`$1`"; } return unless $self->is_table($candidate); return $candidate; }
僕も同じような慣例なので、例外的なものを追加するくらいでOKでした。
全体像は、こんな感じになります。
use strict; use DBI; use parent 'GraphViz::DBI'; my $SCHEMA = "スキーマ"; my $dbh = DBI->connect("dbi:mysql:$SCHEMA;host=DB_HOST", 'user', 'password'); use Tie::IxHash; tie my %GROUP_TABLES, 'Tie::IxHash'; %GROUP_TABLES = ( # グルーピング => 表示したいテーブルの正規表現を書く 'グループ1' => qr{}, 'グループ2' => qr{}, ); my %SPECIAL_FOREIGN_KEYS = ( '特殊な命名の外部キー1' => "`$SCHEMA`.`テーブル`", # 外部キーの参照しているテーブル '特殊な命名の外部キー2' => "`$SCHEMA`.`テーブル`", # ... ); my %IGNORE_COLUMNS = ( created_at => 1, delete_flag => 1, updated_at => 1, ); sub is_foreign_key { my ($self, $table, $field) = @_; my $candidate; if (not $candidate = $SPECIAL_FOREIGN_KEYS{$field}) { # TABLE_NAME_ID は TABLE_NAME の外部キーとみなす return unless $field =~ /^(.*)[_-]id$/i; $candidate = "`$SCHEMA`.`$1`"; } return unless $self->is_table($candidate); return $candidate; } sub graph_tables { my $self = shift; my %table = map { $_ => 1 } $self->get_tables; for my $table ($self->get_tables) { my $sth = $self->get_dbh->prepare( "select * from $table where 1 = 0"); $sth->execute; my @fields = @{ $sth->{NAME} }; $sth->finish; my $label = "{$table|"; for my $field (@fields) { next if $IGNORE_COLUMNS{$field}; $label .= $field.'\l'; if (my $dep = $self->is_foreign_key($table, $field)) { $self->{g}->add_edge({ from => $table, to => $dep }); } } $self->{g}->add_node({ name => $table, shape => 'record', label => "$label}", }); } return $self->{g}; } foreach my $group (keys %GROUP_TABLES) { my $filter = $GROUP_TABLES{$group}; my $g = __PACKAGE__->new($dbh); $g->{tables} = [ grep {$_ =~ $filter} $g->get_dbh->tables]; my $txt = $g->graph_tables->as_text; $txt =~ s{`$SCHEMA`\.`(.+?)`}{$1}g; print "## $group\n\n"; print "```plantuml\n\@startuml\n", $txt, "\n\@enduml\n```\n\n"; # Growiならこっち # print "\@startuml\n", $txt, "\n\@enduml\n\n"; }
こんな感じでやると、下記のようなER図になります(自分で作ってたサービスのDBです)。
ちなみに、GraphViz::DBIは、13年前の更新が最後ですが、最新のPerlでも動くと思いますよ(5.8.9 でも、5.25.1でも動いたので)。
これを定期的に回して、Wikiのページも自動的に更新とかしてやれば、良いかもしれません。 ER図をDBの情報を読み取って吐き出すツールは普通にありますが、テーブル数が多いと、使い物になりませんので、こんな感じで特定部分をフィルタリングして出すのも割と有用だと思いますよ。
21日のPerl Advent Calendar 2019はMorichanさんによる「PerlだけでWebサイトを作る - Qiita」です。
tcat という行頭に日時を付加するコマンドを作った
cat のように標準入力かファイルの中身を出力するものですが、先頭行に日時がつきます。
https://github.com/ktat/tcat にあります。
なんで、こんなものが必要かというと、例えば、サーバの様子を top -b を記録して後からみたいなーとかいう時とかに...
% top -b >> top.log
みたいにしたすると、ファイルの中身はこんな感じ。
top - 00:44:33 up 16:59, 7 users, load average: 1.76, 1.75, 1.62 Tasks: 345 total, 1 running, 344 sleeping, 0 stopped, 0 zombie %Cpu(s): 30.3 us, 11.1 sy, 0.0 ni, 57.9 id, 0.4 wa, 0.0 hi, 0.2 si, 0.0 st KiB Mem : 16312908 total, 409000 free, 9663728 used, 6240180 buff/cache KiB Swap: 16657404 total, 16655344 free, 2060 used. 4595576 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9618 ktat 20 0 42952 3800 3140 R 12.5 0.0 0:00.03 top 5253 ktat 20 0 1745952 424904 69868 S 6.2 2.6 6:14.04 slack 5306 ktat 20 0 1497416 376924 181836 S 6.2 2.3 17:12.10 slack 1 root 20 0 185412 6012 3944 S 0.0 0.0 0:04.64 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.04 kthreadd
後から見ようとした時に、どの時間のものだったのか調べようとすると....
top - 00:44:33 up 16:59, 7 users, load average: 1.76, 1.75, 1.62
ここにあるけど、とっても使いにくいし、日またいだら、どうするんだという感じなので...
% top -b | tcat >> top.log
とすることで、
2017-01-25 00:48:25: top - 00:48:25 up 17:03, 7 users, load average: 1.29, 1.63, 1.62 2017-01-25 00:48:25: Tasks: 341 total, 1 running, 340 sleeping, 0 stopped, 0 zombie 2017-01-25 00:48:25: %Cpu(s): 30.1 us, 11.0 sy, 0.0 ni, 58.3 id, 0.4 wa, 0.0 hi, 0.2 si, 0.0 st 2017-01-25 00:48:25: KiB Mem : 16312908 total, 582792 free, 9512076 used, 6218040 buff/cache 2017-01-25 00:48:25: KiB Swap: 16657404 total, 16655352 free, 2052 used. 4772668 avail Mem 2017-01-25 00:48:25: 2017-01-25 00:48:25: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2017-01-25 00:48:25: 9850 ktat 20 0 42952 3732 3076 R 12.5 0.0 0:00.03 top 2017-01-25 00:48:25: 1 root 20 0 185412 6012 3944 S 0.0 0.0 0:04.64 systemd 2017-01-25 00:48:25: 2 root 20 0 0 0 0 S 0.0 0.0 0:00.04 kthreadd 2017-01-25 00:48:25: 3 root 20 0 0 0 0 S 0.0 0.0 0:08.62 ksoftirqd/0 2017-01-25 00:48:25: 5 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H
のように記録される(日付のフォーマットは自由に変えられます)ので、
% grep '2017-01-25 00:48' top.log
のようにして、検索しやすくなります。
で、作ってから気づいたのですが、まったく同じ名前の同様のコマンドが C で書かれていました。
https://github.com/marcomorain/tcat
orz
とりあえず、違いを出すために、cat のオプションを全て実装してみました("-n" 以外使ったことないけど)。
"-v", "-E", "-T" あたりの実装は、cat のソースコードからロジックをそのまま移した感じになります。
Go言語さわってみた
一年前(2014年5月末の作成日の.goなファイルがあった)くらいにGo Tourを途中までやったけど、ほとんど覚えてなかったです。
ディレクトリとか環境変数とかは、前若干コード書いたので、それっぽく残ってました。emacsのgo-modeも入ってた。
という状況から、ちょうど一週間くらいたった感じです。
作ったもの
https://github.com/ktat/go-coloring テキストを正規表現で色付けするもの。そういうツールってあるっけ?
https://github.com/ktat/go-pager ↑のやつにlessっぽくしようとしたpagerを組み込んでいたけど、分離した(go-termboxを利用)。
コードは、まぁ、まだまだアレというか間違ってる/分かってない可能性が高いです。
参考にしたところ/したいところ
- Goチュートリアル
- Go言語仕様
- Effective Go
- Go Language Programming
- Go Web プログラミング(別にWebプログラミングしたかったわけではないけどまとまってそう)
- Goでのエラーハンドリングについて
- Go言語 FAQ
- YAPC::Asia 2014での牧さんのトークとスライド
- mapの使い方
- Emacsの設定
golang.org には書いてるけど、golang.jp には書いてないこともあったので、
golang.org 見たほうが良いんじゃないかなとか思う。
作法
変数/関数の命名規則などが慣習も含め決まっているので先に読んだほうが良いと思う。
https://golang.org/doc/effective_go.html#names
例えば、セッターは
SetParamName
だが、ゲッターは
GetParamName
ではなく、単純に
ParamName
とする。
パブリックなものは大文字から、プライベートなものは小文字の名前をつける(メソッドも構造体の変数も)
gofmt というコマンドがあるので、それで整形を行う(必ず)。インデントはタブです。
ファイル構成
詳しくはこちらをご覧ください。
https://golang.org/doc/code.html
go/src/github.com/ユーザー名/プロジェクト名/ソースコード.go
にしておいて、
GOPATH=/home/USER_NAME/go
としておくと、$GOPATH/src/ $GOPATH/pkg/ 以下にソースコードやコンパイル済みのライブラリがあると想定する。
import ( "github.com/mattn/go-getopt" )
のように import すると、
/home/USER_NAME/go/src/github.com/*/ /home/USER_NAME/go/pkg/ARCHITECTURE/github.com/*/
以下の *.go, *.a ファイルが読まれるもよう。
クォート
シングルクォートはルーン(rune)というもの(int32のalias)で、ユニコードのコードポイント
fmt.Println('a') // 97 == 0x61
ダブルクォートはstring 。byte(uint8 の alias)の配列
"12345" "12345"[1] == '2'
以下のような比較はエラー
"あ"[0] == 'あ' // overflows uint8
"あ"[0] の0番目のバイト(uint8)と 'あ'(rune(int32のalias))の比較になるため
バッククォートは複数行にまたがって書ける。
fmt.Println(`... ... `)
main
実行したいというときは、main package に main 関数をつくればよい。
// test.go package main func main () { // なんか書く }
そして、以下で実行できる。
go run test.go
変数宣言
var hoge string = "abcd" hoge := "abcd"
:= は型推論してくれる。
最初の代入で使うものなので、同一スコープで := を再度使うとエラー。var hoge の後に := 使ってもエラー。
スコープはブロックスコープ(https://golang.org/ref/spec#Declarations_and_scope)なので、以下はセーフ。
var str string = "abcde" println(str[3:4]) { str := "eee" println(str) }
以下のように複数の宣言もできる。
var ( hoge string foo bool )
トップレベルで宣言するときは、:= は使えない。
var hoge string var hoge string = "aaa"
blank idenitifier
"_"はblank idenitifier(https://golang.org/ref/spec#Blank_identifier)であり、無視される。
i, _ := strconv.Atoi("12")
配列
array := make([]string, 0)
追加するときは
array = append(array, "hogehoge")
map
ハッシュ的なもの。
https://blog.golang.org/go-maps-in-action
dict := map[string]string { "key": "value" } result := make(map[string]string) result["a"] = "b"
キーの存在チェック
if _, ok := dict["key"]; ok { println("key exists") }
http://stackoverflow.com/questions/2050391/how-to-check-if-a-map-contains-a-key-in-go
キーの削除
delete(mapVar, "a")
map の値に関数
はもたせられない(nested func not allowed と言われる)
map[string]func
みたいな定義はできるけど、代入したらエラーになる。
定義できる意味ないんじゃ...?
牧さんからコメントいただいて、出来るということです。
http://play.golang.org/p/voWrwbQ2oe
test := map[staing]func() {} test["a"] = func () {println(1)} test["a"]()
みたいに、引数とかを書いてなかったからだそうです。言われてみれば、そらそうだーという感じがしました。
funcの定義を書くわけなので、別のタイプの関数は取れません。
test := map[string] func() {} test["aiueo"] = func() {println(123)} // これはいいけど test["akasa"] = func(x string) {println(x)} // これはダメ
最初にググってでてきた、interface を使う方法は、どんなタイプの関数でも入れれるといえば入れれる(使いドコロがあるのかは知らないけど)
test := map[string] interface{} {} test["aiueo"] = func() {println(123)} test["akasa"] = func(x string) {println(x)} test["aiueo"].(func () )() test["akasa"].(func (x string))("a")
追記終わり
interaface とやらを使うらしい(http://stackoverflow.com/questions/6769020/go-map-of-functions)。
colorFunc := map[string]interface{}{ "red": func(s coloring.String) string { return s.Red() }, "green": func(s coloring.String) string { return s.Green() }, "blue": func(s coloring.String) string { return s.Blue() }, "yellow": func(s coloring.String) string { return s.Yellow() }, "white": func(s coloring.String) string { return s.White() }, "cyan": func(s coloring.String) string { return s.Cyan() }, "black": func(s coloring.String) string { return s.Black() }, "purple": func(s coloring.String) string { return s.Magenta() }, }
これを呼ぶときは、
colorFunc["red"].(func (colorling.string) string)(val)
のように呼ぶ。
メソッドリストのない空のinterface は何が来てもOKだから、func も入るということかと思う。
型変換
[]byte(str) string(someByte)
数値 <-> 文字列
str := strconv.Itoa(12) i, err := strconv.Atoi("12")
文字列連結
"hoge" + "fuga"
関数
func 名前 (型) 戻り型 { return ... }
戻り値が複数の時は
func 名前 (型) (戻り型,戻り型) { return ... }
戻り値にも名前をつけられる
func 名前 (型) (名前 戻り型, 名前 戻り型) { return // 変数を渡さなくても良い }
パッケージの関数
packageName.FunctionName()
で呼べる。
パッケージの型とメソッド
package hogehoge type String string func (i String) Name String{ // ... }
とかすると
hogehoge.String("hoge").Name()
とかって使える。
ループ
for しかない
ノーマル
for i := 0; i < len(str); i++ { }
無限ループ
for {
}
break で止める
continue で次のループへ
goto ラベルへ
break, continue も ラベルを取れる。
ラベルは以下のように記述。
Loop:
配列の取り出し
indexのみを取る
for i := range array { }
indexと値
for i, v := range array { }
map取り出し
キーのみ
for k := range array { }
キー、バリュー
for k, v := range map { }
if
if len(str) == 0 { } else if ... { } else { }
条件は bool でないとダメ
switch
case のところは同一タイプじゃないとダメらしい。
switch c { case 'f': fileName = OptArg case 'h': usage() os.Exit(1) default: if color, ok := colorMap[string(c)]; ok { replace = append(replace, fmt.Sprintf("(?P<%s>%s)", color, OptArg)) } else { os.Exit(1) } }
三項演算子
ありません。
substring
スライスで取る。
var str string str = "abcde" println(str[0:3]) // abc println(str[3:4]) // d println(str[4:5]) // e
文字列分割
strings.SplitN(str, sep, -1)
最後の引数はsplitする数。-1だと以下と同じ。
strings.Split(str, sep)
正規表現
関数がいっぱいあるなー。
http://tip.golang.org/pkg/regexp/
http://tip.golang.org/pkg/regexp/syntax/
https://gobyexample.com/regular-expressions
regexp.Compile
変な正規表現渡しても死なないので、自分でハンドリング。
re,regexpErr := regexp.Compile(pattern) if regexpErr != nil { log.Fatal(regexpErr) // しかし、log.Fatal や os.Exit は使わないほうがいいらしい }
regexp.MustCompile
変な正規表現渡すとエラーで死ぬ。
どっかからもらったんじゃなくて、自分で書いた正規表現はこっちで良いでしょう。
re := regexp.MustCompile(pattern)
置換
以下、全て同じ
regexp.MustCompile("^(a)(b)").ReplaceAllString("abcdef", "$2$1") regexp.MustCompile("^(a)(b)").ReplaceAllString("abcdef", "${2}${1}") regexp.MustCompile("^(?P<first>a)(?P<second>b)").ReplaceAllString("abcdef", "$second$first")
(?P<名前>パターン)
は、名前付きキャプチャ。
マッチしたものを後で使う
re := regexp.MustCompile("^(?P<first>a)(?P<second>b)") match := re.FindSubmatchIndex([]byte("abcdefab")) var dst []byte dst = re.ExpandString(dst, "$second$first", "abcdef", match) fmt.Println(string(dst))
http://astaxie.gitbooks.io/build-web-application-with-golang/content/ja/07.3.html
regexp関係のFuncと付く関数でやっても意味ないと思われる。
フラグは先頭につける
(?フラグ)パターン
以下のような感じ。
(?im)^[a-z]+$
エラーハンドリング
error という型がある。log.Fatal に投げるとエラーメッセージを出してくれる
log.Fatal(err)
// 既述ですが、log.Fatal や os.Exit は使わないほうがいいらしい
以下を読むと良いのかもしれません。
http://blog.golang.org/error-handling-and-go
一行ずつ
scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { var l = scanner.Text() }
コマンドライン引数
go-getopt
mattnさんの mattn/go-getopt が使える
https://github.com/mattn/go-getopt
example に使い方あり。
https://github.com/mattn/go-getopt/blob/master/example/example.go
OptEror = 0
にすると、エラーメッセージ(おかしなコマンドライン引数を渡した時とか)がでなくなる。
おかしなコマンドライン引数を渡した時を判別する場合は、switch の default でやれば良いかも。
ポインタ
以下のような感じで戻り値を受け取らずに、files にファイルを突っ込める。
ただ、こういうケースでポインタ使うのは間違いらしい。
http://qiita.com/ruiu/items/e60aa707e16f8f6dccd8
seekDir(&files, "./") func seekDir (files *[]string, dirName string) { fileInfo, ioerr := ioutil.ReadDir(dirName) errCheck(ioerr) for i := 0; i < len(fileInfo); i++ { if fileInfo[i].IsDir() == false { *files = append(*files, dirName + "/" + fileInfo[i].Name()) } else { seekDir(files, dirName + "/" + fileInfo[i].Name()) } } }
&変数
が、ポインタ
*変数
で、デリファレンス
メソッド
型に対してメソッドを作れる
type S string func (s *S) aaaa () { fmt.Println("=== S aaaa ===") fmt.Println(s) } func main() { var str S = "Type S" S.aaaa() }
struct で名前なしで型を埋め込むことで、埋め込んだ型のメソッドを委譲できる。
type S2 struct { str string S } func (s *S2) bbbb () { fmt.Println("=== S bbbb ===") fmt.Println(s.str) } func main() { var str S2 S2.str = "Type S2" S2.aaaa() // 委譲したもの S2.bbbb() // S2のメソッド }
以下のように "*S2" じゃなくて、"S2" のようにもかけるけど、そうすると、メソッド内でメンバを書き換えたりしても、呼び出し元は変わらない。
func (s S2) cccc() string{ s.str = "cccc" println(s.str) // cccc return s.str } var s S2 s.str = "s2" println(s.cccc()) // cccc println(s.str) // s2
Go言語はオブジェクト指向かどうかという話は、以下。
http://golang.jp/go_faq#Is_Go_an_object-oriented_language
インターフェイス
インターフェースはメソッドの羅列。インターフェースに定義されているメソッドを実装すれば、
インターフェースを受け取れる関数を受け取れる。
※ちなみに以下は微妙な例になっておりますが、書き直すのがだるいのでそのままです。
Animalizer インターフェースを実装してみた
Moveメソッドがインターフェースのメソッドとして定義されている。
AsAnimal メソッドは引数に Animalizer インターフェースを受け取れる。
package animal import "fmt" type Animalizer interface { Move() } type Animal struct {} func (a Animal) Move (){ fmt.Printf("move as animal %T\n", a); } func AsAnimal (a Animalizer) { fmt.Printf("animal is passed %T\n", a) }
Humanizer インターフェースを実装してみた
Talk と Move メソッドがインターフェースのメソッドとして定義されている。
AsHuman メソッドは引数に Humanizer インターフェースを受け取れる。
package human import "fmt" type Humanizer interface { Talk() Move() } type Human struct {} func (h Human) Move (){ fmt.Printf("move as human %T\n", h); } func (h Human) Talk (){ fmt.Printf("I'm human %T\n", h) } func AsHuman (h Humanizer) { fmt.Printf("human is passed %T\n", h) }
使ってみる
package main import ( "github.com/ktat/test/animal" "github.com/ktat/test/human" ) var a animal.Animal; var h human.Human; func main () { a.Move() h.Move() h.Talk() animal.AsAnimal(a) animal.AsAnimal(h) human.AsHuman(h) }
結果
move as animal animal.Animal move as human human.Human I'm human human.Human animal is passed animal.Animal animal is passed human.Human human is passed human.Human
仮に、
human.AsHuman(a)
なんてことをすると、
./test.go:18: cannot use a (type animal.Animal) as type human.Humanizer in function argument: animal.Animal does not implement human.Humanizer (missing Talk method)
と怒られる。
インターフェイスに定義されたメソッドを実装することで、同一のインターフェースのものということになる。
ロボット型に人のインターフェースをもたせれば人として扱える。
なんか面白いですね。
ちなみに、命名規則的に interface は、-er でつけて、- なメソッドを持つ(Stringer は、Stringを持つ)となってるので、上記は変な例だと思います。
例えば、Stringer インターフェースは、
func String string {}
なメソッドを持っています。
ついでに説明すると、適当な型に
type AnyType { str string } func (a AnyType) String string { return a.Str }
のようなメソッドを作ってやれば
fmt.Printf は、Stringer インターフェースを受け取ることができるため、
var a AnyType a.Str = "1234" fmt.Printf("%s", a)
と渡すことが出来ます。
なお、以下のようなメソッド定義だと、なんでも受け取れるようになります。
func (h Human) Any (i interface{}) { }
なお、インターフェースを満たすというのはメソッドを満たせばいいだけなので、単純に型を埋め込んでやるだけでも、インターフェースのメソッドを満たすことが出来ます。
type Human struct { animal.Animal }
インターフェースを満たすだけであれば、これだけでも良いわけですが、結果はこうなります。
move as animal animal.Animal move as animal animal.Animal // さっきは、 move as human human.Human I'm human human.Human animal is passed animal.Animal animal is passed human.Human human is passed human.Human
メソッドは委譲されていますが、もともと、
func (a Animal) Move (){ fmt.Printf("move as animal %T\n", a); }
という定義なので、a は Animal なのです。
次は、gorutine とかチャンネルとか触ってみると思います(書くかは分かんないけど)。
翻訳 CPAN::Meta::Spec、Module::CPANFile、Minilla、Milla::Tutorial、cpanm、Carton、plenv、plenv-contrib
CPANまわりのツールたちの開発が活発なので、翻訳しようと前々から思ってたのですが、ようやく翻訳しました。
- CPAN::Meta::Spec ... CPANメタデータ仕様
- cpanfile ... PerlアプリケーションのためにCPANの依存性を記述するためのフォーマット
- cpanfile-faq ... cpanfile FAQ
- Minilla ... CPANモジュールオーサリングツール
- Minilla::Tutorial ... Minilla チュートリアルドキュメント
- Dist::Milla::Tutorial ... Milla HOW TO
- App::cpanminus ... App::cpanminus
- cpanm ... cpanm コマンド
- Carton ... Carton
- plenv
- plenv-contrib
最初は、cpanfile とMinillaだけのつもりだったんですが CPAN::Meta::Specも文章中に出てくるし、Minilla::Tutorialは、Milla::Tutorialから取ってるとのことだし...ということで、Milla::Tutorialも訳しました...と、芋づる式に色々訳す結果となりました。
追記: cpanm も訳しました。
追記2: Carton も訳しました。
追記3: plenv & plenv-contrib も訳しました。