Nim言語で任意の深さでJSONを整形表示するコマンドを作る

# 概要

Nim言語の勉強がてら、JSONの深さを指定して整形表示するツールを作ってみました。

この記事では、Nim言語の布教も兼ねて、
整形表示ツールの制作過程で見つかったNim言語の良さを共有させていただきたいと思います。

## 完成したツールのデモンストレーション

まず、いきなりですが、完成したコマンドのデモを以下に示したいと思います。

```text:完成したツールのデモ
$ ./jsonfmt --depth 0 '{"num1":4,"num2":3,"data1":{"length":26,"text":"AAAAA","data2":{"num3":1}}}'
{"num1":4,"num2":3,"data1":{"length":26,"text":"AAAAA","data2":{"num3":1}}}

$ ./jsonfmt --depth 1 '{"num1":4,"num2":3,"data1":{"length":26,"text":"AAAAA","data2":{"num3":1}}}'
{
  "num1": 4,
  "num2": 3,
  "data1": {"length":26,"text":"AAAAA","data2":{"num3":1}}
}

$ ./jsonfmt --depth 2 '{"num1":4,"num2":3,"data1":{"length":26,"text":"AAAAA","data2":{"num3":1}}}'
{
  "num1": 4,
  "num2": 3,
  "data1": {
      "length": 26,
      "text": "AAAAA",
      "data2": {"num3":1}
  }
}

$ ./jsonfmt --depth 3 '{"num1":4,"num2":3,"data1":{"length":26,"text":"AAAAA","data2":{"num3":1}}}'
{
  "num1": 4,
  "num2": 3,
  "data1": {
      "length": 26,
      "text": "AAAAA",
      "data2": {
            "num3": 1
      }
  }
}
```


JSONを木構造として見立てた時に、フォーマットする最大の木の深さを`--depth`オプションで指定できるようにしています。
`--depth 0`を指定するとただ単にJSONを圧縮する処理になり、`--depth`を1以上にすると木の`depth`以下の階層で整形するようにしています。

Github: https://github.com/cacapouh/jsonfmt

こちらのコマンドはHomebrewでインストールできるようにしていますので、
もしよかったら使ってみてください m(_ _)m

```
$ brew tap cacapouh/jsonfmt
$ brew install cacapouh/jsonfmt/jsonfmt
```

# Nimの良さ

## cligenが最強

いきなり、Nim言語じゃなくて、Nim言語のライブラリの話なって恐縮ですが、cligenというライブラリについて共有させていただきます。
こちらのライブラリは、コマンドライン引数の受け取りの実装で使用しました。

使い方は簡単で cligenで提供されている`dispatch`関数の引数に自作の関数を渡してやるだけで、CLIツールとして使えるようになります。

例えば、以下のような実装をした場合、

```nim:example.nim
import std/strutils

proc nimEcho(repeat: int = 1, args: seq[string]): int =
    echo args[0].repeat(repeat).join()
    0 # exit code

when isMainModule:
    import cligen
    dispatch(nimEcho)
```

以下のようにCLIツールとして呼び出すことができます。

```
$ ./example "hoge" 
hoge

$ ./example --repeat 10 "hoge"
hogehogehogehogehogehogehogehogehogehoge
```

`cligen`というライブラリはかなり強力で、
タイポした際に指摘してくれたり、自動で`--help`オプションをサポートしてくれたりします。


```
$ ./example --repleat 10 "hoge"
Unknown long option: "repleat"

Maybe you meant one of:
        repeat

Run with --help for full usage.

$ ./example --help
Usage:
  nimEcho [optional-params] [args: string...]
Options:
  -h, --help              print this cligen-erated help
  --help-syntax           advanced: prepend,plurals,..
  -r=, --repeat=  int  1  set repeat
```

最強ですね 🤩

## メソッドチェーンの仕組みが優秀

Nimでは以下のように、ドット演算子で関数呼び出しをすることができます。

```nim
proc addOne(num: int): int =
    num + 1

echo 10.addOne # 11
echo addOne(10) # 11（普通に呼び出した場合）
```

標準ライブラリの呼び出しにも、このドット演算子を利用することができまして、
簡単にメソッドチェーンを構築することができます。

具体例を挙げますと、JSON整形ツールでは、以下のような実装でJSONの各キーバリューを整形しています。
雰囲気だけ伝われば幸いですが、メソッドチェーンを駆使した実装になっていまして、
(ワンライナーで複雑な処理を書くのは、考えようですが)かなり楽に実装することができました。

```nim
let kvs = jsonFileds.toSeq.mapIt(convert(it)).mapIt(&"{nextState.indent()}{it}").join(",\n")
&"{{\n{kvs}\n}}"
```

## 軽量プログラミング言語 & 静的型付け言語

Pythonと比較した話になりますが、
型が静的で、コンパイルするだけで軽いテストになります故かなり開発しやすかったです。

軽量プログラミング言語(LL)のような側面を持ちながら、静的型付け言語の恩恵を受けられるというが嬉しいですね。

Nimはジェネリクスも利用可能でして、型を駆使した実装もやれそうです。

```nim:ジェネリクスも利用可能
proc identity[T](value: T): T =
    value

echo identity "あああ" # 「あああ」と標準出力される
```

## オブジェクトの生成も簡単

Nim言語はオブジェクト指向もサポートしていまして、簡単にオブジェクトを生成することができます。
以下はオブジェクトを使ったサンプルコードですが、やはり新しめの言語ということもあって、この辺の文法もスッキリしていますね :thought_balloon: 

```nim
import std/strformat

type Language = object
    name: string
    version: string

proc show(language: Language): string = &"{language.name} {language.version}"

let scala: Language = Language(name: "Scala", version: "3.0.2")

echo show(scala) # Scala 3.0.2
```

継承を使用する場合は以下のように `of`キーワードで継承元を指定できます。

```nim
type
    Animal = object of RootObj
        age: int
    Cat = object of Animal # Animalというオブジェクトが継承元
        owerName: string
```

## ビルド/実行がシンプル

最後にちょっと微妙な内容かもしれないですが、ビルドして実行可能なバイナリファイルができる上がるというのが、
シンプルでいいですね。

```text:ビルドして実行可能なjsonfmtファイルを生成する
$ nim c jsonfmt.nim
Hint: used config file '/Users/tobita_yoshiki/Nim/config/nim.cfg' [Conf]
Hint: used config file '/Users/tobita_yoshiki/Nim/config/config.nims' [Conf]
......................................................................................................................
CC: ../../Nim/lib/system.nim
CC: ../../Nim/lib/pure/collections/tables.nim
CC: ../../Nim/lib/pure/strutils.nim
CC: ../../Nim/lib/pure/strformat.nim
CC: ../../Nim/lib/pure/os.nim
CC: ../../.nimble/pkgs/cligen-1.5.31/cligen/parseopt3.nim
CC: ../../.nimble/pkgs/cligen-1.5.31/cligen/textUt.nim
CC: ../../.nimble/pkgs/cligen-1.5.31/cligen/argcvt.nim
CC: ../../.nimble/pkgs/cligen-1.5.31/cligen/sysUt.nim
CC: jsonfmt.nim
Hint:  [Link]
Hint: gc: refc; opt: none (DEBUG BUILD, `-d:release` generates faster code)
84615 lines; 0.864s; 116.832MiB peakmem; proj: /Users/tobita_yoshiki/work/jsonfmt/jsonfmt.nim; out: /Users/tobita_yoshiki/work/jsonfmt/jsonfmt [SuccessX]

$ ls
README.md       build           jsonfmt         jsonfmt.nim
```