Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement parser and typescript code generator #5

Merged
merged 10 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/workflows/sails-js.yaml
osipov-mit marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: 'CI-CD sails-js'

on:
pull_request:
types: [opened, synchronize, reopened, labeled]
branches: [master]
push:
branches: [master]
paths:
- js/**
osipov-mit marked this conversation as resolved.
Show resolved Hide resolved
workflow_dispatch:

jobs:
test:
if: github.event_name == 'pull_request'

runs-on: ubuntu-22.04
env:
RUSTUP_HOME: /tmp/rustup_home
steps:
- name: Cancel previous workflow runs
uses: styfle/cancel-workflow-action@0.4.0
with:
access_token: ${{ github.token }}

- name: Checkout
uses: actions/checkout@v3

- name: "Install: NodeJS 18.x"
uses: actions/setup-node@v3
with:
node-version: 18.x

- name: "Install: pkg dependencies"
working-directory: js
run: yarn install

- name: "Build: sails-js"
working-directory: js
run: yarn build

- name: "Test: run"
working-directory: js
run: yarn test

publish-to-npm:
if: github.event_name == 'push'

runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Use node 18.x
uses: actions/setup-node@v3
with:
node-version: 18.x

- name: Check package version
uses: EndBug/version-check@v1
id: check
with:
file-name: js/package.json
file-url: https://unpkg.com/sails-js@latest/package.json
static-checking: localIsNew

- name: Install dependencies
if: steps.check.outputs.changed == 'true'
working-directory: js
run: yarn install

- name: Build sails-js
if: steps.check.outputs.changed == 'true'
working-directory: js
run: yarn build

- name: Publish
if: steps.check.outputs.changed == 'true'
working-directory: js
run: |
export token=$(printenv $(printenv GITHUB_ACTOR))
npm config set //registry.npmjs.org/:_authToken=$token
npm publish
env:
osipov-mit: ${{ secrets.OSIPOV_NPM_TOKEN }}
osipov-mit marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/

.DS_Store

.vscode
osipov-mit marked this conversation as resolved.
Show resolved Hide resolved
*.log
2 changes: 2 additions & 0 deletions js/.gitignore
osipov-mit marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib/
lib.ts
29 changes: 29 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Installation
The package can be installed as either a global dependency or a package dependency.

```bash
npm install -g sails-js
```
or
```bash
npm install sails-js
```

## Usage

### CLI
- Generate typescript code from the IDL file
```bash
sails-js generate path/to/sails.idl -o path/to/out/dir
```
This command will generate 2 files to the specified directory.

- Parse IDL file and print the result
```bash
sails-js parse-and-print path/to/sails.idl
```

- Parse IDL file and save the result to a json file
```bash
sails-js parse-into-file path/to/sails.idl path/to/out.json
```
18 changes: 18 additions & 0 deletions js/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Config } from '@jest/types';

const config: Config.InitialOptions = {
clearMocks: true,
coverageProvider: 'v8',
testEnvironment: 'node',
verbose: true,
preset: 'ts-jest/presets/js-with-babel',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': ['ts-jest', { useESM: true }],
},
};

export default config;
50 changes: 50 additions & 0 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "sails-js",
"version": "0.0.1",
"description": "Parser and typescript code generator from Sails IDL files",
"main": "lib/index.js",
"preferGlobal": true,
"type": "module",
"license": "GPL-3.0",
"author": "Gear Technologies",
"bugs": {
"url": "https://github.com/gear-tech/sails/issues"
},
"homepage": "https://github.com/gear-tech/sails/tree/master/js#readme",
"repository": {
"type": "git",
"url": "git+https://githib.com/gear-tech/sails.git"
},
"keywords": [
"gear",
"sails"
],
"bin": {
"sails-js": "./lib/app.js"
},
"exports": {
"require": "./lib/index.js",
"import": "./lib/index.js",
"types": "./lib/index.d.ts"
},
"scripts": {
"build": "rm -rf lib && tsc",
"test": "yarn node --experimental-vm-modules $(yarn bin jest)"
},
"devDependencies": {
"@types/jest": "^29.5.10",
"@types/node": "^20.10.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
},
"dependencies": {
"chevrotain": "^11.0.3",
"commander": "^11.1.0"
},
"files": [
"lib",
"templates"
]
}
44 changes: 44 additions & 0 deletions js/sails.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
type Alias = u32;

type OptionAlias = opt u8;

type ResultAlias = result (u8, string)

type VecAlias = vec u8;

type ThisThatAppTupleStruct = struct {
bool,
};

type ThisThatAppDoThatParam = struct {
p1: u32,
p2: string,
p3: ThisThatAppManyVariants,
};

type ThisThatAppManyVariants = variant {
One,
Two: u32,
Three: opt vec u8,
Four: struct { a: u32, b: opt u16 },
Five: struct { string, u32 },
Six: struct { u32 },
};

type CustomGeneric<u8, struct { u8, u32 }> = struct {
p1: u8,
p2: struct { u8, u32 }
};

type CustomGeneric<u32, opt vec u8> = struct {
p1: u32,
p2: opt vec u8
};

service {
async DoThis : (a1: u32, a2: string, a3: struct { opt string, u8 }, a4: ThisThatAppTupleStruct) -> result (struct { string, u32 }, string);
async DoThat : (a1: ThisThatAppDoThatParam) -> result (struct { string, u32 }, struct { string });
async Fail : (a2: string) -> result (null, string);
query This : () -> result (u32, string);
query That : () -> result (string, string);
};
44 changes: 44 additions & 0 deletions js/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env node

import { Command } from 'commander';
import { readFileSync, writeFileSync } from 'fs';

import { parse } from './parser/index.js';
import { generate } from './generate/index.js';

const program = new Command();

program
.command('generate <path-to-file.sails.idl>')
.option('-o --out <path-to-dir>', 'Output directory')
.description('Generate typescript code from .sails.idl file')
.action((path, options) => {
const idl = readFileSync(path, 'utf-8');
const parsed = parse(idl);

generate(parsed, options.out || '.');
});

program
.command('parse-and-print <path-to-file.sails.idl>')
.description('Parse .sails.idl file and print the result')
.action((path) => {
const idl = readFileSync(path, 'utf-8');
const parsed = parse(idl);

console.log(JSON.stringify(parsed, null, 2));
});

program
.command('parse-into-file <path-to-file.sails.idl> <path-to-output.json>')
.description('Parse .sails.idl file and write the result to a json file')
.action((path, out) => {
const idl = readFileSync(path, 'utf-8');
const parsed = parse(idl);

const json = JSON.stringify(parsed, null, 2);

writeFileSync(out, json);
});

program.addHelpCommand().parse();
28 changes: 28 additions & 0 deletions js/src/generate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { cpSync } from 'fs';
import path from 'path';

import { ServiceGenerator } from './service-gen.js';
import { IType, IService } from '../types/index.js';
import { TypesGenerator } from './types-gen.js';
import { Output } from './output.js';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

export function generate(definition: { types: IType[]; services: IService[] }, outDir: string) {
const out = new Output();
const typesGen = new TypesGenerator(out);
const serviceGen = new ServiceGenerator(out);

typesGen.prepare(definition.types);
typesGen.generate(definition.types);

for (const service of definition.services) {
serviceGen.generate(service, typesGen.scaleTypes);
}

out.save(path.join(outDir, 'lib.ts'));

cpSync(path.join(__dirname, '..', '..', 'templates', 'transaction.ts'), path.join(outDir, 'transaction.ts'));
}
81 changes: 81 additions & 0 deletions js/src/generate/output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { writeFileSync } from 'fs';

export class Output {
private _rows: string[] = [];
private _firstRows: string[] = [];
private _indent = '';
private _imports: Map<string, Set<string>>;

constructor() {
this._rows = [];
this._firstRows = [];
this._imports = new Map();
}

import(module_: string, import_: string) {
if (this._imports.has(module_)) {
this._imports.get(module_).add(import_);
} else {
this._imports.set(module_, new Set([import_]));
}
return this;
}

firstLine(data: string | string[]) {
if (Array.isArray(data)) {
data = data.map((line) => {
if (!line.endsWith(';')) return line + ';';
return line;
});
this._firstRows.push(...data);
} else {
if (!data.endsWith(';')) data += ';';
this._firstRows.push(data);
}
return this;
}

line(data?: string, semicolon = true) {
if (data && semicolon && !data.endsWith(';')) data += ';';
data = data ? `${this._indent}${data}` : '';
this._rows.push(data);
return this;
}

block(beginning: string, content?: () => void, bracket: '{' | '[' | '(' = '{') {
const openBracket = bracket;
const closeBracket = bracket === '{' ? '}' : bracket === '[' ? ']' : ')';
this._rows.push(`${this._indent}${beginning} ${openBracket}${!content ? ' ' + closeBracket : ''}`);
if (content) {
this.increaseIndent();
content();
this.reduceIndent();
this._rows.push(`${this._indent}${closeBracket}`);
}
return this;
}

increaseIndent() {
this._indent += ' ';
return this;
}

reduceIndent() {
this._indent = this._indent.substring(2);
return this;
}

save(path: string) {
const result = [];
const imports = Array.from(this._imports).map(
([module_, imports_]) => `import { ${Array.from(imports_).join(', ')} } from '${module_}';`,
);
if (imports.length > 0) result.push(imports.join('\n'));

if (this._firstRows.length > 0) result.push(this._firstRows.join('\n'));

if (this._rows.length > 0) result.push(this._rows.join('\n'));

writeFileSync(path, result.join('\n\n'));
}
}
Loading
Loading