mike-neckのブログ

Java or Groovy or Swift or Golang

datomicチュートリアルを支えるgroovyのあれ

dev.classmethod.jp

こんなのがあったので、便乗。

datomicチュートリアル今日のエントリーのednファイル、心のこもった手作りのednファイルを作るのが、元からハートフルな精神のない僕には辛い作業だったので、groovyで書いてた。で、groovyで書くなら、書きやすいようにやりたい。ということで、groovyのdslを作ってやってました。

上記の渡辺さんの記事では最後の方にいい感じに実装していれば云々ってあったのですが、dslの実装ってあまり例を見かけないので、groovyでdslを書くときの参考に、このクソ記事書いています。


で、このエントリーで実行したいdslはこれ。

おい 1 たす 12 だろ jk
おい 1 たす 11だろ jk

上のdslは通って、下のdslは通らないようにしたい。


ちなみに、このdsl、きよたかさんがだれかjggugの人がやったやつで、groovy界隈の人にはかなり有名。

で、これの実装は次のような感じ。

def おい = {int left ->
    [たす: {int right ->
        [は: {int expected ->
            [だろ: {Object anything ->
                try {
                    assert left + right == expected
                } catch (AssertionError e) {
                    throw new AssertionError("おい ${left} たす ${right}${expected} だろ jk\n${e.message}")
                }
            }]
        }]
    }]
}

def jk = null

では、おもむろに実行!

java.lang.AssertionError: おい 1 たす 1 は 1 だろ jk
assert left + right == expected
       |    | |     |  |
       1    2 1     |  1
                    false

では、この書き方を利用して、ednファイルを作り出す実装を書いてみましょう。

enum Region {
    NE(':region/ne'), SW(':region/sw'), N(':region/n'), S(':region/s')
    private final String v
    Region(String v) {
        this.v = v
    }
    String asKey() {v}
    static Region random() {
        def r = values()
        return r.collect {it}[new Random().nextInt(r.size())]
    }
}

enum CommunityType {
    Twitter('twitter'), Facebook('facebook-page'), Wiki('wiki')
    final String type
    CommunityType(String type) {
        this.type = type
    }
    String asKey(){":community.type/${type}"}
    static CommunityType random() {
        def c = values()
        return c.collect{it}[new Random().nextInt(c.size())]
    }
}

enum OrgType {
    Community, Commercial
    String asKey() {":community.orgtype/${this.toString().toLowerCase()}"}
    static OrgType random() {
        def o = values()
        return o.collect {it}[new Random().nextInt(o.size())]
    }
}

assert Region.random() in Region.values()
assert CommunityType.random() in CommunityType.values()
assert OrgType.random() in OrgType.values()

abstract class Entity {
    static String DB_ID = ':db/id'
    final long dbId = {
        def id = new Random().nextLong()
        id < 0 ? id : -id
    }()
    String toDbId() {
        "#db/id[:db.part/user ${dbId}]"
    }
}

class District extends Entity {
    static String ENTITY = ':district'
    String name
    Region region
    @Override String toString() {
        def dq = '"'
        $/{${DB_ID} ${toDbId()},
    ${ENTITY}/name $dq$name$dq,
    ${ENTITY}/region ${region.asKey()}}/$
    }
}

class Neighborhood extends Entity {
    static String ENTITY = ':neighborhood'
    String name
    District district
    @Override String toString() {
        def dq = '"'
        $/{${DB_ID} ${toDbId()},
    ${ENTITY}/name $dq$name$dq,
    ${ENTITY}/district ${district.toDbId()}}/$
    }
}

class Community extends Entity {
    static String ENTITY = ':community'
    static String DQ = '"'
    String name
    String url
    List<String> categories
    String category() {
        "[${categories.collect {"$DQ$it$DQ"}.join(' ')}]"
    }
    OrgType orgType
    CommunityType type
    Neighborhood neighborhood
    @Override String toString() {
        $/{$DB_ID ${toDbId()},
    $ENTITY/name $DQ$name$DQ,
    $ENTITY/url $DQ$url$DQ,
    $ENTITY/category ${category()},
    $ENTITY/orgtype ${orgType.asKey()},
    $ENTITY/type ${type.asKey()},
    $ENTITY/neighborhood ${neighborhood.toDbId()}}/$
    }
}

class Data {
    def list = []
    Data self = this
    def community = {String cn ->
        [url: {String cu ->
            [category: {List<String> cc ->
                [orgtype: {OrgType co ->
                    [type: {CommunityType ct ->
                        [neighbor: {String nn ->
                            [district: {String dn ->
                                [region: {Region rr ->
                                    def district = new District(name: dn, region: rr)
                                    def neighborhood = new Neighborhood(name: nn, district: district)
                                    def community = new Community(name: cn, url: cu, categories: cc, orgType: co, type: ct, neighborhood: neighborhood)
                                    list << [district: district, neighborhood: neighborhood, community: community]
                                    return [next:{
                                        return self
                                    }]
                                }]
                            }]
                        }]
                    }]
                }]
            }]
        }]
    }
    @Override String toString() {
        $/[
${list.collect{
    it.collect {
        it.value.toString()
    }
}.flatten().join('\n')}
]/$
    }
}

def data = new Data()

data.community('Foo')
        .url('http://localhost:8000/foo')
        .category(['test'])
        .orgtype(OrgType.Commercial)
        .type(CommunityType.random())
        .neighbor('Hoo')
        .district('Koo')
        .region(Region.random())
        .next()
        .community('Hoge')
        .url('http://localhost:8000/hoge')
        .category(['test', 'kudoh'])
        .orgtype(OrgType.Commercial)
        .type(CommunityType.random())
        .neighbor('Kan Java')
        .district('Kansai')
        .region(Region.random())

println data

data以下の部分は横一列で書けば、ドットも括弧もいらないんだけど、読みづらくなるので、改行してドット入れて、括弧つけた。


以上、クソコード屋からは以上です。