{"id":13877994,"url":"https://github.com/nicholaides/cecil","last_synced_at":"2025-07-16T14:30:41.817Z","repository":{"id":199053245,"uuid":"695143709","full_name":"nicholaides/cecil","owner":"nicholaides","description":"An experimental templating library designed specifically for generating source code (especially for languages that aren’t as meta-programmable as Ruby).  Cecil templates closely resemble the target source code, making templates easier to write, read, and maintain.","archived":false,"fork":false,"pushed_at":"2024-03-31T19:09:02.000Z","size":188,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-08-08T22:19:34.765Z","etag":null,"topics":["code-generation","codegen","source-code-generator","template-engine"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nicholaides.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2023-09-22T12:59:08.000Z","updated_at":"2024-03-06T12:06:13.000Z","dependencies_parsed_at":"2024-03-17T04:02:51.280Z","dependency_job_id":"e85c83ad-76c8-4539-851b-99d3cd028824","html_url":"https://github.com/nicholaides/cecil","commit_stats":null,"previous_names":["nicholaides/cecil"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholaides%2Fcecil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholaides%2Fcecil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholaides%2Fcecil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nicholaides%2Fcecil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nicholaides","download_url":"https://codeload.github.com/nicholaides/cecil/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226134226,"owners_count":17578778,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["code-generation","codegen","source-code-generator","template-engine"],"created_at":"2024-08-06T08:01:36.996Z","updated_at":"2025-07-16T14:30:41.799Z","avatar_url":"https://github.com/nicholaides.png","language":"Ruby","readme":"# Cecil\n[![Yard Documentation](https://img.shields.io/badge/rdoc.info-blue?label=docs)](https://www.rubydoc.info/github/nicholaides/cecil)\n[![Gem Version](https://img.shields.io/gem/v/cecil)](https://rubygems.org/gems/cecil)\n\nAn experimental templating library designed specifically for generating source code (especially for languages that aren’t as meta-programmable as Ruby).\n\nCecil templates closely resemble the target source code, making templates easier to write, read, and maintain.\n\n## Features\n\n### Write templates in plain Ruby\n\nCall `Cecil::Code.generate_string` and pass it a block. Inside the block, add lines of code via backticks (or use `src` if you prefer). Cecil returns your generated source code as a string.\n\n#### Example\n\n```ruby\nmodel_code = Cecil::Code.generate_string do\n  # Use backticks to add lines of code\n  `import Model from '../model'`\n\n  # Multi-line strings work, too.\n  # Cecil preserves indentation.\n  `class User extends Model {\n    id: number\n    name: string\n    companyId: number | undefined\n  }`\n\n  # use #src if you prefer to avoid backticks\n  src \"export type Username = User['name']\"\nend\n\nputs model_code\n```\n\nReturns:\n\n```typescript\nimport Model from '../model'\nclass User extends Model {\n  id: number\n  name: string\n  companyId: number | undefined\n}\nexport type Username = User['name']\n```\n\n### Interpolate values with Cecil's low-noise syntax\n\nUse `#[]` on the backticks to replace placeholders with actual values.\n\nBy default, placeholders start with `$` and are followed by an identifier.\n\nPositional arguments match up with placeholders in order. Named arguments match placeholders by name.\n\n#### Example\n\n```ruby\nfield = \"user\"\ntypes = [\"string\", \"string[]\"]\ndefault_value = [\"SilentHaiku\", \"DriftingSnowfall\"]\nfield_class = \"Model\"\n\nCecil::Code.generate_string do\n  # positional arguments match placeholders by position\n  `let $field: $FieldType = $default`[field, types.join('|'), default_value.sort.to_json]\n\n  # named arguments match placeholders by name\n  `let $field: $FieldClass\u003c$Types\u003e = new $FieldClass($default)`[\n    field: field,\n    FieldClass: field_class,\n    Types: types.join('|'),\n    default: default_value.sort.to_json\n  ]\nend\n```\n\nReturns:\n\n```typescript\nlet user: string|string[] = [\"DriftingSnowfall\",\"SilentHaiku\"]\nlet user: Model\u003cstring|string[]\u003e = new Model([\"DriftingSnowfall\",\"SilentHaiku\"])\n```\n\n\n#### \"Doesn't Ruby already have string interpolation?\"\n\nYes, but compare the readability of these two approaches:\n\n```ruby\n`let $field: $FieldClass\u003c$Types\u003e = new $FieldClass($default)`[\n  field: field,\n  FieldClass: field_class,\n  Types: types.join('|'),\n  default: default_value.sort.to_json\n]\n\n# vs\n\nfield_types = types.join('|'),\ndefault_json = default_value.sort.to_json\n\"let #{field}: #{field_class}\u003c#{field_types}\u003e = new #{field_class}(#{default_json})\"\n```\n\n### Indents code blocks \u0026 closes brackets automatically\n\nA block passed to `#[]` gets indented and open brackets get closed automatically.\n\n#### Example\n\n```ruby\nmodel = \"User\"\nfield_name = \"name\"\nfield_default = \"Unnamed\"\n\nCecil::Code.generate_string do\n  `class $Class extends Model {`[model] do\n    # indentation is preserved\n    `id: number`\n\n    `override get $field() {`[field_name] do\n      `return super.$field ?? $defaultValue`[field_name, field_default.to_json]\n    end\n  end # the open bracket from `... Model {` gets closed with \"}\"\nend\n```\n\nReturns:\n\n```typescript\nclass User extends Model {\n    id: number\n    override get name() {\n        return super.name ?? \"Unnamed\"\n    }\n}\n```\n\n### Emit source code to other locations\n\nWhen generating source code, things like functions, parameters, classes, etc, often need to be declared, imported, or otherwise setup before being used.\n\n`content_for` can be used to add content to a different location of your file.\n\nCall `content_for(some_key) { ... }` with key and a block to store content under the key you provide. Call `content_for(some_key)` with the key and *no* block to insert your stored content at that location.\n\n#### Example\n\n```ruby\nmodels = [\n  { name: 'User', inherits: 'AuthModel' },\n  { name: 'Company', inherits: 'Model' },\n]\n\nCecil::Code.generate_string do\n  # insert content collected for :imports\n  content_for :imports\n\n  models.each do |model|\n    ``\n    `class $Class extends $SuperClass {`[model[:name], model[:inherits]] do\n      `id: number`\n    end\n\n    content_for :imports do\n      # this gets inserted above\n      `import $SuperClass from '../models/$SuperClass'`[SuperClass: model[:inherits]]\n    end\n\n    content_for :registrations do\n      # this gets inserted below\n      `$SuperClass.registerAncestor($Class)`[model[:inherits], model[:name]]\n    end\n  end\n\n  ``\n  # insert content collected for :registrations\n  content_for :registrations\nend\n```\n\nReturns:\n\n```typescript\nimport AuthModel from '../models/AuthModel'\nimport Model from '../models/Model'\n\nclass User extends AuthModel {\n    id: number\n}\n\nclass Company extends Model {\n    id: number\n}\n\nAuthModel.registerAncestor(User)\nModel.registerAncestor(Company)\n```\n\n### Collect data as you go then use it earlier in the document\n\nThe `#defer` method takes a block and waits to call it until the rest of the template is evaluated. The block's result is inserted at the location where `#defer` was called.\n\nThis gives a similar ability to `#content_for`, but is more flexible because you can collect any kind of data, not just source code.\n\n#### Example\n\n```ruby\nmodels = [\n  { name: 'User', inherits: 'AuthModel' },\n  { name: 'Company', inherits: 'Model' },\n  { name: 'Candidate', inherits: 'AuthModel' },\n]\n\nCecil::Code.generate_string do\n  superclasses = []\n\n  defer do\n    # This block gets called after the rest of the parent block is finished.\n    #\n    # By the time this block is called, the `superclasses` array is full of data\n    #\n    # Even though this block is called later, the output is added at the location where `defer` was called\n    `import { $SuperClasses } from '../models'`[superclasses.uniq.sort.join(', ')]\n    ``\n  end\n\n  models.each do |model|\n    superclasses \u003c\u003c model[:inherits] # add more strings to `superclasses`, which is used in the block above\n\n    `class $Class extends $SuperClass {}`[model[:name], model[:inherits]]\n  end\nend\n```\n\nReturns:\n\n```typescript\nimport { AuthModel, Model } from '../models'\n\nclass User extends AuthModel {}\nclass Company extends Model {}\nclass Candidate extends AuthModel {}\n```\n\n### Customizable syntax and behaviors\n\nEasily customize the following features to make Cecil suit your needs/preferences:\n\n- placeholder syntax\n- auto-closing brackets\n- indentation\n\nCustomizations are performed by subclassing [`Cecil::Code`][{Code}] and overriding the relevant methods.\n\nFor example, Cecil comes with [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] that you can use instead of of `Cecil::Code`. It has a few JavaScript/TypeScript-specific customizations. It's a subclass of `Cecil::Code` so it can be used the same way:\n\n```ruby\nCecil::Lang::TypeScript.generate_string do\n  # ...\nend\n```\n\n## Use cases\n\nThings I've personally used Cecil to generate:\n\n- **serialization/deserialization code** generated from from specs (e.g. OpenAPI)\n- **diagrams** (e.g. Mermaid, PlantUML, Dot/Graphviz)\n    - ERDs/schemas\n    - state machine diagrams\n    - graphs\n    - data visualizations\n- **state machines** generated from a list of states and transitions\n- **test cases** generated from data that describes inputs/setup and expected outputs; because parameterized tests can be very hard to debug\n- **complex types** because meta-programming in TypeScript can get complex quickly\n\n## Quick Reference\n\nReference documentation is on RubyDoc.info:\n  [gem](https://www.rubydoc.info/gems/cecil)\n  |\n  [repo](https://www.rubydoc.info/github/nicholaides/cecil/main)\n\n### Calling Cecil\n\nCall\n  [`Cecil::Code.generate`][{Code.generate}] /\n  [`generate_string`][{Code.generate_string}]\n  with a block and inside the block, use backticks or `#src` to emit lines of source code.\n  E.g.\n\n```ruby\n# returns a string\nCecil::Code.generate_string do\n  `function greet() {}`\n  `function respond() {}`\nend\n\n# outputs to $stdout\nCecil::Code.generate do\n  `function greet() {}`\n  `function respond() {}`\nend\n```\n\nSee: [Methods available inside a Cecil block][{BlockContext}]\n\n### Emitting source code\n\n- [backticks/``` #`` ```/`#src`][{BlockContext#src}] emit source code.\n  E.g.:\n    ```ruby\n    Cecil::Code.generate_string do\n      `function greet() {}`\n      `function respond() {}`\n      src \"function ask() {}\"\n    end\n    # outputs:\n    # function greet() {}\n    # function respond() {}\n    # function ask() {}\n    ```\n- [`#[]`][{Node#with}] interpolates data into placeholders. E.g.\n    ```ruby\n    Cecil::Code.generate_string do\n      `function $fn() {}`[\"greet\"]\n      `function $fn() {}`[fn: \"respond\"]\n    end\n    # outputs:\n    # function greet() {}\n    # function respond() {}\n    ```\n- [`#[]`][{Node#with}]`{ ... }` given a block, interpolates and indents the code emitted in its block.\n    E.g.\n    ```ruby\n    Cecil::Code.generate_string do\n      `function $fn() {`[\"greet\"] do\n        `console.log(\"hello\")`\n      end\n    end\n    # outputs:\n    # function greet() {\n    #     console.log(\"hello\")\n    # }\n    ```\n- [`#\u003c\u003c`][{Node#\u003c\u003c}] adds code the last line of the block.\n    E.g.\n    ```ruby\n    Cecil::Code.generate_string do\n      `(function ${fn}Now() {`[\"greet\"] do\n        `console.log(\"hello\")`\n      end \u003c\u003c ')()'\n    end\n    # outputs:\n    # (function greetNow() {\n    #     console.log(\"hello\")\n    # })()\n    ```\n- [`#content_for`][{BlockContext#content_for}] emits source code to different locations\n- [`#defer`][{BlockContext#defer}] waits to emit the given source until after data has been gathered\n\n### Customizing behavior for the language of the source code you're generating\n\nMany of Cecil's defaults can be customized by creating a subclass of [`Cecil::Code`][{Code}] and overriding methods to customize syntax and behavior of:\n\n- placeholder syntax\n- indentation\n- auto-closing brackets\n\nCurrently, Cecil comes with:\n\n- [`Cecil::Code`][{Code}] for generic code\n- [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] for JavaScript and TypeScript\n- [`Cecil::Lang::Rust`][{Lang::Rust}] for Rust\n\n\n### Auto-closing brackets\n\n\u003e Customize which opening brackets are auto-closed by overriding [`Cecil::Code#block_ending_pairs`][{Code#block_ending_pairs}] in a subclass.\n\nWhen nesting code blocks with `#[] { ... }`, open brackets at the end of the string get closed automatically.\n\nFor example, notice how we don't have to manually provide a closing `}` in the following:\n\n```ruby\n`$var = {`[var: \"user\"] do\n  `id: 42`\nend\n```\nbecomes\n```javascript\nuser = {\n    id: 42\n}\n```\n\n#### Multiple brackets\n\nEvery consecutive closing bracket at the end of the string gets closed. E.g.\n\n```ruby\n`$var = [{(`[var: \"user\"] do\n  `id: 42`\nend\n```\n\nbecomes\n\n```javascript\nuser = ([{\n    id: 42\n}])\n```\n\nCurrently, the algorithm is simplistic, so open brackets that aren't at the end of the string will *not* get closed.\n\nIn this example, the `(` in `test(` needs to be closed manually:\n\n```ruby\n`test(\"getter $fn\", () =\u003e {`[fn: 'getUsername'] do\n  `assert(false)`\nend \u003c\u003c `)`\n```\n\n```javascript\ntest(\"getter getUsername\", () =\u003e {\n    assert(false)\n})\n```\n\n### Placeholder syntax\n\nDefault placeholder rules:\n\n- start with `$`-- e.g. `$foo`\n- named can contain alpha-numeric and underscore characters-- e.g. `$foo_bar123`\n- names can optionally be surrounded by brackets -- e.g  `${my_placeholder}`, `$[my_placeholder]`, `$\u003cmy_placeholder\u003e`, or `$(my_placeholder)`\n\nSurrounding with brackets can be useful to separate a placeholder from subsequent characters that would otherwise get parsed as a placeholder.\n\nE.g. `function ${fn}Sync()`-- without curly brackets `$fnSync` would be the placeholder.\n\nCustomize placeholder syntax by subclassing [`Cecil::Code`][{Code}]\nand overriding [placeholder-related methods][{Code}].\n\n\n### Helper methods\n\nIf you use your generator frequently it can be helpful to define reusable helper methods on a subclass of [`Cecil::Code`][{Code}].\n\nFor example, the [`Cecil::Lang::TypeScript`][{Lang::TypeScript}] subclass\ndefines several [helper methods][{Lang::TypeScript::Helpers}] for generating TypeScript code.\n\n[{BlockContext#content_for}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#content_for-instance_method\n[{BlockContext#defer}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#defer-instance_method\n[{BlockContext#src}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext#src-instance_method\n[{BlockContext}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/BlockContext\n[{Code#block_ending_pairs}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#block_ending_pairs-instance_method\n[{Code.generate_string}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate_string-class_method\n[{Code.generate}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate-class_method\n[{Code}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code\n[{Lang::TypeScript.generate_string}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript#generate_string-class_method\n[{Lang::TypeScript.generate}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Code#generate-class_method\n[{Lang::TypeScript::Helpers}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript/Helpers\n[{Lang::TypeScript}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/TypeScript\n[{Lang::Rust}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Lang/Rust\n[{Node#\u003c\u003c}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Node#\u003c\u003c-instance_method\n[{Node#with}]: https://www.rubydoc.info/github/nicholaides/cecil/main/Cecil/Node#with-instance_method\n\n## Installation\n\nFrom your shell:\n\n```sh\nbundle add cecil\n```\n\nIn your Gemfile like:\n\n```ruby\ngem 'cecil'\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/nicholaides/cecil.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicholaides%2Fcecil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnicholaides%2Fcecil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnicholaides%2Fcecil/lists"}