テキストファイルをJSON用に変換するスクリプトをPythonで作ってみた(vscodeのユーザースニペット用)

## はじめに
現在web制作の勉強をしているのですが、VSCodeでユーザースニペットを登録する時にいちいち行頭や行末にダブルクォーテーション付けたりしないといけないのが面倒だったので、
ファイルの中身をJSONで使いやすい形に変換してくれるpythonスクリプトを自作してみました。
(webなのにJavaScriptじゃないのは、そっちはまだそこまで慣れていないからです汗)

## ソースコード
convはconversionの略です

```python:json_conv.py
'''
対象のファイル内のテキストを、JSONの値として使えるように変換するスクリプト。
(i)全ての「"」の後ろに「\\」を追加し、
(ii)全てのタブ入力をタブ記号(\\t)に置換し、
(iii)すべての行頭に3個のタブ入力と「"」、行末に「",」を追加し、
(iv)文書の一番最後に「"」を追加する。

第一引数のファイル(input_file)を元に変換し、その結果を第二引数のファイル(output_file)に書き込む。
（output_file を指定しないと、input_file をそのまま上書きする）

モジュールとして同名の関数をインポートすることもできる。

(コマンドの書き方)
> python -m json_conv.py (input_file) (output_file(任意))

'''


def json_conv(input_file, output_file=None, n_tab=3):
    '''
    対象のファイル内のテキストを、JSONの値として使えるように変換する関数。
    (i)全ての「"」の後ろに「\\」を追加し、
    (ii)全てのタブ入力をタブ記号(\\t)に置換し、
    (iii)すべての行頭に(n_tab)個のタブ入力と「"」、行末に「",」を追加し、
    (iv)文書の一番最後に「"」を追加する。

    行頭に挿入するタブ入力の数(n_tab)のデフォルトは3。

    input_file を元に変換し、その結果を output_file に書き込む。
    （output_file を指定しないと、input_file をそのまま上書きする）
    '''

    # output_file を指定しない場合、input_fileを出力先に設定する
    if output_file is None:
        output_file = input_file

    with open(input_file, mode="r", encoding="utf-8") as file:
        processed_txt = ""  # 出力用の文字列を定義

        for line in file:
            replaced_line = line.replace('\"', '\\\"')            # 文書中の「"」を「\"」に置換
            replaced_line = replaced_line.replace("\t", "\\t")    # タブ入力をタブ記号(\t)に置換
            replaced_line = replaced_line.replace('\n', '\",\n')  # 改行の直前(=行末)に「",」を挿入
            processed_line = '\t' * n_tab + '\"' + replaced_line  # 行頭に(n_tab)個のタブと「"」を挿入（引数にn_tabを指定しなければ 3個のタブを挿入）
            processed_txt += processed_line                       # 処理済みの行を出力用の文字列に追加
        
        processed_txt += "\""                                     # 文書の一番最後(=\nがない行末)に「"」を追加

    print("\n保存先 : " + output_file + "\n")
    print("変換後の文書")
    print("-----------------------------------------\n")
    print(processed_txt)
    print("\n-----------------------------------------\n")

    while True:
        # 指定したファイルに書き込んで本当に良いか聞かれる
        answer = input("指定したファイルに保存しても本当によろしいですか？（既に存在する場合は上書きされます）(y/n) ")

        # yを選択した場合、output_file に指定したファイルに変換後の文書を書き込む(デフォルトでは input_file を上書きする)
        if answer == 'y':
            with open(output_file, mode="w", encoding="utf-8") as file:
                file.write(processed_txt)
            break

        # nを選択した場合、書き込まずに終了する
        if answer == 'n':
            break

        # それ以外を入力した場合は再度聞かれる
        else:
            continue



# コマンドラインで実行した場合の処理
if __name__ == "__main__":
    import sys

    try:
        selected_input = sys.argv[1]  # コマンドラインの第1引数を、変換元のファイルとして指定

    # コマンドライン引数を渡していない場合、エラーを発生させる
    except IndexError:
        raise TypeError("入力ファイルが指定されていません")


    try:
        selected_output = sys.argv[2]     # コマンドラインの第2引数を、変換後ファイルの保存先に指定
        json_conv(selected_input, selected_output)   # 変換用の関数を実行

    except IndexError:
        json_conv(selected_input)    # 第2引数を指定しない場合、第1引数のみを渡して関数を実行する(変換元のファイルを上書きする)
```

1. 全ての`"`を`\"`に置換
2. 全てのタブ空白を`\t`に置換
3. すべての行の行頭に3個のタブ空白と`"`、行末に`",`を追加
4. 文書の一番最後に`"`を追加

という処理を順に行います。

JSONではタブによる空白が入っているとエラーが出たので、タブを`\t`に置換しています。`"`を最初にエスケープしているのは、そうしないと`"`による各行の囲みがおかしくなるからです。

