試しに Deno で CSV から SQL 文を生成する処理を実装してみる

表題のよくありがちな簡単な作業があったので、 Deno を触ってみるのにいい題材かなと思い使ってみました。


## Deno のキャッチアップ
[Deno について知っていることと、今後への期待](https://qiita.com/euxn23/items/f3b45e3c093e448acf9b)
[Deno](https://deno.land)


## VSCode で Deno を書く準備
こちらの [denoの標準ライブラリをVS Codeでいい感じに補完するためにやった作業](https://qiita.com/syumai/items/b84f9508036b026bef26) という記事を参考に、 `deno --types` で生成できる標準モジュールの型定義と、`~/.deno/deps`以下にDLされる外部モジュールへパスを通して補完するようになりました。

また、上記記事の方がこの設定を生成するツール [denoinit](https://github.com/syumai/deno-libs/tree/master/denoinit) というのも作っています。



## 実装してみる

とりあえず何を使ったらいいかみてみました

- [API Reference](https://deno.land/typedoc/index.html)
- [Deno Standard Modules](https://github.com/denoland/deno_std)
- [Awesome-Deno](https://github.com/denolib/awesome-deno)


### コマンドの引数を取得する

[`args`](https://deno.land/typedoc/index.html#args) を使う方法

```ts:args.ts
import { args } from "deno";
console.log(args);
```

```sh
$ deno args.ts param1 param2
[ "args.ts", "param1", "param2" ]
```


### ファイルの内容を取得する

[`open`](https://deno.land/typedoc/index.html#open) を使う方法

```ts:open.ts
import { args, copy, open, stdout } from "deno";

(async () => {
  const file = await open(args[1]);
  await copy(stdout, file);
  file.close();
})();
```

[`readFile`](https://deno.land/typedoc/index.html#readfile) を使う方法

```ts:readFile.ts
import { args, readFile, stdout } from "deno";

(async () => {
  const data = await readFile(args[1]);
  stdout.write(data);
})();
```

```sh
$ deno open.ts example.csv
1,a
2,b
$ deno readFile.ts example.csv
1,a
2,b
```


### 他のコマンドを実行する
対象の CSV が Shift_JIS なので文字コードを変換したかったのですが、 iconv 的なモジュールはないようで、自作したくないので、とりあえず Linux コマンドの iconv を実行することにしました。

[`run`](https://deno.land/typedoc/index.html#run) を使って子プロセスを起動するようです。

```ts:iconv.ts
import { args, run, stdout } from "deno";

(async () => {
  const process = run({
    args: ["iconv", "-f", "sjis", "-t", "utf8", args[1]],
    stdout: "piped"
  });
  stdout.write(await process.output());
  process.close();
})();
```

実行する際に子プロセス実行許可オプションを付けます。

```sh
$ deno --allow-run iconv.ts example.csv
1,a,あ
2,b,い
```


### CSV をパースする

[`deno-fnparse`](https://github.com/hashrock/deno-fnparse)
どうやら年末にもくもく会があったみたいですが、ちょうど CSV パーサが作られていてよかったです。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">というわけで今日のもくもく会の成果。パーサコンビネータで書いたDeno用CSVパーサ。 <a href="https://twitter.com/hashtag/denots?src=hash&amp;ref_src=twsrc%5Etfw">#denots</a><a href="https://t.co/Y2tUznpP53">https://t.co/Y2tUznpP53</a></p>&mdash; はっしゅろっく (@hashedrock) <a href="https://twitter.com/hashedrock/status/1079018722881224704?ref_src=twsrc%5Etfw">2018年12月29日</a></blockquote>

```ts:parse.ts
import { args, run } from "deno";
import { parseCsv } from "https://denopkg.com/hashrock/deno-fnparse/parsers/csv.ts";

(async () => {
  const process = run({
    args: ["iconv", "-f", "sjis", "-t", "utf8", args[1]],
    stdout: "piped"
  });
  const csv = new TextDecoder().decode(await process.output());
  process.close();

  const rows = parseCsv(csv);
  console.log(rows);
})();
```

```sh
$ deno --allow-run parse.ts example.csv
[ [ "1", "a", "あ" ], [ "2", "b", "い" ] ]
```


### 一通り書いたので実行可能にする

Shebang を付ける

```ts:convert.ts
#!/usr/bin/env deno --allow-run

import { args, run, stdout } from "deno";
import { parseCsv } from "https://denopkg.com/hashrock/deno-fnparse/parsers/csv.ts";

(async () => {
  const process = run({
    args: ["iconv", "-f", "sjis", "-t", "utf8", args[1]],
    stdout: "piped"
  });
  const csv = new TextDecoder().decode(await process.output());
  process.close();

  const rows = parseCsv(csv) as string[][];

  const table = args[1].slice(0, -4);
  const encoder = new TextEncoder();
  rows.forEach(row => {
    stdout.write(
      encoder.encode(`INSERT INTO ${table} VALUES ('${row.join("', '")}');\n`)
    );
  });
})();
```

```sh
$ chmod +x convert.ts
$ ./convert.ts example.csv
INSERT INTO examples VALUES ('1', 'a', 'あ');
INSERT INTO examples VALUES ('2', 'b', 'い');
```

以上です。
下記のようにディスカッションされていますが、この手の変換処理は本当は Reader / Writer ベースで処理を書いた方が望ましいと思うので宿題にしようと思います。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">StreamはNodeにしか無いので、Denoだとio packageを使ってReader / Writerでデータをやり取りしていく感じですね…！</p>&mdash; しゅーまい (@__syumai) <a href="https://twitter.com/__syumai/status/1080745542991327234?ref_src=twsrc%5Etfw">2019年1月3日</a></blockquote>


## 今回のソースコード
https://github.com/boiyaa/deno-example-csvsql