{"id":13429585,"url":"https://github.com/ysbaddaden/artanis","last_synced_at":"2025-08-24T05:12:55.345Z","repository":{"id":33821166,"uuid":"37518465","full_name":"ysbaddaden/artanis","owner":"ysbaddaden","description":"Sinatra-like DSL for the Crystal language (abusing macros)","archived":false,"fork":false,"pushed_at":"2021-12-02T09:42:09.000Z","size":43,"stargazers_count":58,"open_issues_count":2,"forks_count":8,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-04T16:12:09.405Z","etag":null,"topics":["crystal"],"latest_commit_sha":null,"homepage":null,"language":"Crystal","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/ysbaddaden.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-16T08:42:20.000Z","updated_at":"2024-07-10T04:37:26.000Z","dependencies_parsed_at":"2022-08-07T23:16:22.892Z","dependency_job_id":null,"html_url":"https://github.com/ysbaddaden/artanis","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysbaddaden%2Fartanis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysbaddaden%2Fartanis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysbaddaden%2Fartanis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysbaddaden%2Fartanis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ysbaddaden","download_url":"https://codeload.github.com/ysbaddaden/artanis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251017751,"owners_count":21523633,"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":["crystal"],"created_at":"2024-07-31T02:00:42.100Z","updated_at":"2025-04-26T16:32:14.607Z","avatar_url":"https://github.com/ysbaddaden.png","language":"Crystal","funding_links":[],"categories":["Misc"],"sub_categories":[],"readme":"# Artanis\n\nCrystal's metaprogramming macros to build a Sinatra-like DSL for the Crystal\nlanguage.\n\n## Rationale\n\nThe DSL doesn't stock blocks to be invoked later on, but rather produces actual\nmethods using macros (`match` and the sugar `get`, `post`, etc.) where special\nchars like `/`, `.`, `(` and `)` are replaced as `_SLASH_`, `_DOT_`, `_LPAREN_`\nand `_RPAREN_`. Also, `:param` segments are replaced to `_PARAM_`.\n\nEventually methods look like:\n\n    get \"/posts\"        |    def match_GET__SLASH_posts\n    get \"/posts.xml\"    |    def match_GET__SLASH_posts_DOT_xml\n\nPlease read [dsl.cr](https://github.com/ysbaddaden/artanis/tree/master/src/dsl.cr)\nfor more details.\n\nEventually a call method is generated. It iterates over the class methods,\nselects the generated `match_*` methods and generates a big `case` statement,\ntransforming the method names back to regular expressions, and eventually\ncalling the method with matched data (if any).\n\nPlease read [application.cr](https://github.com/ysbaddaden/artanis/tree/master/src/application.cr)\nfor more details.\n\n## Usage\n\n```crystal\nrequire \"http/server\"\nrequire \"artanis\"\n\nclass App \u003c Artanis::Application\n  get \"/\" do\n    \"ROOT\"\n  end\n\n  get \"/forbidden\" do\n    403\n  end\n\n  get \"/posts/:id.:format\" do\n    p params[\"id\"]\n    p params[\"format\"]\n    \"post\"\n  end\n\n  get \"/posts/:post_id/comments/:id(.:format)\" do |post_id, id, format|\n    p params[\"format\"]?\n    200\n  end\nend\n\nserver = HTTP::Server.new(9292) do |context|\n  App.call(context)\nend\n\nputs \"Listening on http://0.0.0.0:9292\"\nserver.listen\n```\n\n## Benchmark\n\nRunning wrk against the above example (pointless hello world) gives the following\nresults (TL;DR 12µs per request):\n\n```\n$ wrk -c 1000 -t 2 -d 5 http://localhost:9292/fast\nRunning 5s test @ http://localhost:9292/fast\n  2 threads and 1000 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    12.26ms   14.48ms 423.28ms   99.05%\n    Req/Sec    41.17k     2.93k   48.65k    76.00%\n  409663 requests in 5.01s, 25.79MB read\nRequests/sec:  81722.08\nTransfer/sec:      5.14MB\n```\n\nA better benchmark is available in `test/dsl_bench.cr` which monitors some\nlimits of the generated Crystal code, like going over all routes to find nothing\ntakes an awful lot of time, since it must build/execute a regular expression\nagainst EVERY routes to eventually... find nothing.\n\n```\n$ crystal run --release test/dsl_bench.cr\nget root: 0.84 µs\nget param: 1.51 µs\nget params (block args): 2.18 µs\nget many params: 3.75 µs\nget many params (block args): 2.59 µs\nnot found (method): 0.73 µs\nnot found (path): 15.93 µs\n```\n\nKeep in mind these numbers tell nothing about reality. They only measure how\nfast the generated `Application.call(request)` method is in predefined cases.\n\n## License\n\nLicensed under the MIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysbaddaden%2Fartanis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fysbaddaden%2Fartanis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysbaddaden%2Fartanis/lists"}