Skip to content
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
191 changes: 125 additions & 66 deletions internal/documentation/scripts/generateCliDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ function parseOutput(stdout) {
}
if (section.includes("Options:")) {
obj.addOptions = section.split("\n").filter(function(el) {
// Skip section headers (non-indented lines ending with ":", e.g. "Options:")
if (!el.startsWith(" ") && el.trim().endsWith(":")) return false;
const array = obj.commonOptions;
array.forEach(function(item, index, array) {
array[index] = item.replace(/\s+/g, "");
Expand All @@ -77,6 +79,121 @@ function parseOutput(stdout) {
}
}


function parseAliases(details) {
if (!details || !details.startsWith("[aliases:")) return [];
return details.slice("[aliases:".length, -1).trim().split(",").map((s) => s.trim()).filter(Boolean);
}

// Calls --help for commandPath and builds a full command entry from the current obj state.
/**
* @param {string} commandPath
* @param {string[]} aliases
*/
function buildCommandEntry(commandPath, aliases = []) {
execute(commandPath + " --help");

const commandsObj = [];
obj.commands.shift();
// `< 1` (not `<= 1`) so commands with exactly one subcommand are included too
if (!(obj.commands.length < 1)) {
for (const all of obj.commands) {
const temp = checkChars(all);
const {command, description} = splitString(temp);
commandsObj.push({childCommand: command, commandDescription: description});
}
}

const positionalObj = [];
obj.positionals.shift();
if (!(obj.positionals.length < 1)) {
let index = 0;
for (const all of obj.positionals) {
const temp = checkChars(all);
const {command, description, details} = splitString(temp);
if (!(/\S/.test(command))) {
if (index > 0) {
positionalObj[index - 1].positionalDescription =
positionalObj[index - 1].positionalDescription.concat("<br>", description);
positionalObj[index - 1].positionalDetails = details;
}
continue;
}
positionalObj.push({
positional: command,
positionalDescription: description,
positionalDetails: details
});
index++;
}
}

const optionObj = [];
if (!(obj.addOptions.length < 1)) {
for (const all of obj.addOptions) {
const temp = checkChars(all);
// yargs appends a trailing empty line to some sections; skip it to avoid ghost table rows
if (temp == "") {
continue;
}
const {command, description, details} = splitString(temp);
optionObj.push({option: command, optionDescription: description, optionDetails: details});
}
}

const exampleObj = [];
obj.examples.shift();
if (!(obj.examples.length <= 1)) {
for (const all of obj.examples) {
const temp = checkChars(all);
if (temp == "") {
continue;
}
const {command, description} = splitString(temp);
exampleObj.push({example: command, exampleDescription: description});
}
}

return {
command: commandPath,
description: obj.desc,
usage: obj.usage,
aliases,
childCommands: commandsObj,
positionals: positionalObj,
options: optionObj,
examples: exampleObj
};
}

/**
* Iterates commandsArray by index so newly appended subcommand entries are
* automatically picked up on subsequent iterations — depth-first without explicit recursion.
*
* @param {Record<string, any>[]} commandsArray
*/
function discoverSubCommands(commandsArray) {
const seen = new Set(commandsArray.map((e) => e.command));

for (let i = 0; i < commandsArray.length; i++) {
for (const child of commandsArray[i].childCommands || []) {
const subPath = child.childCommand && child.childCommand.trim();
if (!subPath || !/\S/.test(subPath)) {
continue;
}
// A command with positionals (<arg> or [arg]) is a leaf — skip recursion.
if (subPath.includes("<") || subPath.includes("[")) {
continue;
}
if (seen.has(subPath)) {
continue;
}
seen.add(subPath);
commandsArray.push(buildCommandEntry(subPath));
}
}
}

function generateDoc() {
execute("ui5 --help");

Expand Down Expand Up @@ -104,74 +221,16 @@ function generateDoc() {
const commands = obj.commands;
for (const all of commands) {
const command = all.trim().split(" ").slice(0, 2).join(" ");
execute(command + " --help");

const commandsObj = [];
obj.commands.shift();
if (!(obj.commands.length <= 1)) {
for (const all of obj.commands) {
const temp = checkChars(all);
const {command, description} = splitString(temp);
commandsObj.push({childCommand: command, commandDescription: description});
}
}

const positionalObj = [];
obj.positionals.shift();

if (!(obj.positionals.length < 1)) {
let index = 0;
for (const all of obj.positionals) {
const temp = checkChars(all);
const {command, description, details} = splitString(temp);
if (!(/\S/.test(command))) {
positionalObj[index - 1].positionalDescription =
positionalObj[index - 1].positionalDescription.concat("<br>", description);
positionalObj[index - 1].positionalDetails =details;
continue;
}
positionalObj.push({
positional: command,
positionalDescription: description,
positionalDetails: details
});
index++;
}
}
const optionObj = [];
obj.addOptions.shift();
if (!(obj.addOptions.length <= 1)) {
for (const all of obj.addOptions) {
const temp = checkChars(all);
const {command, description, details} = splitString(temp);
optionObj.push({option: command, optionDescription: description, optionDetails: details});
}
}

const exampleObj = [];
obj.examples.shift();
if (!(obj.examples.length <= 1)) {
for (const all of obj.examples) {
const temp = checkChars(all);
if (temp == "") {
continue;
}
const {command, description} = splitString(temp);
exampleObj.push({example: command, exampleDescription: description});
}
}

const commandObj = {
command: command,
description: obj.desc,
usage: obj.usage,
childCommands: commandsObj,
positionals: positionalObj,
options: optionObj,
examples: exampleObj
};
// Parse [aliases: ...] from the root commands-list line before execute() overwrites obj
const {details: lineDetails} = splitString(checkChars(all));
const commandObj = buildCommandEntry(command, parseAliases(lineDetails));
commandsArray.push(commandObj);
}

discoverSubCommands(commandsArray);
// Order subcommands right after their parent commands
commandsArray.sort((a, b) => a.command.localeCompare(b.command));

let content = template({
common: obj.common.split("Usage:").join(""),
commonOptions: optionObj,
Expand Down
4 changes: 4 additions & 0 deletions internal/documentation/scripts/resources/CLI.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ These options you can use with each command.

{{description}}

{{#if aliases}}
**Aliases**: {{#each aliases}}`ui5 {{this}}`{{#unless @last}}, {{/unless}}{{/each}}

{{/if}}
**Usage**

`
Expand Down
Loading