mirror of
https://github.com/appium/appium.git
synced 2026-10-25 18:21:19 +08:00
291 lines
8.8 KiB
JavaScript
291 lines
8.8 KiB
JavaScript
import path from 'path';
|
|
import yaml from 'yaml-js';
|
|
import { fs, mkdirp, util } from 'appium-support';
|
|
import validate from 'validate.js';
|
|
import Handlebars from 'handlebars';
|
|
import _ from 'lodash';
|
|
import { asyncify } from 'asyncbox';
|
|
import { validator, CLIENT_URL_TYPES } from './validator';
|
|
import url from 'url';
|
|
import log from 'fancy-log';
|
|
import findRoot from 'find-root';
|
|
|
|
|
|
// What range of platforms do the driver's support
|
|
const platformRanges = {
|
|
xcuitest: ['9.3'],
|
|
uiautomation: ['8.0', '9.3'],
|
|
espresso: ['?'],
|
|
uiautomator2: ['?'],
|
|
uiautomator: ['4.3'],
|
|
windows: ['10'],
|
|
mac: ['?'], // TODO
|
|
};
|
|
|
|
// When was the driver supported in Appium?
|
|
const appiumRanges = {
|
|
xcuitest: ['1.6.0'],
|
|
uiautomator2: ['1.6.0'],
|
|
espresso: ['1.9.0'],
|
|
windows: ['1.6.0'],
|
|
mac: ['1.6.4'],
|
|
};
|
|
|
|
const rootFolder = findRoot(__dirname);
|
|
|
|
// Create Handlebars helper that shows a version range
|
|
Handlebars.registerHelper('versions', function versionHelper (object, name, driverName) {
|
|
if (!object) {
|
|
return 'None';
|
|
}
|
|
|
|
if (!_.isObject(object)) {
|
|
object = {};
|
|
}
|
|
|
|
let min = object[name ? `${name}_min` : 'min'];
|
|
let max = object[name ? `${name}_max` : 'max'];
|
|
|
|
if (!min) {
|
|
if (name === 'appium' && _.isArray(appiumRanges[driverName])) {
|
|
min = appiumRanges[driverName][0];
|
|
} else if (name === 'platform' && _.isArray(platformRanges[driverName])) {
|
|
min = platformRanges[driverName][0];
|
|
}
|
|
}
|
|
|
|
if (!max) {
|
|
if (name === 'appium' && appiumRanges[driverName]) {
|
|
max = appiumRanges[driverName][1];
|
|
} else if (name === 'platform' && platformRanges[driverName]) {
|
|
max = platformRanges[driverName][1];
|
|
}
|
|
}
|
|
|
|
if (!min && !max) {
|
|
return 'All';
|
|
} else if (!max) {
|
|
return `${min}+`;
|
|
} else if (!min) {
|
|
return `<= ${max}`;
|
|
}
|
|
|
|
return `${min} to ${max}`;
|
|
});
|
|
|
|
Handlebars.registerHelper('hyphenate', (str) => str.replace('_', '-'));
|
|
Handlebars.registerHelper('uppercase', (str) => str.toUpperCase());
|
|
|
|
Handlebars.registerHelper('capitalize', function capitalizeDriver (driverName) {
|
|
switch (driverName.toLowerCase()) {
|
|
case 'xcuitest':
|
|
return 'XCUITest';
|
|
case 'uiautomation':
|
|
return 'UIAutomation';
|
|
case 'uiautomator2':
|
|
return 'UiAutomator2';
|
|
case 'uiautomator':
|
|
return 'UiAutomator';
|
|
case 'espresso':
|
|
return 'Espresso';
|
|
default:
|
|
return driverName.length === 0 ? driverName : driverName[0].toUpperCase() + driverName.substr(1);
|
|
}
|
|
});
|
|
|
|
Handlebars.registerHelper('if_eq', function ifEq (a, b, opts) {
|
|
if (a === b) {
|
|
return opts.fn(this);
|
|
} else {
|
|
return opts.inverse(this);
|
|
}
|
|
});
|
|
|
|
function getBaseHostname (fullUrl) {
|
|
const baseUrl = url.parse(fullUrl);
|
|
return baseUrl.hostname;
|
|
}
|
|
|
|
Handlebars.registerHelper('base_url', function baseUrl (fullUrl) {
|
|
return getBaseHostname(fullUrl);
|
|
});
|
|
|
|
Handlebars.registerHelper('client_url', function clientUrl (clientUrl) {
|
|
if (!clientUrl) {
|
|
return;
|
|
}
|
|
|
|
const createUrlString = function createUrlString (clientUrl, name = getBaseHostname(clientUrl)) {
|
|
return `[${name}](${clientUrl})`;
|
|
};
|
|
|
|
if (!_.isArray(clientUrl)) {
|
|
return createUrlString(clientUrl);
|
|
}
|
|
|
|
let urlStrings = [];
|
|
for (const item of clientUrl) {
|
|
for (let [key, value] of _.toPairs(item)) {
|
|
key = key.toLowerCase();
|
|
const urlStr = CLIENT_URL_TYPES[key] === 'hostname'
|
|
? createUrlString(value)
|
|
: createUrlString(value, CLIENT_URL_TYPES[key]);
|
|
urlStrings.push(urlStr);
|
|
}
|
|
}
|
|
return urlStrings.join(' ');
|
|
});
|
|
|
|
async function registerSpecUrlHelper () {
|
|
const routesFile = await fs.readFile(path.resolve(rootFolder, 'node_modules', 'appium-base-driver', 'lib', 'protocol', 'routes.js'), 'utf8');
|
|
const routesFileLines = routesFile.split('\n');
|
|
|
|
Handlebars.registerHelper('spec_url', function specUrl (specUrl, endpoint) {
|
|
// return the url if it is not a link to our routes doc
|
|
if (!specUrl.includes('routes.js')) {
|
|
return specUrl;
|
|
}
|
|
// make sure it is a full url
|
|
if (specUrl.startsWith('routes.js')) {
|
|
specUrl = `https://github.com/appium/appium-base-driver/blob/master/lib/protocol/${specUrl}`;
|
|
}
|
|
|
|
// strip off any line numbers
|
|
specUrl = specUrl.split('#L')[0];
|
|
|
|
// the endpoint here is often `session_id` and we need `sessionId`
|
|
endpoint = endpoint.replace('session_id', 'sessionId');
|
|
// and `element_id` and we need `elementId`
|
|
endpoint = endpoint.replace('element_id', 'elementId');
|
|
|
|
// find the line number for this endpoint
|
|
let index;
|
|
for (const i in routesFileLines) {
|
|
if (routesFileLines[i].includes(endpoint)) {
|
|
// line numbers are 1-indexed
|
|
index = parseInt(i, 10) + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (_.isUndefined(index)) {
|
|
throw new Error(`Unable to find entry in 'appium-base-driver#routes' for endpoint '${endpoint}'`);
|
|
}
|
|
|
|
return `${specUrl}#L${index}`;
|
|
});
|
|
}
|
|
|
|
async function generateCommands () {
|
|
await registerSpecUrlHelper();
|
|
|
|
const commands = path.resolve(rootFolder, 'commands-yml', 'commands/**/*.yml');
|
|
log('Traversing YML files', commands);
|
|
await fs.rimraf(path.resolve(rootFolder, 'docs', 'en', 'commands'));
|
|
|
|
// get the template from which the md files will be created
|
|
const template = Handlebars.compile(await fs.readFile(path.resolve(rootFolder, 'commands-yml', 'template.md'), 'utf8'), {noEscape: true, strict: true});
|
|
|
|
let fileCount = 0;
|
|
for (const filename of await fs.glob(commands)) {
|
|
const relativeFilename = path.relative(path.resolve(rootFolder, 'commands-yml'), filename);
|
|
log(`Rendering file: ${filename} ${relativeFilename}`);
|
|
|
|
// Translate the YML specs to JSON
|
|
const inputYML = await fs.readFile(filename, 'utf8');
|
|
const inputJSON = yaml.load(inputYML);
|
|
inputJSON.ymlFileName = `/${path.relative(rootFolder, filename)}`;
|
|
const validationErrors = validate(inputJSON, validator);
|
|
if (validationErrors) {
|
|
throw new Error(`Data validation error for ${filename}: ${JSON.stringify(validationErrors)}`);
|
|
}
|
|
|
|
// Pass the inputJS into our Handlebars template
|
|
const markdown = template(inputJSON);
|
|
|
|
// Write the markdown to its right place
|
|
const ext = path.extname(relativeFilename);
|
|
const markdownPath = `${relativeFilename.substring(0, relativeFilename.length - ext.length)}.md`;
|
|
const outfile = path.resolve(rootFolder, 'docs', 'en', markdownPath);
|
|
log(` Writing to: ${outfile}`);
|
|
await mkdirp(path.dirname(outfile));
|
|
await fs.writeFile(outfile, markdown, 'utf8');
|
|
|
|
fileCount++;
|
|
}
|
|
log(`Done writing ${fileCount} command documents`);
|
|
}
|
|
|
|
async function generateCommandIndex () {
|
|
function getTree (element, path) {
|
|
let node = {
|
|
name: element[0],
|
|
};
|
|
if (!_.isArray(element[1])) {
|
|
node.path = `${path}/${element[1]}`;
|
|
} else {
|
|
node.path = `${path}/${element[1][0]}`;
|
|
const name = element[1].shift();
|
|
node.commands = [];
|
|
for (let subElement of element[1]) {
|
|
node.commands.push(getTree(subElement, `${path}/${name}`));
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
// parse the toc.js file and get the commands into the form of
|
|
// {commands: [{name: '', path: ''}, {name: '', commands: [...]}]}
|
|
const toc = require(path.resolve(rootFolder, 'docs', 'toc.js'));
|
|
const commandToc = _.find(toc.en, (value) => value.indexOf('Commands') === 0);
|
|
const commands = [];
|
|
for (let el of commandToc[1].slice(1)) {
|
|
commands.push(getTree(el, '/docs/en/commands'));
|
|
}
|
|
|
|
const commandTemplate = Handlebars.compile(await fs.readFile(path.resolve(rootFolder, 'commands-yml', 'api-template.md'), 'utf8'), {noEscape: true, strict: true});
|
|
|
|
async function writeIndex (index, commands, indexPath) {
|
|
log(`Creating API index '${index}'`);
|
|
const commandMarkdown = commandTemplate({
|
|
commands,
|
|
path: indexPath,
|
|
});
|
|
await fs.writeFile(index, commandMarkdown, 'utf8');
|
|
}
|
|
|
|
const apiIndex = path.resolve(rootFolder, 'docs', 'en', 'about-appium', 'api.md');
|
|
await writeIndex(apiIndex, commands);
|
|
log(`Done writing main API index`);
|
|
|
|
async function writeIndividualIndexes (command) {
|
|
if (!util.hasValue(command.commands)) {
|
|
// this is a leaf, so end
|
|
return;
|
|
}
|
|
|
|
// write this node
|
|
const relPath = command.path.startsWith(path.sep) ? command.path.substring(1) : command.path;
|
|
const index = path.resolve(rootFolder, relPath, 'README.md');
|
|
await writeIndex(index, command.commands, command.path);
|
|
|
|
// go through all the sub-commands
|
|
for (const el of command.commands) {
|
|
await writeIndividualIndexes(el);
|
|
}
|
|
}
|
|
|
|
// go through the full tree and generate readme files
|
|
const index = path.resolve(rootFolder, 'docs', 'en', 'commands', 'README.md');
|
|
await writeIndex(index, commands);
|
|
for (const el of commands) {
|
|
await writeIndividualIndexes(el);
|
|
}
|
|
}
|
|
|
|
async function main () {
|
|
await generateCommands();
|
|
await generateCommandIndex();
|
|
}
|
|
|
|
asyncify(main);
|