Skip to content

Add wrapper generation for natives #1

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,7 @@ dist
# TernJS port file
.tern-port

# VS code
.vscode/

.DS_Store
61 changes: 61 additions & 0 deletions src/docsStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { IParsed } from "./interfaces";
import { fetchSampInc, fetchActorInc, fetchHttpInc, fetchNPCInc, fetchObjectsInc, fetchPlayersInc, fetchSampDBInc, fetchVehiclesInc } from './requests';
import { parseInclude } from './parser';
import ora from "ora";

export class DocsStore {
private constructor(
public readonly a_samp: IParsed,
public readonly a_actor: IParsed,
public readonly a_http: IParsed,
public readonly a_npc: IParsed,
public readonly a_objects: IParsed,
public readonly a_players: IParsed,
public readonly a_sampdb: IParsed,
public readonly a_vehicles: IParsed,
) {}

public static async fromSampStdlib() {
const parsingSpinner = ora('Parsing docs from samp-stdlib...').start();

// A_SAMP
const a_sampPromise = fetchSampInc().then(data => parseInclude(data, true, true));
// A_ACTOR
const a_actorPromise = fetchActorInc().then(data => parseInclude(data, true, true));
// A_HTTP
const a_httpPromise = fetchHttpInc().then(data => parseInclude(data, true, true));

// A_NPC
const a_npcPromise = fetchNPCInc().then(data => parseInclude(data, false, true));
// Removed because it contains most of the things a_samp already has and you shouldn't include both in a pawn gamemode either

// A_OBJECTS
const a_objectsPromise = fetchObjectsInc().then(data => parseInclude(data, true, true));
// A_PLAYERS
const a_playersPromise = fetchPlayersInc().then(data => parseInclude(data, true, true));
// A_SAMPDB
const a_sampdbPromise = fetchSampDBInc().then(data => parseInclude(data, false, true));
// A_VEHICLES
const a_vehiclesPromise = fetchVehiclesInc().then(data => parseInclude(data, true, true));

const results = await Promise.all([
a_sampPromise,
a_actorPromise,
a_httpPromise,
a_npcPromise,
a_objectsPromise,
a_playersPromise,
a_sampdbPromise,
a_vehiclesPromise,
]);

parsingSpinner.succeed("Parsing docs finished.");

return new DocsStore(...results);
}
}

// Equals to `"a_samp" | "a_actor" | ...` etc.
export type ParsedModules = {
[P in keyof DocsStore]: DocsStore[P] extends IParsed ? P : never;
}[keyof DocsStore];
9 changes: 6 additions & 3 deletions src/enums/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ export enum EPaths {
TEMPLATE_EVENTS = './src/templates/events.d.ts.hjs',
TEMPLATE_SAMP = './src/templates/samp.d.ts.hjs',
TEMPLATE_GLOBALS = './src/templates/globals.d.ts.hjs',
GENERATED_EVENTS = './generated/events.d.ts',
GENERATED_SAMP = './generated/samp.d.ts',
GENERATED_GLOBALS = './generated/globals.d.ts',
TEMPLATE_WRAPPERS = './src/templates/wrappers/wrappers.ts.hjs',
TEMPLATE_WRAPPERS_INDEX = './src/templates/wrappers/index.ts.hjs',
GENERATED_EVENT_TYPES = './generated/types/events.d.ts',
GENERATED_SAMP_TYPES = './generated/types/samp.d.ts',
GENERATED_GLOBAL_TYPES = './generated/types/globals.d.ts',
GENERATED_WRAPPERS_FOLDER = './generated/wrappers',
}
2 changes: 1 addition & 1 deletion src/generators/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const generateGlobalConstants = async () => {
};

const template = Handlebars.compile(await fs.readFile(EPaths.TEMPLATE_GLOBALS, 'utf8'));
await fs.outputFile(EPaths.GENERATED_GLOBALS, template({ globalConstants }));
await fs.outputFile(EPaths.GENERATED_GLOBAL_TYPES, template({ globalConstants }));
};

