const fs = require('fs'); const path = require('path'); const format = require('string-format'); const { DocsWriter } = require('../docswriter'); const { TLObject, Usability } = require('../parsers'); const { snakeToCamelCase } = require('../utils'); const CORE_TYPES = new Set([ 'int', 'long', 'int128', 'int256', 'double', 'vector', 'string', 'bool', 'true', 'bytes', 'date', ]); const mkdir = path => fs.mkdirSync(path, { recursive: true }); const titleCase = text => text .toLowerCase() .split(/(\W)/) .map(word => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`) .join(''); /** * ``ClassName -> class_name.html``. */ const getFileName = tlobject => { const name = tlobject instanceof TLObject ? tlobject.name : tlobject; // Courtesy of http://stackoverflow.com/a/1176023/4759433 const s1 = name.replace(/(.)([A-Z][a-z]+)/, '$1_$2'); const result = s1.replace(/([a-z0-9])([A-Z])/, '$1_$2').toLowerCase(); return `${result}.html`; }; /** * ``TLObject -> const { ... } = require(...);``. */ const getImportCode = tlobject => { const kind = tlobject.isFunction ? 'functions' : 'types'; const ns = tlobject.namespace ? `/${tlobject.namespace}` : ''; return `const { ${tlobject.className} } = require('gramjs/tl/${kind}${ns}');`; }; /** * Returns the path for the given TLObject. */ const getPathFor = tlobject => { let outDir = tlobject.isFunction ? 'methods' : 'constructors'; if (tlobject.namespace) { outDir += `/${tlobject.namespace}`; } return `${outDir}/${getFileName(tlobject)}`; }; /** * Similar to `getPathFor` but for only type names. */ const getPathForType = type => { if (CORE_TYPES.has(type.toLowerCase())) { return `index.html#${type.toLowerCase()}`; } else if (type.includes('.')) { const [namespace, name] = type.split('.'); return `types/${namespace}/${getFileName(name)}`; } else { return `types/${getFileName(type)}`; } }; /** * Finds the
null
and can be omitted.'
);
otherwise = true;
}
if (
[
'InputPeer',
'InputUser',
'InputChannel',
'InputNotifyPeer',
'InputDialogPeer',
].includes(arg.type)
) {
desc.push(
'Anything entity-like will work if the library can find its Input
version (e.g., usernames, Peer
, User
or Channel
objects, etc.).'
);
}
if (arg.isVector) {
if (arg.isGeneric) {
desc.push('A list of other Requests must be supplied.');
} else {
desc.push('A list must be supplied.');
}
} else if (arg.isGeneric) {
desc.push('A different Request must be supplied for this argument.');
} else {
otherwise = false; // Always reset to false if no other text is added
}
if (otherwise) {
desc.splice(1, 0, 'Otherwise,');
desc[desc.length - 1] =
desc[desc.length - 1].slice(0, -1).toLowerCase() +
desc[desc.length - 1].slice(1);
}
return desc
.join(' ')
.replace(
/list/g,
'list'
);
};
/**
* Copies the src file into dst applying the replacements dict
*/
const copyReplace = (src, dst, replacements) => {
const infile = fs.readFileSync(src, { encoding: 'utf-8' });
fs.writeFileSync(
dst,
format(infile, replacements)
// infile.replace(
// new RegExp(
// Object.keys(replacements)
// .map(k => escapeRegex(k))
// .join('|')
// ),
// m => replacements[m].toString()
// )
);
};
/**
* Generates the documentation HTML files from from ``scheme.tl``
* to ``/methods`` and ``/constructors``, etc.
*/
const writeHtmlPages = (tlobjects, methods, layer, inputRes) => {
// Save 'Type: [Constructors]' for use in both:
// * Seeing the return type or constructors belonging to the same type.
// * Generating the types documentation, showing available constructors.
const paths = {
'404': '404.html',
css: 'css',
arrow: 'img/arrow.svg',
'search.js': 'js/search.js',
indexAll: 'index.html',
botIndex: 'botindex.html',
indexTypes: 'types/index.html',
indexMethods: 'methods/index.html',
indexConstructors: 'constructors/index.html',
defaultCss: 'light',
};
const typeToConstructors = {};
const typeToFunctions = {};
for (const tlobject of tlobjects) {
const d = tlobject.isFunction ? typeToFunctions : typeToConstructors;
if (!d[tlobject.innermostResult]) {
d[tlobject.innermostResult] = [];
}
d[tlobject.innermostResult].push(tlobject);
}
for (const [t, cs] of Object.entries(typeToConstructors)) {
typeToConstructors[t] = cs.sort((x, y) => x.name.localeCompare(y.name));
}
methods = methods.reduce((x, m) => ({ ...x, [m.name]: m }), {});
const botDocsPath = [];
for (const tlobject of tlobjects) {
const filename = getPathFor(tlobject);
const docs = new DocsWriter(filename, getPathForType).open();
docs.writeHead(tlobject.className, paths.css, paths.defaultCss);
// Create the menu (path to the current TLObject)
docs.setMenuSeparator(paths.arrow);
buildMenu(docs);
// Create the page title
docs.writeTitle(tlobject.className);
if (tlobject.isFunction) {
let start;
if (tlobject.usability === Usability.USER) {
start = 'Only users can';
} else if (tlobject.usability === Usability.BOT) {
botDocsPath.push(filename);
start = 'Only bots can';
} else if (tlobject.usability === Usability.BOTH) {
botDocsPath.push(filename);
start = 'Both users and bots can';
} else {
botDocsPath.push(filename);
start = 'Both users and bots may be able to';
}
docs.writeText(
`${start} use this method. See code examples.`
);
}
// Write the code definition for this TLObject
docs.writeCode(tlobject);
docs.writeCopyButton(
'Copy import to clipboard',
getImportCode(tlobject)
);
// Write the return type (or constructors belonging to the same type)
docs.writeTitle(tlobject.isFunction ? 'Returns' : 'Belongs to', 3);
let [genericArg] = tlobject.args
.filter(arg => arg.genericDefinition)
.map(arg => arg.name);
if (tlobject.result === genericArg) {
// We assume it's a function returning a generic type
[genericArg] = tlobject.args
.filter(arg => arg.isGeneric)
.map(arg => arg.name);
docs.writeText(
`This function returns the result of whatever the result from invoking the request passed through ${genericArg} is.`
);
} else {
let inner = tlobject.result;
if (/^vector !a.flagIndicator && !a.genericDefinition);
if (args.length) {
// Writing parameters
docs.beginTable(3);
for (const arg of args) {
// Name row
docs.addRow(arg.name, null, true);
// Type row
const friendlyType = arg.type === 'true' ? 'flag' : arg.type;
if (arg.isGeneric) {
docs.addRow(`!${friendlyType}`, null, null, 'center');
} else {
docs.addRow(
friendlyType,
getPathForType(arg.type),
null,
'center'
);
}
// Add a description for this argument
docs.addRow(getDescription(arg));
}
docs.endTable();
} else {
if (tlobject.isFunction) {
docs.writeText('This request takes no input parameters.');
} else {
docs.writeText('This type has no members.');
}
}
if (tlobject.isFunction) {
docs.writeTitle('Known RPC errors');
const methodInfo = methods[tlobject.fullname];
const errors = methodInfo && methodInfo.errors;
if (!errors || !errors.length) {
docs.writeText(
"This request can't cause any RPC error as far as we know."
);
} else {
docs.writeText(
`This request can cause ${errors.length} known error${
errors.length === 1 ? '' : 's'
}:`
);
docs.beginTable(2);
for (const error of errors) {
docs.addRow(`${error.name}
`);
docs.addRow(`${error.description}.`);
}
docs.endTable();
docs.writeText(
'You can import these from gramjs/errors
.'
);
}
docs.writeTitle('Example', null, 'examples');
if (tlobject.friendly) {
const [ns, friendly] = tlobject.friendly;
docs.writeText(
`Please refer to the documentation of client.${friendly}()
to learn about the parameters and see several code examples on how to use it.`
);
docs.writeText(
'The method above is the recommended way to do it. If you need more control over the parameters or want to learn how it is implemented, open the details by clicking on the "Details" text.'
);
docs.write('const { TelegramClient } = require('gramjs'); const { functions, types } = require('gramjs/tl'); (async () => { const client = new TelegramClient(name, apiId, apiHash); await client.start(); const result = await client.invoke(`); tlobject.asExample(docs, 1); docs.write(');\n'); if (tlobject.result.startsWith('Vector')) { docs.write( `for x in result: print(x` ); } else { docs.write(' console.log(result'); if ( tlobject.result !== 'Bool' && !tlobject.result.startsWith('Vector') ) { docs.write('.stringify()'); } } docs.write(');\n})();'); if (tlobject.friendly) { docs.write('