Skip to content

Commit d4813bb

Browse files
committed
Continue utility class cleanup
- Move `ellipsize()`, `expandTabs()` and `expandLeadingTabs()` from `Convert` to `Str` - Move `Convert::linesToLists()` to `Str::mergeLists()`
1 parent f2bf03d commit d4813bb

File tree

4 files changed

+656
-632
lines changed

4 files changed

+656
-632
lines changed

src/Utility/Convert.php

Lines changed: 0 additions & 261 deletions
Original file line numberDiff line numberDiff line change
@@ -20,96 +20,6 @@
2020
*/
2121
final class Convert extends Utility
2222
{
23-
/**
24-
* Expand tabs to spaces
25-
*/
26-
public static function expandTabs(
27-
string $text,
28-
int $tabSize = 8,
29-
int $column = 1
30-
): string {
31-
if (strpos($text, "\t") === false) {
32-
return $text;
33-
}
34-
$eol = Get::eol($text) ?: "\n";
35-
$expanded = '';
36-
foreach (explode($eol, $text) as $i => $line) {
37-
!$i || $expanded .= $eol;
38-
$parts = explode("\t", $line);
39-
$last = array_key_last($parts);
40-
foreach ($parts as $p => $part) {
41-
$expanded .= $part;
42-
if ($p === $last) {
43-
break;
44-
}
45-
$column += mb_strlen($part);
46-
// e.g. with $tabSize 4, a tab at $column 2 occupies 3 spaces
47-
$spaces = $tabSize - (($column - 1) % $tabSize);
48-
$expanded .= str_repeat(' ', $spaces);
49-
$column += $spaces;
50-
}
51-
$column = 1;
52-
}
53-
return $expanded;
54-
}
55-
56-
/**
57-
* Expand leading tabs to spaces
58-
*/
59-
public static function expandLeadingTabs(
60-
string $text,
61-
int $tabSize = 8,
62-
bool $preserveLine1 = false,
63-
int $column = 1
64-
): string {
65-
if (strpos($text, "\t") === false) {
66-
return $text;
67-
}
68-
$eol = Get::eol($text) ?: "\n";
69-
$softTab = str_repeat(' ', $tabSize);
70-
$expanded = '';
71-
foreach (explode($eol, $text) as $i => $line) {
72-
!$i || $expanded .= $eol;
73-
if ($i || (!$preserveLine1 && $column === 1)) {
74-
$expanded .= Pcre::replace('/(?<=\n|\G)\t/', $softTab, $line);
75-
continue;
76-
}
77-
if ($preserveLine1) {
78-
$expanded .= $line;
79-
continue;
80-
}
81-
$parts = explode("\t", $line);
82-
while (($part = array_shift($parts)) !== null) {
83-
$expanded .= $part;
84-
if (!$parts) {
85-
break;
86-
}
87-
if ($part) {
88-
$expanded .= "\t" . implode("\t", $parts);
89-
break;
90-
}
91-
$column += mb_strlen($part);
92-
$spaces = $tabSize - (($column - 1) % $tabSize);
93-
$expanded .= str_repeat(' ', $spaces);
94-
$column += $spaces;
95-
}
96-
}
97-
return $expanded;
98-
}
99-
100-
/**
101-
* Replace the end of a multi-byte string with an ellipsis ("...") if its
102-
* length exceeds a limit
103-
*/
104-
public static function ellipsize(string $value, int $length): string
105-
{
106-
if (mb_strlen($value) > $length) {
107-
return rtrim(mb_substr($value, 0, $length - 3)) . '...';
108-
}
109-
110-
return $value;
111-
}
112-
11323
/**
11424
* Convert a list of "key=value" strings to an array like ["key" => "value"]
11525
*
@@ -134,177 +44,6 @@ public static function queryToData(array $query): array
13444
);
13545
}
13646

137-
/**
138-
* Remove duplicates in a string where top-level lines ("sections") are
139-
* grouped with "list items" below
140-
*
141-
* Lines that match `$regex` are regarded as list items, and other lines are
142-
* used as the section name for subsequent list items. If `$loose` is
143-
* `false` (the default), blank lines between list items clear the current
144-
* section name.
145-
*
146-
* Top-level lines with no children, including any list items orphaned by
147-
* blank lines above them, are returned before sections with children.
148-
*
149-
* If a named subpattern in `$regex` called `indent` matches a non-empty
150-
* string, subsequent lines with the same number of spaces for indentation
151-
* as there are characters in the match are treated as part of the item,
152-
* including any blank lines.
153-
*
154-
* Line endings used in `$text` may be any combination of LF, CRLF and CR,
155-
* but LF (`"\n"`) line endings are used in the return value.
156-
*
157-
* @param string $separator Used between top-level lines and sections. Has
158-
* no effect on the end-of-line sequence used between items, which is always
159-
* LF (`"\n"`).
160-
* @param string|null $marker Added before each section name. Nested list
161-
* items are indented by the equivalent number of spaces. To add a leading
162-
* `"- "` to top-level lines and indent others with two spaces, set
163-
* `$marker` to `"-"`.
164-
* @param bool $clean If `true`, the first match of `$regex` in each section
165-
* name is removed.
166-
* @param bool $loose If `true`, blank lines between list items are ignored.
167-
*/
168-
public static function linesToLists(
169-
string $text,
170-
string $separator = "\n",
171-
?string $marker = null,
172-
string $regex = '/^(?<indent>\h*[-*] )/',
173-
bool $clean = false,
174-
bool $loose = false
175-
): string {
176-
$marker = (string) $marker !== '' ? $marker . ' ' : null;
177-
$indent = $marker !== null ? str_repeat(' ', mb_strlen($marker)) : '';
178-
$markerIsItem = $marker !== null && Pcre::match($regex, $marker);
179-
180-
/** @var array<string,string[]> */
181-
$sections = [];
182-
$lastWasItem = false;
183-
$lines = Pcre::split('/\r\n|\n|\r/', $text);
184-
for ($i = 0; $i < count($lines); $i++) {
185-
$line = $lines[$i];
186-
187-
// Remove pre-existing markers early to ensure sections with the
188-
// same name are combined
189-
if ($marker !== null && !$markerIsItem && strpos($line, $marker) === 0) {
190-
$line = substr($line, strlen($marker));
191-
}
192-
193-
// Treat blank lines between items as section breaks
194-
if (trim($line) === '') {
195-
if (!$loose && $lastWasItem) {
196-
unset($section);
197-
}
198-
continue;
199-
}
200-
201-
// Collect any subsequent indented lines
202-
if (Pcre::match($regex, $line, $matches)) {
203-
$matchIndent = $matches['indent'] ?? '';
204-
if ($matchIndent !== '') {
205-
$matchIndent = str_repeat(' ', mb_strlen($matchIndent));
206-
$pendingWhitespace = '';
207-
$backtrack = 0;
208-
while ($i < count($lines) - 1) {
209-
$nextLine = $lines[$i + 1];
210-
if (trim($nextLine) === '') {
211-
$pendingWhitespace .= $nextLine . "\n";
212-
$backtrack++;
213-
} elseif (substr($nextLine, 0, strlen($matchIndent)) === $matchIndent) {
214-
$line .= "\n" . $pendingWhitespace . $nextLine;
215-
$pendingWhitespace = '';
216-
$backtrack = 0;
217-
} else {
218-
$i -= $backtrack;
219-
break;
220-
}
221-
$i++;
222-
}
223-
}
224-
} else {
225-
$section = $line;
226-
}
227-
228-
$key = $section ?? $line;
229-
230-
if (!array_key_exists($key, $sections)) {
231-
$sections[$key] = [];
232-
}
233-
234-
if ($key !== $line) {
235-
if (!in_array($line, $sections[$key])) {
236-
$sections[$key][] = $line;
237-
}
238-
$lastWasItem = true;
239-
} else {
240-
$lastWasItem = false;
241-
}
242-
}
243-
244-
// Move lines with no associated list to the top
245-
/** @var array<string,string[]> */
246-
$top = [];
247-
$last = null;
248-
foreach ($sections as $section => $lines) {
249-
if (count($lines)) {
250-
continue;
251-
}
252-
253-
unset($sections[$section]);
254-
255-
if ($clean) {
256-
$top[$section] = [];
257-
continue;
258-
}
259-
260-
// Collect second and subsequent consecutive top-level list items
261-
// under the first so they don't form a loose list
262-
if (Pcre::match($regex, $section)) {
263-
if ($last !== null) {
264-
$top[$last][] = $section;
265-
continue;
266-
}
267-
$last = $section;
268-
} else {
269-
$last = null;
270-
}
271-
$top[$section] = [];
272-
}
273-
/** @var array<string,string[]> */
274-
$sections = array_merge($top, $sections);
275-
276-
$groups = [];
277-
foreach ($sections as $section => $lines) {
278-
if ($clean) {
279-
$section = Pcre::replace($regex, '', $section, 1);
280-
}
281-
282-
$marked = false;
283-
if ($marker !== null &&
284-
!($markerIsItem && strpos($section, $marker) === 0) &&
285-
!Pcre::match($regex, $section)) {
286-
$section = $marker . $section;
287-
$marked = true;
288-
}
289-
290-
if (!$lines) {
291-
$groups[] = $section;
292-
continue;
293-
}
294-
295-
// Don't separate or indent top-level list items collected above
296-
if (!$marked && Pcre::match($regex, $section)) {
297-
$groups[] = implode("\n", [$section, ...$lines]);
298-
continue;
299-
}
300-
301-
$groups[] = $section;
302-
$groups[] = $indent . implode("\n" . $indent, $lines);
303-
}
304-
305-
return implode($separator, $groups);
306-
}
307-
30847
/**
30948
* Undo wordwrap(), preserving Markdown-style paragraphs and lists
31049
*

0 commit comments

Comments
 (0)