diff --git a/src/filesystem/linesofalarge/LinesOfALarge.php b/src/filesystem/linesofalarge/LinesOfALarge.php index 3d1443d..94496cd 100644 --- a/src/filesystem/linesofalarge/LinesOfALarge.php +++ b/src/filesystem/linesofalarge/LinesOfALarge.php @@ -1,210 +1,224 @@ delimiter = $character; return $this; } /* -( Internals )---------------------------------------------------------- */ /** * Hook, called before @{method:rewind()}. Allows a concrete implementation * to open resources or reset state. * * @return void * @task internals */ abstract protected function willRewind(); /** * Called when the iterator needs more data. The subclass should return more * data, or empty string to indicate end-of-stream. * * @return string Data, or empty string for end-of-stream. * @task internals */ abstract protected function readMore(); /* -( Iterator Interface )------------------------------------------------- */ /** * @task iterator */ final public function rewind() { $this->willRewind(); $this->buf = ''; $this->pos = 0; $this->num = 0; $this->eof = false; $this->valid = true; $this->next(); } /** * @task iterator */ final public function key() { return $this->num; } /** * @task iterator */ final public function current() { return $this->line; } /** * @task iterator */ final public function valid() { return $this->valid; } /** * @task iterator */ final public function next() { // Consume the stream a chunk at a time into an internal buffer, then // read lines out of that buffer. This gives us flexibility (stream sources // only need to be able to read blocks of bytes) and performance (we can // read in reasonably-sized chunks of many lines), at the cost of some // complexity in buffer management. // We do this in a loop to avoid recursion when consuming more bytes, in // case the size of a line is very large compared to the chunk size we // read. while (true) { if (strlen($this->buf)) { + + // If we don't have a delimiter, return the entire buffer. + if ($this->delimiter === null) { + $this->num++; + $this->line = substr($this->buf, $this->pos); + $this->buf = ''; + $this->pos = 0; + return; + } + // If we already have some data buffered, try to get the next line from // the buffer. Search through the buffer for a delimiter. This should be // the common case. $endl = strpos($this->buf, $this->delimiter, $this->pos); if ($endl !== false) { // We found a delimiter, so return the line it delimits. We leave // the buffer as-is so we don't need to reallocate it, in case it is // large relative to the size of a line. Instead, we move our cursor // within the buffer forward. $this->num++; $this->line = substr($this->buf, $this->pos, ($endl - $this->pos)); $this->pos = $endl + 1; return; } // We only have part of a line left in the buffer (no delimiter in the // remaining piece), so throw away the part we've already emitted and // continue below. $this->buf = substr($this->buf, $this->pos); $this->pos = 0; } // We weren't able to produce the next line from the bytes we already had // buffered, so read more bytes from the input stream. if ($this->eof) { // NOTE: We keep track of EOF (an empty read) so we don't make any more // reads afterward. Normally, we'll return from the first EOF read, // emit the line, and then next() will be called again. Without tracking // EOF, we'll attempt another read. A well-behaved implementation should // still return empty string, but we can protect against any issues // here by keeping a flag. $more = ''; } else { $more = $this->readMore(); } if (strlen($more)) { // We got some bytes, so add them to the buffer and then try again. $this->buf .= $more; continue; } else { // No more bytes. If we have a buffer, return its contents. We // potentially return part of a line here if the last line had no // delimiter, but that currently seems reasonable as a default // behavior. If we don't have a buffer, we're done. $this->eof = true; if (strlen($this->buf)) { $this->num++; $this->line = $this->buf; $this->buf = null; } else { $this->valid = false; } break; } } } } diff --git a/src/utils/PhutilRope.php b/src/utils/PhutilRope.php index 79d203d..31d7aa2 100644 --- a/src/utils/PhutilRope.php +++ b/src/utils/PhutilRope.php @@ -1,116 +1,144 @@ length += $len; if ($len <= $this->segmentSize) { $this->buffers[] = $string; } else { for ($cursor = 0; $cursor < $len; $cursor += $this->segmentSize) { $this->buffers[] = substr($string, $cursor, $this->segmentSize); } } return $this; } /** * Get the length of the rope. * * @return int Length of the rope in bytes. */ public function getByteLength() { return $this->length; } /** * Get an arbitrary, nonempty prefix of the rope. * * @return string Some rope prefix. */ public function getAnyPrefix() { $result = reset($this->buffers); if ($result === false) { return null; } return $result; } + /** + * Get prefix bytes of the rope, up to some maximum size. + * + * @param int Maximum number of bytes to read. + * @return string Bytes. + */ + public function getPrefixBytes($length) { + $result = array(); + + $remaining_bytes = $length; + foreach ($this->buffers as $buf) { + $length = strlen($buf); + if ($length <= $remaining_bytes) { + $result[] = $buf; + $remaining_bytes -= $length; + } else { + $result[] = substr($buf, 0, $remaining_bytes); + $remaining_bytes = 0; + } + if (!$remaining_bytes) { + break; + } + } + + return implode('', $result); + } + + /** * Return the entire rope as a normal string. * * @return string Normal string. */ public function getAsString() { return implode('', $this->buffers); } /** * Remove a specified number of bytes from the head of the rope. * * @param int Bytes to remove. * @return this */ public function removeBytesFromHead($remove) { if ($remove <= 0) { throw new InvalidArgumentException( pht('Length must be larger than 0!')); } $remaining_bytes = $remove; foreach ($this->buffers as $key => $buf) { $len = strlen($buf); if ($len <= $remaining_bytes) { unset($this->buffers[$key]); $remaining_bytes -= $len; if (!$remaining_bytes) { break; } } else { $this->buffers[$key] = substr($buf, $remaining_bytes); break; } } if ($this->buffers) { $this->length -= $remove; } else { $this->length = 0; } return $this; } }