Nimからコマンドプロンプト/ターミナルを起動する

# 概要

* Nim(というか普通のプロセス)からコマンドプロンプト/ターミナルを起動するのはちょっと癖がありますよ

## Nimからプロセスを起動するには

Nimからプロセスを起動するには、[osproc](https://nim-lang.org/docs/osproc.html)モジュール内のプロシジャ **startProcess** を利用します。
なので、このstartProcessに、WindowsならC:\windows\system32\cmd.exeを指定すれば、コマンドプロンプトが表示されるかと思っていたのですが、このプロシジャだとウィンドウを伴った**コマンドプロンプトが起動せず**、起動元のコマンドプロンプト内でさらにコマンドプロンプトが起動されるという状況になっているようで、**意図した挙動になりません**でした。
そういや、コマンドプロンプトから別のコマンドプロンプトを起動するときに、startコマンドで起動していたなと思いだし、

```nim
discard startProcess("c:\\windows\\cmd.exe", "" , @["-K","start"]))
```

こんなコードを実行してみたのですが、これもうまくいかず・・・
はて、どうしたもんかな～ネットをさ迷っていますと、osモジュールの[**execShellCmd**](https://nim-lang.org/docs/os.html#execShellCmd%2Cstring)を使うといいよという記事を見かけ、こちらを試したところ、

```nim
discard execShellCmd("start")
```

意図通りの動作ができました。(やったね)
そもそも、execShellCmdってどんなソースかと見てみたところ、stdlib.hのsystem関数を呼んでいるだけでした。

```nim
proc c_system(cmd: cstring): cint {.
  importc: "system", header: "<stdlib.h>".}

proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
  tags: [ExecIOEffect].} =
  ## Executes a `shell command`:idx:.
  ##
  ## Command has the form 'program args' where args are the command
  ## line arguments given to program. The proc returns the error code
  ## of the shell when it has finished. The proc does not return until
  ## the process has finished. To execute a program without having a
  ## shell involved, use the `execProcess` proc of the `osproc`
  ## module.
  when defined(posix):
    result = c_system(command) shr 8
  else:
    result = c_system(command)
```

## 各OSでのターミナル実行

この調子で他のOS(Mac/Linux)も調べてみたところ、結果としてこうなりました。

| OS Type | execShellCmd | startProcess | 引き渡すコマンド  |
| :----------: | :----------: | :-----------: | :---------- |
| Windows | 〇 | | start |
| Mac | 〇 | | open Terminal.app |
| Linux | | 〇  | /usr/bin/gnome-terminal  |

Macの場合は、Windowsと同様にexecShellCmdでopenコマンドの引数にTerminal.app(もしくはiTerm.app等)を指定します。
片やLinuxでは、startProcessにgnome-terminalを指定すればターミナルを起動できました。さすがLinux。

## まとめ

今回の調査でいろいろとNimのソースを眺めることになり、以下のことに気づきました。

* NimのAPI仕様(ドキュメント)とソースは、よく眺めたほうがいい
* Nimのコードは、そもそもC/C++にトランスパイルされるので、Cのstdlib等のAPIもしっかり押さえておいたほうがいい
* 各OS対応って意外とめんどくさい

## おまけ

今回の記事ですが、こちらのツールを作っている時に調べたものです。

https://github.com/6in/workspace-switcher

任意の環境変数のセットをYamlで定義しておいて、環境変数が設定済みのターミナルを起動するというツールです。

もちろん、シェルやバッチファイル等で環境変数の再定義することも承知しているのですが、各OS毎に方法が異なるのも面倒だなと思い、Dockerまでとはいかずとも、環境変数の切り替えが簡単にできるものがあったら便利かもと思い、会社の温泉開発合宿で作ってみました。

AnyEnvやDirEnvも使ってはいるのですが、AnyEnvは各バージョンのインストーラとして利用して、バージョン切り替えはこのツールを使って切り替えようかなと思っています。DirEnvは、ディレクトリに移動した時点で環境変数の書き換えがおきますが、こちらのツールは、ディレクトリ関係ないのと、Windowsでも同じように動かせるのがメリットでしょうか。
(このツールもDirEnvをWindowsで動かせないかなーという発想から作っています)

Nimであればchoosenimもありますけど、**なんちゃらEnv疲れ**しているので、これもNimのインストーラ替わりですね。

9割9分ほど完成はしているので、興味のある方は試してみてください。

環境変数の定義(Yaml)はこんな感じになります。

* パス区切りの環境変数は、Yamlの配列表記でも記述できます
  * OSにより；か：で連結されます
* 配列定義の組み立ては上から順番に実施
* (変数名)はプレースホルダで、OS依存していません。(${変数名}やら%変数名%とか書かなくても良い)

```yaml:LEGACY.yml
env:
  JAVA_VER: 1.7.0-111
  JAVA_HOME: /opt/java/(JAVA_VER)
  MAVEN_HOME: /opt/maven/2.1
  PYTHON_PATH: /opt/python/2.4
  PATH:
    - (JAVA_HOME)/bin   # JAVA
    - (MAVEN_HOME)/bin  # MAVEN
    - (PYTHON_PATH)/bin # Python
    - (PATH)            # PATH引き継ぎ
```

上記のYaml(拡張子は不要)を指定してターミナルを起動する(wsはツールの名前)

```
ws shell LEGACY
```

上記のYamlを指定して、VSCodeを起動する

```
ws exec LEGACY /usr/bin/code "$PWD"
```

あと追加したい機能として、
* ターミナルを起動する前に、任意のNimScriptを呼び出して、前処理を記述
* プレースホルダのネームスペース対応(::変数名)とか(環境名::変数名)とか

という感じで、**おまけの方が本体**だったのと、Nimでツール作るのは**楽しい**ですよ。