またdocstringでは、vscodeで関数にマウスホバーさせた時の表示を正しくするために`\`の後ろにさらに`\`を付けてエスケープしています。

一応モジュールとしても使えるように関数として定義しました。
関数の方では`n_tab`を指定することで行頭に挿入されるタブ空白の数を調節できるようにしましたが、コマンドラインでの実装までは至らず・・・
いつかその気になったらコマンドラインでも調節できるようにするかもしれません。


## 実際に使ってみた

### 実行環境

- OS : windows10 64bit
- Python : Python 3.7.4 
- Anaconda : conda 4.8.3
- シェル : Anaconda Power Shell (Anacondaに対応した Windows Power Shell)
- 開発環境 : Visual Studio Code

Visual Studio Codeのターミナルとして Anaconda Power Shellを使い、Anacondaのbase環境で実行しています。
(Visual Studio Code や Anaconda を使わなくても、コマンドプロンプトや Windows Power Shellなどのシェル上で pythonが使えれば多分問題なく動くと思います。)


### 前準備
json_conv.py をモジュールパスが通してあるディレクトリに置き、モジュールとしてimportできるようにしておきます。

パスの通し方が分からない人は、以下の記事が参考になると思います。
[Windows10でTempやPathなどの環境変数を設定する方法 | サービス | プロエンジニア](https://proengineer.internous.co.jp/content/columnfeature/5205)
[【Python環境構築】環境変数について解説！ | WEBCAMP NAVI](https://web-camp.io/magazine/archives/19240)

私は自作スクリプトをまとめて置くディレクトリを1つ作り、PYTHONPATHに自作スクリプト用ディレクトリのパスを追加して使っています。

### 実践

例えばこんなコードを書いたとします。

```html:test.html
<ul class="_list">
	<li class="_listItem">
		<div class="_listWrapper">
			<img class="_listImg" src="" alt="">
			<div class="_listContent">
				
			</div>
		</div>
	</li>
</ul>
```

シェルを開き、cdコマンドで test.html のあるディレクトリに移動した後、次のコマンドを実行します。
（`python -m`はPythonのモジュールをスクリプトとして実行するためのコマンドです。モジュール名なので json_conv の後に .py は付かないことに注意してください。）

```
python -m json_conv test.html
```

すると、以下のような画面がシェルで表示されます。

```
保存先 : test.html

変換後の文書
-----------------------------------------

                        "<ul class=\"_list\">",
                        "\t<li class=\"_listItem\">",
                        "\t\t<div class=\"_listWrapper\">",
                        "\t\t\t<img class=\"_listImg\" src=\"\" alt=\"\">", 
                        "\t\t\t<div class=\"_listContent\">",
                        "\t\t\t\t",
                        "\t\t\t</div>",
                        "\t\t</div>",
                        "\t</li>",
                        "</ul>"

-----------------------------------------

本当に指定したファイルに保存してよろしいですか？（既に存在する場合は上書きさ
れます）(y/n)
```

ここで`n`を入力するとそのまま何もせずにプログラムを終了します。
`y`を入力した場合、test.htmlは上書きされ、以下のようになります。

```html:test.html
			"<ul class=\"_list\">",
			"\t<li class=\"_listItem\">",
			"\t\t<div class=\"_listWrapper\">",
			"\t\t\t<img class=\"_listImg\" src=\"\" alt=\"\">",
			"\t\t\t<div class=\"_listContent\">",
			"\t\t\t\t",
			"\t\t\t</div>",
			"\t\t</div>",
			"\t</li>",
			"</ul>"
```
このように変換することで、あとはコピペするだけでJSONの値として使えるようになるので、簡単にVSCodeのユーザースニペットが作れます。

```json
	 "Print to console": {
	 	"prefix": "log",
	 	"body": [
	 		"console.log('$1');",
	 		"$2"
	 	],
	 	"description": "Log output to console"
	 }
```
html.jsonの最初に書いてある、↑この雛形のbody部分を先ほどの
変換したコードで置き換えて、ついでにprefixとdescriptionも書き換えると・・・

```json:html.json
	 "list with image": {
	 	"prefix": "listImg",
	 	"body": [
			"<ul class=\"_list\">",
			"\t<li class=\"_listItem\">",
			"\t\t<div class=\"_listWrapper\">",
			"\t\t\t<img class=\"_listImg\" src=\"\" alt=\"\">",
			"\t\t\t<div class=\"_listContent\">",
			"\t\t\t\t",
			"\t\t\t</div>",
			"\t\t</div>",
			"\t</li>",
			"</ul>"	 	],
	 	"description": "a list, each of its row has an image and texts."
	 }
```
これで完成です。
このように、いちいち手入力で`"`を書いたりエスケープしなくても、1つのコマンドを実行するだけで簡単にユーザースニペットを作ることができます。

ユーザースニペットが作りたくなったら、適当なファイルを作ってそこにコードを書きこのコマンドを実行すれば作れます。

# まとめ
今回は、書いたコードをJSON用に変換するためのpythonスクリプトを紹介しました。
基本的には自分用に作ったので、何かおかしい所があったらごめんなさい汗

今回はJSON用に作りましたが、

```python
for line in file:
            replaced_line = line.replace('\"', '\\\"')            # 文書中の「"」を「\"」に置換
            replaced_line = replaced_line.replace("\t", "\\t")    # タブ入力をタブ記号(\t)に置換
            replaced_line = replaced_line.replace('\n', '\",\n')  # 改行の直前(=行末)に「",」を挿入
            processed_line = '\t' * n_tab + '\"' + replaced_line  # 行頭に(n_tab)個のタブと「"」を挿入（引数にn_tabを指定しなければ 3個のタブを挿入）
            processed_txt += processed_line                       # 処理済みの行を出力用の文字列に追加
        
        processed_txt += "\""                                     # 文書の一番最後(=\nがない行末)に「"」を追加
```

実際に文の処理を行っているここを書き換えれば、他の変換にも流用できると思います。

いつかより汎用性を高めたバージョンも作るかもしれません。