JS初心者がDNSホスト情報変換ツールを作ってみた

自宅内でお遊び用にローカル用のDNSサーバーを設置してるのですが、設定ファイルの管理が面倒なのでGoogleスプレットシートのデータから設定情報に変換できるツールを作ってみました。
どこでも実行できるように完全初心者ですがHTML+JavaStipt+JQueryで作成しています。
特にJSはコーディングが拙い部分があると思いますがご容赦ください。

# つくりたいもの

![スプレッドシート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524454/30491a17-867e-5f09-0c37-f4a07c096104.png)
上のようにホストをスプレッドシートで管理をし、そのデータから以下のような設定ファイルを生成します。
とりあえず自宅で使っているunboundやルーターの簡易DNSサーバー機能向けのコンフィグに対応させます。

```:例：unbound
server:
    local-data: "www.hoge.home. IN A 192.168.10.30"
    local-data: "www.hoge.home. IN AAAA fe80::10"
    local-data: "ns.hoge.home. IN A 192.168.10.5"
    local-data: "mail.hoge.home. IN A 192.168.10.35"
```

```:例：NEC IXルーター
dns host www.hoge.home ip 192.168.10.30
dns host www.hoge.home ip fe80::10
dns host ns.hoge.home ip 192.168.10.5
dns host mail.hoge.home ip 192.168.10.35
```

# 画面の作成

HTMLで適当に画面を作ります。
デザイン性皆無ですが、余力ができたらCSSで装飾したいと思います。
```html:index.html(抜粋)
<body>
  <h1>ホスト情報変換ツール</h1>
  <div>
    <h2>入力</h2>
    <div>
      <label for="input-format">形式</label>
      <select name="input-format" id="input-format">
      </select>
    </div>
    <div>
      <textarea name="input-text" id="input-text" cols="64" rows="24"></textarea>
    </div>
    <button id="translate">変換</button>
  </div>
  <div>
    <h2>出力</h2>
    <label for="output-format">形式</label>
    <select name="output-format" id="output-format">
    </select>
-- 省略 --
    <div>
      <textarea name="output-text" id="output-text" cols="64" rows="24"></textarea>
    </div>
  </div>

  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
    crossorigin="anonymous"></script>
  <script src="./js/hostconv.js"></script>
</body>
```

# スクリプト

画面の対応形式一覧はJSの`formatList`オブジェクトにまとめてJQeury使ってリスト候補を更新させます。対応形式ごとに入出力用の関数を作り、`translate`関数から`formatList`に登録してある担当の関数を呼び出します。

```javascript:全体像
const formatList = {
    json: {
        name: 'JSON',
        input: null,
        output: (data) => { return JSON.stringify(data) }
    },
    spreadsheet: {
        name: 'スプレッドシート',
        input: inputSpreadsheet,
        output: outputSpreadsheet
    },
    hosts: {
        name: 'hosts',
        input: null,
        output: outputHosts
    },
    unbound: {
        name: 'unbound',
        input: null,
        output: outputUnbound,
        outOption: '#outopt-unbound'
    },
    ix: {
        name: 'IX',
        input: null,
        output: outputIX,
        outOption: '#outopt-ix'
    }
};

$(() => {
    //形式選択のコンボボックスに選択肢追加
    Object.entries(formatList).forEach(format => {
        const option = $('<option></option>', { value: format[0], text: format[1].name });
        if (format[1].input) $('select#input-format').append(option.clone());
        if (format[1].output) $('select#output-format').append(option.clone());
    });

    // --- 省略 ---

    $('button#translate').click(translate); //変換ボタンが押されたら変換
})

function translate() {
    const inputText = $('textarea#input-text').val();
    const inputFormat = $('#input-format').val();
    const outputFormat = $('#output-format').val();

    if (inputFormat in formatList && formatList[inputFormat].input &&
        outputFormat in formatList && formatList[outputFormat].output) {
        //読み込み
        const data = formatList[inputFormat].input(inputText);
        //出力
        const outputText = formatList[outputFormat].output(data);
        $('textarea#output-text').val(outputText);
    }
}

```

## データ読み込み