export const applyFixes = () => {}; // Apply fixes to generated code
78 changes: 78 additions & 0 deletions src/generators/handlebarsHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { IParam, IPawnDoc } from '../interfaces';
import Handlebars from 'handlebars';

export function initHandlerbars() {
Handlebars.registerHelper({
eq: (v1, v2) => v1 === v2,
ne: (v1, v2) => v1 !== v2,
lt: (v1, v2) => v1 < v2,
gt: (v1, v2) => v1 > v2,
lte: (v1, v2) => v1 <= v2,
gte: (v1, v2) => v1 >= v2,
and: (v1, v2) => v1 && v2,
or: (v1, v2) => v1 || v2,
});

Handlebars.registerHelper({
// has variadic params
hvp: (pa: Array<IParam>) => pa.some(p => p.isVariadic),
// specifier values string
svs: (pa: Array<IParam>) => pa.map(p => p.isReference ? p.type.toUpperCase() : p.type).join(''),
// specifier values without references
svw: (pa: Array<IParam>) => pa.filter(p => !p.isReference),
// specifier values without references length
svwl: (pa: Array<IParam>) => pa.filter(p => !p.isReference).length,
// reference values
rv: (pa: Array<IParam>) => pa.filter(p => p.isReference),
// reference values string
rvs: (pa: Array<IParam>) => pa.filter(p => p.isReference).map(p => p.type.toUpperCase()).join(''),
// reference values length
rvl: (pa: Array<IParam>) => pa.filter(p => p.isReference).length,

// pawndoc param description
ppd: (pd: IPawnDoc, pa: IParam) => pd.param?.find(pdpa => pdpa.name === pa.name)?.description,
// typescript type for specifier
tts: (t: IParam['type'], b = false) => {
const type = referenceToTsType(t);
return b ? `{${type}}` : type;
},
restriction: (param: IParam) => {
if (param.type === 'i' || param.type === 'd') {
return "Must be a whole number."
} else if (param.type === 'a') {
return "All numbers must be whole";
}
return null;
},
outputtype: (params: Array<IParam>) => {
const types = params.filter(p => p.isReference).map(r => referenceToTsType(r.type));
if (!types.length) {
return "number";
}
if (types.length === 1) {
return types[0];
}
return `[${types.join(", ")}]`;
}
});
}

function referenceToTsType(t: IParam['type']) {
switch (t) {
case 's':
case 'S':
return "string";
case 'f':
case 'F':
case 'd':
case 'D':
case 'i':
case 'I':
return "number";
case 'a':
case 'A':
case 'v':
case 'V':
return "Array<number>";
}
}
8 changes: 4 additions & 4 deletions src/generators/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as typeDefinitions from './typeDefinitions';
import * as globals from './globals';

export { typeDefinitions, globals };
export * as typeDefinitions from './typeDefinitions';
export * as globals from './globals';
export * as handlebarsHelper from './handlebarsHelper';
export * as wrappers from './wrappers';
135 changes: 25 additions & 110 deletions src/generators/typeDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,114 +2,29 @@ import fs from 'fs-extra';
import Handlebars from 'handlebars';
import { constantCase } from 'constant-case';
import ora from 'ora';

import { fetchSampInc, fetchActorInc, fetchHttpInc, fetchObjectsInc, fetchPlayersInc, fetchSampDBInc, fetchVehiclesInc } from '../requests';
import { parseInclude } from '../parser';
import { IParsed, IParam, IPawnDoc } from '../interfaces';
import { IParsed } from '../interfaces';
import { EPaths } from '../enums';
import { DocsStore } from '../docsStore';

let a_sampParsed: IParsed;
let a_actorParsed: IParsed;
let a_httpParsed: IParsed;
// let a_npcParsed: IParsed;
let a_objectsParsed: IParsed;
let a_playersParsed: IParsed;
let a_sampdbParsed: IParsed;
let a_vehiclesParsed: IParsed;

