{"id":13396369,"url":"https://github.com/soveran/cuba","last_synced_at":"2025-05-14T09:09:42.427Z","repository":{"id":882143,"uuid":"627535","full_name":"soveran/cuba","owner":"soveran","description":"Rum based microframework for web development.","archived":false,"fork":false,"pushed_at":"2024-01-24T13:00:14.000Z","size":311,"stargazers_count":1442,"open_issues_count":3,"forks_count":248,"subscribers_count":38,"default_branch":"master","last_synced_at":"2025-04-03T17:08:17.552Z","etag":null,"topics":["lesscode","micro-framework","rack","ruby"],"latest_commit_sha":null,"homepage":"http://cuba.is","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"ronaldlokers/grunt-casperjs","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soveran.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":"CONTRIBUTING","funding":null,"license":"LICENSE","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":"2010-04-25T04:02:48.000Z","updated_at":"2025-03-22T22:27:48.000Z","dependencies_parsed_at":"2023-07-06T08:01:54.044Z","dependency_job_id":"4d61e386-8c30-4105-8389-a9d8fa39c4ce","html_url":"https://github.com/soveran/cuba","commit_stats":{"total_commits":277,"total_committers":30,"mean_commits":9.233333333333333,"dds":"0.44404332129963897","last_synced_commit":"a4cf09d6c6c591a05e08f3411c745da36c91c801"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fcuba","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fcuba/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fcuba/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soveran%2Fcuba/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soveran","download_url":"https://codeload.github.com/soveran/cuba/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248334960,"owners_count":21086482,"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":["lesscode","micro-framework","rack","ruby"],"created_at":"2024-07-30T18:00:48.101Z","updated_at":"2025-04-11T03:27:06.646Z","avatar_url":"https://github.com/soveran.png","language":"Ruby","readme":"Cuba\n====\n\n_n_. a microframework for web development.\n\n![Cuba and Rum, by Jan Sochor](http://farm3.static.flickr.com/2619/4032103097_8324c6fecf.jpg)\n\nCommunity\n---------\n\nMeet us on IRC: [#cuba.rb][irc] on [freenode.net][freenode].\n\n[irc]: irc://chat.freenode.net/#cuba.rb\n[freenode]: http://freenode.net/\n\nDescription\n-----------\n\nCuba is a microframework for web development originally inspired\nby [Rum][rum], a tiny but powerful mapper for [Rack][rack]\napplications.\n\nIt integrates many templates via [Tilt][tilt], and testing via\n[Cutest][cutest] and [Capybara][capybara].\n\n[rum]: http://github.com/chneukirchen/rum\n[rack]: http://github.com/rack/rack\n[tilt]: http://github.com/rtomayko/tilt\n[cutest]: http://github.com/djanowski/cutest\n[capybara]: http://github.com/jnicklas/capybara\n[rack-test]: https://github.com/brynary/rack-test\n\nInstallation\n------------\n\n``` console\n$ gem install cuba\n```\n\nUsage\n-----\n\nHere's a simple application:\n\n``` ruby\n# cat hello_world.rb\nrequire \"cuba\"\nrequire \"cuba/safe\"\n\nCuba.use Rack::Session::Cookie, :secret =\u003e \"__a_very_long_string__\"\n\nCuba.plugin Cuba::Safe\n\nCuba.define do\n  on get do\n    on \"hello\" do\n      res.write \"Hello world!\"\n    end\n\n    on root do\n      res.redirect \"/hello\"\n    end\n  end\nend\n```\n\nAnd the test file:\n\n``` ruby\n# cat hello_world_test.rb\nrequire \"cuba/test\"\nrequire \"./hello_world\"\n\nscope do\n  test \"Homepage\" do\n    get \"/\"\n\n    follow_redirect!\n\n    assert_equal \"Hello world!\", last_response.body\n  end\nend\n```\n\nTo run it, you can create a `config.ru` file:\n\n``` ruby\n# cat config.ru\nrequire \"./hello_world\"\n\nrun Cuba\n```\n\nYou can now run `rackup` and enjoy what you have just created.\n\nMatchers\n--------\n\nHere's an example showcasing how different matchers work:\n\n``` ruby\nrequire \"cuba\"\nrequire \"cuba/safe\"\n\nCuba.use Rack::Session::Cookie, :secret =\u003e \"__a_very_long_string__\"\n\nCuba.plugin Cuba::Safe\n\nCuba.define do\n\n  # only GET requests\n  on get do\n\n    # /\n    on root do\n      res.write \"Home\"\n    end\n\n    # /about\n    on \"about\" do\n      res.write \"About\"\n    end\n\n    # /styles/basic.css\n    on \"styles\", extension(\"css\") do |file|\n      res.write \"Filename: #{file}\" #=\u003e \"Filename: basic\"\n    end\n\n    # /post/2011/02/16/hello\n    on \"post/:y/:m/:d/:slug\" do |y, m, d, slug|\n      res.write \"#{y}-#{m}-#{d} #{slug}\" #=\u003e \"2011-02-16 hello\"\n    end\n\n    # /username/foobar\n    on \"username/:username\" do |username|\n      user = User.find_by_username(username) # username == \"foobar\"\n\n      # /username/foobar/posts\n      on \"posts\" do\n\n        # You can access `user` here, because the `on` blocks\n        # are closures.\n        res.write \"Total Posts: #{user.posts.size}\" #=\u003e \"Total Posts: 6\"\n      end\n\n      # /username/foobar/following\n      on \"following\" do\n        res.write user.following.size #=\u003e \"1301\"\n      end\n    end\n\n    # /search?q=barbaz\n    on \"search\", param(\"q\") do |query|\n      res.write \"Searched for #{query}\" #=\u003e \"Searched for barbaz\"\n    end\n  end\n\n  # only POST requests\n  on post do\n    on \"login\" do\n\n      # POST /login, user: foo, pass: baz\n      on param(\"user\"), param(\"pass\") do |user, pass|\n        res.write \"#{user}:#{pass}\" #=\u003e \"foo:baz\"\n      end\n\n      # If the params `user` and `pass` are not provided, this\n      # block will get executed.\n      on true do\n        res.write \"You need to provide user and pass!\"\n      end\n    end\n  end\nend\n```\n\nNote that once an `on` block matches, processing halts at the conclusion of that block.\n\nStatus codes\n------------\n\nIf you don't assign a status code and you don't write to the `res`\nobject, the status will be set as `404`. The method `not_found` is\nin charge of setting the proper status code, and you can redefine\nit if you want to render a template or configure custom headers.\n\nFor example:\n\n``` ruby\nCuba.define do\n  on get do\n    on \"hello\" do\n      res.write \"hello world\"\n    end\n  end\nend\n\n# Requests:\n#\n# GET /            # 404\n# GET /hello       # 200\n# GET /hello/world # 200\n```\n\nAs you can see, as soon as something was written to the response,\nthe status code was changed to 200.\n\nIf you want to match just \"hello\", but not \"hello/world\", you can do\nas follows:\n\n``` ruby\nCuba.define do\n  on get do\n    on \"hello\" do\n      on root do\n        res.write \"hello world\"\n      end\n    end\n  end\nend\n\n# Requests:\n#\n# GET /            # 404\n# GET /hello       # 200\n# GET /hello/world # 404\n```\n\nYou can also use a regular expression to match the end of line:\n\n``` ruby\nCuba.define do\n  on get do\n    on /hello\\/?\\z/ do\n      res.write \"hello world\"\n    end\n  end\nend\n\n# Requests:\n#\n# GET /            # 404\n# GET /hello       # 200\n# GET /hello/world # 404\n```\n\nThis last example is not a common usage pattern. It's here only to\nillustrate how Cuba can be adapted for different use cases.\n\nIf you need this behavior, you can create a helper:\n\n``` ruby\nmodule TerminalMatcher\n  def terminal(path)\n    /#{path}\\/?\\z/\n  end\nend\n\nCuba.plugin TerminalMatcher\n\nCuba.define do\n  on get do\n    on terminal(\"hello\") do\n      res.write \"hello world\"\n    end\n  end\nend\n```\n\nSecurity\n--------\n\nThe most important security consideration is to use `https` for all\nrequests. If that's not the case, any attempt to secure the application\ncould be in vain. The rest of this section assumes `https` is\nenforced.\n\nWhen building a web application, you need to include a security\nlayer. Cuba ships with the `Cuba::Safe` plugin, which applies several\nsecurity related headers to prevent attacks like clickjacking and\ncross-site scripting, among others. It is not included by default\nbecause there are legitimate uses for plain Cuba (for instance,\nwhen designing an API).\n\nHere's how to include it:\n\n```ruby\nrequire \"cuba/safe\"\n\nCuba.plugin Cuba::Safe\n```\n\nYou should also always set a session secret to some undisclosed\nvalue. Keep in mind that the content in the session cookie is\n*not* encrypted.\n\n``` ruby\nCuba.use(Rack::Session::Cookie, :secret =\u003e \"__a_very_long_string__\")\n```\n\nIn the end, your application should look like this:\n\n```ruby\nrequire \"cuba\"\nrequire \"cuba/safe\"\n\nCuba.use Rack::Session::Cookie, :secret =\u003e \"__a_very_long_string__\"\n\nCuba.plugin Cuba::Safe\n\nCuba.define do\n  on csrf.unsafe? do\n    csrf.reset!\n\n    res.status = 403\n    res.write(\"Not authorized\")\n\n    halt(res.finish)\n  end\n\n  # Now your app is protected against a wide range of attacks.\n  ...\nend\n```\n\nThe `Cuba::Safe` plugin is composed of two modules:\n\n* `Cuba::Safe::SecureHeaders`\n* `Cuba::Safe::CSRF`\n\nYou can include them individually, but while the modularity is good\nfor development, it's very common to use them in tandem. As that's\nthe normal use case, including `Cuba::Safe` is the preferred way.\n\nCross-Site Request Forgery\n--------------------------\n\nThe `Cuba::Safe::CSRF` plugin provides a `csrf` object with the\nfollowing methods:\n\n* `token`: the current security token.\n* `reset!`: forces the token to be recreated.\n* `safe?`: returns `true` if the request is safe.\n* `unsafe?`: returns `true` if the request is unsafe.\n* `form_tag`: returns a string with the `csrf_token` hidden input tag.\n* `meta_tag`: returns a string with the `csrf_token` meta tag.\n\nHere's an example of how to use it:\n\n```ruby\nrequire \"cuba\"\nrequire \"cuba/safe\"\n\nCuba.use Rack::Session::Cookie, :secret =\u003e \"__a_very_long_string__\"\n\nCuba.plugin Cuba::Safe\n\nCuba.define do\n  on csrf.unsafe? do\n    csrf.reset!\n\n    res.status = 403\n    res.write(\"Forbidden\")\n\n    halt(res.finish)\n  end\n\n  # Here comes the rest of your application\n  # ...\nend\n```\n\nYou have to include `csrf.form_tag` in your forms and `csrf.meta_tag`\namong your meta tags. Here's an example that assumes you are using\n`Cuba::Mote` from `cuba-contrib`:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    {{ app.csrf.meta_tag }}\n    ...\n  \u003c/head\u003e\n  ...\n  \u003cbody\u003e\n    \u003cform action=\"/foo\" method=\"POST\"\u003e\n      {{ app.csrf.form_tag }}\n      ...\n    \u003c/form\u003e\n  ...\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nHTTP Verbs\n----------\n\nThere are matchers defined for the following HTTP Verbs: `get`,\n`post`, `put`, `patch`, `delete`, `head`, `options`, `link`, `unlink`\nand `trace`. As you have the whole request available via the `req`\nobject, you can also query it with helper methods like `req.options?`\nor `req.head?`, or you can even go to a lower level and inspect the\nenvironment via the `env` object, and check for example if\n`env[\"REQUEST_METHOD\"]` equals the verb `PATCH`.\n\nWhat follows is an example of different ways of saying the same thing:\n\n``` ruby\non env[\"REQUEST_METHOD\"] == \"GET\", \"api\" do ... end\n\non req.get?, \"api\" do ... end\n\non get, \"api\" do ... end\n```\n\nActually, `get` is syntax sugar for `req.get?`, which in turn is syntax sugar\nfor `env[\"REQUEST_METHOD\"] == \"GET\"`.\n\nHeaders\n-------\n\nYou can set the headers by assigning values to the hash `req.headers`.\nIf you want to inspect the incoming headers, you have to read from\nthe `env` hash. For example, if you want to know the referrer you\ncan check `env[\"HTTP_REFERER\"]`.\n\nRequest and Response\n--------------------\n\nYou may have noticed we use `req` and `res` a lot. Those variables are\ninstances of [Rack::Request][request] and `Cuba::Response` respectively, and\n`Cuba::Response` is just an optimized version of\n[Rack::Response][response].\n\n[request]: http://www.rubydoc.info/github/rack/rack/Rack/Request\n[response]: http://www.rubydoc.info/github/rack/rack/Rack/Response\n\nThose objects are helpers for accessing the request and for building\nthe response. Most of the time, you will just use `res.write`.\n\nIf you want to use custom `Request` or `Response` objects, you can\nset the new values as follows:\n\n``` ruby\nCuba.settings[:req] = MyRequest\nCuba.settings[:res] = MyResponse\n```\n\nMake sure to provide classes compatible with those from Rack.\n\nCaptures\n--------\n\nYou may have noticed that some matchers yield a value to the block. The rules\nfor determining if a matcher will yield a value are simple:\n\n1. Regex captures: `\"posts/(\\\\d+)-(.*)\"` will yield two values, corresponding to each capture.\n2. Placeholders: `\"users/:id\"` will yield the value in the position of :id.\n3. Symbols: `:foobar` will yield if a segment is available.\n4. File extensions: `extension(\"css\")` will yield the basename of the matched file.\n5. Parameters: `param(\"user\")` will yield the value of the parameter user, if present.\n\nThe first case is important because it shows the underlying effect of regex\ncaptures.\n\nIn the second case, the substring `:id` gets replaced by `([^\\\\/]+)` and the\nstring becomes `\"users/([^\\\\/]+)\"` before performing the match, thus it reverts\nto the first form we saw.\n\nIn the third case, the symbol--no matter what it says--gets replaced\nby `\"([^\\\\/]+)\"`, and again we are in presence of case 1.\n\nThe fourth case, again, reverts to the basic matcher: it generates the string\n`\"([^\\\\/]+?)\\.#{ext}\\\\z\"` before performing the match.\n\nThe fifth case is different: it checks if the the parameter supplied is present\nin the request (via POST or QUERY_STRING) and it pushes the value as a capture.\n\nComposition\n-----------\n\nYou can mount a Cuba app, along with middlewares, inside another Cuba app:\n\n``` ruby\nclass API \u003c Cuba; end\n\nAPI.use SomeMiddleware\n\nAPI.define do\n  on param(\"url\") do |url|\n    ...\n  end\nend\n\nCuba.define do\n  on \"api\" do\n    run API\n  end\nend\n```\n\nIf you need to pass information to one sub-app, you can use the\n`with` method and access it with `vars`:\n\n```ruby\nclass Platforms \u003c Cuba\n  define do\n    platform = vars[:platform]\n\n    on default do\n      res.write(platform) # =\u003e \"heroku\" or \"salesforce\"\n    end\n  end\nend\n\nCuba.define do\n  on \"(heroku|salesforce)\" do |platform|\n    with(platform: platform) do\n      run(Platforms)\n    end\n  end\nend\n```\n\n## Embedding routes from other modules\n\nWhile the `run` command allows you to handle over the control to a\nsub app, sometimes you may want to just embed routes defined in\nanother module. There's no built-in method to do it, but if you are\nwilling to experiment you can try the following.\n\nLet's say you have defined routes in modules `A` and `B`, and you\nwant to mount those routes in your application.\n\nFirst, you will have to extend Cuba with this code:\n\n```ruby\nclass Cuba\n  def mount(app)\n    result = app.call(req.env)\n    halt result if result[0] != 404\n  end\nend\n```\n\nIt doesn't matter where you define it as long as Cuba has already\nbeen required. For instance, you could extract that to a plugin and\nit would work just fine.\n\nThen, in your application, you can use it like this:\n\n```ruby\nCuba.define do\n  on default do\n    mount A \n    mount B\n  end\nend\n```\n\nIt should halt the request only if the resulting status from calling\nthe mounted app is not 404. If you run into some unexpected behavior,\nlet me know by creating an issue and we'll look at how to workaround\nany difficulties.\n\nTesting\n-------\n\nGiven that Cuba is essentially Rack, it is very easy to test with\n`Rack::Test`, `Webrat` or `Capybara`. Cuba's own tests are written\nwith a combination of [Cutest][cutest] and [Rack::Test][rack-test],\nand if you want to use the same for your tests it is as easy as\nrequiring `cuba/test`:\n\n``` ruby\nrequire \"cuba/test\"\nrequire \"your/app\"\n\nscope do\n  test \"Homepage\" do\n    get \"/\"\n\n    assert_equal \"Hello world!\", last_response.body\n  end\nend\n```\n\nIf you prefer to use [Capybara][capybara], instead of requiring\n`cuba/test` you can require `cuba/capybara`:\n\n``` ruby\nrequire \"cuba/capybara\"\nrequire \"your/app\"\n\nscope do\n  test \"Homepage\" do\n    visit \"/\"\n\n    assert has_content?(\"Hello world!\")\n  end\nend\n```\n\nTo read more about testing, check the documentation for\n[Cutest][cutest], [Rack::Test][rack-test] and [Capybara][capybara].\n\nSettings\n--------\n\nEach Cuba app can store settings in the `Cuba.settings` hash. The settings are\ninherited if you happen to subclass `Cuba`\n\n``` ruby\nCuba.settings[:layout] = \"guest\"\n\nclass Users \u003c Cuba; end\nclass Admin \u003c Cuba; end\n\nAdmin.settings[:layout] = \"admin\"\n\nassert_equal \"guest\", Users.settings[:layout]\nassert_equal \"admin\", Admin.settings[:layout]\n```\n\nFeel free to store whatever you find convenient.\n\nRendering\n---------\n\nCuba includes a plugin called `Cuba::Render` that provides a couple of helper\nmethods for rendering templates. This plugin uses [Tilt][tilt], which serves as\nan interface to a bunch of different Ruby template engines (ERB, Haml, Sass,\nCoffeeScript, etc.), so you can use the template engine of your choice.\n\nTo set up `Cuba::Render`, do:\n\n```ruby\nrequire \"cuba\"\nrequire \"cuba/render\"\nrequire \"erb\"\n\nCuba.plugin Cuba::Render\n```\n\nThis example uses ERB, a template engine that comes with Ruby. If you want to\nuse another template engine, one [supported by Tilt][templates], you need to\ninstall the required gem and change the `template_engine` setting as shown\nbelow.\n\n```ruby\nCuba.settings[:render][:template_engine] = \"haml\"\n```\n\nThe plugin provides three helper methods for rendering templates: `partial`,\n`view` and `render`.\n\n```ruby\nCuba.define do\n  on \"about\" do\n    # `partial` renders a template called `about.erb` without a layout.\n    res.write partial(\"about\")\n  end\n\n  on \"home\" do\n    # Opposed to `partial`, `view` renders the same template\n    # within a layout called `layout.erb`.\n    res.write view(\"about\")\n  end\n\n  on \"contact\" do\n    # `render` is a shortcut to `res.write view(...)`\n    render(\"contact\")\n  end\nend\n```\n\nBy default, `Cuba::Render` assumes that all templates are placed in a folder\nnamed `views` and that they use the proper extension for the chosen template\nengine. Also for the `view` and `render` methods, it assumes that the layout\ntemplate is called `layout`.\n\nThe defaults can be changed through the `Cuba.settings` method:\n\n```ruby\nCuba.settings[:render][:template_engine] = \"haml\"\nCuba.settings[:render][:views] = \"./views/admin/\"\nCuba.settings[:render][:layout] = \"admin\"\n```\n\nNOTE: Cuba doesn't ship with Tilt. You need to install it (`gem install tilt`).\n\n[templates]: https://github.com/rtomayko/tilt/blob/master/docs/TEMPLATES.md\n\nPlugins\n-------\n\nCuba provides a way to extend its functionality with plugins.\n\n### How to create plugins\n\nAuthoring your own plugins is pretty straightforward.\n\n``` ruby\nmodule MyOwnHelper\n  def markdown(str)\n    BlueCloth.new(str).to_html\n  end\nend\n\nCuba.plugin MyOwnHelper\n```\n\nThat's the simplest kind of plugin you'll write. In fact, that's exactly how\nthe `markdown` helper is written in `Cuba::TextHelpers`.\n\nA more complicated plugin can make use of `Cuba.settings` to provide default\nvalues. In the following example, note that if the module has a `setup` method, it will\nbe called as soon as it is included:\n\n``` ruby\nmodule Render\n  def self.setup(app)\n    app.settings[:template_engine] = \"erb\"\n  end\n\n  def partial(template, locals = {})\n    render(\"#{template}.#{settings[:template_engine]}\", locals)\n  end\nend\n\nCuba.plugin Render\n```\n\nThis sample plugin actually resembles how `Cuba::Render` works.\n\nFinally, if a module called `ClassMethods` is present, `Cuba` will be extended\nwith it.\n\n``` ruby\nmodule GetSetter\n  module ClassMethods\n    def set(key, value)\n      settings[key] = value\n    end\n\n    def get(key)\n      settings[key]\n    end\n  end\nend\n\nCuba.plugin GetSetter\n\nCuba.set(:foo, \"bar\")\n\nassert_equal \"bar\", Cuba.get(:foo)\nassert_equal \"bar\", Cuba.settings[:foo]\n```\n\nContributing\n------------\n\nA good first step is to meet us on IRC and discuss ideas. If that's\nnot possible, you can create an issue explaining the proposed change\nand a use case. We pay a lot of attention to use cases, because our\ngoal is to keep the code base simple. In many cases, the result of\na conversation will be the creation of another tool, instead of the\nmodification of Cuba itself.\n\nIf you want to test Cuba, you may want to use a gemset to isolate\nthe requirements. We recommend the use of tools like [dep][dep] and\n[gs][gs], but you can use similar tools like [gst][gst] or [bs][bs].\n\nThe required gems for testing and development are listed in the\n`.gems` file. If you are using [dep][dep], you can create a gemset\nand run `dep install`.\n\n[dep]: http://cyx.github.io/dep/\n[gs]: http://soveran.github.io/gs/\n[gst]: https://github.com/tonchis/gst\n[bs]: https://github.com/educabilia/bs\n","funding_links":[],"categories":["Cuba Components \u0026 Related Gems","Web Apps, Services \u0026 Interaction","Ruby","Gems","Frameworks"],"sub_categories":["Web App Frameworks","Web Frameworks"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fcuba","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoveran%2Fcuba","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoveran%2Fcuba/lists"}