入力されたテキストから以下の形式のオブジェクトに変換します。
```javascript:オブジェクト
[
  { domain: 'ドメイン名', ipv4: 'X.X.X.X', ipv6 : 'XXXX:XX::X'},
  { domain: 'hoge.example.com', ipv4: '10.1.1.1', ipv6 : ''},
  .....,
  ....
]
```

スプレッドシートやExcelのデータをコピペするとタブ区切りになるので、`split`で改行とタブ文字で分割しオブジェクトに代入しています。一応、アドレス形式が合ってるか正規表現でチェックする関数を作り、不正なデータは弾いてます。

```javascript:読み込み
function inputSpreadsheet(str) {
    return str.split(/\r\n|\n/)
        .map(line => line.split('\t'))
        .map(v => ({ domain: v[0], ipv4: v[1], ipv6: v[2] }))
        .filter(v => (checkDomain(v.domain)) && (checkIPv4(v.ipv4) || !v.ipv4) && (checkIPv6(v.ipv6) || !v.ipv6) && (v.ipv4 || v.ipv6));
}
```

## データ出力

unboundのlocal-data(-ptr)の出力部分です。先ほど作成したオブジェクトの情報もとに設定テキストを生成します。逆引きのコンフィグの出力はON/OFF切り替えできるようにしています。また、出力テキストをそのまま設定ファイルにコピペできるようにインデントを入れられるようにもしています。
```javascript:unbound出力
function outputUnbound(data) {
    const indentList = {
        none: '',
        tab: '\t',
        space2: '  ',
        space4: '    ',
    };

    const indent = indentList[$('#unbound-indent').val()] || '';
    const reverse = $('#unbound-reverse').prop("checked");

    let output = data.reduce((acc, v) => (
        acc + (v.ipv4 ? (indent + 'local-data: \"' + v.domain + '. IN A ' + v.ipv4 + '\"\n') : '')
        + (v.ipv6 ? (indent + 'local-data: \"' + v.domain + '. IN AAAA ' + v.ipv6 + '\"\n') : '')
    ), '');

    if (reverse) {
        output += '\n'
            + data.filter(v => v.ipv4)
                .reduce((acc, v) => (acc + indent + 'local-data-ptr: \"' + v.ipv4 + ' ' + v.domain + '.\"\n'), '')
            + data.filter(v => v.ipv6)
                .reduce((acc, v) => (acc + indent + 'local-data-ptr: \"' + v.ipv6 + ' ' + v.domain + '.\"\n'), '');
    }
    return output;
}
```

NEC IXルーター用のコンフィグ出力は以下になります。
```javascript:IX出力
function outputIX(data) {
    const cmdIP = $('#ix-ip').val();
    const ipv4 = (cmdIP === 'both' || cmdIP === 'ipv4');
    const ipv6 = (cmdIP === 'both' || cmdIP === 'ipv6');
    const pre = 'dns host ';

    return data.reduce((acc, v) => (
        acc +
        (ipv4 ? (
            (v.ipv4 ? (pre + v.domain + ' ip ' + v.ipv4 + '\n') : '') +
            (v.ipv6 ? (pre + v.domain + ' ip ' + v.ipv6 + '\n') : '')
        ) : '') +
        (ipv6 ? (
            (v.ipv4 ? (pre + v.domain + ' ipv6 ' + v.ipv4 + '\n') : '') +
            (v.ipv6 ? (pre + v.domain + ' ipv6 ' + v.ipv6 + '\n') : '')
        ) : '')
    ), '');
}
```
# できあがったもの
GitHub Pageに上げています。https://caribouhy.github.io/hostconv/
ソース：https://github.com/caribouHY/hostconv

シートのセルをコピーし、
![copy](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524454/342255b4-9006-8bfc-845e-b4b223f2cd16.png)
入力エリアにペーストします。
![input](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524454/15ad8ff4-5596-9e04-72b9-86d4eb57d13b.png)
出力形式を選んで変換ボンタを押すと出力できます。
![output](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524454/bed380f5-7b21-4f92-f13a-b06d758dc46b.png)

# 最後に
対応形式やオプションを増やしたり、画面のデザインを改良していきたいです。