Skip to content

Commit 567466c

Browse files
committed
[wip]: Add renderer skeleton
1 parent c34742e commit 567466c

27 files changed

+1634
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist/
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dist/
2+
e2e/
3+
test-ledger/
4+
target/
5+
CHANGELOG.md
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Codama
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
"Software"), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Codama ➤ Renderers ➤ Pinocchio
2+
3+
[![npm][npm-image]][npm-url]
4+
[![npm-downloads][npm-downloads-image]][npm-url]
5+
6+
[npm-downloads-image]: https://img.shields.io/npm/dm/@codama/renderers-rust.svg?style=flat
7+
[npm-image]: https://img.shields.io/npm/v/@codama/renderers-rust.svg?style=flat&label=%40codama%2Frenderers-pinocchio
8+
[npm-url]: https://www.npmjs.com/package/@codama/renderers-pinocchio
9+
10+
This package generates Pinocchio-based Rust clients from your Codama IDLs.
11+
12+
## Installation
13+
14+
```sh
15+
pnpm install @codama/renderers-pinocchio
16+
```
17+
18+
> [!NOTE]
19+
> This package is **not** included in the main [`codama`](../library) package.
20+
>
21+
> However, note that the [`renderers`](../renderers) package re-exports the `renderVisitor` function of this package as `renderRustVisitor`.
22+
23+
## Usage
24+
25+
Once you have a Codama IDL, you can use the `renderVisitor` of this package to generate Rust clients. You will need to provide the base directory where the generated files will be saved and an optional set of options to customize the output.
26+
27+
```ts
28+
// node ./codama.mjs
29+
import { renderVisitor } from '@codama/renderers-pinocchio';
30+
31+
const pathToGeneratedFolder = path.join(__dirname, 'clients', 'pinocchio', 'src', 'generated');
32+
const options = {}; // See below.
33+
codama.accept(renderVisitor(pathToGeneratedFolder, options));
34+
```
35+
36+
## Options
37+
38+
The `renderVisitor` accepts the following options.
39+
40+
| Name | Type | Default | Description |
41+
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
42+
| `deleteFolderBeforeRendering` | `boolean` | `true` | Whether the base directory should be cleaned before generating new files. |
43+
| `formatCode` | `boolean` | `false` | Whether we should use `cargo fmt` to format the generated code. When set to `true`, the `crateFolder` option must be provided. |
44+
| `toolchain` | `string` | `"+stable"` | The toolchain to use when formatting the generated code. |
45+
| `crateFolder` | `string` | none | The path to the root folder of the Rust crate. This option is required when `formatCode` is set to `true`. |
46+
| `linkOverrides` | `Record<'accounts' \| 'definedTypes' \| 'instructions' \| 'pdas' \| 'programs' \| 'resolvers', Record<string, string>>` | `{}` | A object that overrides the import path of link nodes. For instance, `{ definedTypes: { counter: 'hooked' } }` uses the `hooked` folder to import any link node referring to the `counter` type. |
47+
| `dependencyMap` | `Record<string, string>` | `{}` | A mapping between import aliases and their actual crate name or path in Rust. |
48+
| `renderParentInstructions` | `boolean` | `false` | When using nested instructions, whether the parent instructions should also be rendered. When set to `false` (default), only the instruction leaves are being rendered. |
49+
| `traitOptions` | [`TraitOptions`](#trait-options) | `DEFAULT_TRAIT_OPTIONS` | A set of options that can be used to configure how traits are rendered for every Rust types. See [documentation below](#trait-options) for more information. |
50+
| `anchorTraits` | `boolean` | `true` | Whether to generate Anchor traits `impl` for account types. |
51+
52+
## Trait Options
53+
54+
The Rust renderer provides sensible default traits when generating the various Rust types you client will use. However, you may wish to configure these traits to better suit your needs. The `traitOptions` attribute is here to help you with that. Let's see the various settings it provides.
55+
56+
### Default traits
57+
58+
Using the `traitOptions` attribute, you may configure the default traits that will be applied to every Rust type. These default traits can be configured using 4 different attributes:
59+
60+
- `baseDefaults`: The default traits to implement for all types.
61+
- `dataEnumDefaults`: The default traits to implement for all data enum types, in addition to the `baseDefaults` traits. Data enums are enums with at least one non-unit variant — e.g. `pub enum Command { Write(String), Quit }`.
62+
- `scalarEnumDefaults`: The default traits to implement for all scalar enum types, in addition to the `baseDefaults` traits. Scalar enums are enums with unit variants only — e.g. `pub enum Feedback { Good, Bad }`.
63+
- `structDefaults`: The default traits to implement for all struct types, in addition to the `baseDefaults` traits.
64+
65+
Note that you must provide the fully qualified name of the traits you provide (e.g. `serde::Serialize`). Here are the default values for these attributes:
66+
67+
```ts
68+
const traitOptions = {
69+
baseDefaults: [
70+
'Clone',
71+
'Debug',
72+
'Eq',
73+
'PartialEq',
74+
],
75+
dataEnumDefaults: [],
76+
scalarEnumDefaults: ['Copy', 'PartialOrd', 'Hash', 'num_derive::FromPrimitive'],
77+
structDefaults: [],
78+
};
79+
```
80+
81+
### Overridden traits
82+
83+
In addition to configure the default traits, you may also override the traits for specific types. This will completely replace the default traits for the given type. To do so, you may use the `overrides` attribute of the `traitOptions` object.
84+
85+
This attribute is a map where the keys are the names of the types you want to override, and the values are the traits you want to apply to these types. Here is an example:
86+
87+
```ts
88+
const traitOptions = {
89+
overrides: {
90+
myCustomType: ['Clone', 'my::custom::Trait', 'my::custom::OtherTrait'],
91+
myTypeWithNoTraits: [],
92+
},
93+
};
94+
```
95+
96+
### Feature Flags
97+
98+
You may also configure which traits should be rendered under a feature flag by using the `featureFlags` attribute. This attribute is a map where the keys are feature flag names and the values are the traits that should be rendered under that feature flag. Here is an example:
99+
100+
```ts
101+
const traitOptions = {
102+
featureFlags: { fruits: ['fruits::Apple', 'fruits::Banana'] },
103+
};
104+
```
105+
106+
Now, if at any point, we encounter a `fruits::Apple` or `fruits::Banana` trait to be rendered (either as default traits or as overridden traits), they will be rendered under the `fruits` feature flag. For instance:
107+
108+
```rust
109+
#[cfg_attr(feature = "fruits", derive(fruits::Apple, fruits::Banana))]
110+
```
111+
112+
Note that for feature flags to be effective, they must be added to the `Cargo.toml` file of the generated Rust client.
113+
114+
### Using the Fully Qualified Name
115+
116+
By default, all traits are imported using the provided Fully Qualified Name which means their short name will be used within the `derive` attributes.
117+
118+
However, you may want to avoid importing these traits and use the Fully Qualified Name directly in the generated code. To do so, you may use the `useFullyQualifiedName` attribute of the `traitOptions` object by setting it to `true`:
119+
120+
```ts
121+
const traitOptions = {
122+
useFullyQualifiedName: true,
123+
};
124+
```
125+
126+
Here is an example of rendered traits with this option set to `true` and `false` (which is the default):
127+
128+
```rust
129+
// With `useFullyQualifiedName` set to `false` (default).
130+
use serde::Serialize;
131+
use serde::Deserialize;
132+
// ...
133+
#[derive(Serialize, Deserialize)]
134+
135+
// With `useFullyQualifiedName` set to `true`.
136+
#[derive(serde::Serialize, serde::Deserialize)]
137+
```
138+
139+
Note that any trait rendered under a feature flag will always use the Fully Qualified Name in order to ensure we only reference the trait when the feature is enabled.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"name": "@codama/renderers-pinocchio",
3+
"version": "0.1.0",
4+
"description": "Renders Pinocchio-based Rust clients for your programs",
5+
"exports": {
6+
"types": "./dist/types/index.d.ts",
7+
"node": {
8+
"import": "./dist/index.node.mjs",
9+
"require": "./dist/index.node.cjs"
10+
}
11+
},
12+
"main": "./dist/index.node.cjs",
13+
"module": "./dist/index.node.mjs",
14+
"types": "./dist/types/index.d.ts",
15+
"type": "commonjs",
16+
"files": [
17+
"./dist/templates",
18+
"./dist/types",
19+
"./dist/index.*"
20+
],
21+
"sideEffects": false,
22+
"keywords": [
23+
"solana",
24+
"framework",
25+
"standard",
26+
"renderers",
27+
"rust",
28+
"client"
29+
],
30+
"scripts": {
31+
"build": "rimraf dist && pnpm build:src && pnpm build:types",
32+
"build:src": "zx ../../node_modules/@codama/internals/scripts/build-src.mjs node",
33+
"build:types": "zx ../../node_modules/@codama/internals/scripts/build-types.mjs",
34+
"dev": "zx ../../node_modules/@codama/internals/scripts/test-unit.mjs node --watch",
35+
"lint": "zx ../../node_modules/@codama/internals/scripts/lint.mjs",
36+
"lint:fix": "zx ../../node_modules/@codama/internals/scripts/lint.mjs --fix",
37+
"test": "pnpm test:types && pnpm test:treeshakability && pnpm test:node && pnpm test:e2e && pnpm test:exports",
38+
"test:e2e": "./e2e/test.sh",
39+
"test:exports": "node ./test/exports/module.mjs && node ./test/exports/commonjs.cjs",
40+
"test:node": "zx ../../node_modules/@codama/internals/scripts/test-unit.mjs node",
41+
"test:treeshakability": "zx ../../node_modules/@codama/internals/scripts/test-treeshakability.mjs",
42+
"test:types": "zx ../../node_modules/@codama/internals/scripts/test-types.mjs"
43+
},
44+
"dependencies": {
45+
"@codama/errors": "workspace:*",
46+
"@codama/nodes": "workspace:*",
47+
"@codama/renderers-core": "workspace:*",
48+
"@codama/visitors-core": "workspace:*",
49+
"@solana/codecs-strings": "rc",
50+
"nunjucks": "^3.2.4"
51+
},
52+
"devDependencies": {
53+
"@types/nunjucks": "^3.2.6"
54+
},
55+
"license": "MIT",
56+
"repository": {
57+
"type": "git",
58+
"url": "https://github.com/codama-idl/codama"
59+
},
60+
"bugs": {
61+
"url": "http://github.com/codama-idl/codama/issues"
62+
},
63+
"browserslist": [
64+
"supports bigint and not dead",
65+
"maintained node versions"
66+
]
67+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% extends "layout.njk" %}
2+
3+
{% block main %}
4+
5+
{% for definedType in definedTypesToExport | sort(false, false, 'name') %}
6+
pub(crate) mod r#{{ definedType.name | snakeCase }};
7+
{% endfor %}
8+
9+
{% for definedType in definedTypesToExport | sort(false, false, 'name') %}
10+
pub use self::r#{{ definedType.name | snakeCase }}::*;
11+
{% endfor %}
12+
13+
{% endblock %}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{% extends "layout.njk" %}
2+
{% import "macros.njk" as macros %}
3+
4+
{% block main %}
5+
6+
{{ imports }}
7+
8+
{{ macros.docblock(definedType.docs) }}
9+
{{- typeManifest.type }}
10+
11+
{% for nestedStruct in typeManifest.nestedStructs %}
12+
{{ nestedStruct }}
13+
{% endfor %}
14+
15+
{% endblock %}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{% extends "layout.njk" %}
2+
{% import "macros.njk" as macros %}
3+
4+
{% block main %}
5+
6+
{{ imports }}
7+
8+
/// `{{ instruction.name | snakeCase }}` CPI helper.
9+
pub struct {{ instruction.name | pascalCase }}{{ '<\'a>' if instruction.accounts.length > 0 }} {
10+
{# Accounts #}
11+
{% for account in instruction.accounts %}
12+
{% if account.docs.length > 0 %}
13+
{{ macros.docblock(account.docs) }}
14+
{% endif %}
15+
16+
{% if account.isSigner === 'either' %}
17+
{% set type = '(&\'a pinocchio::account_info::AccountInfo, bool)' %}
18+
{% else %}
19+
{% set type = '&\'a pinocchio::account_info::AccountInfo' %}
20+
{% endif %}
21+
22+
{% if account.isOptional %}
23+
pub {{ account.name | snakeCase }}: Option<{{ type }}>,
24+
{% else %}
25+
pub {{ account.name | snakeCase }}: {{ type }},
26+
{% endif %}
27+
{% endfor %}
28+
{% for arg in instructionArgs %}
29+
{% if not arg.default %}
30+
pub {{ arg.name | snakeCase }}: {{ arg.type }},
31+
{% endif %}
32+
{% endfor %}
33+
}
34+
35+
impl{{ '<\'a>' if instruction.accounts.length > 0 }} {{ instruction.name | pascalCase }}{{ '<\'a>' if instruction.accounts.length > 0 }} {
36+
#[inline(always)]
37+
pub fn invoke(&self) -> pinocchio::ProgramResult {
38+
self.invoke_signed(&[])
39+
}
40+
41+
pub fn invoke_signed(&self, _signers: &[pinocchio::instruction::Signer]) -> pinocchio::ProgramResult {
42+
Ok(())
43+
}
44+
}
45+
46+
{% endblock %}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% extends "layout.njk" %}
2+
3+
{% block main %}
4+
5+
{% for instruction in instructionsToExport | sort(false, false, 'name') %}
6+
pub(crate) mod r#{{ instruction.name | snakeCase }};
7+
{% endfor %}
8+
9+
{% for instruction in instructionsToExport | sort(false, false, 'name') %}
10+
pub use self::r#{{ instruction.name | snakeCase }}::*;
11+
{% endfor %}
12+
13+
{% endblock %}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//! This code was AUTOGENERATED using the codama library.
2+
//! Please DO NOT EDIT THIS FILE, instead use visitors
3+
//! to add features, then rerun codama to update it.
4+
//!
5+
//! <https://github.com/codama-idl/codama>
6+
//!
7+
{% block main %}{% endblock %}

0 commit comments

Comments
 (0)