{"id":21654623,"url":"https://github.com/localvoid/osh","last_synced_at":"2026-04-18T09:36:13.146Z","repository":{"id":57316477,"uuid":"107242542","full_name":"localvoid/osh","owner":"localvoid","description":":cyclone: Component-based library for code generation","archived":false,"fork":false,"pushed_at":"2018-05-17T13:40:13.000Z","size":125,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-25T12:31:50.767Z","etag":null,"topics":["codegen","golang","javascript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/localvoid.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":"2017-10-17T08:52:13.000Z","updated_at":"2019-09-12T18:20:58.000Z","dependencies_parsed_at":"2022-08-25T21:11:08.258Z","dependency_job_id":null,"html_url":"https://github.com/localvoid/osh","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/localvoid%2Fosh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localvoid%2Fosh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localvoid%2Fosh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/localvoid%2Fosh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/localvoid","download_url":"https://codeload.github.com/localvoid/osh/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244207720,"owners_count":20416105,"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":["codegen","golang","javascript"],"created_at":"2024-11-25T08:28:27.694Z","updated_at":"2026-04-18T09:36:08.125Z","avatar_url":"https://github.com/localvoid.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [osh](https://github.com/localvoid/osh) \u0026middot; [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/localvoid/osh/blob/master/LICENSE) [![codecov](https://codecov.io/gh/localvoid/osh/branch/master/graph/badge.svg)](https://codecov.io/gh/localvoid/osh) [![CircleCI Status](https://circleci.com/gh/localvoid/osh.svg?style=shield\u0026circle-token=:circle-token)](https://circleci.com/gh/localvoid/osh) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/localvoid/osh)\n\n`osh` is a javascript library that provides component-based model for code generation.\n\n|Package    |NPM version                                                                                              |\n|-----------|---------------------------------------------------------------------------------------------------------|\n|osh        |[![npm version](https://img.shields.io/npm/v/osh.svg)](https://www.npmjs.com/package/osh)                |\n|osh-code   |[![npm version](https://img.shields.io/npm/v/osh-code.svg)](https://www.npmjs.com/package/osh-code)      |\n|osh-code-js|[![npm version](https://img.shields.io/npm/v/osh-code-js.svg)](https://www.npmjs.com/package/osh-code-js)|\n|osh-code-go|[![npm version](https://img.shields.io/npm/v/osh-code-go.svg)](https://www.npmjs.com/package/osh-code-go)|\n|osh-text   |[![npm version](https://img.shields.io/npm/v/osh-text.svg)](https://www.npmjs.com/package/osh-text)      |\n\n## Documentation\n\n### Tutorial\n\nIn this tutorial we will create a code generator that generates type validation functions from simple schemas like this:\n\n```js\nconst USER_SCHEMA = {\n  id: \"number\",\n  name: \"string\",\n};\n```\n\n`USER_SCHEMA` is a schema that defines expected types for object properties. With this schema, we would like to\ngenerate validation function that look like this:\n\n```js\nfunction validateUser(user) {\n  let errors;\n  let type;\n\n  type = typeof user.id;\n  if (type !== \"number\") {\n    if (errors === undefined) {\n      errors = [];\n    }\n    errors.push({ prop: \"id\", type: type });\n  }\n\n  type = typeof user.name;\n  if (type !== \"string\") {\n    if (errors === undefined) {\n      errors = [];\n    }\n    errors.push({ prop: \"name\", type: type });\n  }\n\n  return errors;\n}\n```\n\nFull source for this tutorial is available [here](https://github.com/localvoid/osh/tree/master/tutorial).\n\n#### Install dependencies\n\nTo build codegenerator we will use four packages:\n\n- [osh](https://npmjs.com/package/osh) package provides basic building blocks for text generation and a string renderer.\n- [osh-text](https://npmjs.com/package/osh-text) package provides general purpose text utilities.\n- [osh-code](https://npmjs.com/package/osh-code) package provides generic components that can be used in many different\n programming languages (`line()`, `indent()`, `comment()` etc).\n- [osh-code-js](https://npmjs.com/package/osh-code-js) package provides javascript specific components and a preset that\n sets up `osh-code` environment.\n- [incode](https://npmjs.com/package/incode) package will be used for injecting generated code.\n\n```sh\n$ npm install --save osh osh-text osh-code osh-code-js incode\n```\n\n#### Set up environment\n\nFirst thing that we need to do is set up `osh-code` environment for javascript code generation.\n[osh-code-js](https://npmjs.com/package/osh-code-js) provides a configurable `jsCode` preset that sets up codegen\nenvironment for javascript.\n\n```js\nimport { renderToString } from \"osh\";\nimport { jsCode } from \"osh-code-js\";\n\nfunction emit(...children) {\n  return renderToString(\n    jsCode(\n      {\n        lang: \"es2016\",\n      },\n      children,\n    ),\n  );\n}\n```\n\n`emit` function will take any `osh` nodes and render them inside a javascript codegen environment.\n\n#### Function declaration\n\nLet's start from generating a function declaration for our validate function:\n\n```js\nfunction validateUser() {\n  return;\n}\n```\n\nTo generate this declaration we will use three different components: `capitalize`, `line` and `indent`.\n\n`line(...children)` and `indent(...children)` are basic components and `capitalize(...children)` is a transformer. The\nmain difference between components and transformers is that transformers perform transformation step on a final\nstring, and components are producing another components and strings.\n\n`indent(...children)` component increases indentation level.\n\n`line(...children)` component wraps children into a line.\n\n`capitalize(...children)` transformer converts first character to uppercase.\n\n```js\nimport { capitalize } from \"osh\";\nimport { line, indent } from \"osh-code\";\n\nfunction validateFunction(name, schema) {\n  return [\n    line(\"function validate\", capitalize(name), \"() {\"),\n    indent(\n      line(\"return;\"),\n    ),\n    line(\"}\"),\n  ];\n}\n```\n\n#### Function arguments\n\nOne of the most common problems when automatically generating code is preventing name collisions for symbols.\n\n`osh-code` package has a `scope` component that solves this problem. With `scope` component we can define symbols with\nautomatic conflict resolution for colliding symbol names. `jsCode()` environment automatically registers all reserved\nkeywords as scope symbols, so symbols like `for` will be automatically renamed to prevent invalid javascript code\ngeneration.\n\nWhen defining a scope, there are three mandatory properties: `type`, `symbols` and `children`.\n\n- `type` is a scope type, it is used for symbol lookups when there are different symbols associated with the same key.\n- `symbols` is an array of symbols, all symbols should be created with `declSymbol(key, symbol)` factory.\n- `children` is an array of `osh` nodes.\n\nThere are two ways to retrieve symbols from the current scope:\n\n`getSymbol(context, type, key)` is a low-level function that retrieves symbol from the context provided as a first\nargument.\n\n`sym(type, key)` is a component that will retrieve symbol from the current context.\n\n```js\nimport { scope, declSymbol, sym } from \"osh-code\";\n\nconst ARGUMENTS = Symbol(\"Arguments\");\n\nfunction arg(name) {\n  return sym(ARGUMENTS, name);\n}\n\nfunction declArg(arg, children) {\n  return scope({\n    type: ARGUMENTS,\n    symbols: [declSymbol(\"data\", arg)],\n    children: children,\n  });\n}\n\nfunction validateFunction(name, schema) {\n  return (\n    declArg(\n      name,\n      [\n        line(\"function validate\", capitalize(name), \"(\", arg(\"data\"), \") {\"),\n        indent(\n          line(\"return;\"),\n        ),\n        line(\"}\"),\n      ],\n    )\n  );\n}\n```\n\nHere we defined two functions: `arg()` and `declArg()`. `arg()` is a helper function that will retrieve symbols from\nscopes with `ARGUMENTS` type. And `declArg()` will declare a `name` symbol with `\"data\"` key.\n\n#### Local variables\n\nIn our validation function we are using two local variables: `errors` and `type`, so we should create another `scope`\nand declare this variables.\n\n```js\nconst LOCAL_VARS = Symbol(\"LocalVars\");\n\nfunction lvar(name) {\n  return sym(LOCAL_VARS, name);\n}\n\nfunction declVars(vars, children) {\n  return scope({\n    type: LOCAL_VARS,\n    symbols: vars.map((name) =\u003e declSymbol(name, name)),\n    children: children,\n  });\n}\n\nfunction validateFunction(name, schema) {\n  return (\n    declArg(\n      name,\n      declVars(\n        [\"errors\", \"type\"],\n        [\n          line(\"function validate\", capitalize(name), \"(\", arg(\"data\"), \") {\"),\n          indent(\n            line(\"let \", lvar(\"errors\"), \";\"),\n            line(\"let \", lvar(\"type\"), \";\"),\n            line(),\n            line(\"return \", lvar(\"errors\"), \";\"),\n          ),\n          line(\"}\"),\n        ],\n      ),\n    )\n  );\n}\n```\n\n#### Generating type checking code\n\nNow we just need to generate type checking code for all fields.\n\n```js\nimport { intersperse } from \"osh-text\";\n\nfunction checkType(prop, type) {\n  return [\n    line(lvar(\"type\"), \" = typeof \", arg(\"data\"), \".\", prop),\n    line(\"if (\", lvar(\"type\"), \" !== \\\"\", type, \"\\\") {\"),\n    indent(\n      line(\"if (\", lvar(\"errors\"), \" === undefined) {\"),\n      indent(\n        line(lvar(\"errors\"), \" = [];\"),\n      ),\n      line(\"}\"),\n    ),\n    line(\"}\"),\n  ];\n}\n\nfunction validateFunction(name, schema) {\n  return (\n    declArg(\n      name,\n      declVars(\n        [\"errors\", \"type\"],\n        [\n          line(\"function validate\", capitalize(name), \"(\", arg(\"data\"), \") {\"),\n          indent(\n            line(\"let \", lvar(\"errors\"), \";\"),\n            line(\"let \", lvar(\"type\"), \";\"),\n            line(),\n            intersperse(\n              Object.keys(schema).map((prop) =\u003e checkType(prop, schema[prop])),\n              line(),\n            ),\n            line(),\n            line(\"return \", lvar(\"errors\"), \";\"),\n          ),\n          line(\"}\"),\n        ],\n      ),\n    )\n  );\n}\n```\n\n#### Injecting generated code into existing code\n\nAnd the final step is to inject generated code into existing code. To inject generated code we will use `incode`\npackage, it is using different directives for defining injectable regions:\n\n- `chk:emit()` injects a code block into the region\n\n```js\n// chk:emit(\"validate\", \"user\")\n// chk:end\n\nfunction getUser() {\n  const user = fetchUser();\n  const errors = validateUser(user);\n  if (errors !== undefined) {\n    throw new Error(\"Invalid user\");\n  }\n  return user;\n}\n```\n\n`incode` automatically detects line paddings for injectable regions, and we would like to use this information to indent\nour generated code. To do this, we will need to change our `emit()` function and assign a padding value to a `PADDING`\nsymbol in the context.\n\n```js\nimport * as fs from \"fs\";\nimport { context } from \"osh\";\nimport { PADDING } from \"osh-code\";\nimport { createDirectiveMatcher, inject } from \"incode\";\nimport { USER_SCHEMA } from \"./schema\";\n\nconst FILE = \"./code.js\";\n\nconst SCHEMAS = {\n  user: USER_SCHEMA,\n};\n\nfunction emit(padding, ...children) {\n  return renderToString(\n    context(\n      {\n        [PADDING]: padding === undefined ? \"\" : padding,\n      },\n      jsCode(\n        {\n          lang: \"es2016\",\n        },\n        children,\n      ),\n    ),\n  );\n}\n\nconst result = inject(\n  fs.readFileSync(FILE).toString(),\n  createDirectiveMatcher(\"chk\"),\n  (region) =\u003e {\n    const args = region.args;\n    if (args[0] === \"validate\") {\n      const name = args[1];\n      const schema = SCHEMAS[name];\n      return emit(region.padding, validateFunction(name, schema));\n    }\n\n    throw new Error(`Invalid region type: ${region.type}.`);\n  },\n);\n\nfs.writeFileSync(FILE, result);\n```\n\n### API\n\n#### Components\n\nComponents are declared with a simple functions that has two optional parameters `ctx` and `props`.\n\n```js\nfunction Component(ctx, props) {\n  return \"text\";\n}\n```\n\nComponent nodes are created with `component` function:\n\n```ts\nfunction component(fn: () =\u003e TChildren): ComponentNode\u003cundefined\u003e;\nfunction component(fn: (context: Context) =\u003e TChildren): ComponentNode\u003cundefined\u003e;\nfunction component\u003cT\u003e(fn: (context: Context, props: T) =\u003e TChildren, props: T): ComponentNode\u003cT\u003e;\nfunction component(fn: (context: Context, props: any) =\u003e TChildren, props?: any): ComponentNode\u003cany\u003e;\n```\n\n##### Example\n\n```js\nimport { component, renderToString } from \"osh\";\nimport { line, indent } from \"osh-code\";\n\nfunction Example(ctx, msg) {\n  return [\n    line(\"Example\"),\n    indent(\n      line(\"Indented Text: \", msg),\n    ),\n  ];\n}\n\nconsole.log(renderToString(component(Example, \"Hello\")));\n```\n\n#### Context\n\nContext is an immutable object that propagates contextual data to components.\n\nContext nodes are created with `context` factory function:\n\n```ts\nfunction context(ctx: Context, children: TChildren): ContextNode;\n```\n\n##### Example\n\n```js\nimport { component, context, renderToString } from \"osh\";\n\n// Unique symbol to prevent name collisions\nconst VARS = Symbol(\"Vars\");\n\n// Def component will assign variables to the current context\nfunction Def(ctx, props) {\n  return context(\n    { [VARS]: { ...ctx[VARS], ...{ props.vars } } },\n    props.children,\n  );\n}\n\nfunction def(vars, ...children) {\n  return component(Def, { vars, children });\n}\n\n// V component will extract variable from the current context\nfunction V(ctx, name) {\n  return ctx[VARS][name];\n}\n\nfunction v(name) {\n  return component(V, name);\n}\n\nfunction Example(ctx, msg) {\n  return line(\"Var: \", v(\"var\"))];\n}\n\nconsole.log(\n  renderToString(\n    def(\n      { \"var\": \"Hello\" },\n      component(Example),\n    ),\n  ),\n);\n```\n\n#### Transformers\n\nTransformer components perform transformations on rendered strings.\n\n```ts\nfunction transform(fn: (s: string) =\u003e string, ...children: TChildren[]): TransformNode;\nfunction transform(fn: (s: string, context: Context) =\u003e string, ...children: TChildren[]): TransformNode;\n```\n\n### Additional Packages\n\n- [osh-code](https://npmjs.com/package/osh-code) provides a basic set of components for generating program code.\n- [osh-text](https://npmjs.com/package/osh-text) provide general purpose text utilities.\n- [osh-code-go](https://npmjs.com/package/osh-code-go) provides a basic set of components for generating Go program\n code.\n- [osh-code-js](https://npmjs.com/package/osh-code-js) provides a basic set of components for generating\n Javascript(TypeScript) program code.\n- [osh-debug](https://npmjs.com/package/osh-debug) debug utilities.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocalvoid%2Fosh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flocalvoid%2Fosh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flocalvoid%2Fosh/lists"}