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\Core\EscapeSequence as Colour;
9:
10: /**
11: * Applies inline character sequences to console output
12: */
13: final class ConsoleFormat implements ConsoleFormatInterface
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 Colour::* $colour The terminal control sequence of the desired
93: * colour.
94: */
95: public static function ttyColour(string $colour): self
96: {
97: /** @var string&Colour::* $colour */
98: return new self(
99: $colour,
100: Colour::DEFAULT,
101: [
102: Colour::DEFAULT => $colour,
103: ],
104: );
105: }
106:
107: /**
108: * Get an instance that uses terminal control sequences to increase the
109: * intensity of TTY output and optionally apply a colour
110: *
111: * @param Colour::*|null $colour The terminal control sequence of the
112: * desired colour. If `null`, no colour changes are applied.
113: */
114: public static function ttyBold(?string $colour = null): self
115: {
116: if ($colour !== null) {
117: return new self(
118: Colour::BOLD . $colour,
119: Colour::DEFAULT . Colour::UNBOLD_UNDIM,
120: [
121: Colour::UNBOLD_UNDIM => Colour::UNDIM_BOLD,
122: Colour::DEFAULT => $colour,
123: ],
124: );
125: }
126:
127: return new self(
128: Colour::BOLD,
129: Colour::UNBOLD_UNDIM,
130: [
131: Colour::UNBOLD_UNDIM => Colour::UNDIM_BOLD,
132: ],
133: );
134: }
135:
136: /**
137: * Get an instance that uses terminal control sequences to decrease the
138: * intensity of TTY output and optionally apply a colour
139: *
140: * @param Colour::*|null $colour The terminal control sequence of the
141: * desired colour. If `null`, no colour changes are applied.
142: */
143: public static function ttyDim(?string $colour = null): self
144: {
145: if ($colour !== null) {
146: return new self(
147: Colour::DIM . $colour,
148: Colour::DEFAULT . Colour::UNBOLD_UNDIM,
149: [
150: Colour::UNBOLD_UNDIM => Colour::UNBOLD_DIM,
151: Colour::DEFAULT => $colour,
152: ],
153: );
154: }
155:
156: return new self(
157: Colour::DIM,
158: Colour::UNBOLD_UNDIM,
159: [
160: Colour::UNBOLD_UNDIM => Colour::UNBOLD_DIM,
161: ],
162: );
163: }
164:
165: /**
166: * Get an instance that uses terminal control sequences to apply bold and
167: * dim attributes to TTY output and optionally apply a colour
168: *
169: * If bold (increased intensity) and dim (decreased intensity) attributes
170: * cannot be set simultaneously, output will be dim, not bold.
171: *
172: * @param Colour::*|null $colour The terminal control sequence of the
173: * desired colour. If `null`, no colour changes are applied.
174: */
175: public static function ttyBoldDim(?string $colour = null): self
176: {
177: if ($colour !== null) {
178: return new self(
179: Colour::BOLD . Colour::DIM . $colour,
180: Colour::DEFAULT . Colour::UNBOLD_UNDIM,
181: [
182: Colour::UNBOLD_UNDIM => Colour::BOLD . Colour::DIM,
183: Colour::DEFAULT => $colour,
184: ],
185: );
186: }
187:
188: return new self(
189: Colour::BOLD . Colour::DIM,
190: Colour::UNBOLD_UNDIM,
191: [
192: Colour::UNBOLD_UNDIM => Colour::BOLD . Colour::DIM,
193: ],
194: );
195: }
196:
197: /**
198: * Get an instance that uses terminal control sequences to underline and
199: * optionally apply a colour to TTY output
200: *
201: * @param Colour::*|null $colour The terminal control sequence of the
202: * desired colour. If `null`, no colour changes are applied.
203: */
204: public static function ttyUnderline(?string $colour = null): self
205: {
206: if ($colour !== null) {
207: return new self(
208: $colour . Colour::UNDERLINE,
209: Colour::NO_UNDERLINE . Colour::DEFAULT,
210: [
211: Colour::DEFAULT => $colour,
212: Colour::NO_UNDERLINE => '',
213: ],
214: );
215: }
216:
217: return new self(
218: Colour::UNDERLINE,
219: Colour::NO_UNDERLINE,
220: [
221: Colour::NO_UNDERLINE => '',
222: ],
223: );
224: }
225: }
226: