/** * 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; }
public function format(Diff $diff) { $messages = new Multiline($diff->getMessages()); $messages->prefix('#'); $lines = new Multiline(array_merge($this->header, $messages->toArray())); $lines->setEol("\n"); $inOld = false; $i = 0; $old = []; $diffLines = $diff->getDiffLines(); foreach ($diffLines as $line) { if ($line->isSame()) { if ($inOld === false) { $inOld = $i; } } elseif ($inOld !== false) { if ($i - $inOld > 5) { $old[$inOld] = $i - 1; } $inOld = false; } ++$i; } $start = isset($old[0]) ? $old[0] : 0; $end = count($diffLines); if ($tmp = array_search($end, $old)) { $end = $tmp; } $newChunk = true; for ($i = $start; $i < $end; ++$i) { if (isset($old[$i])) { $lines->add(''); $newChunk = true; $i = $old[$i]; } if ($newChunk) { if ($this->showNonDiffLines === true) { $lines->add('@@ @@'); } $newChunk = false; } if ($diffLines[$i]->isAddition()) { $lines->add('+' . $diffLines[$i]->getText()); } elseif ($diffLines[$i]->isRemoval()) { $lines->add('-' . $diffLines[$i]->getText()); } elseif ($this->showNonDiffLines === true) { $lines->add(' ' . $diffLines[$i]->getText()); } } $lines->add(''); return (string) $lines; }
/** * @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 removeConsecutiveEmptyDocs(Multiline $lines, $consecutive, $lastStartPos, $pos) { if ($consecutive > 0 && $lastStartPos === $pos - $consecutive - 1 || $consecutive > 1) { foreach (range($pos - $consecutive, $pos - 1) as $needle) { $lines->remove($needle); } } return 0; }
/** * @param \nochso\Omni\Multiline $lines * * @return string */ private function extractTags(Multiline $lines) { $tags = new Multiline(array_slice($lines->toArray(), $this->position)); $tags->setEol((string) $lines->getEol()); return (string) $tags; }
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'); }