Handlebars.registerHelper({
eq: (v1, v2) => v1 === v2,
ne: (v1, v2) => v1 !== v2,
lt: (v1, v2) => v1 < v2,
gt: (v1, v2) => v1 > v2,
lte: (v1, v2) => v1 <= v2,
gte: (v1, v2) => v1 >= v2,
and: (v1, v2) => v1 && v2,
or: (v1, v2) => v1 || v2,
});

Handlebars.registerHelper({
// has variadic params
hvp: (pa: Array<IParam>) => pa.some(p => p.isVariadic),
// specifier values string
svs: (pa: Array<IParam>) => pa.map(p => p.isReference ? p.type.toUpperCase() : p.type).join(''),
// specifier values without references
svw: (pa: Array<IParam>) => pa.filter(p => !p.isReference),
// specifier values without references length
svwl: (pa: Array<IParam>) => pa.filter(p => !p.isReference).length,
// reference values string
rvs: (pa: Array<IParam>) => pa.filter(p => p.isReference).map(p => p.type.toUpperCase()).join(''),
// reference values length
rvl: (pa: Array<IParam>) => pa.filter(p => p.isReference).length,

// pawndoc param description
ppd: (pd: IPawnDoc, pa: IParam) => pd.param?.find(pdpa => pdpa.name === pa.name)?.description,
// typescript type for specifier
tts: (t: IParam['type'], b = false) => {
let r: string;
if (t === 's') r = 'string';
else if (t === 'a' || t === 'v') r = 'Array<number>';
else r = 'number';
return b ? `{${r}}` : r;
},
});

