Swift gybの環境構築

Swift gybの環境構築

ふとSwiftのgybが気になったので環境構築を試して見ました

gybとは

gybは「Generate Your Boilerplate」の略称みたいです(gyb --helpより)

具体的に何かと言うと、Swiftの公式リポジトリの中でSwiftコードの自動生成に使われているPython製のテンプレートエンジンになります

gybを導入する

qiita.com

というわけで、こちらを参考に環境構築して見ました

まずgyb自体はapple/swift/utils/の中にそのまま入っています

Qiitaの紹介記事ままですがcurlでgybファイルを取得していきます

mkdir gyb
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb.py" -o "gyb/gyb.py"
curl "https://raw.githubusercontent.com/apple/swift/master/utils/gyb" -o "gyb/gyb"
chmod +x gyb/gyb

上記のshellをそのまま実行すればgybを動かすのに必要なファイルが揃います

実際に動くかどうか確認します

$ cd gyb
$ ./gyb --help

これでhelpの一覧が出れば成功です

usage: gyb [-h] [-D NAME=VALUE] [-o TARGET] [--test] [--verbose-test] [--dump]
           [--line-directive LINE_DIRECTIVE]
           [file]

Generate Your Boilerplate!

positional arguments:
  file                  Path to GYB template file (defaults to stdin)

optional arguments:
  -h, --help            show this help message and exit
  -D NAME=VALUE         Bindings to be set in the template's execution context
  -o TARGET             Output file (defaults to stdout)
  --test                Run a self-test
  --verbose-test        Run a verbose self-test
  --dump                Dump the parsed template to stdout
  --line-directive LINE_DIRECTIVE
                        Line directive prefix; empty => no line markers

    A GYB template consists of the following elements:

      - Literal text which is inserted directly into the output

      - %% or $$ in literal text, which insert literal '%' and '$'
        symbols respectively.

      - Substitutions of the form ${<python-expression>}.  The Python
        expression is converted to a string and the result is inserted
        into the output.

      - Python code delimited by %{...}%.  Typically used to inject
        definitions (functions, classes, variable bindings) into the
        evaluation context of the template.  Common indentation is
        stripped, so you can add as much indentation to the beginning
        of this code as you like

      - Lines beginning with optional whitespace followed by a single
        '%' and Python code.  %-lines allow you to nest other
        constructs inside them.  To close a level of nesting, use the
        "%end" construct.

      - Lines beginning with optional whitespace and followed by a
        single '%' and the token "end", which close open constructs in
        %-lines.

    Example template:

          - Hello -
        %{
             x = 42
             def succ(a):
                 return a+1
        }%

        I can assure you that ${x} < ${succ(x)}

        % if int(y) > 7:
        %    for i in range(3):
        y is greater than seven!
        %    end
        % else:
        y is less than or equal to seven
        % end

          - The End. -

    When run with "gyb -Dy=9", the output is

          - Hello -

        I can assure you that 42 < 43

        y is greater than seven!
        y is greater than seven!
        y is greater than seven!

          - The End. -

gybの使い方

ヘルプ

$ ./gyb --help

もしくは

$ ./gyb -h

テンプレートをコンソール上に出力

下記コマンドからgyb形式のテンプレートを元に出力を行えます

注意点として--line-directive=のオプションを追加しない場合は// ###sourceLocationというgyb側がSwiftコンパイラの為に出力するコメントも一緒に出力されてしまうので見た目的にも追加した方が良いです

$ ./gyb {gyb_file} --line-directive=

Int/UIntの全てのビットの型に対して最大値を返す関数を追加

テンプレートがこち

template.swift.gyb

%{
  intTypes = [8,16,32,64]
}%

% for intType in intTypes:
    % for sign in ['','U']:

/// Extension that adds a few additional functionalities to ${sign}Int${intType}
extension ${sign}Int${intType} {

    /// Returns a ${sign}Int${intType} with all ones
        %if sign == '':
    public static var allOnes:Int${intType} {
       return Int${intType}(bitPattern: UInt${intType}.max)
    }
        %else:
    public static var allOnes:UInt${intType} {
       return UInt${intType}.max
    }
        %end
}
    %end
%end

出力してみます、下記コマンドを叩いて見ます

$ ./gyb template.swift.gyb --line-directive=

これでInt / UIntの8から64ビットの型に対してextensionするコードが生成されてるのが確認できれば成功です

/// Extension that adds a few additional functionalities to Int8
extension Int8 {

    /// Returns a Int8 with all ones
    public static var allOnes:Int8 {
       return Int8(bitPattern: UInt8.max)
    }
}

/// Extension that adds a few additional functionalities to UInt8
extension UInt8 {

    /// Returns a UInt8 with all ones
    public static var allOnes:UInt8 {
       return UInt8.max
    }
}

/// Extension that adds a few additional functionalities to Int16
extension Int16 {

    /// Returns a Int16 with all ones
    public static var allOnes:Int16 {
       return Int16(bitPattern: UInt16.max)
    }
}

/// Extension that adds a few additional functionalities to UInt16
extension UInt16 {

    /// Returns a UInt16 with all ones
    public static var allOnes:UInt16 {
       return UInt16.max
    }
}

/// Extension that adds a few additional functionalities to Int32
extension Int32 {

    /// Returns a Int32 with all ones
    public static var allOnes:Int32 {
       return Int32(bitPattern: UInt32.max)
    }
}

/// Extension that adds a few additional functionalities to UInt32
extension UInt32 {

    /// Returns a UInt32 with all ones
    public static var allOnes:UInt32 {
       return UInt32.max
    }
}

/// Extension that adds a few additional functionalities to Int64
extension Int64 {

    /// Returns a Int64 with all ones
    public static var allOnes:Int64 {
       return Int64(bitPattern: UInt64.max)
    }
}

/// Extension that adds a few additional functionalities to UInt64
extension UInt64 {

    /// Returns a UInt64 with all ones
    public static var allOnes:UInt64 {
       return UInt64.max
    }
}

あと例えばファイル内のgybファイルを一気に出力するような場合はomochiさんがこういうスクリプトを書いてくれてたりするので参考にすると良いかもしれません

github.com

終わりに

こんな感じで書くことが決まってるのに型の都合で書かないといけないコードだったりが、gybを使うことで人間が手を動かす量が減るのは最高ですね

まだ使い始めたばっかりなのでなんともですけど、swagger.ymlからAPI定義を元にgyb経由して自動でモデル定義吐けたりとかが理想です、どんどん楽していきたい…(切実)