1: | <?php declare(strict_types=1); |
2: | |
3: | namespace Salient\Cli; |
4: | |
5: | use Salient\Console\Support\ConsoleLoopbackFormat as LoopbackFormat; |
6: | use Salient\Console\Support\ConsoleManPageFormat as ManPageFormat; |
7: | use Salient\Console\Support\ConsoleMarkdownFormat as MarkdownFormat; |
8: | use Salient\Console\ConsoleFormatter as Formatter; |
9: | use Salient\Contract\Cli\CliHelpSectionName; |
10: | use Salient\Contract\Cli\CliHelpStyleInterface; |
11: | use Salient\Contract\Cli\CliHelpTarget; |
12: | use Salient\Contract\Cli\CliOptionVisibility; |
13: | use Salient\Contract\Console\ConsoleFormatterInterface as FormatterInterface; |
14: | use Salient\Core\Concern\HasMutator; |
15: | use Salient\Core\Facade\Console; |
16: | use Salient\Utility\Regex; |
17: | use LogicException; |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
24: | final class CliHelpStyle implements CliHelpStyleInterface |
25: | { |
26: | use HasMutator; |
27: | |
28: | private ?int $Width; |
29: | private FormatterInterface $Formatter; |
30: | private string $Bold = ''; |
31: | private string $Italic = ''; |
32: | private string $Escape = '\\'; |
33: | private string $SynopsisPrefix = ''; |
34: | private string $SynopsisNewline = "\n "; |
35: | private string $SynopsisSoftNewline = ''; |
36: | private bool $CollapseSynopsis = false; |
37: | private string $OptionIndent = ' '; |
38: | private string $OptionPrefix = ''; |
39: | private string $OptionDescriptionPrefix = "\n "; |
40: | |
41: | private int $Visibility = CliOptionVisibility::HELP; |
42: | |
43: | |
44: | |
45: | |
46: | private int $Target; |
47: | private bool $HasMarkup = false; |
48: | private int $Margin = 0; |
49: | |
50: | |
51: | |
52: | |
53: | public function __construct( |
54: | int $target = CliHelpTarget::PLAIN, |
55: | ?int $width = null, |
56: | ?FormatterInterface $formatter = null |
57: | ) { |
58: | $this->Target = $target; |
59: | $this->Width = $width; |
60: | |
61: | if ($target === CliHelpTarget::PLAIN) { |
62: | $this->Formatter = $formatter ?? LoopbackFormat::getFormatter(); |
63: | return; |
64: | } |
65: | |
66: | $this->HasMarkup = true; |
67: | $this->Italic = '_'; |
68: | |
69: | switch ($target) { |
70: | case CliHelpTarget::NORMAL: |
71: | $this->Bold = '__'; |
72: | $this->Width ??= self::getConsoleWidth(); |
73: | $this->Margin = 4; |
74: | $this->Formatter = $formatter ?? Console::getFormatter(); |
75: | break; |
76: | |
77: | case CliHelpTarget::MARKDOWN: |
78: | $this->Bold = '`'; |
79: | $this->SynopsisNewline = " \\\n\ \ \ \ "; |
80: | $this->SynopsisSoftNewline = "\n"; |
81: | $this->OptionIndent = ' '; |
82: | $this->OptionPrefix = '- '; |
83: | $this->OptionDescriptionPrefix = "\n\n "; |
84: | $this->Visibility = CliOptionVisibility::MARKDOWN; |
85: | $this->Formatter = $formatter ?? MarkdownFormat::getFormatter(); |
86: | break; |
87: | |
88: | case CliHelpTarget::MAN_PAGE: |
89: | $this->Bold = '`'; |
90: | |
91: | $this->SynopsisPrefix = '| '; |
92: | $this->SynopsisNewline = "\n| "; |
93: | $this->SynopsisSoftNewline = "\n "; |
94: | |
95: | $this->OptionDescriptionPrefix = "\n\n: "; |
96: | $this->Visibility = CliOptionVisibility::MAN_PAGE; |
97: | $this->Formatter = $formatter ?? ManPageFormat::getFormatter(); |
98: | break; |
99: | |
100: | default: |
101: | throw new LogicException(sprintf('Invalid CliHelpTarget: %d', $target)); |
102: | } |
103: | } |
104: | |
105: | |
106: | |
107: | |
108: | public function getFormatter(): FormatterInterface |
109: | { |
110: | return $this->Formatter; |
111: | } |
112: | |
113: | |
114: | |
115: | |
116: | public function getWidth(): ?int |
117: | { |
118: | return $this->Width === null |
119: | ? null |
120: | : $this->Width - $this->Margin; |
121: | } |
122: | |
123: | |
124: | |
125: | |
126: | public function getBold(): string |
127: | { |
128: | return $this->Bold; |
129: | } |
130: | |
131: | |
132: | |
133: | |
134: | public function getItalic(): string |
135: | { |
136: | return $this->Italic; |
137: | } |
138: | |
139: | |
140: | |
141: | |
142: | public function getEscape(): string |
143: | { |
144: | return $this->Escape; |
145: | } |
146: | |
147: | |
148: | |
149: | |
150: | public function getSynopsisPrefix(): string |
151: | { |
152: | return $this->SynopsisPrefix; |
153: | } |
154: | |
155: | |
156: | |
157: | |
158: | public function getSynopsisNewline(): string |
159: | { |
160: | return $this->SynopsisNewline; |
161: | } |
162: | |
163: | |
164: | |
165: | |
166: | public function getSynopsisSoftNewline(): string |
167: | { |
168: | return $this->SynopsisSoftNewline; |
169: | } |
170: | |
171: | |
172: | |
173: | |
174: | public function getCollapseSynopsis(): bool |
175: | { |
176: | return $this->CollapseSynopsis; |
177: | } |
178: | |
179: | |
180: | |
181: | |
182: | public function getOptionIndent(): string |
183: | { |
184: | return $this->OptionIndent; |
185: | } |
186: | |
187: | |
188: | |
189: | |
190: | public function getOptionPrefix(): string |
191: | { |
192: | return $this->OptionPrefix; |
193: | } |
194: | |
195: | |
196: | |
197: | |
198: | public function getOptionDescriptionPrefix(): string |
199: | { |
200: | return $this->OptionDescriptionPrefix; |
201: | } |
202: | |
203: | |
204: | |
205: | |
206: | public function getVisibility(): int |
207: | { |
208: | return $this->Visibility; |
209: | } |
210: | |
211: | |
212: | |
213: | |
214: | public function withCollapseSynopsis(bool $value = true) |
215: | { |
216: | return $this->with('CollapseSynopsis', $value); |
217: | } |
218: | |
219: | public function prepareHelp(string $text, string $indent = ''): string |
220: | { |
221: | $text = $this->Formatter->format( |
222: | $text, |
223: | true, |
224: | $this->Width === null |
225: | ? null |
226: | : $this->Width - $this->Margin - strlen($indent), |
227: | true, |
228: | ); |
229: | |
230: | if ($indent !== '') { |
231: | return $indent . str_replace("\n", "\n" . $indent, $text); |
232: | } |
233: | |
234: | return $text; |
235: | } |
236: | |
237: | |
238: | |
239: | |
240: | public function buildHelp(array $sections): string |
241: | { |
242: | $help = ''; |
243: | foreach ($sections as $heading => $content) { |
244: | $content = rtrim((string) $content); |
245: | |
246: | if ($content === '') { |
247: | continue; |
248: | } |
249: | |
250: | if ($this->Target === CliHelpTarget::NORMAL) { |
251: | $content = str_replace("\n", "\n ", $content); |
252: | $help .= "## $heading\n $content\n\n"; |
253: | continue; |
254: | } |
255: | |
256: | $help .= "## $heading\n\n$content\n\n"; |
257: | } |
258: | |
259: | return Regex::replace('/^\h++$/m', '', rtrim($help)); |
260: | } |
261: | |
262: | public function maybeEscapeTags(string $string): string |
263: | { |
264: | if ($this->HasMarkup) { |
265: | return $string; |
266: | } |
267: | return Formatter::escapeTags($string); |
268: | } |
269: | |
270: | public static function getConsoleWidth(): ?int |
271: | { |
272: | $width = Console::getWidth(); |
273: | |
274: | return $width === null |
275: | ? null |
276: | : max(76, $width); |
277: | } |
278: | } |
279: | |