jqfpyにloadfile(),dumpfile()を追加していた

github.com

loadfile(),dumpfile()がほしくなったので追加した。

jqfpy

jqfpyは、

jsonをparseするためにDSLを覚えるのがめんどくさい(jq)。 各自自分の慣れた言語のワンライナーで十分なのでは?

という発想のもとのpackage(自分はpython)。

重要なことは誰でも最初から使えるということ(pythonの知識があるなら)。

例えば以下のようなファイルがある時に、category情報の一覧を取り出したいときは以下の様に書く。

flask.json

{
  "data": [
    {
      "category": "2.6",
      "downloads": 5287
    },
    {
      "category": "2.7",
      "downloads": 2274964
    },
    {
      "category": "3.2",
      "downloads": 23
    },
    {
      "category": "3.3",
      "downloads": 723
    },
    {
      "category": "3.4",
      "downloads": 120625
    },
    {
      "category": "3.5",
      "downloads": 899583
    },
    {
      "category": "3.6",
      "downloads": 4135489
    },
    {
      "category": "3.7",
      "downloads": 787076
    },
    {
      "category": "3.8",
      "downloads": 3190
    },
    {
      "category": "null",
      "downloads": 44777
    }
  ],
  "package": "flask",
  "type": "python_minor_downloads"
}

これをjqfpyで取り出すのは以下のような形。

$ jqfpy -c '[row["category"] for row in get("data")]' flask.json
["2.6", "2.7", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "null"]

ただのリスト内包表記。ただのリスト内包表記なので誰でも書ける。 get() は渡されたファイルをloadした結果を返す。loadした結果をどうこうするということなので、単にdictをイジるだけの操作ということになる。

ショートカット

とはいえ毎回書くのがめんどくさいと思うこともあるのでショートカットを用意したくなる。

$ jqfpy -c 'get("data[]/category")' flask.json
["2.6", "2.7", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "null"]

個人的にはなるべく対応関係を意識せずにすむようにしたいので、JSON Referenceに近いかたちの表記でアクセスできるようにしている ("[", "]"などは対応関係を意識する必要がある)。

複数ファイル

複数のファイルが渡されていたときの挙動。これはおそらくjqなどと同様。

$ jqfpy -c 'get("data[]/category")' flask.json django.json
["2.6", "2.7", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "null"]
["2.6", "2.7", "3.2", "3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "null"]

というところまでが復習。

loadfile()

ところで、直接JSONデータを渡すのではなくファイル名を渡したい場合がある、そして複数のファイル名を組み合わせたJSONを元に変換を書きたいことがある。そんなわけで loadfile()が欲しくなった。

$ echo '["django.json", "flask.json"]' | jqfpy '{name: h.loadfile(name) for name in get()}'
{
  "django.json": {
    "data": [
      {
        "category": "2.6",
        "downloads": 1320
      },
      {
        "category": "2.7",
        "downloads": 762206
      },
      {
        "category": "3.2",
        "downloads": 24
      },
      {
        "category": "3.3",
        "downloads": 381
      },
      {
        "category": "3.4",
        "downloads": 56461
      },
      {
        "category": "3.5",
        "downloads": 250227
      },
      {
        "category": "3.6",
        "downloads": 1078975
      },
      {
        "category": "3.7",
        "downloads": 595086
      },
      {
        "category": "3.8",
        "downloads": 1764
      },
      {
        "category": "null",
        "downloads": 30137
      }
    ],
    "package": "django",
    "type": "python_minor_downloads"
  },
  "flask.json": {
    "data": [
      {
        "category": "2.6",
        "downloads": 5287
      },
      {
        "category": "2.7",
        "downloads": 2274964
      },
      {
        "category": "3.2",
        "downloads": 23
      },
      {
        "category": "3.3",
        "downloads": 723
      },
      {
        "category": "3.4",
        "downloads": 120625
      },
      {
        "category": "3.5",
        "downloads": 899583
      },
      {
        "category": "3.6",
        "downloads": 4135489
      },
      {
        "category": "3.7",
        "downloads": 787076
      },
      {
        "category": "3.8",
        "downloads": 3190
      },
      {
        "category": "null",
        "downloads": 44777
      }
    ],
    "package": "flask",
    "type": "python_minor_downloads"
  }
}

--here

相対位置をどのように解釈するかのoptionを追加した。defaultはcurrent working directoryからの相対位置。--here を付けると、その位置からの相対位置になる。

# ./x.jsonを見る
$ jqfpy 'h.loadfile("x.json")' a/b/main.json

# ./a/b/x.jsonを見る
$ jqfpy --here a/b/ 'h.loadfile("x.json")' a/b/main.json

--relative-path

--relative-path を付けると渡されたファイルからの相対位置になる。

# ./a/b/x.jsonを見る
$ jqfpy --relative-path 'h.loadfile("x.json")' a/b/main.json

dumpfile()

loadfile() と同様に dumpfile() もある。こちらは逆に複数のファイルに分割して出力したくなることがある。このとき出力は不要なので /dev/null にリダイレクトしたくなるかもしれない。

$ echo '["flask.json", "django.json"]' | jqfpy '[h.dumpfile(get("data[]/category", d=h.loadfile(name)), f"""{name.replace(".json", ".path")}""") for name in get()]; None'
null
$ ls *.path
django.path  flask.path

python3.8 だとさらに便利に。