{"id":35232655,"url":"https://github.com/gepheum/skir-cc-gen","last_synced_at":"2026-02-26T23:01:36.384Z","repository":{"id":284062695,"uuid":"953668547","full_name":"gepheum/skir-cc-gen","owner":"gepheum","description":"Official plugin for generating C++ code from .skir files","archived":false,"fork":false,"pushed_at":"2026-02-21T20:13:18.000Z","size":804,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-21T23:13:44.389Z","etag":null,"topics":["codegen","cpp","rpc-framework","serialization","skir"],"latest_commit_sha":null,"homepage":"https://skir.sh/","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/gepheum.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-23T21:41:37.000Z","updated_at":"2026-02-21T20:13:03.000Z","dependencies_parsed_at":"2025-03-24T00:27:30.484Z","dependency_job_id":"028c0f07-cce5-46f3-a0ad-8ec98c38831e","html_url":"https://github.com/gepheum/skir-cc-gen","commit_stats":null,"previous_names":["gepheum/soia-cc-gen","gepheum/skir-cc-gen"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/gepheum/skir-cc-gen","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gepheum%2Fskir-cc-gen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gepheum%2Fskir-cc-gen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gepheum%2Fskir-cc-gen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gepheum%2Fskir-cc-gen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gepheum","download_url":"https://codeload.github.com/gepheum/skir-cc-gen/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gepheum%2Fskir-cc-gen/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29876340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T22:37:10.609Z","status":"ssl_error","status_checked_at":"2026-02-26T22:37:09.019Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["codegen","cpp","rpc-framework","serialization","skir"],"created_at":"2025-12-30T03:03:18.617Z","updated_at":"2026-02-26T23:01:36.346Z","avatar_url":"https://github.com/gepheum.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm](https://img.shields.io/npm/v/skir-cc-gen)](https://www.npmjs.com/package/skir-cc-gen)\n[![build](https://github.com/gepheum/skir-cc-gen/workflows/Build/badge.svg)](https://github.com/gepheum/skir-cc-gen/actions)\n\n# Skir's C++ Code Generator\n\nOfficial plugin for generating C++ code from [.skir](https://github.com/gepheum/skir) files.\n\nTargets C++17 and higher.\n\n## Set up\n\nIn your `skir.yml` file, add the following snippet under `generators`:\n```yaml\n  - mod: skir-cc-gen\n    outDir: ./src/skirout\n    config:\n      writeGoogleTestHeaders: true  # If you  use GoogleTest\n```\n\n## Runtime dependencies\n\nThe generated C++ code depends on the [skir client library](https://github.com/gepheum/skir-cc-gen/tree/main/client), [absl](https://abseil.io/) and optionally [GoogleTest](https://github.com/google/googletest).\n\nAdd this to your `CMakeLists.txt`:\n\n```cmake\ninclude(FetchContent)\n\n# skir-client\nFetchContent_Declare(\n  skir-client\n  GIT_REPOSITORY https://github.com/gepheum/skir-cc-gen.git\n  GIT_TAG        main  # Or pick a specific commit/tag\n  SOURCE_SUBDIR  client\n)\nFetchContent_MakeAvailable(skir-client)\n```\n\nSee this [example](https://github.com/gepheum/skir-cc-example/blob/main/CMakeLists.txt).\n\n## C++ generated code guide\n\nThe examples below are for the code generated from [this](https://github.com/gepheum/skir-cc-example/blob/main/skir_src/user.skir) .skir file.\n\n### Referring to generated symbols\n\nEvery generated symbol lives in a namespace called `skirout_${path}`,\nwhere `${path}` is the path to the .skir file relative from the root of the\nskir source directory, with the \".skir\" extension removed, and slashes\nreplaced with underscores.\n\n```c++\n#include \"skirout/user.h\"\n\nusing ::skirout_user::SubscriptionStatus;\nusing ::skirout_user::User;\nusing ::skirout_user::UserRegistry;\n```\n\n### Constructing structs\n\n```c++\n// You can construct a struct like this:\nUser john;\njohn.user_id = 42;\njohn.name = \"John Doe\";\n\n// Or you can use the designated initialized syntax:\nUser jane = {\n    // Keep fields in alphabetical order\n    .name = \"Jane Doe\",\n    .pets = {{\n                  .name = \"Fluffy\",\n                  .picture = \"cat\",\n              },\n              {\n                  .name = \"Rex\",\n                  .picture = \"dog\",\n              }},\n    .subscription_status = skirout::kPremium,\n    .user_id = 43,\n};\n\n// ${Struct}::whole forces you to initialize all the fields of the struct.\n// You will get a compile-time error if you miss one.\nUser lyla = User::whole{\n    .name = \"Lyla Doe\",\n    .pets =\n        {\n            User::Pet::whole{\n                .height_in_meters = 0.05f,\n                .name = \"Tiny\",\n                .picture = \"🐁\",\n            },\n        },\n    .quote = \"This is Lyla's world, you just live in it\",\n    .subscription_status = skirout::kFree,\n    .user_id = 44,\n};\n```\n\n### Constructing enums\n\n```c++\n\n// Use skirout::${kFieldName} or ${Enum}::${kFieldName} for constant variants.\nSubscriptionStatus john_status = skirout::kFree;\nSubscriptionStatus jane_status = skirout::kPremium;\nSubscriptionStatus lara_status = SubscriptionStatus::kFree;\n\n// Compilation error: MONDAY is not a field of the SubscriptionStatus enum.\n// SubscriptionStatus sara_status = skirout::kMonday;\n\n// Use wrap_${field_name} for wrapper variants.\nSubscriptionStatus jade_status =\n    skirout::wrap_trial(SubscriptionStatus::Trial({\n        .start_time = absl::FromUnixMillis(1743682787000),\n    }));\nSubscriptionStatus roni_status = SubscriptionStatus::wrap_trial({\n    .start_time = absl::FromUnixMillis(1743682787000),\n});\n```\n\n### Conditions on enums\n\n```c++\nif (john_status == skirout::kFree) {\n  std::cout \u003c\u003c \"John, would you like to upgrade to premium?\\n\";\n}\n\n// Call is_${field_name}() to check if the enum holds a wrapper variant.\nif (jade_status.is_trial()) {\n  // as_${field_name}() returns the wrapped value\n  const SubscriptionStatus::Trial\u0026 trial = jade_status.as_trial();\n  std::cout \u003c\u003c \"Jade's trial started on \" \u003c\u003c trial.start_time \u003c\u003c \"\\n\";\n}\n\n// One way to do an exhaustive switch on an enum.\nswitch (lara_status.kind()) {\n  case SubscriptionStatus::kind_type::kUnknown:\n    // UNKNOWN is the default value for an uninitialized SubscriptionStatus.\n    // ...\n    break;\n  case SubscriptionStatus::kind_type::kFreeConst:\n    // ...\n    break;\n  case SubscriptionStatus::kind_type::kPremiumConst:\n    // ...\n    break;\n  case SubscriptionStatus::kind_type::kTrialWrapper: {\n    const SubscriptionStatus::Trial\u0026 trial = lara_status.as_trial();\n    std::cout \u003c\u003c \"Lara's trial started on \" \u003c\u003c trial.start_time \u003c\u003c \"\\n\";\n  }\n}\n\n// Another way to do an exhaustive switch using the visitor pattern.\nstruct Visitor {\n  void operator()(skirout::k_unknown) const {\n    std::cout \u003c\u003c \"Lara's subscription status is UNKNOWN\\n\";\n  }\n  void operator()(skirout::k_free) const {\n    std::cout \u003c\u003c \"Lara's subscription status is FREE\\n\";\n  }\n  void operator()(skirout::k_premium) const {\n    std::cout \u003c\u003c \"Lara's subscription status is PREMIUM\\n\";\n  }\n  void operator()(SubscriptionStatus::wrap_trial_type\u0026 w) const {\n    const SubscriptionStatus::Trial\u0026 trial = w.value;\n    std::cout \u003c\u003c \"Lara's trial started on \" \u003c\u003c trial.start_time \u003c\u003c \"\\n\";\n  }\n};\nlara_status.visit(Visitor());\n```\n\n### Serialization\n\nUse `ToDenseJson`, `ToReadableJson` or `ToBytes` to serialize a skir value.\n\n```c++\n// Serialize a skir value to JSON with ToDenseJson or ToReadableJson.\nstd::string john_dense_json = skir::ToDenseJson(john);\nstd::cout \u003c\u003c john_dense_json \u003c\u003c \"\\n\";\n// [42,\"John Doe\"]\n\nstd::cout \u003c\u003c skir::ToReadableJson(john) \u003c\u003c \"\\n\";\n// {\n//   \"user_id\": 42,\n//   \"name\": \"John Doe\"\n// }\n\n// The dense flavor is the flavor you should pick if you intend to\n// deserialize the value in the future. Skir allows fields to be renamed, and\n// because fields names are not part of the dense JSON, renaming a field does\n// not prevent you from deserializing the value.\n// You should pick the readable flavor mostly for debugging purposes.\n\n// The binary format is not human readable, but it is a bit more compact than\n// JSON, and serialization/deserialization can be a bit faster.\n// Only use it when this small performance gain is likely to matter, which\n// should be rare.\nskir::ByteString john_bytes = skir::ToBytes(john);\n```\n\n### Deserialization\n\nUse `Parse` to deserialize a skir value from JSON or binary format.\n\n```c++\n// Use Parse to deserialize a skir value from JSON or binary format.\nabsl::StatusOr\u003cUser\u003e reserialized_john = skir::Parse\u003cUser\u003e(john_dense_json);\nassert(reserialized_john.ok() \u0026\u0026 *reserialized_john == john);\n\nreserialized_john = skir::Parse\u003cUser\u003e(john_bytes.as_string());\nassert(reserialized_john.ok() \u0026\u0026 *reserialized_john == john);\n```\n\n### Primitive serializers\n\n```c++\n// Skir type: bool\nassert(skir::ToDenseJson(true) == \"1\");\n// Skir type: int32\nassert(skir::ToDenseJson(int32_t{3}) == \"3\");\n// Skir type: int64\nassert(skir::ToDenseJson(int64_t{9223372036854775807}) ==\n       \"\\\"9223372036854775807\\\"\");\n// Skir type: hash64\nassert(skir::ToDenseJson(uint64_t{18446744073709551615ULL}) ==\n       \"\\\"18446744073709551615\\\"\");\n// Skir type: timestamp\nassert(skir::ToDenseJson(absl::FromUnixMillis(1743682787000)) ==\n       \"1743682787000\");\n// Skir type: float32\nassert(skir::ToDenseJson(3.14f) == \"3.14\");\n// Skir type: float64\nassert(skir::ToDenseJson(3.14) == \"3.14\");\n// Skir type: string\nassert(skir::ToDenseJson(std::string(\"Foo\")) == \"\\\"Foo\\\"\");\n// Skir type: bytes\nassert(skir::ToDenseJson(skir::ByteString(\"\\x01\\x02\\x03\")) == \"\\\"AQID\\\"\");\n```\n\n### Composite serializers\n\n```c++\n// Skir type: string?\nassert(skir::ToDenseJson(std::optional\u003cstd::string\u003e(\"foo\")) == \"\\\"foo\\\"\");\nassert(skir::ToDenseJson(std::optional\u003cstd::string\u003e()) == \"null\");\n\n// Skir type: [bool]\nassert(skir::ToDenseJson(std::vector\u003cbool\u003e{true, false}) == \"[1,0]\");\n```\n\n### Keyed arrays\n\nA `keyed_items\u003cT, get_key\u003e` is a container that stores items of type T\nand allows for fast lookups by key using a hash table.\n\n```c++\nUserRegistry user_registry;\nskir::keyed_items\u003cUser, skirout::get_user_id\u003c\u003e\u003e\u0026 users = user_registry.users;\nusers.push_back(john);\nusers.push_back(jane);\nusers.push_back(lyla);\n\nassert(users.size() == 3);\nassert(users[0] == john);\n\nconst User* maybe_jane = users.find_or_null(43);\nassert(maybe_jane != nullptr \u0026\u0026 *maybe_jane == jane);\n\nassert(users.find_or_default(44).name == \"Lyla Doe\");\nassert(users.find_or_default(45).name == \"\");\n\n// If multiple items have the same key, find_or_null and find_or_default\n// return the last one. Duplicates are allowed but generally discouraged.\nUser evil_lyla = lyla;\nevil_lyla.name = \"Evil Lyla\";\nusers.push_back(evil_lyla);\nassert(users.find_or_default(44).name == \"Evil Lyla\");\n```\n\n### Equality and hashing\n\nSkir structs and enums are equality comparable and hashable.\n\n```c++\nabsl::flat_hash_set\u003cUser\u003e user_set;\nuser_set.insert(john);\nuser_set.insert(jane);\nuser_set.insert(jane);\nuser_set.insert(lyla);\n\nassert(user_set.size() == 3);\n```\n\n### Constants\n\n```c++\nconst User\u0026 tarzan = skirout_user::k_tarzan();\nassert(tarzan.name == \"Tarzan\");\n```\n\n### Skir services\n\n#### Starting a skir service on an HTTP server\n\nFull example [here](https://github.com/gepheum/skir-cc-example/blob/main/service_start.cc).\n\n#### Sending RPCs to a skir service\n\nFull example [here](https://github.com/gepheum/skir-cc-example/blob/main/service_client.cc).\n\n### Dynamic reflection\n\n```c++\nusing ::skir::reflection::GetTypeDescriptor;\nusing ::skir::reflection::TypeDescriptor;\n\n// A TypeDescriptor describes a skir type. It contains the definition of all\n// the structs and enums referenced from the type.\nconst TypeDescriptor\u0026 user_descriptor = GetTypeDescriptor\u003cUser\u003e();\n\n// TypeDescriptor can be serialized/deserialized to/from JSON.\n\nabsl::StatusOr\u003cTypeDescriptor\u003e reserialized_type_descriptor =\n    TypeDescriptor::FromJson(user_descriptor.AsJson());\nassert(reserialized_type_descriptor.ok());\n```\n\n### Static reflection\n\nStatic reflection allows you to inspect and modify values of generated\nskir types in a typesafe manner.\n\nSee [string_capitalizer.h](https://github.com/gepheum/skir-cc-example/blob/main/string_capitalizer.h).\n\n```c++\nUser tarzan_copy = skirout_user::k_tarzan();\n// CapitalizeStrings recursively capitalizes all the strings found within a\n// skir value.\nCapitalizeStrings(tarzan_copy);\n\nstd::cout \u003c\u003c tarzan_copy \u003c\u003c \"\\n\";\n// {\n//   .user_id: 123,\n//   .name: \"TARZAN\",\n//   .quote: \"AAAAAAAAAAYAAAAAAAAAAYAAAAAAAAAA\",\n//   .pets: {\n//     {\n//       .name: \"CHEETA\",\n//       .height_in_meters: 1.67,\n//       .picture: \"🐒\",\n//     },\n//   },\n//   .subscription_status:\n//   ::skirout::wrap_trial_start_time(absl::FromUnixMillis(1743592409000 /*\n//   2025-04-02T11:13:29+00:00 */)),\n// }\n```\n\n### Writing unit tests with GoogleTest\n\nFull example [here](https://github.com/gepheum/skir-cc-example/blob/main/example.test.cc).\n\n#### Struct matchers\n\n```c++\nconst User john = {\n    .name = \"John Doe\",\n    .pets =\n        {\n            {.height_in_meters = 1.67, .name = \"Cheeta\", .picture = \"🐒\"},\n        },\n    .quote = \"Life is like a box of chocolates.\",\n    .user_id = 42,\n};\n\nEXPECT_THAT(john, (StructIs\u003cUser\u003e{\n                      // Only the specified fields are tested\n                      .pets = testing::ElementsAre(StructIs\u003cUser::Pet\u003e{\n                          .height_in_meters = testing::FloatNear(1.7, 0.1),\n                      }),\n                      .quote = testing::StartsWith(\"Life is\"),\n                      .user_id = 42,\n                  }));\n```\n\n#### Enum matchers\n\n```c++\nSubscriptionStatus john_status = skirout::kFree;\n\nEXPECT_THAT(john_status, testing::Eq(skirout::kFree));\n\nSubscriptionStatus jade_status = SubscriptionStatus::wrap_trial(\n    {.start_time = absl::FromUnixMillis(1743682787000)});\n\nEXPECT_THAT(jade_status, IsTrial());\nEXPECT_THAT(jade_status, IsTrial(StructIs\u003cSubscriptionStatus::Trial\u003e{\n                              .start_time = testing::Gt(absl::UnixEpoch())}));\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgepheum%2Fskir-cc-gen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgepheum%2Fskir-cc-gen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgepheum%2Fskir-cc-gen/lists"}