Repository navigation
Expand file tree
/
Copy pathclass-node-schema-help.php
More file actions
139 lines (129 loc) · 5.74 KB
/
Copy pathclass-node-schema-help.php
File metadata and controls
139 lines (129 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<?php
/**
* The `help <NodeType>` block for the REPL. `Command_Interpreter_Node`'s `help`
* command falls through to here when a topic does not name a command but resolves
* to a node class, so an operator gets that class's `node_schema()` as text
* rather than `no such topic`.
*
* @package Newspack_Nodes
*/
namespace Newspack_Nodes;
\defined( 'ABSPATH' ) || exit;
/**
* Errors-as-docs presentation over a `node_schema()`. Ownership of the schema
* stays with `Schema_Reflection`, whose `parse_schema_args()` assigns the same
* `arguments` this renders and whose `auto_wire_interpreter()` builds the
* sibling `{name}:config` command table from the same `commands`, so one
* declaration feeds the runtime and the help text alike. Tables render through
* `Command_Interpreter_Node::tabulate()`, the ONE text-table renderer, which is
* what keeps a help block column-aligned with the `ls` and `stats` listings.
*
* `CommandInterpreterNode._renderNodeSchema()` in
* `src/runtime/command-interpreter-node.js` mirrors this renderer for the
* browser-local interpreter, section for section and column for column. A
* change here wants the same change there.
*/
class Node_Schema_Help {
/**
* Render a node's schema as a help block: a `### Type — Category ###`
* header, the description, the `accepts_fill` and `has_target` flags, then
* the ARGUMENTS, COMMANDS, REQUESTS and REGISTRATIONS sections. `tabulate()`
* ends its last row with a newline, so a blank line falls between a table
* and the label under it.
*
* A section the schema omits prints nothing — no label, no empty table —
* because an empty ARGUMENTS heading reads as a node that takes arguments
* and forgot to describe them. In the ARGUMENTS spec column `required` wins
* over `default`, since a required argument has no default to show. An entry
* that is not an array is skipped, and every cell the schema supplies reads
* through `Core::as_string()`: a schema is hand-written in a subclass and
* reaches here unvalidated, and `help` must not fatal on a malformed one.
*
* @param string $type Shell type token, as `help` received it (e.g. `Partition`).
* @param array<string,mixed> $schema The class's `node_schema()`.
* @return string Newline-terminated help block.
*/
public static function render( string $type, array $schema ): string {
$category = isset( $schema['category'] ) ? ' — ' . Core::as_string( $schema['category'] ) : '';
$out = [ "### {$type}{$category} ###" ];
if ( isset( $schema['description'] ) ) {
$out[] = Core::as_string( $schema['description'] ) . "\n";
}
$flags = [];
foreach ( [ 'accepts_fill', 'has_target' ] as $flag ) {
if ( isset( $schema[ $flag ] ) ) {
$flags[] = $flag . '=' . ( $schema[ $flag ] ? 'true' : 'false' );
}
}
if ( ! empty( $flags ) ) {
$out[] = \implode( ' ', $flags ) . "\n";
}
$arg_rows = [];
foreach ( self::schema_list( $schema, 'arguments' ) as $arg ) {
if ( ! \is_array( $arg ) ) {
continue;
}
$spec = ! empty( $arg['required'] )
? 'required'
: ( \array_key_exists( 'default', $arg ) ? '=' . self::render_default( $arg['default'] ) : '' );
$arg_rows[] = [ Core::as_string( $arg['name'] ?? '' ), Core::as_string( $arg['type'] ?? '' ), $spec, Core::as_string( $arg['description'] ?? '' ) ];
}
if ( ! empty( $arg_rows ) ) {
$out[] = 'ARGUMENTS';
$out[] = Command_Interpreter_Node::tabulate( [ 'left', 'left', 'left', 'left' ], null, $arg_rows );
}
foreach ( [ 'commands' => 'COMMANDS', 'requests' => 'REQUESTS' ] as $field => $label ) {
$rows = [];
foreach ( self::schema_list( $schema, $field ) as $entry ) {
if ( ! \is_array( $entry ) ) {
continue;
}
$rows[] = [ Core::as_string( $entry['name'] ?? '' ), Core::as_string( $entry['description'] ?? '' ) ];
}
if ( ! empty( $rows ) ) {
$out[] = $label;
$out[] = Command_Interpreter_Node::tabulate( [ 'left', 'left' ], null, $rows );
}
}
$registrations = self::schema_list( $schema, 'registrations' );
if ( ! empty( $registrations ) ) {
$out[] = 'REGISTRATIONS: ' . \implode( ', ', \array_map( static fn ( $r ): string => Core::as_string( $r ), $registrations ) );
}
return \implode( "\n", $out ) . "\n";
}
/**
* Render one argument's declared default for the ARGUMENTS table: `true` or
* `false` for a bool, `[]` for an array, otherwise the scalar. An array has
* no useful one-line form, and spelling its contents out would wreck the
* column alignment. A `<ns:key>` default such as `<config:max_segments>`
* passes through verbatim: `Schema_Reflection` resolves it at construction,
* and the declaration is what an operator needs to see.
*
* @param mixed $default The `default` the schema declares for one argument.
* @return string A single-line table cell.
*/
private static function render_default( mixed $default ): string {
if ( \is_bool( $default ) ) {
return $default ? 'true' : 'false';
}
if ( \is_array( $default ) ) {
return '[]';
}
return Core::as_string( $default );
}
/**
* Read one `node_schema()` section as a plain list. A section the schema
* omits, or declares as something other than an array, yields an empty list
* and `render()` then skips it. `array_values()` discards the keys, so a
* section written as a map still renders in declaration order. Each entry's
* own shape is the caller's guard.
*
* @param array<string,mixed> $schema The class's `node_schema()`.
* @param string $key Section name: `arguments`, `commands`, `requests` or `registrations`.
* @return list<mixed> The section's entries, keys discarded.
*/
private static function schema_list( array $schema, string $key ): array {
$list = $schema[ $key ] ?? null;
return \is_array( $list ) ? \array_values( $list ) : [];
}
}