Skip to content

Commit

Permalink
Merge pull request #1283 from lauckhart/cli-docs
Browse files Browse the repository at this point in the history
Document and improve edge cases for CLI syntax
  • Loading branch information
lauckhart authored Oct 14, 2024
2 parents d4081be + 5e508b1 commit ae8e54a
Show file tree
Hide file tree
Showing 26 changed files with 1,021 additions and 564 deletions.
5 changes: 3 additions & 2 deletions codegen/src/mom/common/generate-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { FormattedText } from "#general";
import { Model } from "#model";
import { Block } from "../../util/TsFile.js";
import { camelize, serialize, wordWrap } from "../../util/string.js";
import { camelize, serialize } from "../../util/string.js";

export function generateElement(target: Block, importFrom: string, element: Model, prefix = "", suffix = "") {
const factory = camelize(element.tag, true);
Expand Down Expand Up @@ -59,7 +60,7 @@ export function generateElement(target: Block, importFrom: string, element: Mode

// Next row: Details
if (element.details) {
const lines = wordWrap(element.details, 100);
const lines = FormattedText(element.details, 100);
for (let i = 0; i < lines.length; i++) {
const prefix = i ? " " : "details: ";
const suffix = i < lines.length - 1 ? " +" : "";
Expand Down
6 changes: 3 additions & 3 deletions codegen/src/util/TsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { InternalError, serialize } from "#general";
import { FormattedText, InternalError, serialize } from "#general";
import { Specification } from "#model";
import { Package } from "#tools";
import { relative } from "path";
import { absolute, readMatterFile, writeMatterFile } from "./file.js";
import { asObjectKey, wordWrap } from "./string.js";
import { asObjectKey } from "./string.js";

const HEADER = `/**
* @license
Expand Down Expand Up @@ -95,7 +95,7 @@ export abstract class Entry {
}

// Word wrap documentation
const lines = wordWrap(paragraphs.join("\n"), WRAP_WIDTH - 3 - linePrefix.length);
const lines = FormattedText(paragraphs.join("\n"), WRAP_WIDTH - 3 - linePrefix.length);

// Add xref after wrapping so we can ensure it never wraps
const spec = mapSpec(this.documentation?.xref);
Expand Down
234 changes: 2 additions & 232 deletions codegen/src/util/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

const LIST_INDENT = 2;

export { camelize, describeList, serialize } from "#general";

/**
* Performs word wrap. Input is assumed to be a series of paragraphs separated
* by a newline. Output is an array of formatted lines.
*
* Contains specialized support for lists and ESDoc directives.
*/
export function wordWrap(text: string, width = 120) {
const structure = detectStructure(text);
return formatStructure(structure, width);
}

/**
* Returns a string formatted to function as an object key. This means
* escaping as a string if it can't be a bare identifier.
* Returns a string formatted to function as an object key. This means escaping as a string if it can't be a bare
* identifier.
*/
export function asObjectKey(label: any) {
let str = `${label}`;
Expand All @@ -30,220 +17,3 @@ export function asObjectKey(label: any) {
}
return str;
}

enum ListType {
Bullet1 = "•",
Bullet2 = "◦",
Bullet3 = "▪",
Bullet4 = "○",
Bullet5 = "●",
Bullet6 = "‣",
Bullet7 = "⁃",
Bullet8 = "◘",
Number = "number",
LowerAlpha = "alpha",
UpperAlpha = "ALPHA",
LowerRoman = "roman",
UpperRoman = "ROMAN",
}

function detectList(text: string, listState: ListType[]) {
function enterList(listType: ListType) {
const existing = listState.indexOf(listType);
if (existing == -1) {
listState.push(listType);
} else {
listState.length = existing + 1;
}
}

for (const value of Object.values(ListType)) {
if (text[0] === value && text[1] === " ") {
enterList(text[0] as ListType);
return;
}
}

function detectEnumeration(test: RegExp, listType: ListType, first: string) {
if (!text.match(test)) {
return false;
}

if (listState.indexOf(listType) != -1 || text.startsWith(`${first}.`)) {
enterList(listType);
return true;
}

return false;
}

if (detectEnumeration(/^[0-9]+\./, ListType.Number, "1")) return;
if (detectEnumeration(/^[ivx]+\./, ListType.LowerRoman, "i")) return;
if (detectEnumeration(/^[IVX]+\./, ListType.UpperRoman, "I")) return;
if (detectEnumeration(/^[a-z]+\./, ListType.LowerAlpha, "a")) return;
if (detectEnumeration(/^[A-Z]+\./, ListType.UpperAlpha, "A")) return;

listState.length = 0;
}

type TextStructure = {
prefixWidth: number;
entries: (string | TextStructure)[];
};

function extractPrefix(text: string) {
const match = text.match(/^(\S+)\s+(.*)$/);
if (match) {
return { prefix: match[1], text: match[2] };
}
return { prefix: text, text: "" };
}

function detectStructure(text: string): TextStructure {
if (text == "") {
return { prefixWidth: 0, entries: [] };
}
const paragraphs = text.split(/\n+/).map(paragraph => paragraph.trim().replace(/\s+/g, " "));
if (!paragraphs.length) {
return { prefixWidth: 0, entries: [] };
}

const listState = Array<ListType>();
let index = 0;

function processLevel() {
const level = listState.length;
const structure = {
prefixWidth: 0,
entries: [],
} as TextStructure;

while (index < paragraphs.length) {
detectList(paragraphs[index], listState);

// If we've moved to a higher list, we're done with this level
if (listState.length < level) {
break;
}

// If we've moved to a deeper list, process the new level before
// continuing
if (listState.length > level) {
structure.entries.push(processLevel());
if (listState.length < level || index >= paragraphs.length) {
break;
}
}

// This paragraph is in this level
structure.entries.push(paragraphs[index]);

// In lists, update the prefix width so we know how far out to pad
// when formatting
if (level) {
const { prefix } = extractPrefix(paragraphs[index]);
if (prefix.length > structure.prefixWidth) {
structure.prefixWidth = prefix.length;
}
}

// Move to next line
index++;
}

return structure;
}

return processLevel();
}

function wrapParagraph(input: string, into: string[], wrapWidth: number, padding: number, prefixWidth: number) {
// Note - do not wrap text surrounded by "{@" and "}" as this is likely
// a ESDoc directive and ESDoc doesn't like directives wrapped
const segments = input.match(/(?:{@[^}]+}|\S+)(?:\s+|$)/g);
if (!segments?.length) {
return;
}

// Configure for list prefix formatting
let wrapPrefix: string;
if (prefixWidth) {
// Has a prefix. Extract the prefix from this line and pad out to
// the detected width
const extracted = extractPrefix(input);
input = extracted.prefix.padEnd(prefixWidth + 1, " ");

// After wrapping this prefix will pad out subsequent entries
wrapPrefix = "".padStart(prefixWidth + 1, " ");
} else {
// No prefix
wrapPrefix = "";
}

// Wrapping setup. Track the portions of the line and current length
const line = Array<string>();
let length = 0;

// Perform actual wrapping
let pushedOne = false;
let needWrapPrefix = false;
for (const s of segments) {
// If we'll extend too far, start on a new line
if (length && length + s.length > wrapWidth) {
addLine();
line.length = length = 0;
needWrapPrefix = true;
}

// Add padding if this is a new line
if (!line.length && padding) {
line.push("".padStart(padding, " "));
length += padding;
}

// Add wrap prefix if this is a new line in a list
if (needWrapPrefix) {
needWrapPrefix = false;
line.push(wrapPrefix);
length += wrapPrefix.length;
}

// Add to the line
line.push(s);
length += s.length;
}

// If there is a remaining line, add it
if (line.length) {
addLine();
}

function addLine() {
if (!pushedOne) {
if (into.length) {
into.push("");
}
pushedOne = true;
}

into.push(line.join(""));
}
}

function formatStructure(structure: TextStructure, width: number) {
const lines = Array<string>();

function formatLevel(structure: TextStructure, padding: number) {
for (const entry of structure.entries) {
if (typeof entry == "string") {
wrapParagraph(entry, lines, width, padding, structure.prefixWidth);
} else {
formatLevel(entry, padding + LIST_INDENT);
}
}
}

formatLevel(structure, 0);

return lines;
}
34 changes: 16 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion packages/cli-tool/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
A matter.js-based command line utility for interacting with Matter.
This is a matter.js-based command line utility for interacting with Matter.

Once installed use the `matter` command to start an interactive command line. This tool is self documenting;
use the `help` command for further instructions.
Loading

0 comments on commit ae8e54a

Please sign in to comment.