🦘

定数をYamlで管理するツールを作りました

に公開

はじめに

CLIをツールを作ったので感じていた課題を共有しつつ記事を書いてみようと思いました。
こちら成果物のリンクになります。
https://www.npmjs.com/package/@garebare/shared-constants

どういうツールか

バックエンドとフロントエンドの言語が別だが共通の定数を持っておきたい場合に使うツールになります。
下記のようなYamlから複数の言語向けに定数が定義されたファイルを出力することができます。

format: shared-constants //他のフォーマットが実行されるのを防ぐため
constants:
  values:
    - key: api_url // 定数名
      value: "https://api.example.com" // 定数の中身
      type: "string" //型名
    - key: max_retries
      value: 3
      type: "number|int" //複数言語に出力する場合は複数の型を設定可能、先頭のものから優先される
    - key: debug_mode
      value: true
      type: "boolean"
target:
  - language: typescript
    output: "./output/"
    nameSpace: "config"
  - language: go
    output: "./output/"
    nameSpace: "config"
  - language: python
    output: "./output/"
    nameSpace: "config"
  - language: ruby
    output: "./output/"
    nameSpace: "config"
    comment: ""

YamlからGoへの変換例

package config
const (
  ApiUrl string = "https://api.example.com"
  MaxRetries int = 3
  DebugMode bool = true
)

YamlをTypeScriptへの変換例

export const config = {
  API_URL: 'https://api.example.com' as string,
  MAX_RETRIES: 3 as number,
  DEBUG_MODE: true as boolean,
} as const;

YamlをRubyへの変換例

module config
  API_URL = 'https://api.example.com'
  MAX_RETRIES = 3
  DEBUG_MODE = true
end

YamlをPythonへの変換例

API_URL = "https://api.example.com"
MAX_RETRIES = 3
DEBUG_MODE = True
__all__ = ["API_URL", "MAX_RETRIES", "DEBUG_MODE"]

なぜわざわざYamlから変換するのか

共通の値を使いたいのであれば、同一のYamlをそれぞれの環境でパースし中身を取り出せば目的を達成することができますがこのツールはわざわざ変換挟んでいます。
何故かというとエディタの保管を効かせるのと、パースされたオブジェクトに型を定義するコードを減らすためです。

主な機能

  • 定数をYamlで管理
    • 複数の定数をYamlで一元管理することができます。
  • TypeScript, Go, Python, Rubyへの変換
    • 4つの言語に対応しており、出力先も自由です。
  • Yaml内での型定義
    • Yaml内で複数言語に対応して型を定義することができます。
  • 名前空間の定義
    • 言語ごとにモジュール名やパッケージ名を設定することができます

変換処理について

このツールはTypeScriptで作成されており、ほぼ力技で処理されています。
下記はRubyのコードを生成する処理にになっています。

export function generate(data: YamlFormat, nameSpace: string) {
  const constantMappings = data.constants.values.map((item) => {
    try {
      const { key: originalKey, value, type } = item;
      const key = changeCase.constantCase(originalKey);

      const parsedType = typeParser(type);
      const supportedType = firstIntersectionType(
        parsedType,
        supportedTypes
      ) as SupportedType;

      if (supportedType === 'string') {
        return `${key} = '${value}'`;
      }

      return `${key} = ${value}`;
    } catch (error) {
      throw new Error(
        `Error processing item ${JSON.stringify(item)}:\n ${error}`
      );
    }
  });

  const code = `module ${nameSpace}
  ${constantMappings.join('\n  ')}
end
`;

まず引数でパースされたYamlを受け取ります。

export function generate(data: YamlFormat, nameSpace: string)

Yamlからキーとバリューを取り出しRubyの変数定義の文法(例:bar = 'string')に合わせて整形しなおします。

      const { key: originalKey, value, type } = item;
      const key = changeCase.constantCase(originalKey);

      const parsedType = typeParser(type);
      const supportedType = firstIntersectionType(
        parsedType,
        supportedTypes
      ) as SupportedType;

      if (supportedType === 'string') {
        return `${key} = '${value}'`;
      }

      return `${key} = ${value}`;

最後に整形し終わったものを名前空間と moduleendで挟めばRubyのコードの生成が完了します。

終わりに

すごく便利なツールというわけではないですがせっかく作ったので紹介してみました。

GitHubで編集を提案
SMARTCAMP Engineer Blog

Discussion