1: <?php declare(strict_types=1);
2:
3: namespace Salient\Console\Support;
4:
5: use Salient\Console\Support\ConsoleTagAttributes as TagAttributes;
6: use Salient\Contract\Console\ConsoleFormatInterface;
7: use Salient\Contract\Console\ConsoleTag as Tag;
8: use Salient\Contract\HasEscapeSequence;
9:
10: /**
11: * Applies inline character sequences to console output
12: */
13: final class ConsoleFormat implements ConsoleFormatInterface, HasEscapeSequence
14: {
15: /** @var string */
16: private $Before;
17: /** @var string */
18: private $After;
19: /** @var string[] */
20: private $Search;
21: /** @var string[] */
22: private $Replace;
23: private static ConsoleFormat $DefaultFormat;
24:
25: /**
26: * @param array<string,string> $replace
27: */
28: public function __construct(string $before = '', string $after = '', array $replace = [])
29: {
30: $this->Before = $before;
31: $this->After = $after;
32: $this->Search = array_keys($replace);
33: $this->Replace = array_values($replace);
34: }
35:
36: /**
37: * @inheritDoc
38: */
39: public function apply(?string $text, $attributes = null): string
40: {
41: if ($text === null || $text === '') {
42: return '';
43: }
44:
45: // With fenced code blocks:
46: // - remove indentation from the first line of code
47: // - add a level of indentation to the block
48: if (
49: $attributes instanceof TagAttributes
50: && $attributes->Tag === Tag::CODE_BLOCK
51: ) {
52: $indent = (string) $attributes->Indent;
53: if ($indent !== '') {
54: $length = strlen($indent);
55: if (substr($text, 0, $length) === $indent) {
56: $text = substr($text, $length);
57: }
58: }
59: $text = ' ' . str_replace("\n", "\n ", $text);
60: }
61:
62: if ($this->Search) {
63: $text = str_replace($this->Search, $this->Replace, $text);
64: }
65:
66: $text = $this->Before . $text;
67:
68: if ($this->After === '') {
69: return $text;
70: }
71:
72: // Preserve a trailing carriage return
73: if ($text[-1] === "\r") {
74: return substr($text, 0, -1) . $this->After . "\r";
75: }
76:
77: return $text . $this->After;
78: }
79:
80: /**
81: * Get an instance that doesn't apply any formatting to console output
82: */
83: public static function getDefaultFormat(): self
84: {
85: return self::$DefaultFormat ??= new self();
86: }
87:
88: /**
89: * Get an instance that uses terminal control sequences to apply a colour to
90: * TTY output
91: *
92: * @param ConsoleFormat::* $colour The terminal control sequence of the
93: * desired colour.
94: */
95: public static function ttyColour(string $colour): self
96: {
97: return new self(
98: $colour,
99: self::DEFAULT_FG,
100: [
101: self::DEFAULT_FG => $colour,
102: ],
103: );
104: }
105:
106: /**
107: * Get an instance that uses terminal control sequences to increase the
108: * intensity of TTY output and optionally apply a colour
109: *
110: * @param ConsoleFormat::*|null $colour The terminal control sequence of the
111: * desired colour. If `null`, no colour changes are applied.
112: */
113: public static function ttyBold(?string $colour = null): self
114: {
115: if ($colour !== null) {
116: return new self(
117: self::BOLD . $colour,
118: self::DEFAULT_FG . self::NOT_BOLD_NOT_FAINT,
119: [
120: self::NOT_BOLD_NOT_FAINT => self::BOLD_NOT_FAINT,
121: self::DEFAULT_FG => $colour,
122: ],
123: );
124: }
125:
126: return new self(
127: self::BOLD,
128: self::NOT_BOLD_NOT_FAINT,
129: [
130: self::NOT_BOLD_NOT_FAINT => self::BOLD_NOT_FAINT,
131: ],
132: );
133: }
134:
135: /**
136: * Get an instance that uses terminal control sequences to decrease the
137: * intensity of TTY output and optionally apply a colour
138: *
139: * @param ConsoleFormat::*|null $colour The terminal control sequence of the
140: * desired colour. If `null`, no colour changes are applied.
141: */
142: public static function ttyDim(?string $colour = null): self
143: {
144: if ($colour !== null) {
145: return new self(
146: self::FAINT . $colour,
147: self::DEFAULT_FG . self::NOT_BOLD_NOT_FAINT,
148: [
149: self::NOT_BOLD_NOT_FAINT => self::FAINT_NOT_BOLD,
150: self::DEFAULT_FG => $colour,
151: ],
152: );
153: }
154:
155: return new self(
156: self::FAINT,
157: self::NOT_BOLD_NOT_FAINT,
158: [
159: self::NOT_BOLD_NOT_FAINT => self::FAINT_NOT_BOLD,
160: ],
161: );
162: }
163:
164: /**
165: * Get an instance that uses terminal control sequences to apply bold and
166: * dim attributes to TTY output and optionally apply a colour
167: *
168: * If bold (increased intensity) and dim (decreased intensity) attributes
169: * cannot be set simultaneously, output will be dim, not bold.
170: *
171: * @param ConsoleFormat::*|null $colour The terminal control sequence of the
172: * desired colour. If `null`, no colour changes are applied.
173: */
174: public static function ttyBoldDim(?string $colour = null): self
175: {
176: if ($colour !== null) {
177: return new self(
178: self::BOLD . self::FAINT . $colour,
179: self::DEFAULT_FG . self::NOT_BOLD_NOT_FAINT,
180: [
181: self::NOT_BOLD_NOT_FAINT => self::BOLD . self::FAINT,
182: self::DEFAULT_FG => $colour,
183: ],
184: );
185: }
186:
187: return new self(
188: self::BOLD . self::FAINT,
189: self::NOT_BOLD_NOT_FAINT,
190: [
191: self::NOT_BOLD_NOT_FAINT => self::BOLD . self::FAINT,
192: ],
193: );
194: }
195:
196: /**
197: * Get an instance that uses terminal control sequences to underline and
198: * optionally apply a colour to TTY output
199: *
200: * @param ConsoleFormat::*|null $colour The terminal control sequence of the
201: * desired colour. If `null`, no colour changes are applied.
202: */
203: public static function ttyUnderline(?string $colour = null): self
204: {
205: if ($colour !== null) {
206: return new self(
207: $colour . self::UNDERLINED,
208: self::NOT_UNDERLINED . self::DEFAULT_FG,
209: [
210: self::DEFAULT_FG => $colour,
211: self::NOT_UNDERLINED => '',
212: ],
213: );
214: }
215:
216: return new self(
217: self::UNDERLINED,
218: self::NOT_UNDERLINED,
219: [
220: self::NOT_UNDERLINED => '',
221: ],
222: );
223: }
224: }
225: