{"id":19157778,"url":"https://github.com/coyorkdow/closure","last_synced_at":"2025-05-07T08:34:35.814Z","repository":{"id":65812781,"uuid":"582716022","full_name":"coyorkdow/closure","owner":"coyorkdow","description":"A c++ function closure implementation. It integrates the std::function and std::bind, and even more powerful.","archived":false,"fork":false,"pushed_at":"2023-08-14T08:31:25.000Z","size":135,"stargazers_count":52,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-19T20:53:27.672Z","etag":null,"topics":["bind","closure-library","closure-templates","cpp","function","functional","metaprograming"],"latest_commit_sha":null,"homepage":"","language":"C++","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/coyorkdow.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-27T17:03:35.000Z","updated_at":"2025-02-20T07:29:08.000Z","dependencies_parsed_at":"2024-11-09T08:42:14.489Z","dependency_job_id":"81f55d5b-58e3-4055-b6b2-218a8e113c43","html_url":"https://github.com/coyorkdow/closure","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coyorkdow%2Fclosure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coyorkdow%2Fclosure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coyorkdow%2Fclosure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coyorkdow%2Fclosure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coyorkdow","download_url":"https://codeload.github.com/coyorkdow/closure/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252843343,"owners_count":21812861,"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":["bind","closure-library","closure-templates","cpp","function","functional","metaprograming"],"created_at":"2024-11-09T08:42:03.756Z","updated_at":"2025-05-07T08:34:35.802Z","avatar_url":"https://github.com/coyorkdow.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Closure\n\n **Closure is a c++ functional object implementation. It integrates the std::function and std::bind, and even more powerful.**\n\n[![Linux Status](https://github.com/coyorkdow/closure/actions/workflows/linux.yml/badge.svg)](https://github.com/coyorkdow/closure/actions/workflows/linux.yml)\n[![macOS Status](https://github.com/coyorkdow/closure/actions/workflows/macos.yml/badge.svg)](https://github.com/coyorkdow/closure/actions/workflows/macos.yml)\n[![C++ Standard](https://img.shields.io/badge/cpp14-minimum%20required-success?logo=cplusplus)](https://en.cppreference.com/w/cpp/14)\n\nClosure is header-only. To use Closure, simply copy the directory `include` into your project, and add `#include \"closure/closure.hpp\"` in your source files.\n\n## Features\n\n- Support almost all the methods of `std::function`, except `target_type()` (need RTTI).\n- Support arguments binding, therefore it can replace `std::bind`.\n- Support range placeholder, `PlaceHolder\u003cI, J\u003e()` indicates a placeholders sequence from `I` to `J`.\n- It can store non-copyable object, like `std::unique_ptr`. An extra method `copyable()` is provided to check if the object currently stored in a `Closure` instance is copyable. If it returns `false`, then trying copy this instance (construct or assign) will get an empty `Closure`.\n- Support small object optimization. On x64 machines, any objects of the type which is trivially copyable and `sizeof` not greater than 16 will be stored locally. No dynamical memory allocated.\n- Helper function `MakeClosure` can create an instance of `Closure` and deduce its type, you can use `auto` instead of manually writing the `Closure`'s template arguments. `MakeClosure` also supports arguments binding.\n\nTry it online through the Compiler Explorer https://godbolt.org/z/KrGejazf7\n\nRead more details in\n\n[Compare to std::function](#compare-to-stdfunction)\n\n[Compare to std::bind](#compare-to-stdbind)\n\n[closure::Any](#closureany)\n\n## Basic Usage\n\nUse like `std::function`. Store a function pointer or any callable object (including pointer to member function).\n```C++\nint calculate_sum(const std::string\u0026 exp) {\n  int ans = 0;\n  int cur_num = 0;\n  for (auto iter = exp.begin(); iter \u003c exp.end(); ++iter) {\n    if (*iter == '+') {\n      assert(iter != exp.begin());\n      ans += cur_num;\n      cur_num = 0;\n    } else {\n      assert('0' \u003c= *iter \u0026\u0026 *iter \u003c= '9');\n      cur_num = cur_num * 10 + *iter - '0';\n    }\n  }\n  ans += cur_num;\n  return ans;\n}\n\nusing namespace closure;\nClosure\u003cint(const std::string\u0026)\u003e closure1;\nclosure1 = calculate_sum;\nclosure1(\"1+2+3\"); // result is 6\n\nstd::string exp = \"1+2+3\";\nauto wrap_sum = [=] (const std::string\u0026 exp2) {\n  return calculate_sum(exp + \"+\" + exp2);\n};\nclosure1 = wrap_sum;\nclosure1(\"4\"); // result is 10\n```\n\n## Binding\n\nYou can fast bind the first n arguments by passing them to the constructor or `MakeClosure`.\n\n```C++\nstd::size_t sum(const int\u0026 v1, double v2, int v3, int v4) noexcept { return v1 + v2 + v3 + v4; }\n\nusing namespace closure;\nauto closure1 = MakeClosure(sum, 1); // bind 1 to arg v1\n// Alternatively, Closure\u003cstd::size_t(double, int, int)\u003e closure1(sum, 1);\nstatic_assert(std::is_same\u003cdecltype(closure1), Closure\u003cstd::size_t(double, int, int)\u003e\u003e::value);\nclosure1(2, 3, 4); // result is 10\n```\n\nOr, you can use placeholders to process more complicatedly binding. The number of placeholders is unlimited.\n\n```C++\n// Change the order of arguments.\nauto lambda1 = [](int v1, int v2) { return v1 - v2; };\n\nauto closure1 = closure::MakeClosure(lambda1, closure::PlaceHolder\u003c1\u003e(), closure::PlaceHolder\u003c0\u003e());\nclosure1(5, 3); // result is -2\n```\n\nOr, you can use range placeholder. With range placeholder, not only bind the first n, the last n or middle n is also quite easy. Just use `PlaceHolder\u003cI, J\u003e()` to make an offset.\n\n```C++\nauto lambda = [](int a, int b, int c, int d, int e, int f, int g) {\n  using std::to_string;\n  return to_string(a) + to_string(b) + to_string(c) + to_string(d) + to_string(e) + to_string(f) + to_string(g);\n};\nauto closure1 = closure::MakeClosure(lambda, closure::PlaceHolder\u003c0, 4\u003e(), 6, 7); // bind the last two arguments\nstd::string res = closure1(1, 2, 3, 4, 5); // result is \"1234567\"\n```\n\n\n## Compare to `std::function`\n\n### Stores non-copyable object\n`std::function` can only store the copyable object. Before c++23 introduced `std::move_only_function`, using only the standard library you cannot create a generic functional type which can hold a non-copyable functor.\n\n```C++\nclass TestClassBindMethod {\n public:\n  int ResIntArg1NonConst(int v) { return v; }\n};\n\nauto ptr = std::make_unique\u003cTestClassBindMethod\u003e();\nauto closure4 = closure::MakeClosure(\u0026TestClassBindMethod::ResIntArg1NonConst, std::move(ptr));\nclosure4(123); // result is 123\nauto bounded = [capture0 = std::make_unique\u003cTestClassBindMethod\u003e()](int v) {\n  return capture0-\u003eResIntArg1NonConst(v);\n};\n//  std::function\u003cint(int)\u003e _ = std::move(bounded);  // can't compile\nclosure4 = std::move(bounded);\n\nassert(!closure4.copyable()); // cannot copy a std::unique_ptr\nauto closure5 = closure4;\nassert(!closure5); // trying copy a non-copyable closure will get an empty result.\n```\n\n### Auto deduction\nBefore c++17, when using `std::function` you have to correctly write the complete type of the object you want to construct. `MakeClosure` can help you omit this step.\n\n```C++\ntemplate \u003cclass C, class... Args\u003e\nauto MakeClosure(C\u0026\u0026, Args\u0026\u0026...); // deduce the return type\n```\n\nIf `C` is a function pointer, or a member function pointer (a.k.a. pointer to class method), or a \"simple functor\". Then `MakeClosure` can be applied, and it will return a `Closure` instance with the proper type.\n\nA simple functor is a non-template, non-generic lambda, or a class type with one and only one `operator()` overloading, while this `operator()` is not a template. More formly, a class `F` is a functor if and only if `decltype(\u0026F::operator())` is a valid expression, and it is a type of member function pointer.\n\nThese rules are similar to the deduction guides of `std::function` that introduced in c++17. However, the deduction rules don't contain the deduction for member function pointers, moreover, `MakeClosure` doesn't need c++17.\n\n```C++\nstruct NonSimple {\n  std::string operator()() const { return \"empty\"; }\n  int operator()(int a, int b) const { return a + b; }\n};\n//  MakeClosure(NonSimple{}); // can't compile\nclosure::Closure\u003cstd::string()\u003e closure1 = NonSimple{};\nclosure1(); // result is \"empty\";\nClosure\u003cint(int, int)\u003e closure2 = NonSimple{};\nclosure2(1, 2); // result is 3\n\nstruct Simple {\n  int operator()(int a, int b) const { return a + b; }\n};\nclosure2 = MakeClosure(Simple{}); // ok\n```\n\n## Compare to `std::bind`\n\n`std::bind` is somehow a bad design and is considered to be deprecated. But `Closure` makes up many drawbacks of `std::bind`. \n\n### Type specified\nThe type of the object return from `std::bind` is unspecified, which means you have to store it in the `std::function` to save it elsewhere. `Closure` integrates the arguments binding, and each `Closure` instance has a determined type.\n\n### Range binding\n`std::bind` cannot bind first n arguments directly. Instead, you have to use `std::placeholders::_1`, `std::placeholders::_2`, ... in order. The standard didn't introduce `std::bind_front` until c++20, and `std::bind_back` until c++23. `Closure` provides such feature that you can simply bind first n arguments, same as `bind_front`.\n\nBesides, with the range placeholder. `closure` can even offer a more flexible range binding than just bind front or bind back.\n\n### No error binding\nUsing `std::bind` you can even create a \"callable\" object that cannot call at all. Later when you try to call it, IDE and compiler will give you a lot of errors that hard to read. But using `Closure` you can never create a closure that unable to call. And the error messages are more human-friendly because it's incurred by a `static_assert`.\n\n```C++\nauto lambda = [](std::unique_ptr\u003cint\u003e) {};\nauto b = std::bind(lambda, std::make_unique\u003cint\u003e()); // you can create b, even if it's wrong from the beginning.\nb(); // the compiler will only complain error when you try to call it.\nclosure::MakeClosure(lambda, std::make_unique\u003cint\u003e()); // error, and the compiler will give an error message that easy to read.\n```\n\nIf you use `std::bind`, when `b()`, the error message given by the gcc maybe very long, for example like (only a small part of the front, since it's too long)\n\n```C++\n: error: no match for call to '(std::_Bind\u003cTestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e(std::unique_ptr\u003cint\u003e)\u003e) ()'\n  620 |   b();\n      |   ~^~\nIn file included from googletest/include/gtest/gtest-printers.h:104,\n                 from googletest/include/gtest/gtest-matchers.h:48,\n                 from googletest/include/gtest/internal/gtest-death-test-internal.h:46,\n                 from googletest/include/gtest/gtest-death-test.h:43,\n                 from googletest/include/gtest/gtest.h:61,\n                 from :\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional:565:9: note: candidate: 'template\u003cclass ... _Args, class _Result\u003e _Result std::_Bind\u003c_Functor(_Bound_args ...)\u003e::operator()(_Args\u0026\u0026 ...) [with _Args = {_Args ...}; _Functor = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e; _Bound_args = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}]'\n  565 |         operator()(_Args\u0026\u0026... __args)\n      |         ^~~~~~~~\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional:565:9: note:   template argument deduction/substitution failed:\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional: In substitution of 'template\u003cclass _Functor, class ... _Bound_args\u003e template\u003cclass _Fn, class _CallArgs, class ... _BArgs\u003e using _Res_type_impl = typename std::result_of\u003c_Fn\u0026(decltype (std::_Mu\u003ctypename std::remove_cv\u003c_BArgs\u003e::type, std::is_bind_expression\u003ctypename std::remove_cv\u003c_BArgs\u003e::type\u003e::value, (std::is_placeholder\u003ctypename std::remove_cv\u003c_BArgs\u003e::type\u003e::value \u003e 0)\u003e()(declval\u003c_BArgs\u0026\u003e(), declval\u003c_CallArgs\u0026\u003e()))\u0026\u0026 ...)\u003e::type [with _Fn = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e; _CallArgs = std::tuple\u003c\u003e; _BArgs = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}; _Functor = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e; _Bound_args = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}]':\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional:532:8:   required by substitution of 'template\u003cclass _Functor, class ... _Bound_args\u003e template\u003cclass _CallArgs\u003e using _Res_type = std::_Bind\u003c_Functor(_Bound_args ...)\u003e::_Res_type_impl\u003c_Functor, _CallArgs, _Bound_args ...\u003e [with _CallArgs = std::tuple\u003c\u003e; _Functor = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e; _Bound_args = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}]'\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional:562:9:   required from here\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/functional:528:15: error: no type named 'type' in 'struct std::result_of\u003cTestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e\u0026(std::unique_ptr\u003cint\u003e\u0026)\u003e'\n  528 |         using _Res_type_impl\n      |               ^~~~~~~~~~~~~~\n```\n\nBut the error message caused by `closure::MakeClosure(lambda, std::make_unique\u003cint\u003e())` is much shorter, and will tell you \"the given arguments don't match the arguments of callee\".\n\n```C++\nIn file included from :\nclosure.hpp: In instantiation of 'class closure::closureimpl::ClosureImpl\u003cvoid(std::unique_ptr\u003cint\u003e), TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e, void\u003e':\n/usr/local/Cellar/gcc/12.2.0/include/c++/12/type_traits:734:38:   required from 'struct std::is_trivially_copyable\u003cclosure::closureimpl::ClosureImpl\u003cvoid(std::unique_ptr\u003cint\u003e), TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e, void\u003e \u003e'\nclosure.hpp:201:68:   required from 'struct closure::closureimpl::soo::IsSmallObject\u003cclosure::closureimpl::ClosureImpl\u003cvoid(std::unique_ptr\u003cint\u003e), TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e, void\u003e \u003e'\nclosure.hpp:216:80:   required by substitution of 'template\u003cclass Tp, class ... Args, typename std::enable_if\u003c(! closure::closureimpl::soo::IsSmallObject\u003cTp\u003e::value), int\u003e::type \u003canonymous\u003e \u003e void closure::closureimpl::StoragePool::emplace(Args\u0026\u0026 ...) [with Tp = closure::closureimpl::ClosureImpl\u003cvoid(std::unique_ptr\u003cint\u003e), TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e, void\u003e; Args = {TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e)\u003e\u0026, std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}; typename std::enable_if\u003c(! closure::closureimpl::soo::IsSmallObject\u003cTp\u003e::value), int\u003e::type \u003canonymous\u003e = \u003cmissing\u003e]'\nclosure.hpp:312:35:   required from 'auto closure::closureimpl::MakeClosureImpl(StoragePool*, closure::ArgList\u003cTps2 ...\u003e, Callable\u0026\u0026, Bounds\u0026\u0026 ...) [with R = void; ClosureArgs = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}; Callable = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e\u0026; Bounds = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}; typename std::enable_if\u003c(! closure::placeholders::HasPlaceHolder\u003cclosure::ArgList\u003cOs2 ...\u003e \u003e::value), int\u003e::type \u003canonymous\u003e = 0]'\nclosure.hpp:500:113:   required from 'auto closure::MakeClosure(Functor\u0026\u0026, Bounds\u0026\u0026 ...) [with Functor = TestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e\u0026; Bounds = {std::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e}; typename std::enable_if\u003c(traits::IsSimpleFunctor\u003ctypename std::remove_reference\u003c_Tp\u003e::type\u003e::value \u0026\u0026 (! placeholders::HasPlaceHolder\u003cArgList\u003cTps2 ...\u003e \u003e::value)), int\u003e::type \u003canonymous\u003e = 0]'\n:   required from here\nclosure.hpp:111:28: error: static assertion failed: the given arguments don't match the arguments of callee\n  111 |   static_assert(validator::is_invokable, \"the given arguments don't match the arguments of callee\");\n      |                            ^~~~~~~~~~~~\nclosure.hpp:111:28: note: 'closure::closureimpl::Validator\u003cTestClosureWithPlaceHolders_Method_Test::TestBody()::\u003clambda(std::unique_ptr\u003cint\u003e)\u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e, closure::ArgList\u003cstd::unique_ptr\u003cint, std::default_delete\u003cint\u003e \u003e \u003e \u003e::is_invokable' evaluates to false\n```\n\n### Better SOO\nSmall object optimization (SOO) for `std::function` is well supported in gcc and other well known compilers. An object applicable SOO will be stored locally, and no more dynamic memory allocating. But SOO has some restrictions. For example, in gcc, only the trivially-copyable objects can be locally-stored. Meanwhile, there is no guaranteed that the return type of the `std::bind` is trivially-copyable, even if the callable object and all the binding arguments are trivially-copyable.\n\n```C++\nauto bad_bind = std::bind([](int a, int b) { return a + b; }, 1, std::placeholders::_1);\nstatic_assert(std::is_trivially_copyable\u003cdecltype(bad_bind)\u003e::value, \"\"); // error, bad_bind is not a trivially copyable object.\n```\n\nIn `Closure`, any callable object (and its binding arguments) is enclosed in an internal type called `ClosureImpl`. If a `ClosureImpl` type satisfies `closure::closureimpl::soo::IsSmallObject`, Then the corresponding closure will be stored locally.\n\nIt is guaranteed that if 1) the callable object's type is trivially-copyable and 2) for each binding arguments the type of argument is trivially-copyable (placeholders are all trivially-copyable), then the `ClosureImpl` is trivially-copyable.\n\n```C++\n// The underlying `ClosureImpl` type is trivially-copyable. \nauto closure = MakeClosure([](int a, int b) { return a + b; }, 1);\n```\n\n## closure::Any\n\nSometimes you may create a closure with the discontinuous placeholders, thus some parameters are useless and will be abandoned when calling. For example. Object `c` takes 4 arguments, but only the 2nd and 4th arguments are meaningful. As for the 1st and 3rd arguments, they can be anything.\n\n```C++\nauto lambda = [](int a, int b) { return a + b; };\nauto c = MakeClosure(lambda, PlaceHolder\u003c1\u003e(), PlaceHolder\u003c3\u003e());\n\nclosure(\"123\", 4, \"567\", 8); // ok, result is 12\nclosure(std::vector\u003cint\u003e{1, 2}, 3, std::vector\u003clong\u003e{4, 5}, 6); // ok, result is 9\n```\n\nWhen the placeholders are discontinuous, `MakeClosure` will let `closure::Any` be the type of the useless parameters. So the type of `c` is `closure::Closure\u003cint(closure::Any, int, closure::Any, int)`.\n\n```C++\nstd::is_same\u003cdecltype(c), closure::Closure\u003cint(closure::Any, int, closure::Any, int)\u003e\u003e::value; // equals to true\n```\n\nSince `closure::Any` means any type, you can assign `c` to any other closure with the type that the useful parameters are matched. Like\n\n```C++\n// it's ok that the 4th parameter is float type, because float can implicitly convert to int\nClosure\u003cint(int, int, std::string, float)\u003e c2(lambda, PlaceHolder\u003c1\u003e(), PlaceHolder\u003c3\u003e());\nc2 = c;\nc2(1, 2, \"3\", 4.2); // result is 6\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoyorkdow%2Fclosure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoyorkdow%2Fclosure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoyorkdow%2Fclosure/lists"}