ちょっとしたswagger spec(yaml, json)の取り扱いというか変換というか
過去に自分が作ったやつの使い方について。もう少しマシなものを考えてみたのでメモ。
yaml上のデータから一部分を取り出したい
こういうyamlがあるとする。swagger specの一部のようなもの。people,person,name,ageが定義されている。
# definitions.yaml definitions: people: items: $ref: "#/definitions/person" person: properties: name: $ref: "#/definitions/name" age: $ref: "#/definitions/age" name: type: string description: 名前 age: type: integer maximum: 100
この定義に$refによる参照が使われている。この$refを取り除くためにderefというコマンドを作っていた。
$ jsonknife deref --src definitions.yaml
結果はこうなる。
definitions: people: items: properties: name: type: string description: 名前 age: type: integer maximum: 100 person: properties: name: type: string description: 名前 age: type: integer maximum: 100 name: type: string description: 名前 age: type: integer maximum: 100
全てが展開される。この内personだけが欲しい場合には --ref
を使う。
$ jsonknife deref --src definitions.yaml --ref "#/definitions/person"
こういう結果になる。
properties: name: type: string description: 名前 age: type: integer maximum: 100
取り出した結果をwrapしたい場合
--ref
によりyamlのデータの中から一部だけ取り出せはしたものの。 #/definitions/person
の位置にあるデータを取り出すということになってしまう。
今までこういう時に再度 definitions/person でラップするために、別のコマンドに渡していた。以下の様な形。
$ jsonknife deref --src definitions.yaml --ref "#/definitions/person" | dictknife transform --code 'lambda d: {"definitions": {"PERSON": d}}'
結果はこう。
definitions: PERSON: properties: name: type: string description: 名前 age: type: integer maximum: 100
もちろん、結果は期待通りなのだけれど。わざわざこの種の変換を手書きするのも面倒くさい。単に包むだけなのだからオプションにしてしまえば良い。元の階層に復元する処理をwrapと呼ぶとすればrefで取り出すことはunwrapにほかならない。そんなわけでwrapとunwrapで指定できるようにしてみると良いかもしれない。
$ jsonknife deref --src definitions.yaml --unwrap "#/definitions/person" --wrap "#/definitions/PERSON"
definitions: PERSON: properties: name: type: string description: 名前 age: type: integer maximum: 100
複数の値を同時に返す場合
全体に対するある部分の内、複数の部分の値が欲しい場合がある。例えば、nameとageを名前付きで欲しい場合。すごく真面目にやるならconcatを使う形になる。 プロセス置換なども使ってすごく頑張った見た目になるけれど。これで上手くいく。
$ dictknife concat <(jsonknife deref --src definitions.yaml --unwrap "#/definitions/name" --wrap "name") <(jsonknife deref --src definitions.yaml --unwrap "#/definitions/age" --wrap "age")
name: type: string description: 名前 age: type: integer maximum: 100
ところで、--ref
は複数渡せた。過去には --with-name
というオプションがあり。これによって名前でラッピングされた結果がマージされて返されていた(今は存在していないオプション)。上と同様のものを以下の様に書けた。ただ元の名前でラップするだけなので全くおんなじというわけではない。
# 今は動かない jsonknife deref --src definitions.yaml --ref "#/definitions/name" --ref "#/definitions/age" --with-name
ここでdefinitionsで包みたい場合に結局またdictknife transformによる同様の変換が必要になる。またwrap,unwrapの対応を数を数えてペアにするというのも馬鹿馬鹿しい。結局以下の様に書けるようにした。unwrapとwrapのペアを@でつなげるという記法。
jsonknife deref --src definitions.yaml --ref "#/definitions/name@#/definitions/NAME" --ref "#/definitions/age@#/definitions/AGE"
結果はこう。
definitions: AGE: type: integer maximum: 100 NAME: type: string description: 名前
packageを指定してのgoコードの生成に対するgofmt(goimports)について
おそらくニッチな話になってしまっているけれど。メモ。
はじめに
goawayを使ってコード生成をしたときの典型的なコードの利用方法は以下の様な形になる。
$ python myscript.py --package=github.com/podhmo/myscript --position=. # 実際にGOPATHに対応した位置に生成したい場合には --positionを外す # python myscript.py --package=github.com/podhmo/myscript
ここで生成されるファイルのパスはpythonスクリプトに委ねられる。一方で生成されるファイルに対してgofmtやgoimportsを適用したい。 解決策は2つ
今回は後者について考えてみた
典型的なコード
goを生成する典型的なコードは以下の様な感じになる。
import logging from goaway import get_repository def run(package_path, position): r = get_repository() package = r.package(package_path) f = package.file("person.go") person = f.struct("person") person.define_field("name", f.string) f = package.file("group.go") group = f.struct("group") group.define_field("name", f.string) group.define_field("members", person.slice) d = r.resolve_package_path(position, package) r.emitter.emit_package(package, d=d) def main(): import argparse parser = argparse.ArgumentParser() parser.add_argument("--package", default=None) parser.add_argument("--position", default=None) args = parser.parse_args() logging.basicConfig(level=logging.INFO) run(args.package, args.position) if __name__ == "__main__": main()
これは実行すると以下の様なperson.goとgroup.goを生成する。
$ python main.py --package="github.com/podhmo/goaway/onemit" --position=. INFO:goaway.emitter:write: ./onemit/person.go INFO:goaway.emitter:write: ./onemit/group.go
person.go
package onemit type person struct { name string }
group.go
package onemit type group struct { name string members []person }
このpythonに委ねられて生成されるgoのファイルにどうやってgofmt(goimports)を適用しようかという話。
on emit hook
とりあえず雑な対応としてonemitというhookを取るようにした。
@@ -2,6 +2,10 @@ from goaway import get_repository +def onemit(f, fname): + print("gofmt -w {}".format(fname)) + + def run(package_path, position): r = get_repository() @@ -17,7 +21,7 @@ group.define_field("members", person.slice) d = r.resolve_package_path(position, package) - r.emitter.emit_package(package, d=d) + r.emitter.emit_package(package, d=d, onemit=onemit) def main():
onemitのhookでは生成されるパス名が手に入るのでここでおもむろにgofmtを実行するコードをprintしてあげるようにする。 そして以下の様な感じでリダイレクトした結果をbashなどで実行して適用する。
$ python main.py --package="github.com/podhmo/goaway/onemit" --position=. > fmt.sh INFO:goaway.emitter:write: ./onemit/person.go INFO:goaway.emitter:write: ./onemit/group.go $ bash -x fmt.sh + gofmt -w ./onemit/person.go + gofmt -w ./onemit/group.go
hookのところでsubprocessを作っても良いけれど。この方が楽な気がした。