Skip to content

Commit

Permalink
feat: add mol cmd with mol-related tools (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
RetricSu authored Sep 2, 2024
1 parent f71b941 commit 5750e85
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Commands:
config <action> [item] [value] do a configuration action
debug [options] CKB Debugger for development
system-scripts [options] Output system scripts of the local devnet
mol [options] Generate CKB Moleculec binding code for development
help [command] display help for command
```

Expand Down
13 changes: 13 additions & 0 deletions example-mol/attributes.mol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import basic_types;

// Each role has 8 attributes. The size is fixed.
struct Attributes {
strength: AttrValue,
dexterity: AttrValue,
endurance: AttrValue,
speed: AttrValue,
intelligence: AttrValue,
wisdom: AttrValue,
perception: AttrValue,
concentration: AttrValue,
}
25 changes: 25 additions & 0 deletions example-mol/basic_types.mol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// AttrValue is an alias of `byte`.
//
// Since Molecule data are strongly-typed, it can gives compile time guarantees
// that the right type of value is supplied to a method.
//
// In this example, we use this alias to define an unsigned integer which
// has an upper limit: 100.
// So it's easy to distinguish between this type and a real `byte`.
// Of course, the serialization wouldn't do any checks for this upper limit
// automatically. You have to implement it by yourself.
//
// **NOTE**:
// - This feature is dependent on the exact implementation.
// In official Rust generated code, we use new type to implement this feature.
array AttrValue [byte; 1];

// SkillLevel is an alias of `byte`, too.
//
// Each skill has only 10 levels, so we use another alias of `byte` to distinguish.
array SkillLevel [byte; 1];

// Define several unsigned integers.
array Uint8 [byte; 1];
array Uint16 [byte; 2];
array Uint32 [byte; 4];
22 changes: 22 additions & 0 deletions example-mol/role.mol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import attributes;
import skills;
import basic_types;

// We have only 3 classes: Fighter, Ranger and Mage. A `byte` is enough.
array Class [byte; 1];

table Hero {
class: Class,
level: Uint8,
experiences: Uint32,
hp: Uint16,
mp: Uint16,
base_damage: Uint16,
attrs: Attributes,
skills: Skills,
}

table Monster {
hp: Uint16,
damage: Uint16,
}
33 changes: 33 additions & 0 deletions example-mol/skills.mol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import basic_types;

// We define several skills.
// None means the role can learn a skill but he/she doesn't learn it.
option ArmorLight (SkillLevel);
option ArmorHeavy (SkillLevel); // only Fighter can learn this
option ArmorShields (SkillLevel); // only Fighter can learn this
option WeaponSwords (SkillLevel); // only Mage can't learn this
option WeaponBows (SkillLevel); // only Ranger can learn this
option WeaponBlunt (SkillLevel);
option Dodge (SkillLevel);
option PickLocks (SkillLevel);
option Mercantile (SkillLevel);
option Survival (SkillLevel);
// ... omit other skills ...

// Any skill which is defined above.
union Skill {
ArmorLight,
ArmorHeavy,
ArmorShields,
WeaponSwords,
WeaponBows,
WeaponBlunt,
Dodge,
PickLocks,
Mercantile,
Survival,
// ... omit other skills ...
}

// A hero can learn several skills. The size of learned skills is dynamic.
vector Skills <Skill>;
14 changes: 14 additions & 0 deletions src/cfg/setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export interface Settings {
gitFolder: string;
downloadPath: string;
};
tools: {
moleculeES: {
downloadPath: string;
cachePath: string;
binFolder: string;
};
};
}

export const defaultSettings: Settings = {
Expand Down Expand Up @@ -94,6 +101,13 @@ export const defaultSettings: Settings = {
gitFolder: 'templates/v3',
downloadPath: path.resolve(cachePath, 'download', 'dapp-template'),
},
tools: {
moleculeES: {
downloadPath: path.resolve(cachePath, 'download', 'molecule-es'),
cachePath: path.resolve(cachePath, 'tools', 'moleculec-es'),
binFolder: path.resolve(dataPath, 'tools', 'moleculec-es'),
},
},
};

export function readSettings(): Settings {
Expand Down
17 changes: 17 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { Config, ConfigItem } from './cmd/config';
import { debugSingleScript, debugTransaction, parseSingleScriptOption } from './cmd/debug';
import { printSystemScripts } from './cmd/system-scripts';
import { proxyRpc, ProxyRpcOptions } from './cmd/proxy-rpc';
import { molFiles, molSingleFile } from './cmd/mol';
import * as fs from 'fs';

const version = require('../package.json').version;
const description = require('../package.json').description;
Expand Down Expand Up @@ -138,6 +140,21 @@ program
return printSystemScripts(exportStyle);
});

program
.command('mol')
.requiredOption('--schema <schema>', 'Specify the scheme .mol file/folders to generate bindings')
.option('--output <output>', 'Specify the output file/folder path')
.option('--output-folder <output-folder>', 'Specify the output folder path, only valid when schema is a folder')
.option('--lang <lang>', 'Specify the binding language, [ts, js, c, rs, go]', 'ts')
.description('Generate CKB Moleculec binding code for development')
.action(async (option) => {
if (fs.statSync(option.schema).isDirectory()) {
const outputFolderPath = option.outputFolder ?? './';
return molFiles(option.schema, outputFolderPath, option.lang);
}
return molSingleFile(option.schema, option.output, option.lang);
});

program.parse(process.argv);

// If no command is specified, display help
Expand Down
22 changes: 22 additions & 0 deletions src/cmd/mol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { BindingLanguage, generateMolBindings } from '../molecule/mol';
import fs from 'fs';
import path from 'path';

export async function molSingleFile(schemeFilePath: string, outputFilePath: string, bindingLang: string) {
await generateMolBindings(schemeFilePath, outputFilePath, bindingLang as BindingLanguage);
}

export async function molFiles(schemaFolderPath: string, outputFolderPath: string, bindingLang: string) {
const files = fs.readdirSync(schemaFolderPath).filter((file) => file.endsWith('.mol'));

if (files.length === 0) {
throw new Error(`No .mol files found in the specified folder: ${schemaFolderPath}`);
}

for (const file of files) {
const filePath = path.join(schemaFolderPath, file);
const outputFileName = path.basename(file, '.mol') + '.' + bindingLang;
const outputFilePath = path.join(outputFolderPath, outputFileName);
await molSingleFile(filePath, outputFilePath, bindingLang as BindingLanguage);
}
}
119 changes: 119 additions & 0 deletions src/molecule/mol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { execSync } from 'child_process';
import { MoleculecES } from '../tools/moleculec-es';
import { MoleculecRust } from '../tools/moleculec-rust';
import { readSettings } from '../cfg/setting';
import path from 'path';
import * as fs from 'fs';
import { MoleculecGo } from '../tools/moleculec-go';

export enum BindingLanguage {
rust = 'rs',
c = 'c',
typescript = 'ts',
javascript = 'js',
go = 'go',
}

export async function generateMolBindings(
schemeFilePath: string,
outputFilePath: string | undefined,
bindingLanguage: BindingLanguage,
) {
await installMolToolsIfNeeded();
const settings = readSettings();
if (bindingLanguage === BindingLanguage.typescript) {
const jsonFilePath = path.join(settings.tools.moleculeES.cachePath, 'schema.json');
fs.mkdirSync(path.dirname(jsonFilePath), { recursive: true });
if (outputFilePath) {
fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });
}

execSync(`moleculec --language - --schema-file ${schemeFilePath} --format json > ${jsonFilePath}`);
execSync(
`${MoleculecES.bin} -generateTypeScriptDefinition -hasBigInt -inputFile ${jsonFilePath} -outputFile ${outputFilePath || '-'}`,
{ stdio: 'inherit' },
);
return;
}

if (bindingLanguage === BindingLanguage.javascript) {
const jsonFilePath = path.join(settings.tools.moleculeES.cachePath, 'schema.json');
fs.mkdirSync(path.dirname(jsonFilePath), { recursive: true });
if (outputFilePath) {
fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });
}

execSync(`moleculec --language - --schema-file ${schemeFilePath} --format json > ${jsonFilePath}`, {
stdio: 'inherit',
});
execSync(`${MoleculecES.bin} -hasBigInt -inputFile ${jsonFilePath} -outputFile ${outputFilePath || '-'}`, {
stdio: 'inherit',
});
return;
}

if (bindingLanguage === BindingLanguage.c) {
if (!outputFilePath) {
execSync(`moleculec --language c --schema-file ${schemeFilePath}`, {
stdio: 'inherit',
});
return;
}

fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });

execSync(`moleculec --language c --schema-file ${schemeFilePath} > ${outputFilePath}`, {
stdio: 'inherit',
});
return;
}

if (bindingLanguage === BindingLanguage.rust) {
if (!outputFilePath) {
execSync(`moleculec --language rust --schema-file ${schemeFilePath}`, {
stdio: 'inherit',
});
return;
}

fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });

execSync(`moleculec --language rust --schema-file ${schemeFilePath} > ${outputFilePath}`, {
stdio: 'inherit',
});
return;
}

if (bindingLanguage === BindingLanguage.go) {
if (!outputFilePath) {
execSync(`moleculec --language go --schema-file ${schemeFilePath}`, {
stdio: 'inherit',
});
return;
}

fs.mkdirSync(path.dirname(outputFilePath), { recursive: true });

execSync(`moleculec --language go --schema-file ${schemeFilePath} | gofmt > ${outputFilePath}`, {
stdio: 'inherit',
});
return;
}

throw new Error(`Unsupported binding language: ${bindingLanguage}`);
}

export async function installMolToolsIfNeeded() {
if (!MoleculecES.isBinaryInstalled()) {
const version = '0.4.6';
await MoleculecES.installMoleculeES(version);
}

if (!MoleculecRust.isBinaryInstalled()) {
MoleculecRust.installBinary();
}

if (!MoleculecGo.isBinaryInstalled()) {
MoleculecGo.installBinary();
}
}
6 changes: 3 additions & 3 deletions src/node/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export function getVersionFromBinary(binPath: string): string | null {
}
}

export function getOS(): string {
function getOS(): string {
const platform = os.platform();
if (platform === 'darwin') {
return 'apple-darwin';
Expand All @@ -130,7 +130,7 @@ export function getOS(): string {
}
}

export function getArch(): string {
function getArch(): string {
const arch = os.arch();
if (arch === 'x64') {
return 'x86_64';
Expand All @@ -141,7 +141,7 @@ export function getArch(): string {
}
}

export function getExtension(): 'tar.gz' | 'zip' {
function getExtension(): 'tar.gz' | 'zip' {
const platform = os.platform();
if (platform === 'linux') {
return 'tar.gz';
Expand Down
Loading

0 comments on commit 5750e85

Please sign in to comment.