export const generate = async () => {
const generating = ora('Generating type definitions...').start();

// A_SAMP
const a_samp = await fetchSampInc();
a_sampParsed = await parseInclude(a_samp, true, true);

// A_ACTOR
const a_actor = await fetchActorInc();
a_actorParsed = await parseInclude(a_actor, true, true);

// A_HTTP
const a_http = await fetchHttpInc();
a_httpParsed = await parseInclude(a_http, true, true);

// A_NPC
// const a_npc = await fetchNPCInc();
// a_npcParsed = await parseInclude(a_npc, false, true);
// Removed because it contains most of the things a_samp already has and you shouldn't include both in a pawn gamemode either

// A_OBJECTS
const a_objects = await fetchObjectsInc();
a_objectsParsed = await parseInclude(a_objects, true, true);

// A_PLAYERS
const a_players = await fetchPlayersInc();
a_playersParsed = await parseInclude(a_players, true, true);

// A_SAMPDB
const a_sampdb = await fetchSampDBInc();
a_sampdbParsed = await parseInclude(a_sampdb, false, true);

// A_VEHICLES
const a_vehicles = await fetchVehiclesInc();
a_vehiclesParsed = await parseInclude(a_vehicles, true, true);

export const generate = async (docsStore: DocsStore) => {
const eventsDefinitionsSpinner = ora('Generating event type definitions...').start();
await generateEventsDefinitions();
await generateEventsDefinitions(docsStore);
eventsDefinitionsSpinner.succeed('Events type definitions generated.');

const sampDefinitionsSpinner = ora('Generating samp type definitions...').start();
await generateSampDefinitions();
await generateSampDefinitions(docsStore);
sampDefinitionsSpinner.succeed('Samp type definitions generated.');

generating.succeed('All type definitions generated.');
};

export const generateEventsDefinitions = async () => {
const a_sampEvents = getEventConstants(a_sampParsed);
const a_actorEvents = getEventConstants(a_actorParsed);
const a_httpEvents = getEventConstants(a_httpParsed);
// const a_npcEvents = getEventConstants(a_npcParsed);
const a_objectsEvents = getEventConstants(a_objectsParsed);
const a_playersEvents = getEventConstants(a_playersParsed);
const a_sampdbEvents = getEventConstants(a_sampdbParsed);
const a_vehiclesEvents = getEventConstants(a_vehiclesParsed);
export const generateEventsDefinitions = async (docsStore: DocsStore) => {
const a_sampEvents = getEventConstants(docsStore.a_samp);
const a_actorEvents = getEventConstants(docsStore.a_actor);
const a_httpEvents = getEventConstants(docsStore.a_http);
// const a_npcEvents = getEventConstants(docsStore.a_npc);
const a_objectsEvents = getEventConstants(docsStore.a_objects);
const a_playersEvents = getEventConstants(docsStore.a_players);
const a_sampdbEvents = getEventConstants(docsStore.a_sampdb);
const a_vehiclesEvents = getEventConstants(docsStore.a_vehicles);

const eventConstants = {
...a_sampEvents,
Expand All @@ -123,26 +38,26 @@ export const generateEventsDefinitions = async () => {
};

const template = Handlebars.compile(await fs.readFile(EPaths.TEMPLATE_EVENTS, 'utf8'));
await fs.outputFile(EPaths.GENERATED_EVENTS, template({ eventConstants }));
await fs.outputFile(EPaths.GENERATED_EVENT_TYPES, template({ eventConstants }));
};

export const generateSampDefinitions = async () => {
export const generateSampDefinitions = async (docsStore: DocsStore) => {
const eventListenerAliases = ['on', 'addListener', 'addEventListener'];
const removeEventListenerAliases = ['removeListener', 'removeEventListener'];

const parsedIncludes = [
a_sampParsed,
a_actorParsed,
a_httpParsed,
// a_npcParsed,
a_objectsParsed,
a_playersParsed,
a_sampdbParsed,
a_vehiclesParsed,
docsStore.a_samp,
docsStore.a_actor,
docsStore.a_http,
// docsStore.a_npc,
docsStore.a_objects,
docsStore.a_players,
docsStore.a_sampdb,
docsStore.a_vehicles,
];

const template = Handlebars.compile(await fs.readFile(EPaths.TEMPLATE_SAMP, 'utf8'));
await fs.outputFile(EPaths.GENERATED_SAMP, template({ eventListenerAliases, removeEventListenerAliases, parsedIncludes }));
await fs.outputFile(EPaths.GENERATED_SAMP_TYPES, template({ eventListenerAliases, removeEventListenerAliases, parsedIncludes }));
};

export const getEventConstants = (parsed: IParsed) => {
Expand Down
38 changes: 38 additions & 0 deletions src/generators/wrappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import fs from 'fs-extra';
import Handlebars from 'handlebars';
import ora from 'ora';
import { EPaths } from '../enums';
import { DocsStore, ParsedModules } from '../docsStore';

export const generate = async (docsStore: DocsStore) => {
// TODO: generate default values
const generating = ora('Generating native wrappers definitions...').start();
await generateNativeWrappers(docsStore);
generating.succeed('All native wrappers generated.');
};

const generateNativeWrappers = async (docsStore: DocsStore) => {
const moduleNames: Array<ParsedModules> = [
"a_samp",
"a_actor",
"a_http",
// "a_npc",
"a_objects",
"a_players",
"a_sampdb",
"a_vehicles",
];

const wrapperTemplate = Handlebars.compile(await fs.readFile(EPaths.TEMPLATE_WRAPPERS, 'utf8'));
const indexTemplate = Handlebars.compile(await fs.readFile(EPaths.TEMPLATE_WRAPPERS_INDEX, 'utf8'));
await Promise.all(moduleNames.map(moduleName => {
const module = docsStore[moduleName];
return fs.outputFile(generatedWrapperPath(moduleName), wrapperTemplate({ module }));
}));

await fs.outputFile(generatedWrapperPath("index"), indexTemplate({ moduleNames }));
};

function generatedWrapperPath(moduleName: string) {
return `${EPaths.GENERATED_WRAPPERS_FOLDER}/${moduleName}.ts`;
}
Loading