thephpleague /
period
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * League.Period (https://period.thephpleague.com) |
||
| 5 | * |
||
| 6 | * (c) Ignace Nyamagana Butera <[email protected]> |
||
| 7 | * |
||
| 8 | * For the full copyright and license information, please view the LICENSE |
||
| 9 | * file that was distributed with this source code. |
||
| 10 | */ |
||
| 11 | |||
| 12 | declare(strict_types=1); |
||
| 13 | |||
| 14 | namespace League\Period\Chart; |
||
| 15 | |||
| 16 | use function array_filter; |
||
| 17 | use function array_map; |
||
| 18 | use function mb_convert_encoding; |
||
| 19 | use function mb_strlen; |
||
| 20 | use function preg_match; |
||
| 21 | use function preg_replace; |
||
| 22 | use function sprintf; |
||
| 23 | use const STDOUT; |
||
| 24 | use const STR_PAD_BOTH; |
||
| 25 | use const STR_PAD_LEFT; |
||
| 26 | use const STR_PAD_RIGHT; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * A class to configure the console output settings. |
||
| 30 | */ |
||
| 31 | final class GanttChartConfig |
||
| 32 | { |
||
| 33 | private const REGEXP_UNICODE = '/\\\\u(?<unicode>[0-9A-F]{1,4})/i'; |
||
| 34 | |||
| 35 | public const ALIGN_LEFT = STR_PAD_RIGHT; |
||
| 36 | |||
| 37 | public const ALIGN_RIGHT = STR_PAD_LEFT; |
||
| 38 | |||
| 39 | public const ALIGN_CENTER = STR_PAD_BOTH; |
||
| 40 | |||
| 41 | /** |
||
| 42 | * @var Output |
||
| 43 | */ |
||
| 44 | private $output; |
||
| 45 | |||
| 46 | /** |
||
| 47 | * @var string[] |
||
| 48 | */ |
||
| 49 | private $colors = [Output::COLOR_DEFAULT]; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @var int |
||
| 53 | */ |
||
| 54 | private $width = 60; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @var string |
||
| 58 | */ |
||
| 59 | private $endExcludedChar = ')'; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * @var string |
||
| 63 | */ |
||
| 64 | private $endIncludedChar = ']'; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * @var string |
||
| 68 | */ |
||
| 69 | private $startExcludedChar = '('; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * @var string |
||
| 73 | */ |
||
| 74 | private $startIncludedChar = '['; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @var string |
||
| 78 | */ |
||
| 79 | private $body = '-'; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * @var string |
||
| 83 | */ |
||
| 84 | private $space = ' '; |
||
| 85 | |||
| 86 | /** |
||
| 87 | * @var int |
||
| 88 | */ |
||
| 89 | private $leftMarginSize = 1; |
||
| 90 | |||
| 91 | /** |
||
| 92 | * @var int |
||
| 93 | */ |
||
| 94 | private $gapSize = 1; |
||
| 95 | |||
| 96 | /** |
||
| 97 | * @var int |
||
| 98 | */ |
||
| 99 | private $alignLabel = self::ALIGN_LEFT; |
||
| 100 | |||
| 101 | /** |
||
| 102 | * New instance. |
||
| 103 | * |
||
| 104 | * @param ?Output $output |
||
| 105 | */ |
||
| 106 | 297 | public function __construct(?Output $output = null) |
|
| 107 | { |
||
| 108 | 297 | $this->output = $output ?? new ConsoleOutput(STDOUT); |
|
| 109 | 297 | } |
|
| 110 | |||
| 111 | /** |
||
| 112 | * Create a Cli Renderer to Display the millipede in Rainbow. |
||
| 113 | * |
||
| 114 | * @param ?Output $output |
||
| 115 | */ |
||
| 116 | 3 | public static function createFromRandom(?Output $output = null): self |
|
| 117 | { |
||
| 118 | 3 | $index = array_rand(Output::COLORS); |
|
| 119 | |||
| 120 | 3 | $config = new self($output); |
|
| 121 | 3 | $config->colors = [Output::COLORS[$index]]; |
|
| 122 | |||
| 123 | 3 | return $config; |
|
| 124 | } |
||
| 125 | |||
| 126 | /** |
||
| 127 | * Create a Cli Renderer to Display the millipede in Rainbow. |
||
| 128 | * |
||
| 129 | * @param ?Output $output |
||
| 130 | */ |
||
| 131 | 3 | public static function createFromRainbow(?Output $output = null): self |
|
| 132 | { |
||
| 133 | 3 | $config = new self($output); |
|
| 134 | 3 | $config->colors = Output::COLORS; |
|
| 135 | |||
| 136 | 3 | return $config; |
|
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * Returns the Output class. |
||
| 141 | */ |
||
| 142 | 3 | public function output(): Output |
|
| 143 | { |
||
| 144 | 3 | return $this->output; |
|
| 145 | } |
||
| 146 | |||
| 147 | /** |
||
| 148 | * Retrieves the start excluded block character. |
||
| 149 | */ |
||
| 150 | 42 | public function startExcluded(): string |
|
| 151 | { |
||
| 152 | 42 | return $this->startExcludedChar; |
|
| 153 | } |
||
| 154 | /** |
||
| 155 | * Retrieves the start included block character. |
||
| 156 | */ |
||
| 157 | 42 | public function startIncluded(): string |
|
| 158 | { |
||
| 159 | 42 | return $this->startIncludedChar; |
|
| 160 | } |
||
| 161 | |||
| 162 | /** |
||
| 163 | * Retrieves the excluded end block character. |
||
| 164 | */ |
||
| 165 | 42 | public function endExcluded(): string |
|
| 166 | { |
||
| 167 | 42 | return $this->endExcludedChar; |
|
| 168 | } |
||
| 169 | |||
| 170 | /** |
||
| 171 | * Retrieves the excluded end block character. |
||
| 172 | */ |
||
| 173 | 42 | public function endIncluded(): string |
|
| 174 | { |
||
| 175 | 42 | return $this->endIncludedChar; |
|
| 176 | } |
||
| 177 | |||
| 178 | /** |
||
| 179 | * Retrieves the row width. |
||
| 180 | */ |
||
| 181 | 15 | public function width(): int |
|
| 182 | { |
||
| 183 | 15 | return $this->width; |
|
| 184 | } |
||
| 185 | |||
| 186 | /** |
||
| 187 | * Retrieves the body block character. |
||
| 188 | */ |
||
| 189 | 42 | public function body(): string |
|
| 190 | { |
||
| 191 | 42 | return $this->body; |
|
| 192 | } |
||
| 193 | |||
| 194 | /** |
||
| 195 | * Retrieves the row space character. |
||
| 196 | */ |
||
| 197 | 42 | public function space(): string |
|
| 198 | { |
||
| 199 | 42 | return $this->space; |
|
| 200 | } |
||
| 201 | |||
| 202 | /** |
||
| 203 | * The selected colors for each rows. |
||
| 204 | * |
||
| 205 | * @return string[] |
||
| 206 | */ |
||
| 207 | 12 | public function colors(): array |
|
| 208 | { |
||
| 209 | 12 | return $this->colors; |
|
| 210 | } |
||
| 211 | |||
| 212 | /** |
||
| 213 | * Retrieves the left margin size before the label name. |
||
| 214 | */ |
||
| 215 | 9 | public function leftMarginSize(): int |
|
| 216 | { |
||
| 217 | 9 | return $this->leftMarginSize; |
|
| 218 | } |
||
| 219 | |||
| 220 | /** |
||
| 221 | * Retrieves the gap size between the label and the line. |
||
| 222 | */ |
||
| 223 | 12 | public function gapSize(): int |
|
| 224 | { |
||
| 225 | 12 | return $this->gapSize; |
|
| 226 | } |
||
| 227 | |||
| 228 | /** |
||
| 229 | * Returns how label should be aligned. |
||
| 230 | */ |
||
| 231 | 12 | public function labelAlign(): int |
|
| 232 | { |
||
| 233 | 12 | return $this->alignLabel; |
|
| 234 | } |
||
| 235 | |||
| 236 | /** |
||
| 237 | * Return an instance with the start excluded pattern. |
||
| 238 | * |
||
| 239 | * This method MUST retain the state of the current instance, and return |
||
| 240 | * an instance that contains the specified start excluded character. |
||
| 241 | */ |
||
| 242 | 39 | public function withStartExcluded(string $startExcludedChar): self |
|
| 243 | { |
||
| 244 | 39 | $startExcludedChar = $this->filterPattern($startExcludedChar, 'startExcluded'); |
|
| 245 | 39 | if ($startExcludedChar === $this->startExcludedChar) { |
|
| 246 | 3 | return $this; |
|
| 247 | } |
||
| 248 | |||
| 249 | 36 | $clone = clone $this; |
|
| 250 | 36 | $clone->startExcludedChar = $startExcludedChar; |
|
| 251 | |||
| 252 | 36 | return $clone; |
|
| 253 | } |
||
| 254 | |||
| 255 | /** |
||
| 256 | * Filter the submitted string. |
||
| 257 | * |
||
| 258 | * @throws \InvalidArgumentException if the pattern is invalid |
||
| 259 | */ |
||
| 260 | 240 | private function filterPattern(string $str, string $part): string |
|
| 261 | { |
||
| 262 | 240 | if (1 === mb_strlen($str)) { |
|
| 263 | 216 | return $str; |
|
| 264 | } |
||
| 265 | |||
| 266 | 24 | if (1 === preg_match(self::REGEXP_UNICODE, $str)) { |
|
| 267 | 21 | return $this->filterUnicodeCharacter($str); |
|
| 268 | } |
||
| 269 | |||
| 270 | 3 | throw new \InvalidArgumentException(sprintf('The %s pattern must be a single character', $part)); |
|
| 271 | } |
||
| 272 | |||
| 273 | /** |
||
| 274 | * Decode unicode characters. |
||
| 275 | * |
||
| 276 | * @see http://stackoverflow.com/a/37415135/2316257 |
||
| 277 | * |
||
| 278 | * @throws \InvalidArgumentException if the character is not valid. |
||
| 279 | */ |
||
| 280 | 21 | private function filterUnicodeCharacter(string $str): string |
|
| 281 | { |
||
| 282 | 21 | $replaced = (string) preg_replace(self::REGEXP_UNICODE, '&#x$1;', $str); |
|
| 283 | 21 | $result = mb_convert_encoding($replaced, 'UTF-16', 'HTML-ENTITIES'); |
|
| 284 | 21 | $result = mb_convert_encoding($result, 'UTF-8', 'UTF-16'); |
|
| 285 | 21 | if (1 === mb_strlen($result)) { |
|
| 286 | 18 | return $result; |
|
| 287 | } |
||
| 288 | |||
| 289 | 3 | throw new \InvalidArgumentException(sprintf('The given string `%s` is not a valid unicode string', $str)); |
|
| 290 | } |
||
| 291 | |||
| 292 | /** |
||
| 293 | * Return an instance with a new output object. |
||
| 294 | * |
||
| 295 | * This method MUST retain the state of the current instance, and return |
||
| 296 | * an instance that contains the specified output class. |
||
| 297 | */ |
||
| 298 | 3 | public function withOutput(Output $output): self |
|
| 299 | { |
||
| 300 | 3 | $clone = clone $this; |
|
| 301 | 3 | $clone->output = $output; |
|
| 302 | |||
| 303 | 3 | return $clone; |
|
| 304 | } |
||
| 305 | |||
| 306 | /** |
||
| 307 | * Return an instance with the start included pattern. |
||
| 308 | * |
||
| 309 | * This method MUST retain the state of the current instance, and return |
||
| 310 | * an instance that contains the specified start included character. |
||
| 311 | */ |
||
| 312 | 39 | public function withStartIncluded(string $startIncludedChar): self |
|
| 313 | { |
||
| 314 | 39 | $startIncludedChar = $this->filterPattern($startIncludedChar, 'startIncluded'); |
|
| 315 | 39 | if ($startIncludedChar === $this->startIncludedChar) { |
|
| 316 | 3 | return $this; |
|
| 317 | } |
||
| 318 | |||
| 319 | 36 | $clone = clone $this; |
|
| 320 | 36 | $clone->startIncludedChar = $startIncludedChar; |
|
| 321 | |||
| 322 | 36 | return $clone; |
|
| 323 | } |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Return an instance with the end excluded pattern. |
||
| 327 | * |
||
| 328 | * This method MUST retain the state of the current instance, and return |
||
| 329 | * an instance that contains the specified end excluded character. |
||
| 330 | */ |
||
| 331 | 39 | public function withEndExcluded(string $endExcludedChar): self |
|
| 332 | { |
||
| 333 | 39 | $endExcludedChar = $this->filterPattern($endExcludedChar, 'endExcluded'); |
|
| 334 | 39 | if ($endExcludedChar === $this->endExcludedChar) { |
|
| 335 | 3 | return $this; |
|
| 336 | } |
||
| 337 | |||
| 338 | 36 | $clone = clone $this; |
|
| 339 | 36 | $clone->endExcludedChar = $endExcludedChar; |
|
| 340 | |||
| 341 | 36 | return $clone; |
|
| 342 | } |
||
| 343 | |||
| 344 | /** |
||
| 345 | * Return an instance with the end included pattern. |
||
| 346 | * |
||
| 347 | * This method MUST retain the state of the current instance, and return |
||
| 348 | * an instance that contains the specified end included character. |
||
| 349 | */ |
||
| 350 | 39 | public function withEndIncluded(string $endIncludedChar): self |
|
| 351 | { |
||
| 352 | 39 | $endIncludedChar = $this->filterPattern($endIncludedChar, 'endIncluded'); |
|
| 353 | 39 | if ($endIncludedChar === $this->endIncludedChar) { |
|
| 354 | 3 | return $this; |
|
| 355 | } |
||
| 356 | |||
| 357 | 36 | $clone = clone $this; |
|
| 358 | 36 | $clone->endIncludedChar = $endIncludedChar; |
|
| 359 | |||
| 360 | 36 | return $clone; |
|
| 361 | } |
||
| 362 | |||
| 363 | /** |
||
| 364 | * Return an instance with the specified row width. |
||
| 365 | * |
||
| 366 | * This method MUST retain the state of the current instance, and return |
||
| 367 | * an instance that contains the specified width. |
||
| 368 | */ |
||
| 369 | 12 | public function withWidth(int $width): self |
|
| 370 | { |
||
| 371 | 12 | if ($width < 10) { |
|
| 372 | 6 | $width = 10; |
|
| 373 | } |
||
| 374 | |||
| 375 | 12 | if ($width === $this->width) { |
|
| 376 | 3 | return $this; |
|
| 377 | } |
||
| 378 | |||
| 379 | 9 | $clone = clone $this; |
|
| 380 | 9 | $clone->width = $width; |
|
| 381 | |||
| 382 | 9 | return $clone; |
|
| 383 | } |
||
| 384 | |||
| 385 | /** |
||
| 386 | * Return an instance with the specified body block. |
||
| 387 | * |
||
| 388 | * This method MUST retain the state of the current instance, and return |
||
| 389 | * an instance that contains the specified body pattern. |
||
| 390 | */ |
||
| 391 | 45 | public function withBody(string $bodyChar): self |
|
| 392 | { |
||
| 393 | 45 | $bodyChar = $this->filterPattern($bodyChar, 'body'); |
|
| 394 | 39 | if ($bodyChar === $this->body) { |
|
| 395 | 3 | return $this; |
|
| 396 | } |
||
| 397 | |||
| 398 | 36 | $clone = clone $this; |
|
| 399 | 36 | $clone->body = $bodyChar; |
|
| 400 | |||
| 401 | 36 | return $clone; |
|
| 402 | } |
||
| 403 | |||
| 404 | /** |
||
| 405 | * Return an instance with the space pattern. |
||
| 406 | * |
||
| 407 | * This method MUST retain the state of the current instance, and return |
||
| 408 | * an instance that contains the specified space character. |
||
| 409 | */ |
||
| 410 | 39 | public function withSpace(string $spaceChar): self |
|
| 411 | { |
||
| 412 | 39 | $spaceChar = $this->filterPattern($spaceChar, 'space'); |
|
| 413 | 39 | if ($spaceChar === $this->space) { |
|
| 414 | 6 | return $this; |
|
| 415 | } |
||
| 416 | |||
| 417 | 33 | $clone = clone $this; |
|
| 418 | 33 | $clone->space = $spaceChar; |
|
| 419 | |||
| 420 | 33 | return $clone; |
|
| 421 | } |
||
| 422 | |||
| 423 | /** |
||
| 424 | * Return an instance with a new color palette. |
||
| 425 | * |
||
| 426 | * This method MUST retain the state of the current instance, and return |
||
| 427 | * an instance that contains the specified color palette. |
||
| 428 | * |
||
| 429 | * @param string... $colors |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 430 | */ |
||
| 431 | 9 | public function withColors(string ...$colors): self |
|
| 432 | { |
||
| 433 | $filter = static function ($value): bool { |
||
| 434 | 6 | return in_array($value, Output::COLORS, true); |
|
| 435 | 9 | }; |
|
| 436 | |||
| 437 | 9 | $colors = array_filter(array_map('strtolower', $colors), $filter); |
|
| 438 | 9 | if ([] === $colors) { |
|
| 439 | 6 | $colors = [Output::COLOR_DEFAULT]; |
|
| 440 | } |
||
| 441 | |||
| 442 | 9 | if ($colors === $this->colors) { |
|
| 443 | 6 | return $this; |
|
| 444 | } |
||
| 445 | |||
| 446 | 3 | $clone = clone $this; |
|
| 447 | 3 | $clone->colors = $colors; |
|
| 448 | |||
| 449 | 3 | return $clone; |
|
| 450 | } |
||
| 451 | |||
| 452 | /** |
||
| 453 | * Return an instance with a new left margin size. |
||
| 454 | * |
||
| 455 | * This method MUST retain the state of the current instance, and return |
||
| 456 | * an instance that contains the specified left margin size. |
||
| 457 | */ |
||
| 458 | 9 | public function withLeftMarginSize(int $leftMarginSize): self |
|
| 459 | { |
||
| 460 | 9 | if ($leftMarginSize === $this->leftMarginSize) { |
|
| 461 | 3 | return $this; |
|
| 462 | } |
||
| 463 | |||
| 464 | 6 | if ($leftMarginSize < 0) { |
|
| 465 | 3 | $leftMarginSize = 1; |
|
| 466 | } |
||
| 467 | |||
| 468 | 6 | $clone = clone $this; |
|
| 469 | 6 | $clone->leftMarginSize = $leftMarginSize; |
|
| 470 | |||
| 471 | 6 | return $clone; |
|
| 472 | } |
||
| 473 | /** |
||
| 474 | * Return an instance with a new gap size. |
||
| 475 | * |
||
| 476 | * This method MUST retain the state of the current instance, and return |
||
| 477 | * an instance that contains the specified gap size. |
||
| 478 | */ |
||
| 479 | 9 | public function withGapSize(int $gapSize): self |
|
| 480 | { |
||
| 481 | 9 | if ($gapSize === $this->gapSize) { |
|
| 482 | 3 | return $this; |
|
| 483 | } |
||
| 484 | |||
| 485 | 6 | if ($gapSize < 0) { |
|
| 486 | 3 | $gapSize = 1; |
|
| 487 | } |
||
| 488 | |||
| 489 | 6 | $clone = clone $this; |
|
| 490 | 6 | $clone->gapSize = $gapSize; |
|
| 491 | |||
| 492 | 6 | return $clone; |
|
| 493 | } |
||
| 494 | |||
| 495 | /** |
||
| 496 | * Return an instance with a left padding. |
||
| 497 | * |
||
| 498 | * This method MUST retain the state of the current instance, and return |
||
| 499 | * an instance that set a left padding to the line label. |
||
| 500 | */ |
||
| 501 | 9 | public function withLabelAlign(int $align): self |
|
| 502 | { |
||
| 503 | 9 | if (!in_array($align, [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH], true)) { |
|
| 504 | 3 | $align = STR_PAD_RIGHT; |
|
| 505 | } |
||
| 506 | |||
| 507 | 9 | if ($this->alignLabel === $align) { |
|
| 508 | 6 | return $this; |
|
| 509 | } |
||
| 510 | |||
| 511 | 3 | $clone = clone $this; |
|
| 512 | 3 | $clone->alignLabel = $align; |
|
| 513 | |||
| 514 | 3 | return $clone; |
|
| 515 | } |
||
| 516 | } |
||
| 517 |