1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Chromabits\Nucleus\Strings;
13:
14: use Chromabits\Nucleus\Data\ArrayList;
15: use Chromabits\Nucleus\Data\ArrayMap;
16: use Chromabits\Nucleus\Data\Interfaces\FunctorInterface;
17: use Chromabits\Nucleus\Data\Interfaces\ListableInterface;
18: use Chromabits\Nucleus\Data\Interfaces\MapInterface;
19: use Chromabits\Nucleus\Data\Interfaces\MappableInterface;
20: use Chromabits\Nucleus\Data\Interfaces\MonoidInterface;
21: use Chromabits\Nucleus\Data\Interfaces\SemigroupInterface;
22: use Chromabits\Nucleus\Foundation\BaseObject;
23: use Chromabits\Nucleus\Support\Std;
24: use Closure;
25:
26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
38: class Rope extends BaseObject implements
39: FunctorInterface,
40: MonoidInterface,
41: ListableInterface,
42: MappableInterface
43: {
44: const ENCODING_UTF8 = 'UTF-8';
45:
46: 47: 48: 49: 50:
51: protected static $snakeCache = [];
52:
53: 54: 55: 56: 57:
58: protected static $camelCache = [];
59:
60: 61: 62: 63: 64:
65: protected static $studlyCache = [];
66:
67: 68: 69: 70: 71:
72: protected $contents;
73:
74: 75: 76: 77: 78:
79: protected $encoding;
80:
81: 82: 83: 84: 85: 86:
87: public function __construct($contents = '', $encoding = null)
88: {
89: parent::__construct();
90:
91: $this->contents = (string) $contents;
92: $this->encoding = Std::coalesce($encoding, mb_internal_encoding());
93: }
94:
95: 96: 97: 98: 99:
100: public static function setSnakeCache($cache)
101: {
102: static::$snakeCache = $cache;
103: }
104:
105: 106: 107: 108: 109:
110: public static function setCamelCache($cache)
111: {
112: static::$camelCache = $cache;
113: }
114:
115: 116: 117: 118: 119:
120: public static function setStudlyCache($cache)
121: {
122: static::$studlyCache = $cache;
123: }
124:
125: 126: 127: 128: 129:
130: public static function zero()
131: {
132: return static::of();
133: }
134:
135: 136: 137: 138: 139:
140: public function getEncoding()
141: {
142: return $this->encoding;
143: }
144:
145: 146: 147: 148: 149:
150: public function __toString()
151: {
152: return $this->contents;
153: }
154:
155: 156: 157: 158: 159:
160: public function toCamel()
161: {
162: $hash = md5($this->contents);
163:
164: if (isset(static::$camelCache[$hash])) {
165: return new static(static::$camelCache[$hash], $this->encoding);
166: }
167:
168: return new static(
169: static::$camelCache[$hash] = mb_lcfirst(
170: $this->toStudly(),
171: $this->encoding
172: ),
173: $this->encoding
174: );
175: }
176:
177: 178: 179: 180: 181:
182: public function toStudly()
183: {
184: $hash = md5($this->contents);
185:
186: if (isset(static::$studlyCache[$hash])) {
187: return new static(static::$studlyCache[$hash], $this->encoding);
188: }
189:
190: $value = mb_ucwords(
191: str_replace(['-', '_'], ' ', $this->contents),
192: " \t\r\n\f\v",
193: $this->encoding
194: );
195:
196: return new static(
197: static::$studlyCache[$hash] = str_replace(' ', '', $value),
198: $this->encoding
199: );
200: }
201:
202: 203: 204: 205: 206: 207: 208:
209: public function toSnake($delimiter = '_')
210: {
211: $hash = md5($this->contents) . $delimiter;
212:
213: if (isset(static::$snakeCache[$hash])) {
214: return new static(static::$snakeCache[$hash], $this->encoding);
215: }
216:
217: $value = $this->contents;
218:
219: if (!mb_ctype_lower($value, $this->encoding)) {
220:
221: $previous = mb_regex_encoding();
222: mb_regex_encoding($this->encoding);
223:
224: $value = mb_strtolower(preg_replace(
225: '/(.)(?=[A-Z])/u',
226: '$1' . $delimiter,
227: $value
228: ),
229: $this->encoding);
230:
231:
232: mb_regex_encoding($previous);
233: }
234:
235: return new static(
236: static::$snakeCache[$value . $delimiter] = $value,
237: $this->encoding
238: );
239: }
240:
241: 242: 243: 244: 245:
246: public function toLower()
247: {
248: return static::of(mb_strtolower(
249: $this->contents,
250: $this->encoding
251: ), $this->encoding);
252: }
253:
254: 255: 256: 257: 258:
259: public function toUpper()
260: {
261: return static::of(mb_strtoupper(
262: $this->contents,
263: $this->encoding
264: ), $this->encoding);
265: }
266:
267: 268: 269: 270: 271:
272: public function lowerFirst()
273: {
274: return static::of(
275: mb_lcfirst($this->contents, $this->encoding),
276: $this->encoding
277: );
278: }
279:
280: 281: 282: 283: 284: 285: 286: 287:
288: public static function of($contents = '', $encoding = null)
289: {
290: if ($contents instanceof Rope) {
291: return $contents;
292: }
293:
294: return new static($contents, $encoding);
295: }
296:
297: 298: 299: 300: 301:
302: public function upperFirst()
303: {
304: return static::of(
305: mb_ucfirst($this->contents, $this->encoding),
306: $this->encoding
307: );
308: }
309:
310: 311: 312: 313: 314:
315: public function isLower()
316: {
317: return mb_ctype_lower($this->contents, $this->encoding);
318: }
319:
320: 321: 322: 323: 324: 325: 326: 327: 328:
329: public function upperWords($delimiters = " \t\r\n\f\v")
330: {
331: return static::of(
332: mb_ucwords($this->contents, $delimiters, $this->encoding),
333: $this->encoding
334: );
335: }
336:
337: 338: 339: 340: 341:
342: public function length()
343: {
344: return mb_strlen($this->contents, $this->encoding);
345: }
346:
347: 348: 349: 350: 351: 352: 353:
354: public function trim($characterMask = " \t\n\r\0\x0B")
355: {
356: return static::of(
357: trim($this->contents, $characterMask),
358: $this->encoding
359: );
360: }
361:
362: 363: 364: 365: 366: 367: 368:
369: public function beginsWith($prefix)
370: {
371: return $prefix === ''
372: || mb_strpos(
373: $this->contents,
374: (string) $prefix,
375: null,
376: $this->encoding
377: ) === 0;
378: }
379:
380: 381: 382: 383: 384: 385: 386:
387: public function endsWith($suffix)
388: {
389: return $suffix === ''
390: || $suffix === mb_substr(
391: $this->contents,
392: -strlen((string) $suffix),
393: null,
394: $this->encoding
395: );
396: }
397:
398: 399: 400: 401: 402: 403: 404:
405: public function contains($inner)
406: {
407: return mb_strpos(
408: $this->contents,
409: (string) $inner,
410: null,
411: $this->encoding
412: ) !== false;
413: }
414:
415: 416: 417: 418: 419:
420: public function reverse()
421: {
422: return static::of(
423: ArrayList::of($this->split())->reverse()->join(),
424: $this->encoding
425: );
426: }
427:
428: 429: 430: 431: 432: 433: 434: 435:
436: public function split($splitLength = 1)
437: {
438: return mb_str_split($this->contents, $splitLength, $this->encoding);
439: }
440:
441: 442: 443: 444: 445: 446: 447:
448: public function toEncoding($encoding)
449: {
450: return static::of($this->toString(), $encoding);
451: }
452:
453: 454: 455: 456: 457:
458: public function toString()
459: {
460: return $this->contents;
461: }
462:
463: 464: 465: 466: 467: 468: 469:
470: public function fmap(callable $closure)
471: {
472: return $this->toList()->fmap($closure);
473: }
474:
475: 476: 477: 478: 479:
480: public function toList()
481: {
482: return new ArrayList($this->split());
483: }
484:
485: 486: 487: 488: 489: 490: 491:
492: public function append(SemigroupInterface $other)
493: {
494: return $this->concat($other);
495: }
496:
497: 498: 499: 500: 501: 502: 503:
504: public function concat(...$others)
505: {
506: return static::of(
507: Std::foldl(
508: function ($carry, $part) {
509: return $carry . (string) $part;
510: },
511: $this->contents,
512: $others
513: ),
514: $this->encoding
515: );
516: }
517:
518: 519: 520:
521: public function toMap()
522: {
523: return new ArrayMap($this->split());
524: }
525:
526: 527: 528: 529: 530: 531: 532:
533: public function equals(Rope $other)
534: {
535: return $this->contents === $other->contents
536: && $this->encoding === $other->encoding;
537: }
538: }
539: