{"id":23902938,"url":"https://github.com/mtumilowicz/groovy-dsl","last_synced_at":"2026-04-06T05:31:12.514Z","repository":{"id":110876080,"uuid":"136378891","full_name":"mtumilowicz/groovy-dsl","owner":"mtumilowicz","description":"Exploring basic features of groovy to produce DSL.","archived":false,"fork":false,"pushed_at":"2018-08-20T15:16:31.000Z","size":72,"stargazers_count":1,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-28T22:57:35.827Z","etag":null,"topics":["closure","delegate","domain-specific-language","dsl","groovy","metaprogramming"],"latest_commit_sha":null,"homepage":"","language":"Groovy","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtumilowicz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-06T19:49:19.000Z","updated_at":"2024-11-29T05:09:26.000Z","dependencies_parsed_at":"2023-04-29T19:30:57.397Z","dependency_job_id":null,"html_url":"https://github.com/mtumilowicz/groovy-dsl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/groovy-dsl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fgroovy-dsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fgroovy-dsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fgroovy-dsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fgroovy-dsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/groovy-dsl/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fgroovy-dsl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31461527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["closure","delegate","domain-specific-language","dsl","groovy","metaprogramming"],"created_at":"2025-01-04T22:51:13.664Z","updated_at":"2026-04-06T05:31:12.508Z","avatar_url":"https://github.com/mtumilowicz.png","language":"Groovy","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.com/mtumilowicz/groovy-dsl.svg?branch=master)](https://travis-ci.com/mtumilowicz/groovy-dsl)\n\n# groovy-dsl\nThe main goal of this project is to explore basic features of groovy to produce \nspecific DSL.\n\n_Reference_: [Groovy specification - core DSLs](http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html)  \n_Reference_: [Learning Groovy - Adam L. Davis](https://www.amazon.com/Learning-Groovy-Adam-L-Davis/dp/1484221168)  \n_Reference_: [Groovy in Action](https://www.amazon.com/Groovy-Action-Covers-2-4/dp/1935182447)  \n_Reference_: [DSL - Martin Fowler](https://www.amazon.com/Domain-Specific-Languages-Addison-Wesley-Signature-Fowler/dp/0321712943)  \n\n# introduction\n**Domain-Specific Languages** are small languages, focused on a particular \naspect of a software system. They allow business experts to read or write \ncode without having to be  programming experts.  \nDSLs come in two main forms:\n* **external** - language that's parsed independently of the host general purpose \nlanguage, examples: `regular expressions` and `CSS`.\n* **internal** - particular form of `API` in a host general purpose language, often \nreferred to as a fluent interface, examples: `Spock` and `Mockito`.\n___\n`Groovy` has many features that make it great for writing `DSLs`:\n* [Closures](http://groovy-lang.org/closures.html) with [delegates](http://groovy-lang.org/closures.html#_delegate_of_a_closure).\n* Parentheses and dots `(.)` are optional.\n* Ability to add methods to standard classes using [Category](http://docs.groovy-lang.org/latest/html/api/groovy/lang/Category.html), \n[Mixins](http://mrhaki.blogspot.com/2013/02/groovy-goodness-apply-mixin-to-object.html) and [Traits](http://docs.groovy-lang.org/next/html/documentation/core-traits.html).\n* The ability to [overload operators](http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html#_operator_overloading).\n* [Metaprogramming](http://groovy-lang.org/metaprogramming.html): `methodMissing` and \n`propertyMissing` features.\n\n## closures with delegates\nWithin `Groovy` you can take a closure as a parameter and then call it using a \nlocal variable as a delegate.\n```\n@ToString\nclass X {\n    String value\n}\n\nclass Y {\n    static def handler(closure) {\n        X x = new X()\n        closure.delegate = x\n        closure()\n        x\n    }\n}\n```\n```\nprintln Y.handler {setValue 'test'} // X(test)\n```\n\n## optional parentheses and dots\nIn `Groovy` it's possible to omit parentheses and dots\n```\nX.resolve {take 10 plus 30 minus 15} // it's same as: new X().take(10).plus(30).minus(15)\n```\nwhere:\n```\nclass X {\n    @Delegate\n    Integer value\n    \n    Integer take(Integer x) {\n        x\n    }\n    \n    static def resolve(Closure closure) {\n        closure.delegate = new X()\n        closure()\n    }\n}\n```\n## category, mixins, traits\n\n### category\n`Groovy` categories are the mechanism to augment classes with new methods.\n```\n@Category(Integer)\nclass X {\n    def reverse() {\n        this.toString().reverse().replaceFirst(/^0+/,'').toInteger()\n    }\n}\n```\n```\nuse(X) {\n    println 123000020.reverse() // 20000321\n}\n```\n* Remarks:\n    * During compilation, all methods are transformed to static ones with \n    an additional self parameter of the type you supply as the annotation \n    parameter (the default type for the self parameters is `Object` which \n    might be more broad reaching than you like so it is usually wise to \n    specify a type). \n    * Properties invoked using `'this'` references are transformed so that \n    they are instead invoked on the additional self parameter and not on \n    the `Category` instance. \n    * Remember that once the category is applied, the reverse will occur and \n    we will be back to conceptually having methods on the `this` references \n    again.\n\n### mixins\n`Groovy` mixin is a mechanism to augment classes with new methods **at runtime**.\n```\nclass X {\n    static def test(String x) {\n        println \"test ${x}\"\n    }\n}\n```\n```\nString.mixin X\n'mixin'.test() // test mixin\n```\n* Remarks:\n    * Static mixins (`@Mixin`) have been deprecated in favour of `traits`.\n    * Methods are only visible at runtime.\n### traits\n`Traits` can be seen as interfaces carrying both default implementations \nand state.\n\n```\nclass Y implements X {\n    @Override\n    def name() {\n        return name\n    }\n}\n\ntrait X {\n    def name = \"X\"\n    \n    abstract def name()\n    def printName() {\n        println \"test ${name()}\"\n    }\n}\n```\n```\nnew Y().printName() // X\n```\n* Remarks:\n    * Methods defined in a `trait` are visible in bytecode.\n    * Internally, the `trait` is represented as an interface \n    (without default methods) and several helper classes \n    this means that an object implementing a trait effectively implements \n    an interface.\n    * Methods are visible from `Java` and they are compatible with \n    type checking and static compilation.\n\n## overriding operators\n```\nclass ComplexNumber {\n    double x\n    double y\n\t\n    ComplexNumber plus(ComplexNumber other) {\n        new ComplexNumber(x: this.x + other.x, y: this.y + other.y)\n    }\n}\n```\n```\nComplexNumber cn1 = new ComplexNumber(x: 1, y: 1)\nComplexNumber cn2 = new ComplexNumber(x: 2, y: 2)\nComplexNumber result = cn1 + cn2 // (3, 3)\n}\n```\n\n## metaprogramming (missing methods and properties)\n`Groovy` provides a way to implement functionality at runtime via the methods:\n* `methodMissing(String name, args)` - invoked only in the case of a \nfailed method dispatch when no method can be found for the given name and/or \nthe given arguments.\n* `propertyMissing(String name)` - called only when no getter method for \nthe given property can be found at runtime.\n* `propertyMissing(String name, Object value)` - called only when no setter\nmethod for the given property can be found at runtime.\n```\nclass X {\n    def methodMissing(String name, args) {\n        println \"methodMissing: $name $args\"\n    }\n\n    def propertyMissing(String name, Object value) {\n        println \"propertyMissing: $name $value\"\n    }\n\n    def propertyMissing(String name) {\n        println \"propertyMissing: $name\"\n    }\n}\n```\n```\ndef x = new X()\nx.nonExsistingMethod \"1\", \"2\", \"3\" // methodMissing: nonExsistingMethod [1, 2, 3]\nx.nonExsistingProperty // propertyMissing: nonExsistingProperty\nx.settingNonExsistingProperty = 5 // \"propertyMissing: settingNonExsistingProperty 5\"\n```\n# project\nElaborated above mechanisms used:\n* closures with delegation:\n    ```\n    def static make(@DelegatesTo(strategy = Closure.DELEGATE_ONLY, value = DeadlineMemo) Closure closure) {\n        def code = closure.rehydrate(new DeadlineMemo(), this, this)\n        code.resolveStrategy = Closure.DELEGATE_ONLY\n        code()\n    }\n    ```\n    * `@DelegatesTo` - for type checking\n    * `closure.rehydrate(new DeadlineMemo(), this, this)` - returns a copy of this closure \n    for which the `delegate`, `owner` and `thisObject` are replaced with the supplied \n    parameters.\n*  metaprogramming:  \n    we dynamically add sections with custom names and bodies\n    ```\n    def methodMissing(String methodName, args) {\n        def section = new ToDo(title: methodName, body: args[0])\n        toDo \u003c\u003c section\n    }    \n    ```\n* optional parentheses:\n    ```\n    DeadlineMemo.make {\n        title 'IMPORTANT'\n        deadline '2020-01-01'\n        idea 'Be a better programmer!'\n        plan 'Commit to github everyday!'\n        xml\n    }\n## project description\nWe provide DSL to create memos and print them in specified format.  \n* Memos have structure:  \n    * title\n    * deadline-date  \n    * any number of arbitrary named sections that have title and body\n* Supported formats:\n    * json\n    * text\n    * xml\n* Exemplary memo looks like:\n    ```\n    shopping-list\n    2018-06-16\n    food: butter, bread, meat\n    cleaning supplies: washing powder\n    ```\n    \n## project structure\n* `DeadlineMemo`, `ToDo` - entities.\n* `json` / `text` / `xml` packages containing appropriate .\nconverters `memo -\u003e specified format`.\n* Full tests.\n\n## use cases\nTo print memo in xml format:\n```\nprintln DeadlineMemo.make {\n                    title 'any title you like'\n                    deadline '2018-06-16'\n                    subsection-title1 'any body you like'\n                    subsection-title2 'any body you like'\n                    xml\n                }\n```\nother formats: `json`, `xml`.\n\n## tests\nWe provide tests for every format converter:\n* `DeadlineMemoJsonConverterTest`\n* `DeadlineMemoTextConverterTest`\n* `DeadlineMemoXmlConverterTest`\n\nAnd we test DSL itself as well: \n* `DslTest`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fgroovy-dsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fgroovy-dsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fgroovy-dsl/lists"}