/** * extractHeaderContents returns a HeaderList containing HeaderContent objects. * * @param \nochso\WriteMe\Document $document * * @return \nochso\WriteMe\Markdown\HeaderList * * @todo Refactor this as it mostly duplicates extractHeaders */ public function extractHeaderContents(Document $document) { $headerList = new HeaderList(); $lines = Multiline::create($document->getContent()); $prevLine = null; $isFenced = false; $currentHeaderContent = null; $isNewHeader = false; foreach ($lines as $key => $line) { if (preg_match('/^```(?!`)/', $line)) { $isFenced = !$isFenced; } if (!$isFenced) { $header = $this->extractHeader($line, $prevLine, $key); if ($header !== null) { $currentHeaderContent = HeaderContent::fromHeader($header); $headerList->add($currentHeaderContent); $isNewHeader = true; } } // Add content only if it's *after* the header line if ($currentHeaderContent !== null && !$isNewHeader) { $currentHeaderContent->addContent($line); } $prevLine = $line; $isNewHeader = false; } return $headerList; }
/** * Splits the DocBlock into a template marker, summary, description and * block of tags. * * @param string $comment Comment to split into the sub-parts. * * @return string[] containing the template marker (if any), summary, * description and a string containing the tags. */ protected function splitDocBlock($comment) { if (strpos($comment, '@') === 0) { return ['', '', '', $comment]; } $lines = Multiline::create($comment); $this->position = 0; $marker = $this->extractTemplateMarker($lines); $summary = $this->extractShortDescription($lines); $description = $this->extractLongDescription($lines); $tags = $this->extractTags($lines); return [$marker, $summary, $description, $tags]; }
/** * @return string */ public function getDescription() { $docBlock = new DocBlock($this->reflectionParameter->getDeclaringFunction()->getDocComment()); /** @var \phpDocumentor\Reflection\DocBlock\Tag\ParamTag[] $paramTags */ $paramTags = $docBlock->getTagsByName('param'); foreach ($paramTags as $paramTag) { if ($paramTag->getVariableName() === '$' . $this->reflectionParameter->getName()) { $lines = Multiline::create($paramTag->getDescription()); $lines->apply('ltrim'); return (string) $lines; } } return ''; }
/** * Extract frontmatter from a raw document and return the remaining document content. * * @param string $input Raw file content * * @return string The remaining document content after extracting the frontmatter into this object. */ public function extract($input) { $this->data = []; $lines = Multiline::create($input); // Frontmatter is missing: assume everything is content. if ($lines->first() !== self::FRONTMATTER_SEPARATOR) { return $input; } $frontmatterEnd = $this->findFrontmatterEndPosition($lines); $rawFrontmatter = implode($lines->getEol(), array_slice($lines->toArray(), 1, $frontmatterEnd - 1)); $this->extractFrontmatter($rawFrontmatter); $content = implode($lines->getEol(), array_slice($lines->toArray(), $frontmatterEnd + 1)); return $content; }
public function setSources($sourceBefore, $sourceAfter, $keepOutput = false) { $this->fileSizeBefore = strlen($sourceBefore); $this->lineCountBefore = Multiline::create($sourceBefore)->count(); $this->fileSizeAfter = strlen($sourceAfter); $this->lineCountAfter = Multiline::create($sourceAfter)->count(); if ($keepOutput) { $this->output = $sourceAfter; } if ($sourceBefore === $sourceAfter) { $this->status = self::STATUS_SAME; } else { $this->status = self::STATUS_CHANGED; } }
/** * `@toc.sub@` collects Markdown headers that are **below** the placeholder and on the same or deeper level. * If there's a header above the placeholder, its depth will be used as a minimum depth. * If there's no header above the placeholder, the first header after the placeholder will be used for the minimum depth. * There is currently no maximum depth for `@toc.sub@`. * * e.g. * ```markdown * # ignore me * * @toc.sub@ * ## sub 1 * # ignore me again * ``` * is converted into * * ```markdown * # ignore me * - [sub 1](#sub-1) * ## sub 1 * # ignore me again * ``` * * @param \nochso\WriteMe\Placeholder\Call $call * @param int $maxDepth How many levels of headers you'd like to keep. * Defaults to zero, meaning all sub-headers are kept. */ public function tocSub(Call $call, $maxDepth = 0) { $parser = new Markdown\HeaderParser(); $headerList = $parser->extractHeaders($call->getDocument()); $lines = Multiline::create($call->getDocument()->getContent()); $lineIndex = $lines->getLineIndexByCharacterPosition($call->getStartPositionOfRawCall()); $headers = $headerList->getHeadersBelowLine($lineIndex); if ($maxDepth > 0) { $minDepth = $this->getMinimumDepth($headers); // Filter headers that are relatively too deep $headers = array_filter($headers, function (Markdown\Header $header) use($minDepth, $maxDepth) { return $header->getLevel() - $minDepth < $maxDepth; }); } $toc = $this->formatTOC($headers); $call->replace($toc); }
/** * @param \Throwable $throwable */ public function exception($throwable) { if (!$throwable instanceof \Exception && !$throwable instanceof \Throwable) { $this->errln($throwable); return; } $template = <<<TAG <<bold red>>%s<<reset>> <<red>>%s<<reset>> TAG; $message = $throwable; $message = Multiline::create($message)->prefix(' '); $string = sprintf($template, get_class($throwable), $message); $string = $this->formatter->format($string, $this->stderr->isPosix()); $this->stderr->fwrite($string); }
/** * Gets the formatted output for one option. * * @param \stdClass $option An option structure. * * @return string */ protected function getHelpOption($option) { if (!$option->name) { // it's an argument return ''; } $text = ' ' . $this->getHelpOptionParam($option->name, $option->param, $option->multi); if ($option->alias) { $text .= ', ' . $this->getHelpOptionParam($option->alias, $option->param, $option->multi); } $text .= PHP_EOL; if (!$option->descr) { $option->descr = 'No description.'; } $indent = 8; $remaining = 80 - $indent; $lines = Multiline::create(wordwrap($option->descr, $remaining, "\n")); $lines->prefix(str_repeat(' ', $indent)); $text .= (string) $lines . PHP_EOL . PHP_EOL; return $text; }
/** * Returns the diff between two arrays or strings as array. * * Each array element contains two elements: * - [0] => string $token * - [1] => 2|1|0 * * - 2: REMOVED: $token was removed from $from * - 1: ADDED: $token was added to $from * - 0: OLD: $token is not changed in $to * * @param array|string $from * @param array|string $to * @param LongestCommonSubsequence $lcs * * @return array */ public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) { $this->messages = []; $diff = []; $this->addLineEndingWarning($from, $to); if (!is_array($from) && !is_string($from)) { $from = (string) $from; } if (!is_array($to) && !is_string($to)) { $to = (string) $to; } if (is_string($from)) { $from = Multiline::create($from)->toArray(); } if (is_string($to)) { $to = Multiline::create($to)->toArray(); } $start = []; $end = []; $fromLength = count($from); $toLength = count($to); $length = min($fromLength, $toLength); for ($i = 0; $i < $length; ++$i) { if ($from[$i] === $to[$i]) { $start[] = $from[$i]; unset($from[$i], $to[$i]); } else { break; } } $length -= $i; for ($i = 1; $i < $length; ++$i) { if ($from[$fromLength - $i] === $to[$toLength - $i]) { array_unshift($end, $from[$fromLength - $i]); unset($from[$fromLength - $i], $to[$toLength - $i]); } else { break; } } if ($lcs === null) { $lcs = $this->selectLcsImplementation($from, $to); } $common = $lcs->calculate(array_values($from), array_values($to)); foreach ($start as $token) { $diff[] = [$token, 0]; } reset($from); reset($to); foreach ($common as $token) { while (($fromToken = reset($from)) !== $token) { $diff[] = [array_shift($from), 2]; } while (($toToken = reset($to)) !== $token) { $diff[] = [array_shift($to), 1]; } $diff[] = [$token, 0]; array_shift($from); array_shift($to); } while (($token = array_shift($from)) !== null) { $diff[] = [$token, 2]; } while (($token = array_shift($to)) !== null) { $diff[] = [$token, 1]; } foreach ($end as $token) { $diff[] = [$token, 0]; } return $diff; }
public function formatClass(ReflectionClass $class) { $ml = Multiline::create($class->getLocatedSource()->getSource()); return trim($ml[$class->getStartLine() - 1]); }
protected function pComments(array $comments) { $lines = Multiline::create(parent::pComments($comments)); // Trim trailing non-Markdown whitespace $lines->apply(function ($line) { if (preg_match('/(?<! |\\*) $/', $line)) { return $line; } return rtrim($line); }); $lastStartPos = -1; $consecutive = 0; $isFenced = false; foreach ($lines->toArray() as $pos => $line) { // Ignore fenced Markdown if (preg_match('/^\\s*\\*\\s?```\\s*$/', $line)) { $isFenced = !$isFenced; } if ($isFenced) { $consecutive = $this->removeConsecutiveEmptyDocs($lines, $consecutive, $lastStartPos, $pos); continue; } // Remember last start /* or /** if (preg_match("/^(\\s*\\/\\*+\\s*)\$/", $line)) { $lastStartPos = $pos; continue; } // Keep matching whitespacey lines if (preg_match("/^\\s*\\*\\s*\$/", $line)) { $consecutive++; continue; } if (preg_match('/^\\s*\\*\\/\\s*$/', $line)) { $lastStartPos = $pos - $consecutive - 1; } // Remove lines if possible $consecutive = $this->removeConsecutiveEmptyDocs($lines, $consecutive, $lastStartPos, $pos); } return (string) $lines; }
public function testGetLineIndexByCharacterPosition() { $ml = Multiline::create("ü\nbb\nccc"); $this->assertSame(0, $ml->getLineIndexByCharacterPosition(1)); $this->assertSame(0, $ml->getLineIndexByCharacterPosition(2), 'Line feeds belong to the line it is ending with'); $this->assertSame(1, $ml->getLineIndexByCharacterPosition(3)); $this->assertSame(2, $ml->getLineIndexByCharacterPosition(9)); $this->assertSame(null, $ml->getLineIndexByCharacterPosition(10), 'Invalid position must return null'); $this->assertSame(null, $ml->getLineIndexByCharacterPosition(-1), 'Invalid (negative) position return null'); }