Build modified streamline images

The official image from streamline does not currently work for the arm64
platform. As a temporary measure, the source code and docker build scripts
have been lifted from the official images and are used to build locally.

Some additional modifications are made to reduce overall image size, these
are documented in docker/README.md
This commit is contained in:
2024-03-10 16:17:50 -07:00
parent 2ffc3c408a
commit a424394109
13506 changed files with 1860172 additions and 6 deletions
@@ -0,0 +1,168 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2023 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
/**
* A Readline interface implementation for GNU Readline.
*
* This is by far the coolest way to do it, if you can.
*
* Oh well.
*/
class GNUReadline implements Readline
{
/** @var string|false */
protected $historyFile;
/** @var int */
protected $historySize;
/** @var bool */
protected $eraseDups;
/**
* GNU Readline is supported iff `readline_list_history` is defined. PHP
* decided it would be awesome to swap out GNU Readline for Libedit, but
* they ended up shipping an incomplete implementation. So we've got this.
*
* NOTE: As of PHP 7.4, PHP sometimes has history support in the Libedit
* wrapper, so that will use the GNUReadline implementation as well!
*/
public static function isSupported(): bool
{
return \function_exists('readline') && \function_exists('readline_list_history');
}
/**
* Check whether this readline implementation supports bracketed paste.
*
* Currently, the GNU readline implementation does, but the libedit wrapper does not.
*/
public static function supportsBracketedPaste(): bool
{
return self::isSupported() && \stripos(\readline_info('library_version') ?: '', 'editline') === false;
}
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
{
$this->historyFile = ($historyFile !== null) ? $historyFile : false;
$this->historySize = $historySize;
$this->eraseDups = $eraseDups;
\readline_info('readline_name', 'psysh');
}
/**
* {@inheritdoc}
*/
public function addHistory(string $line): bool
{
if ($res = \readline_add_history($line)) {
$this->writeHistory();
}
return $res;
}
/**
* {@inheritdoc}
*/
public function clearHistory(): bool
{
if ($res = \readline_clear_history()) {
$this->writeHistory();
}
return $res;
}
/**
* {@inheritdoc}
*/
public function listHistory(): array
{
return \readline_list_history();
}
/**
* {@inheritdoc}
*/
public function readHistory(): bool
{
\readline_read_history();
\readline_clear_history();
return \readline_read_history($this->historyFile);
}
/**
* {@inheritdoc}
*/
public function readline(string $prompt = null)
{
return \readline($prompt);
}
/**
* {@inheritdoc}
*/
public function redisplay()
{
\readline_redisplay();
}
/**
* {@inheritdoc}
*/
public function writeHistory(): bool
{
// We have to write history first, since it is used
// by Libedit to list history
if ($this->historyFile !== false) {
$res = \readline_write_history($this->historyFile);
} else {
$res = true;
}
if (!$res || !$this->eraseDups && !$this->historySize > 0) {
return $res;
}
$hist = $this->listHistory();
if (!$hist) {
return true;
}
if ($this->eraseDups) {
// flip-flip technique: removes duplicates, latest entries win.
$hist = \array_flip(\array_flip($hist));
// sort on keys to get the order back
\ksort($hist);
}
if ($this->historySize > 0) {
$histsize = \count($hist);
if ($histsize > $this->historySize) {
$hist = \array_slice($hist, $histsize - $this->historySize);
}
}
\readline_clear_history();
foreach ($hist as $line) {
\readline_add_history($line);
}
if ($this->historyFile !== false) {
return \readline_write_history($this->historyFile);
}
return true;
}
}
@@ -0,0 +1,57 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Console\Readline\Autocompleter.
*
* Interface for all auto-completers.
*/
interface Autocompleter
{
/**
* Complete a word.
* Returns null for no word, a full-word or an array of full-words.
*/
public function complete(&$prefix);
/**
* Get definition of a word.
* Example: \b\w+\b. PCRE delimiters and options must not be provided.
*/
public function getWordDefinition(): string;
}
@@ -0,0 +1,118 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Readline\Autocompleter\Aggregate.
*
* Aggregate several autocompleters.
*/
class AutocompleterAggregate implements Autocompleter
{
/**
* List of autocompleters.
*/
protected $_autocompleters = null;
/**
* Constructor.
*/
public function __construct(array $autocompleters)
{
$this->setAutocompleters($autocompleters);
return;
}
/**
* Complete a word.
* Returns null for no word, a full-word or an array of full-words.
*/
public function complete(&$prefix)
{
foreach ($this->getAutocompleters() as $autocompleter) {
$preg = \preg_match(
'#('.$autocompleter->getWordDefinition().')$#u',
$prefix,
$match
);
if (0 === $preg) {
continue;
}
$_prefix = $match[0];
if (null === $out = $autocompleter->complete($_prefix)) {
continue;
}
$prefix = $_prefix;
return $out;
}
return null;
}
/**
* Set/initialize list of autocompleters.
*/
protected function setAutocompleters(array $autocompleters)
{
$old = $this->_autocompleters;
$this->_autocompleters = new \ArrayObject($autocompleters);
return $old;
}
/**
* Get list of autocompleters.
*/
public function getAutocompleters()
{
return $this->_autocompleters;
}
/**
* Get definition of a word.
*/
public function getWordDefinition(): string
{
return '.*';
}
}
@@ -0,0 +1,194 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Readline\Autocompleter\Path.
*
* Path autocompleter.
*/
class AutocompleterPath implements Autocompleter
{
/**
* Root is the current working directory.
*/
const PWD = null;
/**
* Root.
*/
protected $_root = null;
/**
* Iterator factory. Please, see the self::setIteratorFactory method.
*/
protected $_iteratorFactory = null;
/**
* Constructor.
*/
public function __construct(
string $root = null,
\Closure $iteratorFactory = null
) {
if (null === $root) {
$root = static::PWD;
}
if (null !== $root) {
$this->setRoot($root);
}
if (null !== $iteratorFactory) {
$this->setIteratorFactory($iteratorFactory);
}
}
/**
* Complete a word.
* Returns null for no word, a full-word or an array of full-words.
*/
public function complete(&$prefix)
{
$root = $this->getRoot();
if (static::PWD === $root) {
$root = \getcwd();
}
$path = $root.\DIRECTORY_SEPARATOR.$prefix;
if (!\is_dir($path)) {
$path = \dirname($path).\DIRECTORY_SEPARATOR;
$prefix = \basename($prefix);
} else {
$prefix = null;
}
$iteratorFactory = $this->getIteratorFactory() ?:
static::getDefaultIteratorFactory();
try {
$iterator = $iteratorFactory($path);
$out = [];
$length = \mb_strlen($prefix);
foreach ($iterator as $fileinfo) {
$filename = $fileinfo->getFilename();
if (null === $prefix ||
(\mb_substr($filename, 0, $length) === $prefix)) {
if ($fileinfo->isDir()) {
$out[] = $filename.'/';
} else {
$out[] = $filename;
}
}
}
} catch (\Exception $e) {
return null;
}
$count = \count($out);
if (1 === $count) {
return $out[0];
}
if (0 === $count) {
return null;
}
return $out;
}
/**
* Get definition of a word.
*/
public function getWordDefinition(): string
{
return '/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*';
}
/**
* Set root.
*/
public function setRoot(string $root)
{
$old = $this->_root;
$this->_root = $root;
return $old;
}
/**
* Get root.
*/
public function getRoot()
{
return $this->_root;
}
/**
* Set iterator factory (a finder).
*/
public function setIteratorFactory(\Closure $iteratorFactory)
{
$old = $this->_iteratorFactory;
$this->_iteratorFactory = $iteratorFactory;
return $old;
}
/**
* Get iterator factory.
*/
public function getIteratorFactory()
{
return $this->_iteratorFactory;
}
/**
* Get default iterator factory (based on \DirectoryIterator).
*/
public static function getDefaultIteratorFactory()
{
return function ($path) {
return new \DirectoryIterator($path);
};
}
}
@@ -0,0 +1,119 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Readline\Autocompleter\Word.
*
* The simplest auto-completer: complete a word.
*/
class AutocompleterWord implements Autocompleter
{
/**
* List of words.
*/
protected $_words = null;
/**
* Constructor.
*/
public function __construct(array $words)
{
$this->setWords($words);
}
/**
* Complete a word.
* Returns null for no word, a full-word or an array of full-words.
*
* @param string &$prefix Prefix to autocomplete
*
* @return mixed
*/
public function complete(&$prefix)
{
$out = [];
$length = \mb_strlen($prefix);
foreach ($this->getWords() as $word) {
if (\mb_substr($word, 0, $length) === $prefix) {
$out[] = $word;
}
}
if (empty($out)) {
return null;
}
if (1 === \count($out)) {
return $out[0];
}
return $out;
}
/**
* Get definition of a word.
*/
public function getWordDefinition(): string
{
return '\b\w+';
}
/**
* Set list of words.
*
* @param array $words words
*
* @return array
*/
public function setWords(array $words)
{
$old = $this->_words;
$this->_words = $words;
return $old;
}
/**
* Get list of words.
*/
public function getWords(): array
{
return $this->_words;
}
}
@@ -0,0 +1,347 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console.
*
* A set of utils and helpers about the console.
*/
class Console
{
/**
* Pipe mode: FIFO.
*/
const IS_FIFO = 0;
/**
* Pipe mode: character.
*/
const IS_CHARACTER = 1;
/**
* Pipe mode: directory.
*/
const IS_DIRECTORY = 2;
/**
* Pipe mode: block.
*/
const IS_BLOCK = 3;
/**
* Pipe mode: regular.
*/
const IS_REGULAR = 4;
/**
* Pipe mode: link.
*/
const IS_LINK = 5;
/**
* Pipe mode: socket.
*/
const IS_SOCKET = 6;
/**
* Pipe mode: whiteout.
*/
const IS_WHITEOUT = 7;
/**
* Advanced interaction is on.
*/
private static $_advanced = null;
/**
* Previous STTY configuration.
*/
private static $_old = null;
/**
* Mode.
*/
protected static $_mode = [];
/**
* Input.
*/
protected static $_input = null;
/**
* Output.
*/
protected static $_output = null;
/**
* Tput.
*/
protected static $_tput = null;
/**
* Prepare the environment for advanced interactions.
*/
public static function advancedInteraction(bool $force = false): bool
{
if (null !== self::$_advanced) {
return self::$_advanced;
}
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return self::$_advanced = false;
}
if (false === $force &&
true === \defined('STDIN') &&
false === self::isDirect(\STDIN)) {
return self::$_advanced = false;
}
self::$_old = ConsoleProcessus::execute('stty -g < /dev/tty', false);
ConsoleProcessus::execute('stty -echo -icanon min 1 time 0 < /dev/tty', false);
return self::$_advanced = true;
}
/**
* Restore previous interaction options.
*/
public static function restoreInteraction()
{
if (null === self::$_old) {
return;
}
ConsoleProcessus::execute('stty '.self::$_old.' < /dev/tty', false);
return;
}
/**
* Get mode of a certain pipe.
* Inspired by sys/stat.h.
*/
public static function getMode($pipe = \STDIN): int
{
$_pipe = (int) $pipe;
if (isset(self::$_mode[$_pipe])) {
return self::$_mode[$_pipe];
}
$stat = \fstat($pipe);
switch ($stat['mode'] & 0170000) {
// named pipe (fifo).
case 0010000:
$mode = self::IS_FIFO;
break;
// character special.
case 0020000:
$mode = self::IS_CHARACTER;
break;
// directory.
case 0040000:
$mode = self::IS_DIRECTORY;
break;
// block special.
case 0060000:
$mode = self::IS_BLOCK;
break;
// regular.
case 0100000:
$mode = self::IS_REGULAR;
break;
// symbolic link.
case 0120000:
$mode = self::IS_LINK;
break;
// socket.
case 0140000:
$mode = self::IS_SOCKET;
break;
// whiteout.
case 0160000:
$mode = self::IS_WHITEOUT;
break;
default:
$mode = -1;
}
return self::$_mode[$_pipe] = $mode;
}
/**
* Check whether a certain pipe is a character device (keyboard, screen
* etc.).
* For example:
* $ php Mode.php
* In this case, self::isDirect(STDOUT) will return true.
*/
public static function isDirect($pipe): bool
{
return self::IS_CHARACTER === self::getMode($pipe);
}
/**
* Check whether a certain pipe is a pipe.
* For example:
* $ php Mode.php | foobar
* In this case, self::isPipe(STDOUT) will return true.
*/
public static function isPipe($pipe): bool
{
return self::IS_FIFO === self::getMode($pipe);
}
/**
* Check whether a certain pipe is a redirection.
* For example:
* $ php Mode.php < foobar
* In this case, self::isRedirection(STDIN) will return true.
*/
public static function isRedirection($pipe): bool
{
$mode = self::getMode($pipe);
return
self::IS_REGULAR === $mode ||
self::IS_DIRECTORY === $mode ||
self::IS_LINK === $mode ||
self::IS_SOCKET === $mode ||
self::IS_BLOCK === $mode;
}
/**
* Set input layer.
*/
public static function setInput(ConsoleInput $input)
{
$old = static::$_input;
static::$_input = $input;
return $old;
}
/**
* Get input layer.
*/
public static function getInput(): ConsoleInput
{
if (null === static::$_input) {
static::$_input = new ConsoleInput();
}
return static::$_input;
}
/**
* Set output layer.
*/
public static function setOutput(ConsoleOutput $output)
{
$old = static::$_output;
static::$_output = $output;
return $old;
}
/**
* Get output layer.
*/
public static function getOutput(): ConsoleOutput
{
if (null === static::$_output) {
static::$_output = new ConsoleOutput();
}
return static::$_output;
}
/**
* Set tput.
*/
public static function setTput(ConsoleTput $tput)
{
$old = static::$_tput;
static::$_tput = $tput;
return $old;
}
/**
* Get the current tput instance of the current process.
*/
public static function getTput(): ConsoleTput
{
if (null === static::$_tput) {
static::$_tput = new ConsoleTput();
}
return static::$_tput;
}
/**
* Check whether we are running behind TMUX(1).
*/
public static function isTmuxRunning(): bool
{
return isset($_SERVER['TMUX']);
}
}
/*
* Restore interaction.
*/
\register_shutdown_function([Console::class, 'restoreInteraction']);
@@ -0,0 +1,695 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Cursor.
*
* Allow to manipulate the cursor.
*/
class ConsoleCursor
{
/**
* Move the cursor.
* Steps can be:
* • u, up, ↑ : move to the previous line;
* • U, UP : move to the first line;
* • r, right, → : move to the next column;
* • R, RIGHT : move to the last column;
* • d, down, ↓ : move to the next line;
* • D, DOWN : move to the last line;
* • l, left, ← : move to the previous column;
* • L, LEFT : move to the first column.
* Steps can be concatened by a single space if $repeat is equal to 1.
*/
public static function move(string $steps, int $repeat = 1)
{
if (1 > $repeat) {
return;
} elseif (1 === $repeat) {
$handle = \explode(' ', $steps);
} else {
$handle = \explode(' ', $steps, 1);
}
$tput = Console::getTput();
$output = Console::getOutput();
foreach ($handle as $step) {
switch ($step) {
case 'u':
case 'up':
case '↑':
$output->writeAll(
\str_replace(
'%p1%d',
$repeat,
$tput->get('parm_up_cursor')
)
);
break;
case 'U':
case 'UP':
static::moveTo(null, 1);
break;
case 'r':
case 'right':
case '→':
$output->writeAll(
\str_replace(
'%p1%d',
$repeat,
$tput->get('parm_right_cursor')
)
);
break;
case 'R':
case 'RIGHT':
static::moveTo(9999);
break;
case 'd':
case 'down':
case '↓':
$output->writeAll(
\str_replace(
'%p1%d',
$repeat,
$tput->get('parm_down_cursor')
)
);
break;
case 'D':
case 'DOWN':
static::moveTo(null, 9999);
break;
case 'l':
case 'left':
case '←':
$output->writeAll(
\str_replace(
'%p1%d',
$repeat,
$tput->get('parm_left_cursor')
)
);
break;
case 'L':
case 'LEFT':
static::moveTo(1);
break;
}
}
}
/**
* Move to the line X and the column Y.
* If null, use the current coordinate.
*/
public static function moveTo(int $x = null, int $y = null)
{
if (null === $x || null === $y) {
$position = static::getPosition();
if (null === $x) {
$x = $position['x'];
}
if (null === $y) {
$y = $position['y'];
}
}
Console::getOutput()->writeAll(
\str_replace(
['%i%p1%d', '%p2%d'],
[$y, $x],
Console::getTput()->get('cursor_address')
)
);
}
/**
* Get current position (x and y) of the cursor.
*/
public static function getPosition(): array
{
$tput = Console::getTput();
$user7 = $tput->get('user7');
if (null === $user7) {
return [
'x' => 0,
'y' => 0,
];
}
Console::getOutput()->writeAll($user7);
$input = Console::getInput();
// Read $tput->get('user6').
$input->read(2); // skip \033 and [.
$x = null;
$y = null;
$handle = &$y;
while (true) {
$char = $input->readCharacter();
switch ($char) {
case ';':
$handle = &$x;
break;
case 'R':
break 2;
default:
$handle .= $char;
}
}
return [
'x' => (int) $x,
'y' => (int) $y,
];
}
/**
* Save current position.
*/
public static function save()
{
Console::getOutput()->writeAll(
Console::getTput()->get('save_cursor')
);
}
/**
* Restore cursor to the last saved position.
*/
public static function restore()
{
Console::getOutput()->writeAll(
Console::getTput()->get('restore_cursor')
);
}
/**
* Clear the screen.
* Part can be:
* • a, all, ↕ : clear entire screen and static::move(1, 1);
* • u, up, ↑ : clear from cursor to beginning of the screen;
* • r, right, → : clear from cursor to the end of the line;
* • d, down, ↓ : clear from cursor to end of the screen;
* • l, left, ← : clear from cursor to beginning of the screen;
* • line, ↔ : clear all the line and static::move(1).
* Parts can be concatenated by a single space.
*/
public static function clear(string $parts = 'all')
{
$tput = Console::getTput();
$output = Console::getOutput();
foreach (\explode(' ', $parts) as $part) {
switch ($part) {
case 'a':
case 'all':
case '↕':
$output->writeAll($tput->get('clear_screen'));
static::moveTo(1, 1);
break;
case 'u':
case 'up':
case '↑':
$output->writeAll("\033[1J");
break;
case 'r':
case 'right':
case '→':
$output->writeAll($tput->get('clr_eol'));
break;
case 'd':
case 'down':
case '↓':
$output->writeAll($tput->get('clr_eos'));
break;
case 'l':
case 'left':
case '←':
$output->writeAll($tput->get('clr_bol'));
break;
case 'line':
case '↔':
$output->writeAll("\r".$tput->get('clr_eol'));
break;
}
}
}
/**
* Hide the cursor.
*/
public static function hide()
{
Console::getOutput()->writeAll(
Console::getTput()->get('cursor_invisible')
);
}
/**
* Show the cursor.
*/
public static function show()
{
Console::getOutput()->writeAll(
Console::getTput()->get('cursor_visible')
);
}
/**
* Colorize cursor.
* Attributes can be:
* • n, normal : normal;
* • b, bold : bold;
* • u, underlined : underlined;
* • bl, blink : blink;
* • i, inverse : inverse;
* • !b, !bold : normal weight;
* • !u, !underlined : not underlined;
* • !bl, !blink : steady;
* • !i, !inverse : positive;
* • fg(color), foreground(color) : set foreground to “color”;
* • bg(color), background(color) : set background to “color”.
* “color” can be:
* • default;
* • black;
* • red;
* • green;
* • yellow;
* • blue;
* • magenta;
* • cyan;
* • white;
* • 0-256 (classic palette);
* • #hexa.
* Attributes can be concatenated by a single space.
*/
public static function colorize(string $attributes)
{
static $_rgbTo256 = null;
if (null === $_rgbTo256) {
$_rgbTo256 = [
'000000', '800000', '008000', '808000', '000080', '800080',
'008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00',
'0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f',
'000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f',
'005f87', '005faf', '005fd7', '005fff', '008700', '00875f',
'008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f',
'00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f',
'00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f',
'00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f',
'5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f',
'5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f',
'5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f',
'5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f',
'5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f',
'5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f',
'870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f',
'875f87', '875faf', '875fd7', '875fff', '878700', '87875f',
'878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f',
'87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f',
'87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f',
'87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f',
'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f',
'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f',
'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f',
'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f',
'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f',
'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f',
'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f',
'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f',
'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f',
'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f',
'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f',
'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f',
'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f',
'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f',
'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f',
'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f',
'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f',
'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212',
'1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e',
'585858', '606060', '666666', '767676', '808080', '8a8a8a',
'949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6',
'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee',
];
}
$tput = Console::getTput();
if (1 >= $tput->count('max_colors')) {
return;
}
$handle = [];
foreach (\explode(' ', $attributes) as $attribute) {
switch ($attribute) {
case 'n':
case 'normal':
$handle[] = 0;
break;
case 'b':
case 'bold':
$handle[] = 1;
break;
case 'u':
case 'underlined':
$handle[] = 4;
break;
case 'bl':
case 'blink':
$handle[] = 5;
break;
case 'i':
case 'inverse':
$handle[] = 7;
break;
case '!b':
case '!bold':
$handle[] = 22;
break;
case '!u':
case '!underlined':
$handle[] = 24;
break;
case '!bl':
case '!blink':
$handle[] = 25;
break;
case '!i':
case '!inverse':
$handle[] = 27;
break;
default:
if (0 === \preg_match('#^([^\(]+)\(([^\)]+)\)$#', $attribute, $m)) {
break;
}
$shift = 0;
switch ($m[1]) {
case 'fg':
case 'foreground':
$shift = 0;
break;
case 'bg':
case 'background':
$shift = 10;
break;
default:
break 2;
}
$_handle = 0;
$_keyword = true;
switch ($m[2]) {
case 'black':
$_handle = 30;
break;
case 'red':
$_handle = 31;
break;
case 'green':
$_handle = 32;
break;
case 'yellow':
$_handle = 33;
break;
case 'blue':
$_handle = 34;
break;
case 'magenta':
$_handle = 35;
break;
case 'cyan':
$_handle = 36;
break;
case 'white':
$_handle = 37;
break;
case 'default':
$_handle = 39;
break;
default:
$_keyword = false;
if (256 <= $tput->count('max_colors') &&
'#' === $m[2][0]) {
$rgb = \hexdec(\substr($m[2], 1));
$r = ($rgb >> 16) & 255;
$g = ($rgb >> 8) & 255;
$b = $rgb & 255;
$distance = null;
foreach ($_rgbTo256 as $i => $_rgb) {
$_rgb = \hexdec($_rgb);
$_r = ($_rgb >> 16) & 255;
$_g = ($_rgb >> 8) & 255;
$_b = $_rgb & 255;
$d = \sqrt(
($_r - $r) ** 2
+ ($_g - $g) ** 2
+ ($_b - $b) ** 2
);
if (null === $distance ||
$d <= $distance) {
$distance = $d;
$_handle = $i;
}
}
} else {
$_handle = (int) ($m[2]);
}
}
if (true === $_keyword) {
$handle[] = $_handle + $shift;
} else {
$handle[] = (38 + $shift).';5;'.$_handle;
}
}
}
Console::getOutput()->writeAll("\033[".\implode(';', $handle).'m');
return;
}
/**
* Change color number to a specific RGB color.
*/
public static function changeColor(int $fromCode, int $toColor)
{
$tput = Console::getTput();
if (true !== $tput->has('can_change')) {
return;
}
$r = ($toColor >> 16) & 255;
$g = ($toColor >> 8) & 255;
$b = $toColor & 255;
Console::getOutput()->writeAll(
\str_replace(
[
'%p1%d',
'rgb:',
'%p2%{255}%*%{1000}%/%2.2X/',
'%p3%{255}%*%{1000}%/%2.2X/',
'%p4%{255}%*%{1000}%/%2.2X',
],
[
$fromCode,
'',
\sprintf('%02x', $r),
\sprintf('%02x', $g),
\sprintf('%02x', $b),
],
$tput->get('initialize_color')
)
);
return;
}
/**
* Set cursor style.
* Style can be:
* • b, block, ▋: block;
* • u, underline, _: underline;
* • v, vertical, |: vertical.
*/
public static function setStyle(string $style, bool $blink = true)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
switch ($style) {
case 'u':
case 'underline':
case '_':
$_style = 2;
break;
case 'v':
case 'vertical':
case '|':
$_style = 5;
break;
case 'b':
case 'block':
case '▋':
default:
$_style = 1;
break;
}
if (false === $blink) {
++$_style;
}
// Not sure what tput entry we can use here…
Console::getOutput()->writeAll("\033[".$_style.' q');
return;
}
/**
* Make a stupid “bip”.
*/
public static function bip()
{
Console::getOutput()->writeAll(
Console::getTput()->get('bell')
);
}
}
/*
* Advanced interaction.
*/
Console::advancedInteraction();
@@ -0,0 +1,46 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Exception.
*
* Extending the \Hoa\Exception\Exception class.
*/
class ConsoleException extends Exception
{
}
@@ -0,0 +1,168 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Console\Input.
*
* This interface represents the input of a program. Most of the time, this is
* going to be `php://stdin` but it can be `/dev/tty` if the former has been
* closed.
*/
class ConsoleInput implements StreamIn
{
/**
* Real input stream.
*/
protected $_input = null;
/**
* Wraps an `Hoa\Stream\IStream\In` stream.
*/
public function __construct(StreamIn $input = null)
{
if (null === $input) {
if (\defined('STDIN') &&
false !== @\stream_get_meta_data(\STDIN)) {
$input = new FileRead('php://stdin');
} else {
$input = new FileRead('/dev/tty');
}
}
$this->_input = $input;
return;
}
/**
* Get underlying stream.
*/
public function getStream(): StreamIn
{
return $this->_input;
}
/**
* Test for end-of-file.
*/
public function eof(): bool
{
return $this->_input->eof();
}
/**
* Read n characters.
*/
public function read(int $length)
{
return $this->_input->read($length);
}
/**
* Alias of $this->read().
*/
public function readString(int $length)
{
return $this->_input->readString($length);
}
/**
* Read a character.
*/
public function readCharacter()
{
return $this->_input->readCharacter();
}
/**
* Read a boolean.
*/
public function readBoolean()
{
return $this->_input->readBoolean();
}
/**
* Read an integer.
*/
public function readInteger(int $length = 1)
{
return $this->_input->readInteger($length);
}
/**
* Read a float.
*/
public function readFloat(int $length = 1)
{
return $this->_input->readFloat($length);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*/
public function readArray($argument = null)
{
return $this->_input->readArray($argument);
}
/**
* Read a line.
*/
public function readLine()
{
return $this->_input->readLine();
}
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = 0)
{
return $this->_input->readAll($offset);
}
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format): array
{
return $this->_input->scanf($format);
}
}
@@ -0,0 +1,208 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Output.
*
* This class represents the output of a program. Most of the time, this is
* going to be STDOUT.
*/
class ConsoleOutput implements StreamOut
{
/**
* Whether the multiplexer must be considered while writing on the output.
*/
protected $_considerMultiplexer = false;
/**
* Real output stream.
*/
protected $_output = null;
/**
* Wraps an `Hoa\Stream\IStream\Out` stream.
*/
public function __construct(StreamOut $output = null)
{
$this->_output = $output;
return;
}
/**
* Get the real output stream.
*/
public function getStream(): StreamOut
{
return $this->_output;
}
/**
* Write n characters.
*/
public function write(string $string, int $length)
{
if (0 > $length) {
throw new ConsoleException('Length must be greater than 0, given %d.', 0, $length);
}
$out = \substr($string, 0, $length);
if (true === $this->isMultiplexerConsidered()) {
if (true === Console::isTmuxRunning()) {
$out =
"\033Ptmux;".
\str_replace("\033", "\033\033", $out).
"\033\\";
}
$length = \strlen($out);
}
if (null === $this->_output) {
echo $out;
} else {
$this->_output->write($out, $length);
}
}
/**
* Write a string.
*/
public function writeString(string $string)
{
$string = (string) $string;
return $this->write($string, \strlen($string));
}
/**
* Write a character.
*/
public function writeCharacter(string $character)
{
return $this->write((string) $character[0], 1);
}
/**
* Write a boolean.
*/
public function writeBoolean(bool $boolean)
{
return $this->write(((bool) $boolean) ? '1' : '0', 1);
}
/**
* Write an integer.
*/
public function writeInteger(int $integer)
{
$integer = (string) (int) $integer;
return $this->write($integer, \strlen($integer));
}
/**
* Write a float.
*/
public function writeFloat(float $float)
{
$float = (string) (float) $float;
return $this->write($float, \strlen($float));
}
/**
* Write an array.
*/
public function writeArray(array $array)
{
$array = \var_export($array, true);
return $this->write($array, \strlen($array));
}
/**
* Write a line.
*/
public function writeLine(string $line)
{
if (false === $n = \strpos($line, "\n")) {
return $this->write($line."\n", \strlen($line) + 1);
}
++$n;
return $this->write(\substr($line, 0, $n), $n);
}
/**
* Write all, i.e. as much as possible.
*/
public function writeAll(string $string)
{
return $this->write($string ?? '', \strlen($string ?? ''));
}
/**
* Truncate a stream to a given length.
*/
public function truncate(int $size): bool
{
return false;
}
/**
* Consider the multiplexer (if running) while writing on the output.
*/
public function considerMultiplexer(bool $consider): bool
{
$old = $this->_considerMultiplexer;
$this->_considerMultiplexer = $consider;
return $old;
}
/**
* Check whether the multiplexer must be considered or not.
*/
public function isMultiplexerConsidered(): bool
{
return $this->_considerMultiplexer;
}
}
@@ -0,0 +1,892 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Manipulate a processus as a stream.
*/
class ConsoleProcessus extends Stream implements StreamIn, StreamOut, StreamPathable
{
/**
* Signal: terminal line hangup (terminate process).
*/
const SIGHUP = 1;
/**
* Signal: interrupt program (terminate process).
*/
const SIGINT = 2;
/**
* Signal: quit program (create core image).
*/
const SIGQUIT = 3;
/**
* Signal: illegal instruction (create core image).
*/
const SIGILL = 4;
/**
* Signal: trace trap (create core image).
*/
const SIGTRAP = 5;
/**
* Signal: abort program, formerly SIGIOT (create core image).
*/
const SIGABRT = 6;
/**
* Signal: emulate instruction executed (create core image).
*/
const SIGEMT = 7;
/**
* Signal: floating-point exception (create core image).
*/
const SIGFPE = 8;
/**
* Signal: kill program (terminate process).
*/
const SIGKILL = 9;
/**
* Signal: bus error.
*/
const SIGBUS = 10;
/**
* Signal: segmentation violation (create core image).
*/
const SIGSEGV = 11;
/**
* Signal: non-existent system call invoked (create core image).
*/
const SIGSYS = 12;
/**
* Signal: write on a pipe with no reader (terminate process).
*/
const SIGPIPE = 13;
/**
* Signal: real-time timer expired (terminate process).
*/
const SIGALRM = 14;
/**
* Signal: software termination signal (terminate process).
*/
const SIGTERM = 15;
/**
* Signal: urgent condition present on socket (discard signal).
*/
const SIGURG = 16;
/**
* Signal: stop, cannot be caught or ignored (stop proces).
*/
const SIGSTOP = 17;
/**
* Signal: stop signal generated from keyboard (stop process).
*/
const SIGTSTP = 18;
/**
* Signal: continue after stop (discard signal).
*/
const SIGCONT = 19;
/**
* Signal: child status has changed (discard signal).
*/
const SIGCHLD = 20;
/**
* Signal: background read attempted from control terminal (stop process).
*/
const SIGTTIN = 21;
/**
* Signal: background write attempted to control terminal (stop process).
*/
const SIGTTOU = 22;
/**
* Signal: I/O is possible on a descriptor, see fcntl(2) (discard signal).
*/
const SIGIO = 23;
/**
* Signal: cpu time limit exceeded, see setrlimit(2) (terminate process).
*/
const SIGXCPU = 24;
/**
* Signal: file size limit exceeded, see setrlimit(2) (terminate process).
*/
const SIGXFSZ = 25;
/**
* Signal: virtual time alarm, see setitimer(2) (terminate process).
*/
const SIGVTALRM = 26;
/**
* Signal: profiling timer alarm, see setitimer(2) (terminate process).
*/
const SIGPROF = 27;
/**
* Signal: Window size change (discard signal).
*/
const SIGWINCH = 28;
/**
* Signal: status request from keyboard (discard signal).
*/
const SIGINFO = 29;
/**
* Signal: User defined signal 1 (terminate process).
*/
const SIGUSR1 = 30;
/**
* Signal: User defined signal 2 (terminate process).
*/
const SIGUSR2 = 31;
/**
* Command name.
*/
protected $_command = null;
/**
* Command options (options => value, or input).
*/
protected $_options = [];
/**
* Current working directory.
*/
protected $_cwd = null;
/**
* Environment.
*/
protected $_environment = null;
/**
* Timeout.
*/
protected $_timeout = 30;
/**
* Descriptor.
*/
protected $_descriptors = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
/**
* Pipe descriptors of the processus.
*/
protected $_pipes = null;
/**
* Seekability of pipes.
*/
protected $_seekable = [];
/**
* Start a processus.
*/
public function __construct(
string $command,
array $options = null,
array $descriptors = null,
string $cwd = null,
array $environment = null,
int $timeout = 30
) {
$this->setCommand($command);
if (null !== $options) {
$this->setOptions($options);
}
if (null !== $descriptors) {
$this->_descriptors = [];
foreach ($descriptors as $descriptor => $nature) {
if (isset($this->_descriptors[$descriptor])) {
throw new ConsoleException('Pipe descriptor %d already exists, cannot '.'redefine it.', 0, $descriptor);
}
$this->_descriptors[$descriptor] = $nature;
}
}
$this->setCwd($cwd ?: \getcwd());
if (null !== $environment) {
$this->setEnvironment($environment);
}
$this->setTimeout($timeout);
parent::__construct($this->getCommandLine(), null, true);
$this->getListener()->addIds(['input', 'output', 'timeout', 'start', 'stop']);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
$out = @\proc_open(
$streamName,
$this->_descriptors,
$this->_pipes,
$this->getCwd(),
$this->getEnvironment()
);
if (false === $out) {
throw new ConsoleException('Something wrong happen when running %s.', 1, $streamName);
}
return $out;
}
/**
* Close the current stream.
*/
protected function _close(): bool
{
foreach ($this->_pipes as $pipe) {
@\fclose($pipe);
}
return (bool) @\proc_close($this->getStream());
}
/**
* Run the process and fire events (amongst start, stop, input, output and
* timeout).
* If an event returns false, it will close the current pipe.
* For a simple run without firing events, use the $this->open() method.
*/
public function run()
{
if (false === $this->isOpened()) {
$this->open();
} else {
$this->_close();
$this->_setStream($this->_open(
$this->getStreamName(),
$this->getStreamContext()
));
}
$this->getListener()->fire('start', new EventBucket());
$_read = [];
$_write = [];
$_except = [];
foreach ($this->_pipes as $p => $pipe) {
switch ($this->_descriptors[$p][1]) {
case 'r':
\stream_set_blocking($pipe, false);
$_write[] = $pipe;
break;
case 'w':
case 'a':
\stream_set_blocking($pipe, true);
$_read[] = $pipe;
break;
}
}
while (true) {
foreach ($_read as $i => $r) {
if (false === \is_resource($r)) {
unset($_read[$i]);
}
}
foreach ($_write as $i => $w) {
if (false === \is_resource($w)) {
unset($_write[$i]);
}
}
foreach ($_except as $i => $e) {
if (false === \is_resource($e)) {
unset($_except[$i]);
}
}
if (empty($_read) && empty($_write) && empty($_except)) {
break;
}
$read = $_read;
$write = $_write;
$except = $_except;
$select = \stream_select($read, $write, $except, $this->getTimeout());
if (0 === $select) {
$this->getListener()->fire('timeout', new EventBucket());
break;
}
foreach ($read as $i => $_r) {
$pipe = \array_search($_r, $this->_pipes);
$line = $this->readLine($pipe);
if (false === $line) {
$result = [false];
} else {
$result = $this->getListener()->fire(
'output',
new EventBucket([
'pipe' => $pipe,
'line' => $line,
])
);
}
if (true === \feof($_r) || \in_array(false, $result, true)) {
\fclose($_r);
unset($_read[$i]);
break;
}
}
foreach ($write as $j => $_w) {
$result = $this->getListener()->fire(
'input',
new EventBucket([
'pipe' => \array_search($_w, $this->_pipes),
])
);
if (true === \feof($_w) || \in_array(false, $result, true)) {
\fclose($_w);
unset($_write[$j]);
}
}
if (empty($_read)) {
break;
}
}
$this->getListener()->fire('stop', new EventBucket());
return;
}
/**
* Get pipe resource.
*/
protected function getPipe(int $pipe)
{
if (!isset($this->_pipes[$pipe])) {
throw new ConsoleException('Pipe descriptor %d does not exist, cannot read from it.', 2, $pipe);
}
return $this->_pipes[$pipe];
}
/**
* Check if a pipe is seekable or not.
*/
protected function isPipeSeekable(int $pipe): bool
{
if (!isset($this->_seekable[$pipe])) {
$_pipe = $this->getPipe($pipe);
$data = \stream_get_meta_data($_pipe);
$this->_seekable[$pipe] = $data['seekable'];
}
return $this->_seekable[$pipe];
}
/**
* Test for end-of-file.
*/
public function eof(int $pipe = 1): bool
{
return \feof($this->getPipe($pipe));
}
/**
* Read n characters.
*/
public function read(int $length, int $pipe = 1)
{
if (0 > $length) {
throw new ConsoleException('Length must be greater than 0, given %d.', 3, $length);
}
return \fread($this->getPipe($pipe), $length);
}
/**
* Alias of $this->read().
*/
public function readString(int $length, int $pipe = 1)
{
return $this->read($length, $pipe);
}
/**
* Read a character.
*/
public function readCharacter(int $pipe = 1)
{
return \fgetc($this->getPipe($pipe));
}
/**
* Read a boolean.
*/
public function readBoolean(int $pipe = 1)
{
return (bool) $this->read(1, $pipe);
}
/**
* Read an integer.
*/
public function readInteger(int $length = 1, int $pipe = 1)
{
return (int) $this->read($length, $pipe);
}
/**
* Read a float.
*/
public function readFloat(int $length = 1, int $pipe = 1)
{
return (float) $this->read($length, $pipe);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*/
public function readArray(string $format = null, int $pipe = 1)
{
return $this->scanf($format, $pipe);
}
/**
* Read a line.
*/
public function readLine(int $pipe = 1)
{
return \stream_get_line($this->getPipe($pipe), 1 << 15, "\n");
}
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = -1, int $pipe = 1)
{
$_pipe = $this->getPipe($pipe);
if (true === $this->isPipeSeekable($pipe)) {
$offset += \ftell($_pipe);
} else {
$offset = -1;
}
return \stream_get_contents($_pipe, -1, $offset);
}
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format, int $pipe = 1): array
{
return \fscanf($this->getPipe($pipe), $format);
}
/**
* Write n characters.
*/
public function write(string $string, int $length, int $pipe = 0)
{
if (0 > $length) {
throw new ConsoleException('Length must be greater than 0, given %d.', 4, $length);
}
return \fwrite($this->getPipe($pipe), $string, $length);
}
/**
* Write a string.
*/
public function writeString(string $string, int $pipe = 0)
{
$string = (string) $string;
return $this->write($string, \strlen($string), $pipe);
}
/**
* Write a character.
*/
public function writeCharacter(string $char, int $pipe = 0)
{
return $this->write((string) $char[0], 1, $pipe);
}
/**
* Write a boolean.
*/
public function writeBoolean(bool $boolean, int $pipe = 0)
{
return $this->write((string) (bool) $boolean, 1, $pipe);
}
/**
* Write an integer.
*/
public function writeInteger(int $integer, int $pipe = 0)
{
$integer = (string) (int) $integer;
return $this->write($integer, \strlen($integer), $pipe);
}
/**
* Write a float.
*/
public function writeFloat(float $float, int $pipe = 0)
{
$float = (string) (float) $float;
return $this->write($float, \strlen($float), $pipe);
}
/**
* Write an array.
*/
public function writeArray(array $array, int $pipe = 0)
{
$array = \var_export($array, true);
return $this->write($array, \strlen($array), $pipe);
}
/**
* Write a line.
*/
public function writeLine(string $line, int $pipe = 0)
{
if (false === $n = \strpos($line, "\n")) {
return $this->write($line."\n", \strlen($line) + 1, $pipe);
}
++$n;
return $this->write(\substr($line, 0, $n), $n, $pipe);
}
/**
* Write all, i.e. as much as possible.
*/
public function writeAll(string $string, int $pipe = 0)
{
return $this->write($string, \strlen($string), $pipe);
}
/**
* Truncate a file to a given length.
*/
public function truncate(int $size, int $pipe = 0): bool
{
return \ftruncate($this->getPipe($pipe), $size);
}
/**
* Get filename component of path.
*/
public function getBasename(): string
{
return \basename($this->getCommand());
}
/**
* Get directory name component of path.
*/
public function getDirname(): string
{
return \dirname($this->getCommand());
}
/**
* Get status.
*/
public function getStatus(): array
{
return \proc_get_status($this->getStream());
}
/**
* Get exit code (alias of $this->getStatus()['exitcode']);.
*/
public function getExitCode(): int
{
$handle = $this->getStatus();
return $handle['exitcode'];
}
/**
* Whether the processus have ended successfully.
*
* @return bool
*/
public function isSuccessful(): bool
{
return 0 === $this->getExitCode();
}
/**
* Terminate the process.
*
* Valid signals are self::SIGHUP, SIGINT, SIGQUIT, SIGABRT, SIGKILL,
* SIGALRM and SIGTERM.
*/
public function terminate(int $signal = self::SIGTERM): bool
{
return \proc_terminate($this->getStream(), $signal);
}
/**
* Set command name.
*/
protected function setCommand(string $command)
{
$old = $this->_command;
$this->_command = \escapeshellcmd($command);
return $old;
}
/**
* Get command name.
*/
public function getCommand()
{
return $this->_command;
}
/**
* Set command options.
*/
protected function setOptions(array $options): array
{
foreach ($options as &$option) {
$option = \escapeshellarg($option);
}
$old = $this->_options;
$this->_options = $options;
return $old;
}
/**
* Get options.
*/
public function getOptions(): array
{
return $this->_options;
}
/**
* Get command-line.
*/
public function getCommandLine(): string
{
$out = $this->getCommand();
foreach ($this->getOptions() as $key => $value) {
if (!\is_int($key)) {
$out .= ' '.$key.'='.$value;
} else {
$out .= ' '.$value;
}
}
return $out;
}
/**
* Set current working directory of the process.
*/
protected function setCwd(string $cwd)
{
$old = $this->_cwd;
$this->_cwd = $cwd;
return $old;
}
/**
* Get current working directory of the process.
*/
public function getCwd(): string
{
return $this->_cwd;
}
/**
* Set environment of the process.
*/
protected function setEnvironment(array $environment)
{
$old = $this->_environment;
$this->_environment = $environment;
return $old;
}
/**
* Get environment of the process.
*/
public function getEnvironment()
{
return $this->_environment;
}
/**
* Set timeout of the process.
*/
public function setTimeout(int $timeout)
{
$old = $this->_timeout;
$this->_timeout = $timeout;
return $old;
}
/**
* Get timeout of the process.
*/
public function getTimeout(): int
{
return $this->_timeout;
}
/**
* Set process title.
*/
public static function setTitle(string $title)
{
\cli_set_process_title($title);
}
/**
* Get process title.
*/
public static function getTitle()
{
return \cli_get_process_title();
}
/**
* Found the place of a binary.
*/
public static function locate(string $binary)
{
if (isset($_ENV['PATH'])) {
$separator = ':';
$path = &$_ENV['PATH'];
} elseif (isset($_SERVER['PATH'])) {
$separator = ':';
$path = &$_SERVER['PATH'];
} elseif (isset($_SERVER['Path'])) {
$separator = ';';
$path = &$_SERVER['Path'];
} else {
return null;
}
foreach (\explode($separator, $path) as $directory) {
if (true === \file_exists($out = $directory.\DIRECTORY_SEPARATOR.$binary)) {
return $out;
}
}
return null;
}
/**
* Quick process execution.
* Returns only the STDOUT.
*/
public static function execute(string $commandLine, bool $escape = true): string
{
if (true === $escape) {
$commandLine = \escapeshellcmd($commandLine);
}
return \rtrim(\shell_exec($commandLine) ?? '');
}
}
@@ -0,0 +1,841 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Console\Tput.
*
* Query terminfo database.
* Resources:
* • http://man.cx/terminfo(5),
* • http://pubs.opengroup.org/onlinepubs/7908799/xcurses/terminfo.html,
*/
class ConsoleTput
{
/**
* Booleans.
*/
protected static $_booleans = [
'auto_left_margin',
'auto_right_margin',
'no_esc_ctlc',
'ceol_standout_glitch',
'eat_newline_glitch',
'erase_overstrike',
'generic_type',
'hard_copy',
'meta_key', // originally has_meta_key
'status_line', // originally has_status_line
'insert_null_glitch',
'memory_above',
'memory_below',
'move_insert_mode',
'move_standout_mode',
'over_strike',
'status_line_esc_ok',
'dest_tabs_magic_smso',
'tilde_glitch',
'transparent_underline',
'xon_xoff',
'needs_xon_xoff',
'prtr_silent',
'hard_cursor',
'non_rev_rmcup',
'no_pad_char',
'non_dest_scroll_region',
'can_change',
'back_color_erase',
'hue_lightness_saturation',
'col_addr_glitch',
'cr_cancels_micro_mode',
'print_wheel', // originally has_print_wheel
'row_addr_glitch',
'semi_auto_right_margin',
'cpi_changes_res',
'lpi_changes_res',
// #ifdef __INTERNAL_CAPS_VISIBLE
'backspaces_with_bs',
'crt_no_scrolling',
'no_correctly_working_cr',
'gnu_meta_key', // originally gnu_has_meta_key
'linefeed_is_newline',
'hardware_tabs', // originally has_hardware_tabs
'return_does_clr_eol',
];
/**
* Numbers.
*/
protected static $_numbers = [
'columns',
'init_tabs',
'lines',
'lines_of_memory',
'magic_cookie_glitch',
'padding_baud_rate',
'virtual_terminal',
'width_status_line',
'num_labels',
'label_height',
'label_width',
'max_attributes',
'maximum_windows',
'max_colors',
'max_pairs',
'no_color_video',
'buffer_capacity',
'dot_vert_spacing',
'dot_horz_spacing',
'max_micro_address',
'max_micro_jump',
'micro_col_size',
'micro_line_size',
'number_of_pins',
'output_res_char',
'output_res_line',
'output_res_horz_inch',
'output_res_vert_inch',
'print_rate',
'wide_char_size',
'buttons',
'bit_image_entwining',
'bit_image_type',
// #ifdef __INTERNAL_CAPS_VISIBLE
'magic_cookie_glitch_ul',
'carriage_return_delay',
'new_line_delay',
'backspace_delay',
'horizontal_tab_delay',
'number_of_function_keys',
];
/**
* Strings.
*/
protected static $_strings = [
'back_tab',
'bell',
'carriage_return',
'change_scroll_region',
'clear_all_tabs',
'clear_screen',
'clr_eol',
'clr_eos',
'column_address',
'command_character',
'cursor_address',
'cursor_down',
'cursor_home',
'cursor_invisible',
'cursor_left',
'cursor_mem_address',
'cursor_normal',
'cursor_right',
'cursor_to_ll',
'cursor_up',
'cursor_visible',
'delete_character',
'delete_line',
'dis_status_line',
'down_half_line',
'enter_alt_charset_mode',
'enter_blink_mode',
'enter_bold_mode',
'enter_ca_mode',
'enter_delete_mode',
'enter_dim_mode',
'enter_insert_mode',
'enter_secure_mode',
'enter_protected_mode',
'enter_reverse_mode',
'enter_standout_mode',
'enter_underline_mode',
'erase_chars',
'exit_alt_charset_mode',
'exit_attribute_mode',
'exit_ca_mode',
'exit_delete_mode',
'exit_insert_mode',
'exit_standout_mode',
'exit_underline_mode',
'flash_screen',
'form_feed',
'from_status_line',
'init_1string',
'init_2string',
'init_3string',
'init_file',
'insert_character',
'insert_line',
'insert_padding',
'key_backspace',
'key_catab',
'key_clear',
'key_ctab',
'key_dc',
'key_dl',
'key_down',
'key_eic',
'key_eol',
'key_eos',
'key_f0',
'key_f1',
'key_f10',
'key_f2',
'key_f3',
'key_f4',
'key_f5',
'key_f6',
'key_f7',
'key_f8',
'key_f9',
'key_home',
'key_ic',
'key_il',
'key_left',
'key_ll',
'key_npage',
'key_ppage',
'key_right',
'key_sf',
'key_sr',
'key_stab',
'key_up',
'keypad_local',
'keypad_xmit',
'lab_f0',
'lab_f1',
'lab_f10',
'lab_f2',
'lab_f3',
'lab_f4',
'lab_f5',
'lab_f6',
'lab_f7',
'lab_f8',
'lab_f9',
'meta_off',
'meta_on',
'newline',
'pad_char',
'parm_dch',
'parm_delete_line',
'parm_down_cursor',
'parm_ich',
'parm_index',
'parm_insert_line',
'parm_left_cursor',
'parm_right_cursor',
'parm_rindex',
'parm_up_cursor',
'pkey_key',
'pkey_local',
'pkey_xmit',
'print_screen',
'prtr_off',
'prtr_on',
'repeat_char',
'reset_1string',
'reset_2string',
'reset_3string',
'reset_file',
'restore_cursor',
'row_address',
'save_cursor',
'scroll_forward',
'scroll_reverse',
'set_attributes',
'set_tab',
'set_window',
'tab',
'to_status_line',
'underline_char',
'up_half_line',
'init_prog',
'key_a1',
'key_a3',
'key_b2',
'key_c1',
'key_c3',
'prtr_non',
'char_padding',
'acs_chars',
'plab_norm',
'key_btab',
'enter_xon_mode',
'exit_xon_mode',
'enter_am_mode',
'exit_am_mode',
'xon_character',
'xoff_character',
'ena_acs',
'label_on',
'label_off',
'key_beg',
'key_cancel',
'key_close',
'key_command',
'key_copy',
'key_create',
'key_end',
'key_enter',
'key_exit',
'key_find',
'key_help',
'key_mark',
'key_message',
'key_move',
'key_next',
'key_open',
'key_options',
'key_previous',
'key_print',
'key_redo',
'key_reference',
'key_refresh',
'key_replace',
'key_restart',
'key_resume',
'key_save',
'key_suspend',
'key_undo',
'key_sbeg',
'key_scancel',
'key_scommand',
'key_scopy',
'key_screate',
'key_sdc',
'key_sdl',
'key_select',
'key_send',
'key_seol',
'key_sexit',
'key_sfind',
'key_shelp',
'key_shome',
'key_sic',
'key_sleft',
'key_smessage',
'key_smove',
'key_snext',
'key_soptions',
'key_sprevious',
'key_sprint',
'key_sredo',
'key_sreplace',
'key_sright',
'key_srsume',
'key_ssave',
'key_ssuspend',
'key_sundo',
'req_for_input',
'key_f11',
'key_f12',
'key_f13',
'key_f14',
'key_f15',
'key_f16',
'key_f17',
'key_f18',
'key_f19',
'key_f20',
'key_f21',
'key_f22',
'key_f23',
'key_f24',
'key_f25',
'key_f26',
'key_f27',
'key_f28',
'key_f29',
'key_f30',
'key_f31',
'key_f32',
'key_f33',
'key_f34',
'key_f35',
'key_f36',
'key_f37',
'key_f38',
'key_f39',
'key_f40',
'key_f41',
'key_f42',
'key_f43',
'key_f44',
'key_f45',
'key_f46',
'key_f47',
'key_f48',
'key_f49',
'key_f50',
'key_f51',
'key_f52',
'key_f53',
'key_f54',
'key_f55',
'key_f56',
'key_f57',
'key_f58',
'key_f59',
'key_f60',
'key_f61',
'key_f62',
'key_f63',
'clr_bol',
'clear_margins',
'set_left_margin',
'set_right_margin',
'label_format',
'set_clock',
'display_clock',
'remove_clock',
'create_window',
'goto_window',
'hangup',
'dial_phone',
'quick_dial',
'tone',
'pulse',
'flash_hook',
'fixed_pause',
'wait_tone',
'user0',
'user1',
'user2',
'user3',
'user4',
'user5',
'user6',
'user7',
'user8',
'user9',
'orig_pair',
'orig_colors',
'initialize_color',
'initialize_pair',
'set_color_pair',
'set_foreground',
'set_background',
'change_char_pitch',
'change_line_pitch',
'change_res_horz',
'change_res_vert',
'define_char',
'enter_doublewide_mode',
'enter_draft_quality',
'enter_italics_mode',
'enter_leftward_mode',
'enter_micro_mode',
'enter_near_letter_quality',
'enter_normal_quality',
'enter_shadow_mode',
'enter_subscript_mode',
'enter_superscript_mode',
'enter_upward_mode',
'exit_doublewide_mode',
'exit_italics_mode',
'exit_leftward_mode',
'exit_micro_mode',
'exit_shadow_mode',
'exit_subscript_mode',
'exit_superscript_mode',
'exit_upward_mode',
'micro_column_address',
'micro_down',
'micro_left',
'micro_right',
'micro_row_address',
'micro_up',
'order_of_pins',
'parm_down_micro',
'parm_left_micro',
'parm_right_micro',
'parm_up_micro',
'select_char_set',
'set_bottom_margin',
'set_bottom_margin_parm',
'set_left_margin_parm',
'set_right_margin_parm',
'set_top_margin',
'set_top_margin_parm',
'start_bit_image',
'start_char_set_def',
'stop_bit_image',
'stop_char_set_def',
'subscript_characters',
'superscript_characters',
'these_cause_cr',
'zero_motion',
'char_set_names',
'key_mouse',
'mouse_info',
'req_mouse_pos',
'get_mouse',
'set_a_foreground',
'set_a_background',
'pkey_plab',
'device_type',
'code_set_init',
'set0_des_seq',
'set1_des_seq',
'set2_des_seq',
'set3_des_seq',
'set_lr_margin',
'set_tb_margin',
'bit_image_repeat',
'bit_image_newline',
'bit_image_carriage_return',
'color_names',
'define_bit_image_region',
'end_bit_image_region',
'set_color_band',
'set_page_length',
'display_pc_char',
'enter_pc_charset_mode',
'exit_pc_charset_mode',
'enter_scancode_mode',
'exit_scancode_mode',
'pc_term_options',
'scancode_escape',
'alt_scancode_esc',
'enter_horizontal_hl_mode',
'enter_left_hl_mode',
'enter_low_hl_mode',
'enter_right_hl_mode',
'enter_top_hl_mode',
'enter_vertical_hl_mode',
'set_a_attributes',
'set_pglen_inch',
// #ifdef __INTERNAL_CAPS_VISIBLE
'termcap_init2',
'termcap_reset',
'linefeed_if_not_lf',
'backspace_if_not_bs',
'other_non_function_keys',
'arrow_key_map',
'acs_ulcorner',
'acs_llcorner',
'acs_urcorner',
'acs_lrcorner',
'acs_ltee',
'acs_rtee',
'acs_btee',
'acs_ttee',
'acs_hline',
'acs_vline',
'acs_plus',
'memory_lock',
'memory_unlock',
'box_chars_1',
];
/**
* Computed informations.
*/
protected $_informations = [];
/**
* Set stream and parse.
*/
public function __construct($terminfo = null)
{
if (null === $terminfo) {
$terminfo = static::getTerminfo();
}
$this->parse($terminfo);
return;
}
/**
* Parse.
*/
protected function parse(string $terminfo): array
{
if (!\file_exists($terminfo)) {
throw new ConsoleException('Terminfo file %s does not exist.', 0, $terminfo);
}
$data = \file_get_contents($terminfo);
$length = \strlen($data);
$out = ['file' => $terminfo];
$headers = [
'data_size' => $length,
'header_size' => 12,
'magic_number' => (\ord($data[1]) << 8) | \ord($data[0]),
'names_size' => (\ord($data[3]) << 8) | \ord($data[2]),
'bool_count' => (\ord($data[5]) << 8) | \ord($data[4]),
'number_count' => (\ord($data[7]) << 8) | \ord($data[6]),
'string_count' => (\ord($data[9]) << 8) | \ord($data[8]),
'string_table_size' => (\ord($data[11]) << 8) | \ord($data[10]),
];
$out['headers'] = $headers;
// Names.
$i = $headers['header_size'];
$nameAndDescription = \explode('|', \substr($data, $i, $headers['names_size'] - 1));
$out['name'] = $nameAndDescription[0];
$out['description'] = $nameAndDescription[1];
// Booleans.
$i += $headers['names_size'];
$booleans = [];
$booleanNames = &static::$_booleans;
for (
$e = 0, $max = $i + $headers['bool_count'];
$i < $max;
++$e, ++$i
) {
$booleans[$booleanNames[$e]] = 1 === \ord($data[$i]);
}
$out['booleans'] = $booleans;
// Numbers.
if (1 === ($i % 2)) {
++$i;
}
$numbers = [];
$numberNames = &static::$_numbers;
for (
$e = 0, $max = $i + $headers['number_count'] * 2;
$i < $max;
++$e, $i += 2
) {
$name = $numberNames[$e];
$data_i0 = \ord($data[$i]);
$data_i1 = \ord($data[$i + 1]);
if ($data_i1 === 255 && $data_i0 === 255) {
$numbers[$name] = -1;
} else {
$numbers[$name] = ($data_i1 << 8) | $data_i0;
}
}
$out['numbers'] = $numbers;
// Strings.
$strings = [];
$stringNames = &static::$_strings;
$ii = $i + $headers['string_count'] * 2;
for (
$e = 0, $max = $ii;
$i < $max;
++$e, $i += 2
) {
$name = $stringNames[$e];
$data_i0 = \ord($data[$i]);
$data_i1 = \ord($data[$i + 1]);
if ($data_i1 === 255 && $data_i0 === 255) {
continue;
}
$a = ($data_i1 << 8) | $data_i0;
$strings[$name] = $a;
if (65534 === $a) {
continue;
}
$b = $ii + $a;
$c = $b;
while ($c < $length && \ord($data[$c])) {
$c++;
}
$value = \substr($data, $b, $c - $b);
$strings[$name] = false !== $value ? $value : null;
}
$out['strings'] = $strings;
return $this->_informations = $out;
}
/**
* Get all informations.
*/
public function getInformations(): array
{
return $this->_informations;
}
/**
* Get a boolean value.
*/
public function has(string $boolean): bool
{
if (!isset($this->_informations['booleans'][$boolean])) {
return false;
}
return $this->_informations['booleans'][$boolean];
}
/**
* Get a number value.
*/
public function count(string $number): int
{
if (!isset($this->_informations['numbers'][$number])) {
return 0;
}
return $this->_informations['numbers'][$number];
}
/**
* Get a string value.
*/
public function get(string $string)
{
if (!isset($this->_informations['strings'][$string])) {
return null;
}
return $this->_informations['strings'][$string];
}
/**
* Get current term profile.
*/
public static function getTerm(): string
{
return
isset($_SERVER['TERM']) && !empty($_SERVER['TERM'])
? $_SERVER['TERM']
: (\defined('PHP_WINDOWS_VERSION_PLATFORM') ? 'windows-ansi' : 'xterm');
}
/**
* Get pathname to the current terminfo.
*/
public static function getTerminfo($term = null): string
{
$paths = [];
if (isset($_SERVER['TERMINFO'])) {
$paths[] = $_SERVER['TERMINFO'];
}
if (isset($_SERVER['HOME'])) {
$paths[] = $_SERVER['HOME'].\DIRECTORY_SEPARATOR.'.terminfo';
}
if (isset($_SERVER['TERMINFO_DIRS'])) {
foreach (\explode(':', $_SERVER['TERMINFO_DIRS']) as $path) {
$paths[] = $path;
}
}
$paths[] = '/usr/share/terminfo';
$paths[] = '/usr/share/lib/terminfo';
$paths[] = '/lib/terminfo';
$paths[] = '/usr/lib/terminfo';
$paths[] = '/usr/local/share/terminfo';
$paths[] = '/usr/local/share/lib/terminfo';
$paths[] = '/usr/local/lib/terminfo';
$paths[] = '/usr/local/ncurses/lib/terminfo';
$paths[] = 'hoa://Library/Terminfo';
$term = $term ?: static::getTerm();
$fileHexa = \dechex(\ord($term[0])).\DIRECTORY_SEPARATOR.$term;
$fileAlpha = $term[0].\DIRECTORY_SEPARATOR.$term;
$pathname = null;
foreach ($paths as $path) {
if (\file_exists($_ = $path.\DIRECTORY_SEPARATOR.$fileHexa) ||
\file_exists($_ = $path.\DIRECTORY_SEPARATOR.$fileAlpha)) {
$pathname = $_;
break;
}
}
if (null === $pathname && 'xterm' !== $term) {
return static::getTerminfo('xterm');
}
return $pathname ?? '';
}
/**
* Check whether all required terminfo capabilities are defined.
*/
public static function isSupported(): bool
{
if (static::getTerminfo() === '') {
return false;
}
$requiredVars = [
'clear_screen',
'clr_bol',
'clr_eol',
'clr_eos',
'initialize_color',
'parm_down_cursor',
'parm_index',
'parm_left_cursor',
'parm_right_cursor',
'parm_rindex',
'parm_up_cursor',
'user6',
'user7',
];
$tput = new self();
foreach ($requiredVars as $var) {
if ($tput->get($var) === null) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,529 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Allow to manipulate the window.
*
* We can listen the event channel hoa://Event/Console/Window:resize to detect
* if the window has been resized. Please, see the constructor documentation to
* get more informations.
*/
class ConsoleWindow implements EventSource
{
/**
* Singleton (only for events).
*/
private static $_instance = null;
/**
* Set the event channel.
* We need to declare(ticks = 1) in the main script to ensure that the event
* is fired. Also, we need the pcntl_signal() function enabled.
*/
private function __construct()
{
Event::register(
'hoa://Event/Console/Window:resize',
$this
);
return;
}
/**
* Singleton.
*/
public static function getInstance(): self
{
if (null === static::$_instance) {
static::$_instance = new self();
}
return static::$_instance;
}
/**
* Set size to X lines and Y columns.
*/
public static function setSize(int $x, int $y)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
Console::getOutput()->writeAll("\033[8;".$y.';'.$x.'t');
return;
}
/**
* Get current size (x and y) of the window.
*/
public static function getSize(): array
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
$modecon = \explode("\n", \ltrim(ConsoleProcessus::execute('mode con')));
$_y = \trim($modecon[2]);
\preg_match('#[^:]+:\s*([0-9]+)#', $_y, $matches);
$y = (int) $matches[1];
$_x = \trim($modecon[3]);
\preg_match('#[^:]+:\s*([0-9]+)#', $_x, $matches);
$x = (int) $matches[1];
return [
'x' => $x,
'y' => $y,
];
}
$term = '';
if (isset($_SERVER['TERM'])) {
$term = 'TERM="'.$_SERVER['TERM'].'" ';
}
$command = $term.'tput cols && '.$term.'tput lines';
$tput = ConsoleProcessus::execute($command, false);
if (!empty($tput)) {
list($x, $y) = \explode("\n", $tput);
return [
'x' => (int) $x,
'y' => (int) $y,
];
}
// DECSLPP.
Console::getOutput()->writeAll("\033[18t");
$input = Console::getInput();
// Read \033[8;y;xt.
$input->read(4); // skip \033, [, 8 and ;.
$x = null;
$y = null;
$handle = &$y;
while (true) {
$char = $input->readCharacter();
switch ($char) {
case ';':
$handle = &$x;
break;
case 't':
break 2;
default:
if (false === \ctype_digit($char)) {
break 2;
}
$handle .= $char;
}
}
if (null === $x || null === $y) {
return [
'x' => 0,
'y' => 0,
];
}
return [
'x' => (int) $x,
'y' => (int) $y,
];
}
/**
* Move to X and Y (in pixels).
*/
public static function moveTo(int $x, int $y)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
// DECSLPP.
Console::getOutput()->writeAll("\033[3;".$x.';'.$y.'t');
return;
}
/**
* Get current position (x and y) of the window (in pixels).
*/
public static function getPosition(): array
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return ['x' => 0, 'y' => 0];
}
// DECSLPP.
Console::getOutput()->writeAll("\033[13t");
$input = Console::getInput();
// Read \033[3;x;yt.
$input->read(4); // skip \033, [, 3 and ;.
$x = null;
$y = null;
$handle = &$x;
while (true) {
$char = $input->readCharacter();
switch ($char) {
case ';':
$handle = &$y;
break;
case 't':
break 2;
default:
$handle .= $char;
}
}
return [
'x' => (int) $x,
'y' => (int) $y,
];
}
/**
* Scroll whole page.
* Directions can be:
* • u, up, ↑ : scroll whole page up;
* • d, down, ↓ : scroll whole page down.
* Directions can be concatenated by a single space.
*/
public static function scroll(string $directions, int $repeat = 1)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
if (1 > $repeat) {
return;
} elseif (1 === $repeat) {
$handle = \explode(' ', $directions);
} else {
$handle = \explode(' ', $directions, 1);
}
$tput = Console::getTput();
$count = ['up' => 0, 'down' => 0];
foreach ($handle as $direction) {
switch ($direction) {
case 'u':
case 'up':
case '↑':
++$count['up'];
break;
case 'd':
case 'down':
case '↓':
++$count['down'];
break;
}
}
$output = Console::getOutput();
if (0 < $count['up']) {
$output->writeAll(
\str_replace(
'%p1%d',
$count['up'] * $repeat,
$tput->get('parm_index')
)
);
}
if (0 < $count['down']) {
$output->writeAll(
\str_replace(
'%p1%d',
$count['down'] * $repeat,
$tput->get('parm_rindex')
)
);
}
return;
}
/**
* Minimize the window.
*/
public static function minimize()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
// DECSLPP.
Console::getOutput()->writeAll("\033[2t");
return;
}
/**
* Restore the window (de-minimize).
*/
public static function restore()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
Console::getOutput()->writeAll("\033[1t");
return;
}
/**
* Raise the window to the front of the stacking order.
*/
public static function raise()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
Console::getOutput()->writeAll("\033[5t");
return;
}
/**
* Lower the window to the bottom of the stacking order.
*/
public static function lower()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
Console::getOutput()->writeAll("\033[6t");
return;
}
/**
* Set title.
*/
public static function setTitle(string $title)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
// DECSLPP.
Console::getOutput()->writeAll("\033]0;".$title."\033\\");
return;
}
/**
* Get title.
*/
public static function getTitle()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return null;
}
// DECSLPP.
Console::getOutput()->writeAll("\033[21t");
$input = Console::getInput();
$read = [$input->getStream()->getStream()];
$write = [];
$except = [];
$out = null;
if (0 === \stream_select($read, $write, $except, 0, 50000)) {
return $out;
}
// Read \033]l<title>\033\
$input->read(3); // skip \033, ] and l.
while (true) {
$char = $input->readCharacter();
if ("\033" === $char) {
$chaar = $input->readCharacter();
if ('\\' === $chaar) {
break;
}
$char .= $chaar;
}
$out .= $char;
}
return $out;
}
/**
* Get label.
*/
public static function getLabel()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return null;
}
// DECSLPP.
Console::getOutput()->writeAll("\033[20t");
$input = Console::getInput();
$read = [$input->getStream()->getStream()];
$write = [];
$except = [];
$out = null;
if (0 === \stream_select($read, $write, $except, 0, 50000)) {
return $out;
}
// Read \033]L<label>\033\
$input->read(3); // skip \033, ] and L.
while (true) {
$char = $input->readCharacter();
if ("\033" === $char) {
$chaar = $input->readCharacter();
if ('\\' === $chaar) {
break;
}
$char .= $chaar;
}
$out .= $char;
}
return $out;
}
/**
* Refresh the window.
*/
public static function refresh()
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
// DECSLPP.
Console::getOutput()->writeAll("\033[7t");
return;
}
/**
* Set clipboard value.
*/
public static function copy(string $data)
{
if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
return;
}
$out = "\033]52;;".\base64_encode($data)."\033\\";
$output = Console::getOutput();
$considerMultiplexer = $output->considerMultiplexer(true);
$output->writeAll($out);
$output->considerMultiplexer($considerMultiplexer);
return;
}
}
/*
* Advanced interaction.
*/
Console::advancedInteraction();
/*
* Event.
*/
if (\function_exists('pcntl_signal')) {
ConsoleWindow::getInstance();
\pcntl_signal(
\SIGWINCH,
function () {
static $_window = null;
if (null === $_window) {
$_window = ConsoleWindow::getInstance();
}
Event::notify(
'hoa://Event/Console/Window:resize',
$_window,
new EventBucket([
'size' => ConsoleWindow::getSize(),
])
);
}
);
}
@@ -0,0 +1,193 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Events are asynchronous at registration, anonymous at use (until we
* receive a bucket) and useful to largely spread data through components
* without any known connection between them.
*/
class Event
{
/**
* Event ID key.
*/
const KEY_EVENT = 0;
/**
* Source object key.
*/
const KEY_SOURCE = 1;
/**
* Static register of all observable objects, i.e. `Hoa\Event\Source`
* object, i.e. object that can send event.
*/
private static $_register = [];
/**
* Collection of callables, i.e. observer objects.
*/
protected $_callable = [];
/**
* Privatize the constructor.
*/
private function __construct()
{
return;
}
/**
* Manage multiton of events, with the principle of asynchronous
* attachments.
*/
public static function getEvent(string $eventId): self
{
if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
self::$_register[$eventId] = [
self::KEY_EVENT => new self(),
self::KEY_SOURCE => null,
];
}
return self::$_register[$eventId][self::KEY_EVENT];
}
/**
* Declares a new object in the observable collection.
* Note: Hoa's libraries use `hoa://Event/anID` for their observable objects.
*/
public static function register(string $eventId, /* Source|string */ $source)
{
if (true === self::eventExists($eventId)) {
throw new EventException('Cannot redeclare an event with the same ID, i.e. the event '.'ID %s already exists.', 0, $eventId);
}
if (\is_object($source) && !($source instanceof EventSource)) {
throw new EventException('The source must implement \Hoa\Event\Source '.'interface; given %s.', 1, \get_class($source));
} else {
$reflection = new \ReflectionClass($source);
if (false === $reflection->implementsInterface('\Psy\Readline\Hoa\EventSource')) {
throw new EventException('The source must implement \Hoa\Event\Source '.'interface; given %s.', 2, $source);
}
}
if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
self::$_register[$eventId][self::KEY_EVENT] = new self();
}
self::$_register[$eventId][self::KEY_SOURCE] = $source;
}
/**
* Undeclares an object in the observable collection.
*
* If `$hard` is set to `true, then the source and its attached callables
* will be deleted.
*/
public static function unregister(string $eventId, bool $hard = false)
{
if (false !== $hard) {
unset(self::$_register[$eventId]);
} else {
self::$_register[$eventId][self::KEY_SOURCE] = null;
}
}
/**
* Attach an object to an event.
*
* It can be a callable or an accepted callable form (please, see the
* `Hoa\Consistency\Xcallable` class).
*/
public function attach($callable): self
{
$callable = Xcallable::from($callable);
$this->_callable[$callable->getHash()] = $callable;
return $this;
}
/**
* Detaches an object to an event.
*
* Please see `self::attach` method.
*/
public function detach($callable): self
{
unset($this->_callable[Xcallable::from($callable)->getHash()]);
return $this;
}
/**
* Checks if at least one callable is attached to an event.
*/
public function isListened(): bool
{
return !empty($this->_callable);
}
/**
* Notifies, i.e. send data to observers.
*/
public static function notify(string $eventId, EventSource $source, EventBucket $data)
{
if (false === self::eventExists($eventId)) {
throw new EventException('Event ID %s does not exist, cannot send notification.', 3, $eventId);
}
$data->setSource($source);
$event = self::getEvent($eventId);
foreach ($event->_callable as $callable) {
$callable($data);
}
}
/**
* Checks whether an event exists.
*/
public static function eventExists(string $eventId): bool
{
return
\array_key_exists($eventId, self::$_register) &&
self::$_register[$eventId][self::KEY_SOURCE] !== null;
}
}
@@ -0,0 +1,109 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* This class is the object which is transmit through event channels.
*/
class EventBucket
{
/**
* The source object (must be of kind `Hoa\Event\Source`).
*/
protected $_source = null;
/**
* Data attached to the bucket.
*/
protected $_data = null;
/**
* Allocates a new bucket with various data attached to it.
*/
public function __construct($data = null)
{
$this->setData($data);
return;
}
/**
* Sends this object on the event channel.
*/
public function send(string $eventId, EventSource $source)
{
return Event::notify($eventId, $source, $this);
}
/**
* Sets a new source.
*/
public function setSource(EventSource $source)
{
$old = $this->_source;
$this->_source = $source;
return $old;
}
/**
* Returns the source.
*/
public function getSource()
{
return $this->_source;
}
/**
* Sets new data.
*/
public function setData($data)
{
$old = $this->_data;
$this->_data = $data;
return $old;
}
/**
* Returns the data.
*/
public function getData()
{
return $this->_data;
}
}
@@ -0,0 +1,44 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Extending the `Hoa\Exception\Exception` class.
*/
class EventException extends Exception
{
}
@@ -0,0 +1,48 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Each object which is listenable must implement this interface.
*/
interface EventListenable extends EventSource
{
/**
* Attaches a callable to a listenable component.
*/
public function on(string $listenerId, $callable): self;
}
@@ -0,0 +1,137 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* A contrario of events, listeners are synchronous, identified at use and
* useful for close interactions between one or some components.
*/
class EventListener
{
/**
* Source of listener (for `Hoa\Event\Bucket`).
*/
protected $_source = null;
/**
* All listener IDs and associated listeners.
*/
protected $_callables = [];
/**
* Build a listener.
*/
public function __construct(EventListenable $source, array $ids)
{
$this->_source = $source;
$this->addIds($ids);
return;
}
/**
* Adds acceptable ID (or reset).
*/
public function addIds(array $ids)
{
foreach ($ids as $id) {
$this->_callables[$id] = [];
}
}
/**
* Attaches a callable to a listenable component.
*/
public function attach(string $listenerId, $callable): self
{
if (false === $this->listenerExists($listenerId)) {
throw new EventException('Cannot listen %s because it is not defined.', 0, $listenerId);
}
$callable = Xcallable::from($callable);
$this->_callables[$listenerId][$callable->getHash()] = $callable;
return $this;
}
/**
* Detaches a callable from a listenable component.
*/
public function detach(string $listenerId, $callable): self
{
unset($this->_callables[$listenerId][Xcallable::from($callable)->getHash()]);
return $this;
}
/**
* Detaches all callables from a listenable component.
*/
public function detachAll(string $listenerId): self
{
unset($this->_callables[$listenerId]);
return $this;
}
/**
* Checks if a listener exists.
*/
public function listenerExists(string $listenerId): bool
{
return \array_key_exists($listenerId, $this->_callables);
}
/**
* Sends/fires a bucket to a listener.
*/
public function fire(string $listenerId, EventBucket $data): array
{
if (false === $this->listenerExists($listenerId)) {
throw new EventException('Cannot fire on %s because it is not defined.', 1, $listenerId);
}
$data->setSource($this->_source);
$out = [];
foreach ($this->_callables[$listenerId] as $callable) {
$out[] = $callable($data);
}
return $out;
}
}
@@ -0,0 +1,83 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Implementation of a listener.
*/
trait EventListens
{
/**
* Listener instance of type `Hoa\Event\Listener`.
*/
protected $_listener = null;
/**
* Attaches a callable to a listenable component.
*/
public function on(string $listenerId, $callable): EventListenable
{
$listener = $this->getListener();
if (null === $listener) {
throw new EventException('Cannot attach a callable to the listener %s because '.'it has not been initialized yet.', 0, static::class);
}
$listener->attach($listenerId, $callable);
return $this;
}
/**
* Sets a new listener.
*/
protected function setListener(EventListener $listener)
{
$old = $this->_listener;
$this->_listener = $listener;
return $old;
}
/**
* Returns the listener.
*/
protected function getListener()
{
return $this->_listener;
}
}
@@ -0,0 +1,44 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Each object which is listenable must implement this interface.
*/
interface EventSource
{
}
@@ -0,0 +1,79 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Each exception must extend `Hoa\Exception\Exception`.
*/
class Exception extends ExceptionIdle implements EventSource
{
/**
* Allocates a new exception.
*
* An exception is built with a formatted message, a code (an ID), and an
* array that contains the list of formatted string for the message. If
* chaining, a previous exception can be added.
*/
public function __construct(
string $message,
int $code = 0,
$arguments = [],
\Throwable $previous = null
) {
parent::__construct($message, $code, $arguments, $previous);
if (false === Event::eventExists('hoa://Event/Exception')) {
Event::register('hoa://Event/Exception', __CLASS__);
}
$this->send();
return;
}
/**
* Sends the exception on `hoa://Event/Exception`.
*/
public function send()
{
Event::notify(
'hoa://Event/Exception',
$this,
new EventBucket($this)
);
}
}
@@ -0,0 +1,267 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* `Hoa\Exception\Idle` is the mother exception class of libraries. The only
* difference between `Hoa\Exception\Idle` and its direct children
* `Hoa\Exception` is that the latter fires events after beeing constructed.
*/
class ExceptionIdle extends \Exception
{
/**
* Delay processing on arguments.
*/
protected $_tmpArguments = null;
/**
* List of arguments to format message.
*/
protected $_arguments = null;
/**
* Backtrace.
*/
protected $_trace = null;
/**
* Previous exception if any.
*/
protected $_previous = null;
/**
* Original exception message.
*/
protected $_rawMessage = null;
/**
* Allocates a new exception.
*
* An exception is built with a formatted message, a code (an ID) and an
* array that contains the list of formatted strings for the message. If
* chaining, we can add a previous exception.
*/
public function __construct(
string $message,
int $code = 0,
$arguments = [],
\Exception $previous = null
) {
$this->_tmpArguments = $arguments;
parent::__construct($message, $code, $previous);
$this->_rawMessage = $message;
$this->message = @\vsprintf($message, $this->getArguments());
return;
}
/**
* Returns the backtrace.
*
* Do not use `Exception::getTrace` any more.
*/
public function getBacktrace()
{
if (null === $this->_trace) {
$this->_trace = $this->getTrace();
}
return $this->_trace;
}
/**
* Returns the previous exception if any.
*
* Do not use `Exception::getPrevious` any more.
*/
public function getPreviousThrow()
{
if (null === $this->_previous) {
$this->_previous = $this->getPrevious();
}
return $this->_previous;
}
/**
* Returns the arguments of the message.
*/
public function getArguments()
{
if (null === $this->_arguments) {
$arguments = $this->_tmpArguments;
if (!\is_array($arguments)) {
$arguments = [$arguments];
}
foreach ($arguments as &$value) {
if (null === $value) {
$value = '(null)';
}
}
$this->_arguments = $arguments;
unset($this->_tmpArguments);
}
return $this->_arguments;
}
/**
* Returns the raw message.
*/
public function getRawMessage(): string
{
return $this->_rawMessage;
}
/**
* Returns the message already formatted.
*/
public function getFormattedMessage(): string
{
return $this->getMessage();
}
/**
* Returns the source of the exception (class, method, function, main etc.).
*/
public function getFrom(): string
{
$trace = $this->getBacktrace();
$from = '{main}';
if (!empty($trace)) {
$t = $trace[0];
$from = '';
if (isset($t['class'])) {
$from .= $t['class'].'::';
}
if (isset($t['function'])) {
$from .= $t['function'].'()';
}
}
return $from;
}
/**
* Raises an exception as a string.
*/
public function raise(bool $includePrevious = false): string
{
$message = $this->getFormattedMessage();
$trace = $this->getBacktrace();
$file = '/dev/null';
$line = -1;
$pre = $this->getFrom();
if (!empty($trace)) {
$file = $trace['file'] ?? null;
$line = $trace['line'] ?? null;
}
$pre .= ': ';
try {
$out =
$pre.'('.$this->getCode().') '.$message."\n".
'in '.$this->getFile().' at line '.
$this->getLine().'.';
} catch (\Exception $e) {
$out =
$pre.'('.$this->getCode().') '.$message."\n".
'in '.$file.' around line '.$line.'.';
}
if (true === $includePrevious &&
null !== $previous = $this->getPreviousThrow()) {
$out .=
"\n\n".' ⬇'."\n\n".
'Nested exception ('.\get_class($previous).'):'."\n".
($previous instanceof self
? $previous->raise(true)
: $previous->getMessage());
}
return $out;
}
/**
* Catches uncaught exception (only `Hoa\Exception\Idle` and children).
*/
public static function uncaught(\Throwable $exception)
{
if (!($exception instanceof self)) {
throw $exception;
}
while (0 < \ob_get_level()) {
\ob_end_flush();
}
echo 'Uncaught exception ('.\get_class($exception).'):'."\n".
$exception->raise(true);
}
/**
* String representation of object.
*/
public function __toString(): string
{
return $this->raise();
}
/**
* Enables uncaught exception handler.
*
* This is restricted to Hoa's exceptions only.
*/
public static function enableUncaughtHandler(bool $enable = true)
{
if (false === $enable) {
return \restore_exception_handler();
}
return \set_exception_handler(function ($exception) {
return self::uncaught($exception);
});
}
}
@@ -0,0 +1,274 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File.
*
* File handler.
*/
abstract class File extends FileGeneric implements StreamBufferable, StreamLockable, StreamPointable
{
/**
* Open for reading only; place the file pointer at the beginning of the
* file.
*/
const MODE_READ = 'rb';
/**
* Open for reading and writing; place the file pointer at the beginning of
* the file.
*/
const MODE_READ_WRITE = 'r+b';
/**
* Open for writing only; place the file pointer at the beginning of the
* file and truncate the file to zero length. If the file does not exist,
* attempt to create it.
*/
const MODE_TRUNCATE_WRITE = 'wb';
/**
* Open for reading and writing; place the file pointer at the beginning of
* the file and truncate the file to zero length. If the file does not
* exist, attempt to create it.
*/
const MODE_TRUNCATE_READ_WRITE = 'w+b';
/**
* Open for writing only; place the file pointer at the end of the file. If
* the file does not exist, attempt to create it.
*/
const MODE_APPEND_WRITE = 'ab';
/**
* Open for reading and writing; place the file pointer at the end of the
* file. If the file does not exist, attempt to create it.
*/
const MODE_APPEND_READ_WRITE = 'a+b';
/**
* Create and open for writing only; place the file pointer at the beginning
* of the file. If the file already exits, the fopen() call with fail by
* returning false and generating an error of level E_WARNING. If the file
* does not exist, attempt to create it. This is equivalent to specifying
* O_EXCL | O_CREAT flags for the underlying open(2) system call.
*/
const MODE_CREATE_WRITE = 'xb';
/**
* Create and open for reading and writing; place the file pointer at the
* beginning of the file. If the file already exists, the fopen() call with
* fail by returning false and generating an error of level E_WARNING. If
* the file does not exist, attempt to create it. This is equivalent to
* specifying O_EXCL | O_CREAT flags for the underlying open(2) system call.
*/
const MODE_CREATE_READ_WRITE = 'x+b';
/**
* Open a file.
*/
public function __construct(
string $streamName,
string $mode,
string $context = null,
bool $wait = false
) {
$this->setMode($mode);
switch ($streamName) {
case '0':
$streamName = 'php://stdin';
break;
case '1':
$streamName = 'php://stdout';
break;
case '2':
$streamName = 'php://stderr';
break;
default:
if (true === \ctype_digit($streamName)) {
$streamName = 'php://fd/'.$streamName;
}
}
parent::__construct($streamName, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
if (\substr($streamName, 0, 4) === 'file' &&
false === \is_dir(\dirname($streamName))) {
throw new FileException('Directory %s does not exist. Could not open file %s.', 1, [\dirname($streamName), \basename($streamName)]);
}
if (null === $context) {
if (false === $out = @\fopen($streamName, $this->getMode(), true)) {
throw new FileException('Failed to open stream %s.', 2, $streamName);
}
return $out;
}
$out = @\fopen(
$streamName,
$this->getMode(),
true,
$context->getContext()
);
if (false === $out) {
throw new FileException('Failed to open stream %s.', 3, $streamName);
}
return $out;
}
/**
* Close the current stream.
*/
protected function _close(): bool
{
return @\fclose($this->getStream());
}
/**
* Start a new buffer.
* The callable acts like a light filter.
*/
public function newBuffer($callable = null, int $size = null): int
{
$this->setStreamBuffer($size);
// @todo manage $callable as a filter?
return 1;
}
/**
* Flush the output to a stream.
*/
public function flush(): bool
{
return \fflush($this->getStream());
}
/**
* Delete buffer.
*/
public function deleteBuffer(): bool
{
return $this->disableStreamBuffer();
}
/**
* Get bufffer level.
*/
public function getBufferLevel(): int
{
return 1;
}
/**
* Get buffer size.
*/
public function getBufferSize(): int
{
return $this->getStreamBufferSize();
}
/**
* Portable advisory locking.
*/
public function lock(int $operation): bool
{
return \flock($this->getStream(), $operation);
}
/**
* Rewind the position of a stream pointer.
*/
public function rewind(): bool
{
return \rewind($this->getStream());
}
/**
* Seek on a stream pointer.
*/
public function seek(int $offset, int $whence = StreamPointable::SEEK_SET): int
{
return \fseek($this->getStream(), $offset, $whence);
}
/**
* Get the current position of the stream pointer.
*/
public function tell(): int
{
$stream = $this->getStream();
if (null === $stream) {
return 0;
}
return \ftell($stream);
}
/**
* Create a file.
*/
public static function create(string $name)
{
if (\file_exists($name)) {
return true;
}
return \touch($name);
}
}
@@ -0,0 +1,221 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Directory.
*
* Directory handler.
*/
class FileDirectory extends FileGeneric
{
/**
* Open for reading.
*/
const MODE_READ = 'rb';
/**
* Open for reading and writing. If the directory does not exist, attempt to
* create it.
*/
const MODE_CREATE = 'xb';
/**
* Open for reading and writing. If the directory does not exist, attempt to
* create it recursively.
*/
const MODE_CREATE_RECURSIVE = 'xrb';
/**
* Open a directory.
*/
public function __construct(
string $streamName,
string $mode = self::MODE_READ,
string $context = null,
bool $wait = false
) {
$this->setMode($mode);
parent::__construct($streamName, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
if (false === \is_dir($streamName)) {
if ($this->getMode() === self::MODE_READ) {
throw new FileDoesNotExistException('Directory %s does not exist.', 0, $streamName);
} else {
self::create(
$streamName,
$this->getMode(),
null !== $context
? $context->getContext()
: null
);
}
}
$out = null;
return $out;
}
/**
* Close the current stream.
*/
protected function _close(): bool
{
return true;
}
/**
* Recursive copy of a directory.
*/
public function copy(string $to, bool $force = StreamTouchable::DO_NOT_OVERWRITE): bool
{
if (empty($to)) {
throw new FileException('The destination path (to copy) is empty.', 1);
}
$from = $this->getStreamName();
$fromLength = \strlen($from) + 1;
$finder = new FileFinder();
$finder->in($from);
self::create($to, self::MODE_CREATE_RECURSIVE);
foreach ($finder as $file) {
$relative = \substr($file->getPathname(), $fromLength);
$_to = $to.\DIRECTORY_SEPARATOR.$relative;
if (true === $file->isDir()) {
self::create($_to, self::MODE_CREATE);
continue;
}
// This is not possible to do `$file->open()->copy();
// $file->close();` because the file will be opened in read and
// write mode. In a PHAR for instance, this operation is
// forbidden. So a special care must be taken to open file in read
// only mode.
$handle = null;
if (true === $file->isFile()) {
$handle = new FileRead($file->getPathname());
} elseif (true === $file->isDir()) {
$handle = new self($file->getPathName());
} elseif (true === $file->isLink()) {
$handle = new FileLinkRead($file->getPathName());
}
if (null !== $handle) {
$handle->copy($_to, $force);
$handle->close();
}
}
return true;
}
/**
* Delete a directory.
*/
public function delete(): bool
{
$from = $this->getStreamName();
$finder = new FileFinder();
$finder->in($from)
->childFirst();
foreach ($finder as $file) {
$file->open()->delete();
$file->close();
}
if (null === $this->getStreamContext()) {
return @\rmdir($from);
}
return @\rmdir($from, $this->getStreamContext()->getContext());
}
/**
* Create a directory.
*/
public static function create(
string $name,
string $mode = self::MODE_CREATE_RECURSIVE,
string $context = null
): bool {
if (true === \is_dir($name)) {
return true;
}
if (empty($name)) {
return false;
}
if (null !== $context) {
if (false === StreamContext::contextExists($context)) {
throw new FileException('Context %s was not previously declared, cannot retrieve '.'this context.', 2, $context);
} else {
$context = StreamContext::getInstance($context);
}
}
if (null === $context) {
return @\mkdir(
$name,
0755,
self::MODE_CREATE_RECURSIVE === $mode
);
}
return @\mkdir(
$name,
0755,
self::MODE_CREATE_RECURSIVE === $mode,
$context->getContext()
);
}
}
@@ -0,0 +1,48 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Exception\FileDoesNotExist.
*
* Extending the \Hoa\File\Exception class.
*
* @license New BSD License
*/
class FileDoesNotExistException extends FileException
{
}
@@ -0,0 +1,48 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Exception.
*
* Extending the \Hoa\Exception\Exception class.
*
* @license New BSD License
*/
class FileException extends Exception
{
}
@@ -0,0 +1,658 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Finder.
*
* This class allows to find files easily by using filters and flags.
*/
class FileFinder implements \IteratorAggregate
{
/**
* SplFileInfo classname.
*/
protected $_splFileInfo = \SplFileInfo::class;
/**
* Paths where to look for.
*/
protected $_paths = [];
/**
* Max depth in recursion.
*/
protected $_maxDepth = -1;
/**
* Filters.
*/
protected $_filters = [];
/**
* Flags.
*/
protected $_flags = -1;
/**
* Types of files to handle.
*/
protected $_types = [];
/**
* What comes first: parent or child?
*/
protected $_first = -1;
/**
* Sorts.
*/
protected $_sorts = [];
/**
* Initialize.
*/
public function __construct()
{
$this->_flags = IteratorFileSystem::KEY_AS_PATHNAME
| IteratorFileSystem::CURRENT_AS_FILEINFO
| IteratorFileSystem::SKIP_DOTS;
$this->_first = \RecursiveIteratorIterator::SELF_FIRST;
return;
}
/**
* Select a directory to scan.
*/
public function in($paths): self
{
if (!\is_array($paths)) {
$paths = [$paths];
}
foreach ($paths as $path) {
if (1 === \preg_match('/[\*\?\[\]]/', $path)) {
$iterator = new \CallbackFilterIterator(
new \GlobIterator(\rtrim($path, \DIRECTORY_SEPARATOR)),
function ($current) {
return $current->isDir();
}
);
foreach ($iterator as $fileInfo) {
$this->_paths[] = $fileInfo->getPathname();
}
} else {
$this->_paths[] = $path;
}
}
return $this;
}
/**
* Set max depth for recursion.
*/
public function maxDepth(int $depth): self
{
$this->_maxDepth = $depth;
return $this;
}
/**
* Include files in the result.
*/
public function files(): self
{
$this->_types[] = 'file';
return $this;
}
/**
* Include directories in the result.
*/
public function directories(): self
{
$this->_types[] = 'dir';
return $this;
}
/**
* Include links in the result.
*/
public function links(): self
{
$this->_types[] = 'link';
return $this;
}
/**
* Follow symbolink links.
*/
public function followSymlinks(bool $flag = true): self
{
if (true === $flag) {
$this->_flags ^= IteratorFileSystem::FOLLOW_SYMLINKS;
} else {
$this->_flags |= IteratorFileSystem::FOLLOW_SYMLINKS;
}
return $this;
}
/**
* Include files that match a regex.
* Example:
* $this->name('#\.php$#');.
*/
public function name(string $regex): self
{
$this->_filters[] = function (\SplFileInfo $current) use ($regex) {
return 0 !== \preg_match($regex, $current->getBasename());
};
return $this;
}
/**
* Exclude directories that match a regex.
* Example:
* $this->notIn('#^\.(git|hg)$#');.
*/
public function notIn(string $regex): self
{
$this->_filters[] = function (\SplFileInfo $current) use ($regex) {
foreach (\explode(\DIRECTORY_SEPARATOR, $current->getPathname()) as $part) {
if (0 !== \preg_match($regex, $part)) {
return false;
}
}
return true;
};
return $this;
}
/**
* Include files that respect a certain size.
* The size is a string of the form:
* operator number unit
* where
* • operator could be: <, <=, >, >= or =;
* • number is a positive integer;
* • unit could be: b (default), Kb, Mb, Gb, Tb, Pb, Eb, Zb, Yb.
* Example:
* $this->size('>= 12Kb');.
*/
public function size(string $size): self
{
if (0 === \preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) {
return $this;
}
$number = (float) ($matches[2]);
$unit = $matches[3] ?? 'b';
$operator = $matches[1];
switch ($unit) {
case 'b':
break;
// kilo
case 'Kb':
$number <<= 10;
break;
// mega.
case 'Mb':
$number <<= 20;
break;
// giga.
case 'Gb':
$number <<= 30;
break;
// tera.
case 'Tb':
$number *= 1099511627776;
break;
// peta.
case 'Pb':
$number *= 1024 ** 5;
break;
// exa.
case 'Eb':
$number *= 1024 ** 6;
break;
// zetta.
case 'Zb':
$number *= 1024 ** 7;
break;
// yota.
case 'Yb':
$number *= 1024 ** 8;
break;
}
$filter = null;
switch ($operator) {
case '<':
$filter = function (\SplFileInfo $current) use ($number) {
return $current->getSize() < $number;
};
break;
case '<=':
$filter = function (\SplFileInfo $current) use ($number) {
return $current->getSize() <= $number;
};
break;
case '>':
$filter = function (\SplFileInfo $current) use ($number) {
return $current->getSize() > $number;
};
break;
case '>=':
$filter = function (\SplFileInfo $current) use ($number) {
return $current->getSize() >= $number;
};
break;
case '=':
$filter = function (\SplFileInfo $current) use ($number) {
return $current->getSize() === $number;
};
break;
}
$this->_filters[] = $filter;
return $this;
}
/**
* Whether we should include dots or not (respectively . and ..).
*/
public function dots(bool $flag = true): self
{
if (true === $flag) {
$this->_flags ^= IteratorFileSystem::SKIP_DOTS;
} else {
$this->_flags |= IteratorFileSystem::SKIP_DOTS;
}
return $this;
}
/**
* Include files that are owned by a certain owner.
*/
public function owner(int $owner): self
{
$this->_filters[] = function (\SplFileInfo $current) use ($owner) {
return $current->getOwner() === $owner;
};
return $this;
}
/**
* Format date.
* Date can have the following syntax:
* date
* since date
* until date
* If the date does not have the “ago” keyword, it will be added.
* Example: “42 hours” is equivalent to “since 42 hours” which is equivalent
* to “since 42 hours ago”.
*/
protected function formatDate(string $date, &$operator): int
{
$operator = -1;
if (0 === \preg_match('#\bago\b#', $date)) {
$date .= ' ago';
}
if (0 !== \preg_match('#^(since|until)\b(.+)$#', $date, $matches)) {
$time = \strtotime($matches[2]);
if ('until' === $matches[1]) {
$operator = 1;
}
} else {
$time = \strtotime($date);
}
return $time;
}
/**
* Include files that have been changed from a certain date.
* Example:
* $this->changed('since 13 days');.
*/
public function changed(string $date): self
{
$time = $this->formatDate($date, $operator);
if (-1 === $operator) {
$this->_filters[] = function (\SplFileInfo $current) use ($time) {
return $current->getCTime() >= $time;
};
} else {
$this->_filters[] = function (\SplFileInfo $current) use ($time) {
return $current->getCTime() < $time;
};
}
return $this;
}
/**
* Include files that have been modified from a certain date.
* Example:
* $this->modified('since 13 days');.
*/
public function modified(string $date): self
{
$time = $this->formatDate($date, $operator);
if (-1 === $operator) {
$this->_filters[] = function (\SplFileInfo $current) use ($time) {
return $current->getMTime() >= $time;
};
} else {
$this->_filters[] = function (\SplFileInfo $current) use ($time) {
return $current->getMTime() < $time;
};
}
return $this;
}
/**
* Add your own filter.
* The callback will receive 3 arguments: $current, $key and $iterator. It
* must return a boolean: true to include the file, false to exclude it.
* Example:
* // Include files that are readable
* $this->filter(function ($current) {
* return $current->isReadable();
* });.
*/
public function filter($callback): self
{
$this->_filters[] = $callback;
return $this;
}
/**
* Sort result by name.
* If \Collator exists (from ext/intl), the $locale argument will be used
* for its constructor. Else, strcmp() will be used.
* Example:
* $this->sortByName('fr_FR');.
*/
public function sortByName(string $locale = 'root'): self
{
if (true === \class_exists('Collator', false)) {
$collator = new \Collator($locale);
$this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) {
return $collator->compare($a->getPathname(), $b->getPathname());
};
} else {
$this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) {
return \strcmp($a->getPathname(), $b->getPathname());
};
}
return $this;
}
/**
* Sort result by size.
* Example:
* $this->sortBySize();.
*/
public function sortBySize(): self
{
$this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) {
return $a->getSize() < $b->getSize();
};
return $this;
}
/**
* Add your own sort.
* The callback will receive 2 arguments: $a and $b. Please see the uasort()
* function.
* Example:
* // Sort files by their modified time.
* $this->sort(function ($a, $b) {
* return $a->getMTime() < $b->getMTime();
* });.
*/
public function sort($callable): self
{
$this->_sorts[] = $callable;
return $this;
}
/**
* Child comes first when iterating.
*/
public function childFirst(): self
{
$this->_first = \RecursiveIteratorIterator::CHILD_FIRST;
return $this;
}
/**
* Get the iterator.
*/
public function getIterator()
{
$_iterator = new \AppendIterator();
$types = $this->getTypes();
if (!empty($types)) {
$this->_filters[] = function (\SplFileInfo $current) use ($types) {
return \in_array($current->getType(), $types);
};
}
$maxDepth = $this->getMaxDepth();
$splFileInfo = $this->getSplFileInfo();
foreach ($this->getPaths() as $path) {
if (1 === $maxDepth) {
$iterator = new \IteratorIterator(
new IteratorRecursiveDirectory(
$path,
$this->getFlags(),
$splFileInfo
),
$this->getFirst()
);
} else {
$iterator = new \RecursiveIteratorIterator(
new IteratorRecursiveDirectory(
$path,
$this->getFlags(),
$splFileInfo
),
$this->getFirst()
);
if (1 < $maxDepth) {
$iterator->setMaxDepth($maxDepth - 1);
}
}
$_iterator->append($iterator);
}
foreach ($this->getFilters() as $filter) {
$_iterator = new \CallbackFilterIterator(
$_iterator,
$filter
);
}
$sorts = $this->getSorts();
if (empty($sorts)) {
return $_iterator;
}
$array = \iterator_to_array($_iterator);
foreach ($sorts as $sort) {
\uasort($array, $sort);
}
return new \ArrayIterator($array);
}
/**
* Set SplFileInfo classname.
*/
public function setSplFileInfo(string $splFileInfo): string
{
$old = $this->_splFileInfo;
$this->_splFileInfo = $splFileInfo;
return $old;
}
/**
* Get SplFileInfo classname.
*/
public function getSplFileInfo(): string
{
return $this->_splFileInfo;
}
/**
* Get all paths.
*/
protected function getPaths(): array
{
return $this->_paths;
}
/**
* Get max depth.
*/
public function getMaxDepth(): int
{
return $this->_maxDepth;
}
/**
* Get types.
*/
public function getTypes(): array
{
return $this->_types;
}
/**
* Get filters.
*/
protected function getFilters(): array
{
return $this->_filters;
}
/**
* Get sorts.
*/
protected function getSorts(): array
{
return $this->_sorts;
}
/**
* Get flags.
*/
public function getFlags(): int
{
return $this->_flags;
}
/**
* Get first.
*/
public function getFirst(): int
{
return $this->_first;
}
}
@@ -0,0 +1,487 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Generic.
*
* Describe a super-file.
*/
abstract class FileGeneric extends Stream implements StreamPathable, StreamStatable, StreamTouchable
{
/**
* Mode.
*/
protected $_mode = null;
/**
* Get filename component of path.
*/
public function getBasename(): string
{
return \basename($this->getStreamName());
}
/**
* Get directory name component of path.
*/
public function getDirname(): string
{
return \dirname($this->getStreamName());
}
/**
* Get size.
*/
public function getSize(): int
{
if (false === $this->getStatistic()) {
return false;
}
return \filesize($this->getStreamName());
}
/**
* Get informations about a file.
*/
public function getStatistic(): array
{
return \fstat($this->getStream());
}
/**
* Get last access time of file.
*/
public function getATime(): int
{
return \fileatime($this->getStreamName());
}
/**
* Get inode change time of file.
*/
public function getCTime(): int
{
return \filectime($this->getStreamName());
}
/**
* Get file modification time.
*/
public function getMTime(): int
{
return \filemtime($this->getStreamName());
}
/**
* Get file group.
*/
public function getGroup(): int
{
return \filegroup($this->getStreamName());
}
/**
* Get file owner.
*/
public function getOwner(): int
{
return \fileowner($this->getStreamName());
}
/**
* Get file permissions.
*/
public function getPermissions(): int
{
return \fileperms($this->getStreamName());
}
/**
* Get file permissions as a string.
* Result sould be interpreted like this:
* * s: socket;
* * l: symbolic link;
* * -: regular;
* * b: block special;
* * d: directory;
* * c: character special;
* * p: FIFO pipe;
* * u: unknown.
*/
public function getReadablePermissions(): string
{
$p = $this->getPermissions();
if (($p & 0xC000) === 0xC000) {
$out = 's';
} elseif (($p & 0xA000) === 0xA000) {
$out = 'l';
} elseif (($p & 0x8000) === 0x8000) {
$out = '-';
} elseif (($p & 0x6000) === 0x6000) {
$out = 'b';
} elseif (($p & 0x4000) === 0x4000) {
$out = 'd';
} elseif (($p & 0x2000) === 0x2000) {
$out = 'c';
} elseif (($p & 0x1000) === 0x1000) {
$out = 'p';
} else {
$out = 'u';
}
$out .=
(($p & 0x0100) ? 'r' : '-').
(($p & 0x0080) ? 'w' : '-').
(($p & 0x0040) ?
(($p & 0x0800) ? 's' : 'x') :
(($p & 0x0800) ? 'S' : '-')).
(($p & 0x0020) ? 'r' : '-').
(($p & 0x0010) ? 'w' : '-').
(($p & 0x0008) ?
(($p & 0x0400) ? 's' : 'x') :
(($p & 0x0400) ? 'S' : '-')).
(($p & 0x0004) ? 'r' : '-').
(($p & 0x0002) ? 'w' : '-').
(($p & 0x0001) ?
(($p & 0x0200) ? 't' : 'x') :
(($p & 0x0200) ? 'T' : '-'));
return $out;
}
/**
* Check if the file is readable.
*/
public function isReadable(): bool
{
return \is_readable($this->getStreamName());
}
/**
* Check if the file is writable.
*/
public function isWritable(): bool
{
return \is_writable($this->getStreamName());
}
/**
* Check if the file is executable.
*/
public function isExecutable(): bool
{
return \is_executable($this->getStreamName());
}
/**
* Clear file status cache.
*/
public function clearStatisticCache()
{
\clearstatcache(true, $this->getStreamName());
}
/**
* Clear all files status cache.
*/
public static function clearAllStatisticCaches()
{
\clearstatcache();
}
/**
* Set access and modification time of file.
*/
public function touch(int $time = null, int $atime = null): bool
{
if (null === $time) {
$time = \time();
}
if (null === $atime) {
$atime = $time;
}
return \touch($this->getStreamName(), $time, $atime);
}
/**
* Copy file.
* Return the destination file path if succeed, false otherwise.
*/
public function copy(string $to, bool $force = StreamTouchable::DO_NOT_OVERWRITE): bool
{
$from = $this->getStreamName();
if ($force === StreamTouchable::DO_NOT_OVERWRITE &&
true === \file_exists($to)) {
return true;
}
if (null === $this->getStreamContext()) {
return @\copy($from, $to);
}
return @\copy($from, $to, $this->getStreamContext()->getContext());
}
/**
* Move a file.
*/
public function move(
string $name,
bool $force = StreamTouchable::DO_NOT_OVERWRITE,
bool $mkdir = StreamTouchable::DO_NOT_MAKE_DIRECTORY
): bool {
$from = $this->getStreamName();
if ($force === StreamTouchable::DO_NOT_OVERWRITE &&
true === \file_exists($name)) {
return false;
}
if (StreamTouchable::MAKE_DIRECTORY === $mkdir) {
FileDirectory::create(
\dirname($name),
FileDirectory::MODE_CREATE_RECURSIVE
);
}
if (null === $this->getStreamContext()) {
return @\rename($from, $name);
}
return @\rename($from, $name, $this->getStreamContext()->getContext());
}
/**
* Delete a file.
*/
public function delete(): bool
{
if (null === $this->getStreamContext()) {
return @\unlink($this->getStreamName());
}
return @\unlink(
$this->getStreamName(),
$this->getStreamContext()->getContext()
);
}
/**
* Change file group.
*/
public function changeGroup($group): bool
{
return \chgrp($this->getStreamName(), $group);
}
/**
* Change file mode.
*/
public function changeMode(int $mode): bool
{
return \chmod($this->getStreamName(), $mode);
}
/**
* Change file owner.
*/
public function changeOwner($user): bool
{
return \chown($this->getStreamName(), $user);
}
/**
* Change the current umask.
*/
public static function umask(int $umask = null): int
{
if (null === $umask) {
return \umask();
}
return \umask($umask);
}
/**
* Check if it is a file.
*/
public function isFile(): bool
{
return \is_file($this->getStreamName());
}
/**
* Check if it is a link.
*/
public function isLink(): bool
{
return \is_link($this->getStreamName());
}
/**
* Check if it is a directory.
*/
public function isDirectory(): bool
{
return \is_dir($this->getStreamName());
}
/**
* Check if it is a socket.
*/
public function isSocket(): bool
{
return \filetype($this->getStreamName()) === 'socket';
}
/**
* Check if it is a FIFO pipe.
*/
public function isFIFOPipe(): bool
{
return \filetype($this->getStreamName()) === 'fifo';
}
/**
* Check if it is character special file.
*/
public function isCharacterSpecial(): bool
{
return \filetype($this->getStreamName()) === 'char';
}
/**
* Check if it is block special.
*/
public function isBlockSpecial(): bool
{
return \filetype($this->getStreamName()) === 'block';
}
/**
* Check if it is an unknown type.
*/
public function isUnknown(): bool
{
return \filetype($this->getStreamName()) === 'unknown';
}
/**
* Set the open mode.
*/
protected function setMode(string $mode)
{
$old = $this->_mode;
$this->_mode = $mode;
return $old;
}
/**
* Get the open mode.
*/
public function getMode()
{
return $this->_mode;
}
/**
* Get inode.
*/
public function getINode(): int
{
return \fileinode($this->getStreamName());
}
/**
* Check if the system is case sensitive or not.
*/
public static function isCaseSensitive(): bool
{
return !(
\file_exists(\mb_strtolower(__FILE__)) &&
\file_exists(\mb_strtoupper(__FILE__))
);
}
/**
* Get a canonicalized absolute pathname.
*/
public function getRealPath(): string
{
if (false === $out = \realpath($this->getStreamName())) {
return $this->getStreamName();
}
return $out;
}
/**
* Get file extension (if exists).
*/
public function getExtension(): string
{
return \pathinfo(
$this->getStreamName(),
\PATHINFO_EXTENSION
);
}
/**
* Get filename without extension.
*/
public function getFilename(): string
{
$file = \basename($this->getStreamName());
if (\defined('PATHINFO_FILENAME')) {
return \pathinfo($file, \PATHINFO_FILENAME);
}
if (\strstr($file, '.')) {
return \substr($file, 0, \strrpos($file, '.'));
}
return $file;
}
}
@@ -0,0 +1,149 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Link.
*
* Link handler.
*/
class FileLink extends File
{
/**
* Open a link.
*/
public function __construct(
string $streamName,
string $mode,
string $context = null,
bool $wait = false
) {
if (!\is_link($streamName)) {
throw new FileException('File %s is not a link.', 0, $streamName);
}
parent::__construct($streamName, $mode, $context, $wait);
return;
}
/**
* Get informations about a link.
*/
public function getStatistic(): array
{
return \lstat($this->getStreamName());
}
/**
* Change file group.
*/
public function changeGroup($group): bool
{
return \lchgrp($this->getStreamName(), $group);
}
/**
* Change file owner.
*/
public function changeOwner($user): bool
{
return \lchown($this->getStreamName(), $user);
}
/**
* Get file permissions.
*/
public function getPermissions(): int
{
return 41453; // i.e. lrwxr-xr-x
}
/**
* Get the target of a symbolic link.
*/
public function getTarget(): FileGeneric
{
$target = \dirname($this->getStreamName()).\DIRECTORY_SEPARATOR.
$this->getTargetName();
$context = null !== $this->getStreamContext()
? $this->getStreamContext()->getCurrentId()
: null;
if (true === \is_link($target)) {
return new FileLinkReadWrite(
$target,
File::MODE_APPEND_READ_WRITE,
$context
);
} elseif (true === \is_file($target)) {
return new FileReadWrite(
$target,
File::MODE_APPEND_READ_WRITE,
$context
);
} elseif (true === \is_dir($target)) {
return new FileDirectory(
$target,
File::MODE_READ,
$context
);
}
throw new FileException('Cannot find an appropriated object that matches with '.'path %s when defining it.', 1, $target);
}
/**
* Get the target name of a symbolic link.
*/
public function getTargetName(): string
{
return \readlink($this->getStreamName());
}
/**
* Create a link.
*/
public static function create(string $name, string $target): bool
{
if (false !== \linkinfo($name)) {
return true;
}
return \symlink($target, $name);
}
}
@@ -0,0 +1,231 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Link\Read.
*
* File handler.
*
* @license New BSD License
*/
class FileLinkRead extends FileLink implements StreamIn
{
/**
* Open a file.
*
* @param string $streamName stream name
* @param string $mode open mode, see the parent::MODE_* constants
* @param string $context context ID (please, see the
* \Hoa\Stream\Context class)
* @param bool $wait differ opening or not
*/
public function __construct(
string $streamName,
string $mode = parent::MODE_READ,
string $context = null,
bool $wait = false
) {
parent::__construct($streamName, $mode, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*
* @param string $streamName Stream name (e.g. path or URL).
* @param \Hoa\Stream\Context $context context
*
* @return resource
*
* @throws \Hoa\File\Exception\FileDoesNotExist
* @throws \Hoa\File\Exception
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
static $createModes = [
parent::MODE_READ,
];
if (!\in_array($this->getMode(), $createModes)) {
throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]);
}
\preg_match('#^(\w+)://#', $streamName, $match);
if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) &&
!\file_exists($streamName)) {
throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName);
}
$out = parent::_open($streamName, $context);
return $out;
}
/**
* Test for end-of-file.
*
* @return bool
*/
public function eof(): bool
{
return \feof($this->getStream());
}
/**
* Read n characters.
*
* @param int $length length
*
* @return string
*
* @throws \Hoa\File\Exception
*/
public function read(int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 2, $length);
}
return \fread($this->getStream(), $length);
}
/**
* Alias of $this->read().
*
* @param int $length length
*
* @return string
*/
public function readString(int $length)
{
return $this->read($length);
}
/**
* Read a character.
*
* @return string
*/
public function readCharacter()
{
return \fgetc($this->getStream());
}
/**
* Read a boolean.
*
* @return bool
*/
public function readBoolean()
{
return (bool) $this->read(1);
}
/**
* Read an integer.
*
* @param int $length length
*
* @return int
*/
public function readInteger(int $length = 1)
{
return (int) $this->read($length);
}
/**
* Read a float.
*
* @param int $length length
*
* @return float
*/
public function readFloat(int $length = 1)
{
return (float) $this->read($length);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*
* @param string $format format (see printf's formats)
*
* @return array
*/
public function readArray(string $format = null)
{
return $this->scanf($format);
}
/**
* Read a line.
*
* @return string
*/
public function readLine()
{
return \fgets($this->getStream());
}
/**
* Read all, i.e. read as much as possible.
*
* @param int $offset offset
*
* @return string
*/
public function readAll(int $offset = 0)
{
return \stream_get_contents($this->getStream(), -1, $offset);
}
/**
* Parse input from a stream according to a format.
*
* @param string $format format (see printf's formats)
*
* @return array
*/
public function scanf(string $format): array
{
return \fscanf($this->getStream(), $format);
}
}
@@ -0,0 +1,279 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Link\ReadWrite.
*
* File handler.
*/
class FileLinkReadWrite extends FileLink implements StreamIn, StreamOut
{
/**
* Open a file.
*/
public function __construct(
string $streamName,
string $mode = parent::MODE_APPEND_READ_WRITE,
string $context = null,
bool $wait = false
) {
parent::__construct($streamName, $mode, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
static $createModes = [
parent::MODE_READ_WRITE,
parent::MODE_TRUNCATE_READ_WRITE,
parent::MODE_APPEND_READ_WRITE,
parent::MODE_CREATE_READ_WRITE,
];
if (!\in_array($this->getMode(), $createModes)) {
throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]);
}
\preg_match('#^(\w+)://#', $streamName, $match);
if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) &&
!\file_exists($streamName) &&
parent::MODE_READ_WRITE === $this->getMode()) {
throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName);
}
$out = parent::_open($streamName, $context);
return $out;
}
/**
* Test for end-of-file.
*/
public function eof(): bool
{
return \feof($this->getStream());
}
/**
* Read n characters.
*/
public function read(int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 2, $length);
}
return \fread($this->getStream(), $length);
}
/**
* Alias of $this->read().
*/
public function readString(int $length)
{
return $this->read($length);
}
/**
* Read a character.
*/
public function readCharacter()
{
return \fgetc($this->getStream());
}
/**
* Read a boolean.
*/
public function readBoolean()
{
return (bool) $this->read(1);
}
/**
* Read an integer.
*/
public function readInteger(int $length = 1)
{
return (int) $this->read($length);
}
/**
* Read a float.
*/
public function readFloat(int $length = 1)
{
return (float) $this->read($length);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*/
public function readArray(string $format = null)
{
return $this->scanf($format);
}
/**
* Read a line.
*/
public function readLine()
{
return \fgets($this->getStream());
}
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = 0)
{
return \stream_get_contents($this->getStream(), -1, $offset);
}
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format): array
{
return \fscanf($this->getStream(), $format);
}
/**
* Write n characters.
*/
public function write(string $string, int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 3, $length);
}
return \fwrite($this->getStream(), $string, $length);
}
/**
* Write a string.
*/
public function writeString(string $string)
{
$string = (string) $string;
return $this->write($string, \strlen($string));
}
/**
* Write a character.
*/
public function writeCharacter(string $char)
{
return $this->write((string) $char[0], 1);
}
/**
* Write a boolean.
*/
public function writeBoolean(bool $boolean)
{
return $this->write((string) (bool) $boolean, 1);
}
/**
* Write an integer.
*/
public function writeInteger(int $integer)
{
$integer = (string) (int) $integer;
return $this->write($integer, \strlen($integer));
}
/**
* Write a float.
*/
public function writeFloat(float $float)
{
$float = (string) (float) $float;
return $this->write($float, \strlen($float));
}
/**
* Write an array.
*/
public function writeArray(array $array)
{
$array = \var_export($array, true);
return $this->write($array, \strlen($array));
}
/**
* Write a line.
*/
public function writeLine(string $line)
{
if (false === $n = \strpos($line, "\n")) {
return $this->write($line."\n", \strlen($line) + 1);
}
++$n;
return $this->write(\substr($line, 0, $n), $n);
}
/**
* Write all, i.e. as much as possible.
*/
public function writeAll(string $string)
{
return $this->write($string, \strlen($string));
}
/**
* Truncate a file to a given length.
*/
public function truncate(int $size): bool
{
return \ftruncate($this->getStream(), $size);
}
}
@@ -0,0 +1,177 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\Read.
*
* File handler.
*/
class FileRead extends File implements StreamIn
{
/**
* Open a file.
*/
public function __construct(
string $streamName,
string $mode = parent::MODE_READ,
string $context = null,
bool $wait = false
) {
parent::__construct($streamName, $mode, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
static $createModes = [
parent::MODE_READ,
];
if (!\in_array($this->getMode(), $createModes)) {
throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]);
}
\preg_match('#^(\w+)://#', $streamName, $match);
if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) &&
!\file_exists($streamName)) {
throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName);
}
$out = parent::_open($streamName, $context);
return $out;
}
/**
* Test for end-of-file.
*/
public function eof(): bool
{
return \feof($this->getStream());
}
/**
* Read n characters.
*/
public function read(int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 2, $length);
}
return \fread($this->getStream(), $length);
}
/**
* Alias of $this->read().
*/
public function readString(int $length)
{
return $this->read($length);
}
/**
* Read a character.
*/
public function readCharacter()
{
return \fgetc($this->getStream());
}
/**
* Read a boolean.
*/
public function readBoolean()
{
return (bool) $this->read(1);
}
/**
* Read an integer.
*/
public function readInteger(int $length = 1)
{
return (int) $this->read($length);
}
/**
* Read a float.
*/
public function readFloat(int $length = 1)
{
return (float) $this->read($length);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*/
public function readArray(string $format = null)
{
return $this->scanf($format);
}
/**
* Read a line.
*/
public function readLine()
{
return \fgets($this->getStream());
}
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = 0)
{
return \stream_get_contents($this->getStream(), -1, $offset);
}
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format): array
{
return \fscanf($this->getStream(), $format);
}
}
@@ -0,0 +1,279 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\File\ReadWrite.
*
* File handler.
*/
class FileReadWrite extends File implements StreamIn, StreamOut
{
/**
* Open a file.
*/
public function __construct(
string $streamName,
string $mode = parent::MODE_APPEND_READ_WRITE,
string $context = null,
bool $wait = false
) {
parent::__construct($streamName, $mode, $context, $wait);
return;
}
/**
* Open the stream and return the associated resource.
*/
protected function &_open(string $streamName, StreamContext $context = null)
{
static $createModes = [
parent::MODE_READ_WRITE,
parent::MODE_TRUNCATE_READ_WRITE,
parent::MODE_APPEND_READ_WRITE,
parent::MODE_CREATE_READ_WRITE,
];
if (!\in_array($this->getMode(), $createModes)) {
throw new FileException('Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), \implode(', ', $createModes)]);
}
\preg_match('#^(\w+)://#', $streamName, $match);
if (((isset($match[1]) && $match[1] === 'file') || !isset($match[1])) &&
!\file_exists($streamName) &&
parent::MODE_READ_WRITE === $this->getMode()) {
throw new FileDoesNotExistException('File %s does not exist.', 1, $streamName);
}
$out = parent::_open($streamName, $context);
return $out;
}
/**
* Test for end-of-file.
*/
public function eof(): bool
{
return \feof($this->getStream());
}
/**
* Read n characters.
*/
public function read(int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 2, $length);
}
return \fread($this->getStream(), $length);
}
/**
* Alias of $this->read().
*/
public function readString(int $length)
{
return $this->read($length);
}
/**
* Read a character.
*/
public function readCharacter()
{
return \fgetc($this->getStream());
}
/**
* Read a boolean.
*/
public function readBoolean()
{
return (bool) $this->read(1);
}
/**
* Read an integer.
*/
public function readInteger(int $length = 1)
{
return (int) $this->read($length);
}
/**
* Read a float.
*/
public function readFloat(int $length = 1)
{
return (float) $this->read($length);
}
/**
* Read an array.
* Alias of the $this->scanf() method.
*/
public function readArray(string $format = null)
{
return $this->scanf($format);
}
/**
* Read a line.
*/
public function readLine()
{
return \fgets($this->getStream());
}
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = 0)
{
return \stream_get_contents($this->getStream(), -1, $offset);
}
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format): array
{
return \fscanf($this->getStream(), $format);
}
/**
* Write n characters.
*/
public function write(string $string, int $length)
{
if (0 > $length) {
throw new FileException('Length must be greater than 0, given %d.', 3, $length);
}
return \fwrite($this->getStream(), $string, $length);
}
/**
* Write a string.
*/
public function writeString(string $string)
{
$string = (string) $string;
return $this->write($string, \strlen($string));
}
/**
* Write a character.
*/
public function writeCharacter(string $char)
{
return $this->write((string) $char[0], 1);
}
/**
* Write a boolean.
*/
public function writeBoolean(bool $boolean)
{
return $this->write((string) (bool) $boolean, 1);
}
/**
* Write an integer.
*/
public function writeInteger(int $integer)
{
$integer = (string) (int) $integer;
return $this->write($integer, \strlen($integer));
}
/**
* Write a float.
*/
public function writeFloat(float $float)
{
$float = (string) (float) $float;
return $this->write($float, \strlen($float));
}
/**
* Write an array.
*/
public function writeArray(array $array)
{
$array = \var_export($array, true);
return $this->write($array, \strlen($array));
}
/**
* Write a line.
*/
public function writeLine(string $line)
{
if (false === $n = \strpos($line, "\n")) {
return $this->write($line."\n", \strlen($line) + 1);
}
++$n;
return $this->write(\substr($line, 0, $n), $n);
}
/**
* Write all, i.e. as much as possible.
*/
public function writeAll(string $string)
{
return $this->write($string, \strlen($string));
}
/**
* Truncate a file to a given length.
*/
public function truncate(int $size): bool
{
return \ftruncate($this->getStream(), $size);
}
}
@@ -0,0 +1,50 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Stream.
*
* Interface for all streams.
*/
interface IStream
{
/**
* Get the current stream.
*/
public function getStream();
}
@@ -0,0 +1,86 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Iterator\FileSystem.
*
* Extending the SPL FileSystemIterator class.
*/
class IteratorFileSystem extends \FilesystemIterator
{
/**
* SplFileInfo classname.
*/
protected $_splFileInfoClass = null;
/**
* Constructor.
* Please, see \FileSystemIterator::__construct() method.
* We add the $splFileInfoClass parameter.
*/
public function __construct(string $path, int $flags = null, string $splFileInfoClass = null)
{
$this->_splFileInfoClass = $splFileInfoClass;
if (null === $flags) {
parent::__construct($path);
} else {
parent::__construct($path, $flags);
}
return;
}
/**
* Current.
* Please, see \FileSystemIterator::current() method.
*/
#[\ReturnTypeWillChange]
public function current()
{
$out = parent::current();
if (null !== $this->_splFileInfoClass &&
$out instanceof \SplFileInfo) {
$out->setInfoClass($this->_splFileInfoClass);
$out = $out->getFileInfo();
}
return $out;
}
}
@@ -0,0 +1,126 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Iterator\Recursive\Directory.
*
* Extending the SPL RecursiveDirectoryIterator class.
*/
class IteratorRecursiveDirectory extends \RecursiveDirectoryIterator
{
/**
* SplFileInfo classname.
*/
protected $_splFileInfoClass = null;
/**
* Relative path.
*/
protected $_relativePath = null;
/**
* Constructor.
* Please, see \RecursiveDirectoryIterator::__construct() method.
* We add the $splFileInfoClass parameter.
*/
public function __construct(string $path, int $flags = null, string $splFileInfoClass = null)
{
if (null === $flags) {
parent::__construct($path);
} else {
parent::__construct($path, $flags);
}
$this->_relativePath = $path;
$this->setSplFileInfoClass($splFileInfoClass);
return;
}
/**
* Current.
* Please, see \RecursiveDirectoryIterator::current() method.
*/
#[\ReturnTypeWillChange]
public function current()
{
$out = parent::current();
if (null !== $this->_splFileInfoClass &&
$out instanceof \SplFileInfo) {
$out->setInfoClass($this->_splFileInfoClass);
$out = $out->getFileInfo();
if ($out instanceof IteratorSplFileInfo) {
$out->setRelativePath($this->getRelativePath());
}
}
return $out;
}
/**
* Get children.
* Please, see \RecursiveDirectoryIterator::getChildren() method.
*/
#[\ReturnTypeWillChange]
public function getChildren()
{
$out = parent::getChildren();
$out->_relativePath = $this->getRelativePath();
$out->setSplFileInfoClass($this->_splFileInfoClass);
return $out;
}
/**
* Set SplFileInfo classname.
*/
public function setSplFileInfoClass($splFileInfoClass)
{
$this->_splFileInfoClass = $splFileInfoClass;
}
/**
* Get relative path (if given).
*/
public function getRelativePath(): string
{
return $this->_relativePath;
}
}
@@ -0,0 +1,122 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Iterator\SplFileInfo.
*
* Enhance SplFileInfo implementation.
*/
class IteratorSplFileInfo extends \SplFileInfo
{
/**
* Hash.
*/
protected $_hash = null;
/**
* Relative path.
*/
protected $_relativePath = null;
/**
* Construct.
*/
public function __construct(string $filename, string $relativePath = null)
{
parent::__construct($filename);
if (-1 !== $mtime = $this->getMTime()) {
$this->_hash = \md5($this->getPathname().$mtime);
}
$this->_relativePath = $relativePath;
return;
}
/**
* Get the hash.
*/
public function getHash(): string
{
return $this->_hash;
}
/**
* Get the MTime.
*/
public function getMTime(): int
{
try {
return parent::getMTime();
} catch (\RuntimeException $e) {
return -1;
}
}
/**
* Set relative path.
*/
public function setRelativePath(string $relativePath)
{
$old = $this->_relativePath;
$this->_relativePath = $relativePath;
return $old;
}
/**
* Get relative path (if given).
*/
public function getRelativePath()
{
return $this->_relativePath;
}
/**
* Get relative pathname (if possible).
*/
public function getRelativePathname(): string
{
if (null === $relative = $this->getRelativePath()) {
return $this->getPathname();
}
return \substr($this->getPathname(), \strlen($relative));
}
}
@@ -0,0 +1,223 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Root of the `hoa://` protocol.
*/
class Protocol extends ProtocolNode
{
/**
* No resolution value.
*
* @const string
*/
const NO_RESOLUTION = '/hoa/flatland';
/**
* Singleton.
*/
private static $_instance = null;
/**
* Cache of resolver.
*/
private static $_cache = [];
/**
* Initialize the protocol.
*/
public function __construct()
{
$this->initialize();
return;
}
/**
* Singleton.
* To use the `hoa://` protocol shared by everyone.
*/
public static function getInstance(): self
{
if (null === static::$_instance) {
static::$_instance = new self();
}
return static::$_instance;
}
/**
* Initialize the protocol.
*/
protected function initialize()
{
$root = \dirname(__DIR__, 3);
$argv0 = isset($_SERVER['argv'][0]) ? \realpath($_SERVER['argv'][0]) : false;
$cwd =
'cli' === \PHP_SAPI
? false !== $argv0 ? \dirname($argv0) : ''
: \getcwd();
$this[] = new ProtocolNode(
'Application',
$cwd.\DIRECTORY_SEPARATOR,
[
new ProtocolNode('Public', 'Public'.\DIRECTORY_SEPARATOR),
]
);
$this[] = new ProtocolNode(
'Data',
\dirname($cwd).\DIRECTORY_SEPARATOR,
[
new ProtocolNode(
'Etc',
'Etc'.\DIRECTORY_SEPARATOR,
[
new ProtocolNode('Configuration', 'Configuration'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Locale', 'Locale'.\DIRECTORY_SEPARATOR),
]
),
new ProtocolNode('Lost+found', 'Lost+found'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Temporary', 'Temporary'.\DIRECTORY_SEPARATOR),
new ProtocolNode(
'Variable',
'Variable'.\DIRECTORY_SEPARATOR,
[
new ProtocolNode('Cache', 'Cache'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Database', 'Database'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Log', 'Log'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Private', 'Private'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Run', 'Run'.\DIRECTORY_SEPARATOR),
new ProtocolNode('Test', 'Test'.\DIRECTORY_SEPARATOR),
]
),
]
);
$this[] = new ProtocolNodeLibrary(
'Library',
$root.\DIRECTORY_SEPARATOR.'Hoathis'.\DIRECTORY_SEPARATOR.';'.
$root.\DIRECTORY_SEPARATOR.'Hoa'.\DIRECTORY_SEPARATOR
);
}
/**
* Resolve (unfold) an `hoa://` path to its real resource.
*
* If `$exists` is set to `true`, try to find the first that exists,
* otherwise returns the first solution. If `$unfold` is set to `true`,
* it returns all the paths.
*/
public function resolve(string $path, bool $exists = true, bool $unfold = false)
{
if (\substr($path, 0, 6) !== 'hoa://') {
if (true === \is_dir($path)) {
$path = \rtrim($path, '/\\');
if ('' === $path) {
$path = '/';
}
}
return $path;
}
if (isset(self::$_cache[$path])) {
$handle = self::$_cache[$path];
} else {
$out = $this->_resolve($path, $handle);
// Not a path but a resource.
if (!\is_array($handle)) {
return $out;
}
$handle = \array_values(\array_unique($handle, \SORT_REGULAR));
foreach ($handle as &$entry) {
if (true === \is_dir($entry)) {
$entry = \rtrim($entry, '/\\');
if ('' === $entry) {
$entry = '/';
}
}
}
self::$_cache[$path] = $handle;
}
if (true === $unfold) {
if (true !== $exists) {
return $handle;
}
$out = [];
foreach ($handle as $solution) {
if (\file_exists($solution)) {
$out[] = $solution;
}
}
return $out;
}
if (true !== $exists) {
return $handle[0];
}
foreach ($handle as $solution) {
if (\file_exists($solution)) {
return $solution;
}
}
return static::NO_RESOLUTION;
}
/**
* Clear the cache.
*/
public static function clearCache()
{
self::$_cache = [];
}
}
@@ -0,0 +1,44 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Extends the `Hoa\Exception\Exception` class.
*/
class ProtocolException extends Exception
{
}
@@ -0,0 +1,323 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Abstract class for all `hoa://`'s nodes.
*/
class ProtocolNode implements \ArrayAccess, \IteratorAggregate
{
/**
* Node's name.
*/
protected $_name = null;
/**
* Path for the `reach` method.
*/
protected $_reach = null;
/**
* Children of the node.
*/
private $_children = [];
/**
* Construct a protocol's node.
* If it is not a data object (i.e. if it does not extend this class to
* overload the `$_name` attribute), we can set the `$_name` attribute
* dynamically. This is useful to create a node on-the-fly.
*/
public function __construct(string $name = null, string $reach = null, array $children = [])
{
if (null !== $name) {
$this->_name = $name;
}
if (null !== $reach) {
$this->_reach = $reach;
}
foreach ($children as $child) {
$this[] = $child;
}
return;
}
/**
* Add a node.
*/
#[\ReturnTypeWillChange]
public function offsetSet($name, $node)
{
if (!($node instanceof self)) {
throw new ProtocolException('Protocol node must extend %s.', 0, __CLASS__);
}
if (empty($name)) {
$name = $node->getName();
}
if (empty($name)) {
throw new ProtocolException('Cannot add a node to the `hoa://` protocol without a name.', 1);
}
$this->_children[$name] = $node;
}
/**
* Get a specific node.
*/
public function offsetGet($name): self
{
if (!isset($this[$name])) {
throw new ProtocolException('Node %s does not exist.', 2, $name);
}
return $this->_children[$name];
}
/**
* Check if a node exists.
*/
public function offsetExists($name): bool
{
return true === \array_key_exists($name, $this->_children);
}
/**
* Remove a node.
*/
#[\ReturnTypeWillChange]
public function offsetUnset($name)
{
unset($this->_children[$name]);
}
/**
* Resolve a path, i.e. iterate the nodes tree and reach the queue of
* the path.
*/
protected function _resolve(string $path, &$accumulator, string $id = null)
{
if (\substr($path, 0, 6) === 'hoa://') {
$path = \substr($path, 6);
}
if (empty($path)) {
return null;
}
if (null === $accumulator) {
$accumulator = [];
$posId = \strpos($path, '#');
if (false !== $posId) {
$id = \substr($path, $posId + 1);
$path = \substr($path, 0, $posId);
} else {
$id = null;
}
}
$path = \trim($path, '/');
$pos = \strpos($path, '/');
if (false !== $pos) {
$next = \substr($path, 0, $pos);
} else {
$next = $path;
}
if (isset($this[$next])) {
if (false === $pos) {
if (null === $id) {
$this->_resolveChoice($this[$next]->reach(), $accumulator);
return true;
}
$accumulator = null;
return $this[$next]->reachId($id);
}
$tnext = $this[$next];
$this->_resolveChoice($tnext->reach(), $accumulator);
return $tnext->_resolve(\substr($path, $pos + 1), $accumulator, $id);
}
$this->_resolveChoice($this->reach($path), $accumulator);
return true;
}
/**
* Resolve choices, i.e. a reach value has a “;”.
*/
protected function _resolveChoice($reach, &$accumulator)
{
if (null === $reach) {
$reach = '';
}
if (empty($accumulator)) {
$accumulator = \explode(';', $reach);
return;
}
if (false === \strpos($reach, ';')) {
if (false !== $pos = \strrpos($reach, "\r")) {
$reach = \substr($reach, $pos + 1);
foreach ($accumulator as &$entry) {
$entry = null;
}
}
foreach ($accumulator as &$entry) {
$entry .= $reach;
}
return;
}
$choices = \explode(';', $reach);
$ref = $accumulator;
$accumulator = [];
foreach ($choices as $choice) {
if (false !== $pos = \strrpos($choice, "\r")) {
$choice = \substr($choice, $pos + 1);
foreach ($ref as $entry) {
$accumulator[] = $choice;
}
} else {
foreach ($ref as $entry) {
$accumulator[] = $entry.$choice;
}
}
}
unset($ref);
return;
}
/**
* Queue of the node.
* Generic one. Must be overrided in children classes.
*/
public function reach(string $queue = null)
{
return empty($queue) ? $this->_reach : $queue;
}
/**
* ID of the component.
* Generic one. Should be overrided in children classes.
*/
public function reachId(string $id)
{
throw new ProtocolException('The node %s has no ID support (tried to reach #%s).', 4, [$this->getName(), $id]);
}
/**
* Set a new reach value.
*/
public function setReach(string $reach)
{
$old = $this->_reach;
$this->_reach = $reach;
return $old;
}
/**
* Get node's name.
*/
public function getName()
{
return $this->_name;
}
/**
* Get reach's root.
*/
protected function getReach()
{
return $this->_reach;
}
/**
* Get an iterator.
*/
public function getIterator(): \ArrayIterator
{
return new \ArrayIterator($this->_children);
}
/**
* Get root the protocol.
*/
public static function getRoot(): Protocol
{
return Protocol::getInstance();
}
/**
* Print a tree of component.
*/
public function __toString(): string
{
static $i = 0;
$out = \str_repeat(' ', $i).$this->getName()."\n";
foreach ($this as $node) {
++$i;
$out .= $node;
--$i;
}
return $out;
}
}
@@ -0,0 +1,90 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* The `hoa://Library/` node.
*/
class ProtocolNodeLibrary extends ProtocolNode
{
/**
* Queue of the component.
*/
public function reach(string $queue = null)
{
$withComposer = \class_exists('Composer\Autoload\ClassLoader', false) ||
('cli' === \PHP_SAPI && \file_exists(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'autoload.php'));
if ($withComposer) {
return parent::reach($queue);
}
if (!empty($queue)) {
$head = $queue;
if (false !== $pos = \strpos($queue, '/')) {
$head = \substr($head, 0, $pos);
$queue = \DIRECTORY_SEPARATOR.\substr($queue, $pos + 1);
} else {
$queue = null;
}
$out = [];
foreach (\explode(';', $this->_reach) as $part) {
$out[] = "\r".$part.\strtolower($head).$queue;
}
$out[] = "\r".\dirname(__DIR__, 5).$queue;
return \implode(';', $out);
}
$out = [];
foreach (\explode(';', $this->_reach) as $part) {
$pos = \strrpos(\rtrim($part, \DIRECTORY_SEPARATOR), \DIRECTORY_SEPARATOR) + 1;
$head = \substr($part, 0, $pos);
$tail = \substr($part, $pos);
$out[] = $head.\strtolower($tail);
}
$this->_reach = \implode(';', $out);
return parent::reach($queue);
}
}
@@ -0,0 +1,473 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Stream wrapper for the `hoa://` protocol.
*/
class ProtocolWrapper
{
/**
* Opened stream as a resource.
*/
private $_stream = null;
/**
* Stream name (filename).
*/
private $_streamName = null;
/**
* Stream context (given by the streamWrapper class) as a resource.
*/
public $context = null;
/**
* Get the real path of the given URL.
* Could return false if the path cannot be reached.
*/
public static function realPath(string $path, bool $exists = true)
{
return ProtocolNode::getRoot()->resolve($path, $exists);
}
/**
* Retrieve the underlying resource.
*
* `$castAs` can be `STREAM_CAST_FOR_SELECT` when `stream_select` is
* calling `stream_cast` or `STREAM_CAST_AS_STREAM` when `stream_cast` is
* called for other uses.
*/
public function stream_cast(int $castAs)
{
return null;
}
/**
* Closes a resource.
* This method is called in response to `fclose`.
* All resources that were locked, or allocated, by the wrapper should be
* released.
*/
public function stream_close()
{
if (true === @\fclose($this->getStream())) {
$this->_stream = null;
$this->_streamName = null;
}
}
/**
* Tests for end-of-file on a file pointer.
* This method is called in response to feof().
*/
public function stream_eof(): bool
{
return \feof($this->getStream());
}
/**
* Flush the output.
* This method is called in respond to fflush().
* If we have cached data in our stream but not yet stored it into the
* underlying storage, we should do so now.
*/
public function stream_flush(): bool
{
return \fflush($this->getStream());
}
/**
* Advisory file locking.
* This method is called in response to flock(), when file_put_contents()
* (when flags contains LOCK_EX), stream_set_blocking() and when closing the
* stream (LOCK_UN).
*
* Operation is one the following:
* * LOCK_SH to acquire a shared lock (reader) ;
* * LOCK_EX to acquire an exclusive lock (writer) ;
* * LOCK_UN to release a lock (shared or exclusive) ;
* * LOCK_NB if we don't want flock() to
* block while locking (not supported on
* Windows).
*/
public function stream_lock(int $operation): bool
{
return \flock($this->getStream(), $operation);
}
/**
* Change stream options.
* This method is called to set metadata on the stream. It is called when
* one of the following functions is called on a stream URL: touch, chmod,
* chown or chgrp.
*
* Option must be one of the following constant:
* * STREAM_META_TOUCH,
* * STREAM_META_OWNER_NAME,
* * STREAM_META_OWNER,
* * STREAM_META_GROUP_NAME,
* * STREAM_META_GROUP,
* * STREAM_META_ACCESS.
*
* Values are arguments of `touch`, `chmod`, `chown`, and `chgrp`.
*/
public function stream_metadata(string $path, int $option, $values): bool
{
$path = static::realPath($path, false);
switch ($option) {
case \STREAM_META_TOUCH:
$arity = \count($values);
if (0 === $arity) {
$out = \touch($path);
} elseif (1 === $arity) {
$out = \touch($path, $values[0]);
} else {
$out = \touch($path, $values[0], $values[1]);
}
break;
case \STREAM_META_OWNER_NAME:
case \STREAM_META_OWNER:
$out = \chown($path, $values);
break;
case \STREAM_META_GROUP_NAME:
case \STREAM_META_GROUP:
$out = \chgrp($path, $values);
break;
case \STREAM_META_ACCESS:
$out = \chmod($path, $values);
break;
default:
$out = false;
}
return $out;
}
/**
* Open file or URL.
* This method is called immediately after the wrapper is initialized (f.e.
* by fopen() and file_get_contents()).
*/
public function stream_open(string $path, string $mode, int $options, &$openedPath): bool
{
$path = static::realPath($path, 'r' === $mode[0]);
if (Protocol::NO_RESOLUTION === $path) {
return false;
}
if (null === $this->context) {
$openedPath = \fopen($path, $mode, $options & \STREAM_USE_PATH);
} else {
$openedPath = \fopen(
$path,
$mode,
(bool) ($options & \STREAM_USE_PATH),
$this->context
);
}
if (false === \is_resource($openedPath)) {
return false;
}
$this->_stream = $openedPath;
$this->_streamName = $path;
return true;
}
/**
* Read from stream.
* This method is called in response to fread() and fgets().
*/
public function stream_read(int $size): string
{
return \fread($this->getStream(), $size);
}
/**
* Seek to specific location in a stream.
* This method is called in response to fseek().
* The read/write position of the stream should be updated according to the
* $offset and $whence.
*
* The possible values for `$whence` are:
* * SEEK_SET to set position equal to $offset bytes,
* * SEEK_CUR to set position to current location plus `$offset`,
* * SEEK_END to set position to end-of-file plus `$offset`.
*/
public function stream_seek(int $offset, int $whence = \SEEK_SET): bool
{
return 0 === \fseek($this->getStream(), $offset, $whence);
}
/**
* Retrieve information about a file resource.
* This method is called in response to fstat().
*/
public function stream_stat(): array
{
return \fstat($this->getStream());
}
/**
* Retrieve the current position of a stream.
* This method is called in response to ftell().
*/
public function stream_tell(): int
{
return \ftell($this->getStream());
}
/**
* Truncate a stream to a given length.
*/
public function stream_truncate(int $size): bool
{
return \ftruncate($this->getStream(), $size);
}
/**
* Write to stream.
* This method is called in response to fwrite().
*/
public function stream_write(string $data): int
{
return \fwrite($this->getStream(), $data);
}
/**
* Close directory handle.
* This method is called in to closedir().
* Any resources which were locked, or allocated, during opening and use of
* the directory stream should be released.
*/
public function dir_closedir()
{
\closedir($this->getStream());
$this->_stream = null;
$this->_streamName = null;
}
/**
* Open directory handle.
* This method is called in response to opendir().
*
* The `$options` input represents whether or not to enforce safe_mode
* (0x04). It is not used here.
*/
public function dir_opendir(string $path, int $options): bool
{
$path = static::realPath($path);
$handle = null;
if (null === $this->context) {
$handle = @\opendir($path);
} else {
$handle = @\opendir($path, $this->context);
}
if (false === $handle) {
return false;
}
$this->_stream = $handle;
$this->_streamName = $path;
return true;
}
/**
* Read entry from directory handle.
* This method is called in response to readdir().
*
* @return mixed
*/
public function dir_readdir()
{
return \readdir($this->getStream());
}
/**
* Rewind directory handle.
* This method is called in response to rewinddir().
* Should reset the output generated by self::dir_readdir, i.e. the next
* call to self::dir_readdir should return the first entry in the location
* returned by self::dir_opendir.
*/
public function dir_rewinddir()
{
\rewinddir($this->getStream());
}
/**
* Create a directory.
* This method is called in response to mkdir().
*/
public function mkdir(string $path, int $mode, int $options): bool
{
if (null === $this->context) {
return \mkdir(
static::realPath($path, false),
$mode,
$options | \STREAM_MKDIR_RECURSIVE
);
}
return \mkdir(
static::realPath($path, false),
$mode,
(bool) ($options | \STREAM_MKDIR_RECURSIVE),
$this->context
);
}
/**
* Rename a file or directory.
* This method is called in response to rename().
* Should attempt to rename $from to $to.
*/
public function rename(string $from, string $to): bool
{
if (null === $this->context) {
return \rename(static::realPath($from), static::realPath($to, false));
}
return \rename(
static::realPath($from),
static::realPath($to, false),
$this->context
);
}
/**
* Remove a directory.
* This method is called in response to rmdir().
* The `$options` input is a bitwise mask of values. It is not used here.
*/
public function rmdir(string $path, int $options): bool
{
if (null === $this->context) {
return \rmdir(static::realPath($path));
}
return \rmdir(static::realPath($path), $this->context);
}
/**
* Delete a file.
* This method is called in response to unlink().
*/
public function unlink(string $path): bool
{
if (null === $this->context) {
return \unlink(static::realPath($path));
}
return \unlink(static::realPath($path), $this->context);
}
/**
* Retrieve information about a file.
* This method is called in response to all stat() related functions.
* The `$flags` input holds additional flags set by the streams API. It
* can hold one or more of the following values OR'd together.
* STREAM_URL_STAT_LINK: for resource with the ability to link to other
* resource (such as an HTTP location: forward, or a filesystem
* symlink). This flag specified that only information about the link
* itself should be returned, not the resource pointed to by the
* link. This flag is set in response to calls to lstat(), is_link(), or
* filetype(). STREAM_URL_STAT_QUIET: if this flag is set, our wrapper
* should not raise any errors. If this flag is not set, we are
* responsible for reporting errors using the trigger_error() function
* during stating of the path.
*/
public function url_stat(string $path, int $flags)
{
$path = static::realPath($path);
if (Protocol::NO_RESOLUTION === $path) {
if ($flags & \STREAM_URL_STAT_QUIET) {
return 0;
} else {
return \trigger_error(
'Path '.$path.' cannot be resolved.',
\E_WARNING
);
}
}
if ($flags & \STREAM_URL_STAT_LINK) {
return @\lstat($path);
}
return @\stat($path);
}
/**
* Get stream resource.
*/
public function getStream()
{
return $this->_stream;
}
/**
* Get stream name.
*/
public function getStreamName()
{
return $this->_streamName;
}
}
/*
* Register the `hoa://` protocol.
*/
\stream_wrapper_register('hoa', ProtocolWrapper::class);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,571 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Stream.
*
* Static register for all streams (files, sockets etc.).
*/
abstract class Stream implements IStream, EventListenable
{
use EventListens;
/**
* Name index in the stream bucket.
*/
const NAME = 0;
/**
* Handler index in the stream bucket.
*/
const HANDLER = 1;
/**
* Resource index in the stream bucket.
*/
const RESOURCE = 2;
/**
* Context index in the stream bucket.
*/
const CONTEXT = 3;
/**
* Default buffer size.
*/
const DEFAULT_BUFFER_SIZE = 8192;
/**
* Current stream bucket.
*/
protected $_bucket = [];
/**
* Static stream register.
*/
private static $_register = [];
/**
* Buffer size (default is 8Ko).
*/
protected $_bufferSize = self::DEFAULT_BUFFER_SIZE;
/**
* Original stream name, given to the stream constructor.
*/
protected $_streamName = null;
/**
* Context name.
*/
protected $_context = null;
/**
* Whether the opening has been deferred.
*/
protected $_hasBeenDeferred = false;
/**
* Whether this stream is already opened by another handler.
*/
protected $_borrowing = false;
/**
* Set the current stream.
* If not exists in the register, try to call the
* `$this->_open()` method. Please, see the `self::_getStream()` method.
*/
public function __construct(string $streamName, string $context = null, bool $wait = false)
{
$this->_streamName = $streamName;
$this->_context = $context;
$this->_hasBeenDeferred = $wait;
$this->setListener(
new EventListener(
$this,
[
'authrequire',
'authresult',
'complete',
'connect',
'failure',
'mimetype',
'progress',
'redirect',
'resolve',
'size',
]
)
);
if (true === $wait) {
return;
}
$this->open();
return;
}
/**
* Get a stream in the register.
* If the stream does not exist, try to open it by calling the
* $handler->_open() method.
*/
private static function &_getStream(
string $streamName,
self $handler,
string $context = null
): array {
$name = \md5($streamName);
if (null !== $context) {
if (false === StreamContext::contextExists($context)) {
throw new StreamException('Context %s was not previously declared, cannot retrieve '.'this context.', 0, $context);
}
$context = StreamContext::getInstance($context);
}
if (!isset(self::$_register[$name])) {
self::$_register[$name] = [
self::NAME => $streamName,
self::HANDLER => $handler,
self::RESOURCE => $handler->_open($streamName, $context),
self::CONTEXT => $context,
];
Event::register(
'hoa://Event/Stream/'.$streamName,
$handler
);
// Add :open-ready?
Event::register(
'hoa://Event/Stream/'.$streamName.':close-before',
$handler
);
} else {
$handler->_borrowing = true;
}
if (null === self::$_register[$name][self::RESOURCE]) {
self::$_register[$name][self::RESOURCE]
= $handler->_open($streamName, $context);
}
return self::$_register[$name];
}
/**
* Open the stream and return the associated resource.
* Note: This method is protected, but do not forget that it could be
* overloaded into a public context.
*/
abstract protected function &_open(string $streamName, StreamContext $context = null);
/**
* Close the current stream.
* Note: this method is protected, but do not forget that it could be
* overloaded into a public context.
*/
abstract protected function _close(): bool;
/**
* Open the stream.
*/
final public function open(): self
{
$context = $this->_context;
if (true === $this->hasBeenDeferred()) {
if (null === $context) {
$handle = StreamContext::getInstance(\uniqid());
$handle->setParameters([
'notification' => [$this, '_notify'],
]);
$context = $handle->getId();
} elseif (true === StreamContext::contextExists($context)) {
$handle = StreamContext::getInstance($context);
$parameters = $handle->getParameters();
if (!isset($parameters['notification'])) {
$handle->setParameters([
'notification' => [$this, '_notify'],
]);
}
}
}
$this->_bufferSize = self::DEFAULT_BUFFER_SIZE;
$this->_bucket = self::_getStream(
$this->_streamName,
$this,
$context
);
return $this;
}
/**
* Close the current stream.
*/
final public function close()
{
$streamName = $this->getStreamName();
if (null === $streamName) {
return;
}
$name = \md5($streamName);
if (!isset(self::$_register[$name])) {
return;
}
Event::notify(
'hoa://Event/Stream/'.$streamName.':close-before',
$this,
new EventBucket()
);
if (false === $this->_close()) {
return;
}
unset(self::$_register[$name]);
$this->_bucket[self::HANDLER] = null;
Event::unregister(
'hoa://Event/Stream/'.$streamName
);
Event::unregister(
'hoa://Event/Stream/'.$streamName.':close-before'
);
return;
}
/**
* Get the current stream name.
*/
public function getStreamName()
{
if (empty($this->_bucket)) {
return null;
}
return $this->_bucket[self::NAME];
}
/**
* Get the current stream.
*/
public function getStream()
{
if (empty($this->_bucket)) {
return null;
}
return $this->_bucket[self::RESOURCE];
}
/**
* Get the current stream context.
*/
public function getStreamContext()
{
if (empty($this->_bucket)) {
return null;
}
return $this->_bucket[self::CONTEXT];
}
/**
* Get stream handler according to its name.
*/
public static function getStreamHandler(string $streamName)
{
$name = \md5($streamName);
if (!isset(self::$_register[$name])) {
return null;
}
return self::$_register[$name][self::HANDLER];
}
/**
* Set the current stream. Useful to manage a stack of streams (e.g. socket
* and select). Notice that it could be unsafe to use this method without
* taking time to think about it two minutes. Resource of type “Unknown” is
* considered as valid.
*/
public function _setStream($stream)
{
if (false === \is_resource($stream) &&
('resource' !== \gettype($stream) ||
'Unknown' !== \get_resource_type($stream))) {
throw new StreamException('Try to change the stream resource with an invalid one; '.'given %s.', 1, \gettype($stream));
}
$old = $this->_bucket[self::RESOURCE];
$this->_bucket[self::RESOURCE] = $stream;
return $old;
}
/**
* Check if the stream is opened.
*/
public function isOpened(): bool
{
return \is_resource($this->getStream());
}
/**
* Set the timeout period.
*/
public function setStreamTimeout(int $seconds, int $microseconds = 0): bool
{
return \stream_set_timeout($this->getStream(), $seconds, $microseconds);
}
/**
* Whether the opening of the stream has been deferred.
*/
protected function hasBeenDeferred()
{
return $this->_hasBeenDeferred;
}
/**
* Check whether the connection has timed out or not.
* This is basically a shortcut of `getStreamMetaData` + the `timed_out`
* index, but the resulting code is more readable.
*/
public function hasTimedOut(): bool
{
$metaData = $this->getStreamMetaData();
return true === $metaData['timed_out'];
}
/**
* Set blocking/non-blocking mode.
*/
public function setStreamBlocking(bool $mode): bool
{
return \stream_set_blocking($this->getStream(), $mode);
}
/**
* Set stream buffer.
* Output using fwrite() (or similar function) is normally buffered at 8 Ko.
* This means that if there are two processes wanting to write to the same
* output stream, each is paused after 8 Ko of data to allow the other to
* write.
*/
public function setStreamBuffer(int $buffer): bool
{
// Zero means success.
$out = 0 === \stream_set_write_buffer($this->getStream(), $buffer);
if (true === $out) {
$this->_bufferSize = $buffer;
}
return $out;
}
/**
* Disable stream buffering.
* Alias of $this->setBuffer(0).
*/
public function disableStreamBuffer(): bool
{
return $this->setStreamBuffer(0);
}
/**
* Get stream buffer size.
*/
public function getStreamBufferSize(): int
{
return $this->_bufferSize;
}
/**
* Get stream wrapper name.
*/
public function getStreamWrapperName(): string
{
if (false === $pos = \strpos($this->getStreamName(), '://')) {
return 'file';
}
return \substr($this->getStreamName(), 0, $pos);
}
/**
* Get stream meta data.
*/
public function getStreamMetaData(): array
{
return \stream_get_meta_data($this->getStream());
}
/**
* Whether this stream is already opened by another handler.
*/
public function isBorrowing(): bool
{
return $this->_borrowing;
}
/**
* Notification callback.
*/
public function _notify(
int $ncode,
int $severity,
$message,
$code,
$transferred,
$max
) {
static $_map = [
\STREAM_NOTIFY_AUTH_REQUIRED => 'authrequire',
\STREAM_NOTIFY_AUTH_RESULT => 'authresult',
\STREAM_NOTIFY_COMPLETED => 'complete',
\STREAM_NOTIFY_CONNECT => 'connect',
\STREAM_NOTIFY_FAILURE => 'failure',
\STREAM_NOTIFY_MIME_TYPE_IS => 'mimetype',
\STREAM_NOTIFY_PROGRESS => 'progress',
\STREAM_NOTIFY_REDIRECTED => 'redirect',
\STREAM_NOTIFY_RESOLVE => 'resolve',
\STREAM_NOTIFY_FILE_SIZE_IS => 'size',
];
$this->getListener()->fire($_map[$ncode], new EventBucket([
'code' => $code,
'severity' => $severity,
'message' => $message,
'transferred' => $transferred,
'max' => $max,
]));
}
/**
* Call the $handler->close() method on each stream in the static stream
* register.
* This method does not check the return value of $handler->close(). Thus,
* if a stream is persistent, the $handler->close() should do anything. It
* is a very generic method.
*/
final public static function _Hoa_Stream()
{
foreach (self::$_register as $entry) {
$entry[self::HANDLER]->close();
}
return;
}
/**
* Transform object to string.
*/
public function __toString(): string
{
return $this->getStreamName();
}
/**
* Close the stream when destructing.
*/
public function __destruct()
{
if (false === $this->isOpened()) {
return;
}
$this->close();
return;
}
}
/**
* Class \Hoa\Stream\_Protocol.
*
* The `hoa://Library/Stream` node.
*
* @license New BSD License
*/
class _Protocol extends ProtocolNode
{
/**
* Component's name.
*
* @var string
*/
protected $_name = 'Stream';
/**
* ID of the component.
*
* @param string $id ID of the component
*
* @return mixed
*/
public function reachId(string $id)
{
return Stream::getStreamHandler($id);
}
}
/*
* Shutdown method.
*/
\register_shutdown_function([Stream::class, '_Hoa_Stream']);
/**
* Add the `hoa://Library/Stream` node. Should be use to reach/get an entry
* in the stream register.
*/
$protocol = Protocol::getInstance();
$protocol['Library'][] = new _Protocol();
@@ -0,0 +1,73 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Bufferable.
*
* Interface for bufferable streams. It's complementary to native buffer support
* of Hoa\Stream (please, see *StreamBuffer*() methods). Classes implementing
* this interface are able to create nested buffers, flush them etc.
*/
interface StreamBufferable extends IStream
{
/**
* Start a new buffer.
* The callable acts like a light filter.
*/
public function newBuffer($callable = null, int $size = null): int;
/**
* Flush the buffer.
*/
public function flush();
/**
* Delete buffer.
*/
public function deleteBuffer(): bool;
/**
* Get bufffer level.
*/
public function getBufferLevel(): int;
/**
* Get buffer size.
*/
public function getBufferSize(): int;
}
@@ -0,0 +1,141 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Stream\Context.
*
* Make a multiton of stream contexts.
*/
class StreamContext
{
/**
* Context ID.
*/
protected $_id = null;
/**
* @var resource
*/
protected $_context;
/**
* Multiton.
*/
protected static $_instances = [];
/**
* Construct a context.
*/
protected function __construct($id)
{
$this->_id = $id;
$this->_context = \stream_context_create();
return;
}
/**
* Multiton.
*/
public static function getInstance(string $id): self
{
if (false === static::contextExists($id)) {
static::$_instances[$id] = new self($id);
}
return static::$_instances[$id];
}
/**
* Get context ID.
*/
public function getId(): string
{
return $this->_id;
}
/**
* Check if a context exists.
*/
public static function contextExists(string $id): bool
{
return \array_key_exists($id, static::$_instances);
}
/**
* Set options.
* Please, see http://php.net/context.
*/
public function setOptions(array $options): bool
{
return \stream_context_set_option($this->getContext(), $options);
}
/**
* Set parameters.
* Please, see http://php.net/context.params.
*/
public function setParameters(array $parameters): bool
{
return \stream_context_set_params($this->getContext(), $parameters);
}
/**
* Get options.
*/
public function getOptions(): array
{
return \stream_context_get_options($this->getContext());
}
/**
* Get parameters.
*/
public function getParameters(): array
{
return \stream_context_get_params($this->getContext());
}
/**
* Get context as a resource.
*/
public function getContext()
{
return $this->_context;
}
}
@@ -0,0 +1,46 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Class \Hoa\Stream\Exception.
*
* Extending the \Hoa\Exception\Exception class.
*/
class StreamException extends Exception
{
}
@@ -0,0 +1,102 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\In.
*
* Interface for input.
*/
interface StreamIn extends IStream
{
/**
* Test for end-of-stream.
*/
public function eof(): bool;
/**
* Read n characters.
*/
public function read(int $length);
/**
* Alias of $this->read().
*/
public function readString(int $length);
/**
* Read a character.
* It could be equivalent to $this->read(1).
*/
public function readCharacter();
/**
* Read a boolean.
*/
public function readBoolean();
/**
* Read an integer.
*/
public function readInteger(int $length = 1);
/**
* Read a float.
*/
public function readFloat(int $length = 1);
/**
* Read an array.
* In most cases, it could be an alias to the $this->scanf() method.
*/
public function readArray();
/**
* Read a line.
*/
public function readLine();
/**
* Read all, i.e. read as much as possible.
*/
public function readAll(int $offset = 0);
/**
* Parse input from a stream according to a format.
*/
public function scanf(string $format): array;
}
@@ -0,0 +1,85 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Lockable.
*
* Interface for lockable input/output.
*
* @license New BSD License
*/
interface StreamLockable extends IStream
{
/**
* Acquire a shared lock (reader).
*
* @const int
*/
const LOCK_SHARED = \LOCK_SH;
/**
* Acquire an exclusive lock (writer).
*
* @const int
*/
const LOCK_EXCLUSIVE = \LOCK_EX;
/**
* Release a lock (shared or exclusive).
*
* @const int
*/
const LOCK_RELEASE = \LOCK_UN;
/**
* If we do not want $this->lock() to block while locking.
*
* @const int
*/
const LOCK_NO_BLOCK = \LOCK_NB;
/**
* Portable advisory locking.
* Should take a look at stream_supports_lock().
*
* @param int $operation operation, use the self::LOCK_* constants
*
* @return bool
*/
public function lock(int $operation): bool;
}
@@ -0,0 +1,95 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Out.
*
* Interface for output.
*/
interface StreamOut extends IStream
{
/**
* Write n characters.
*/
public function write(string $string, int $length);
/**
* Write a string.
*/
public function writeString(string $string);
/**
* Write a character.
*/
public function writeCharacter(string $character);
/**
* Write a boolean.
*/
public function writeBoolean(bool $boolean);
/**
* Write an integer.
*/
public function writeInteger(int $integer);
/**
* Write a float.
*/
public function writeFloat(float $float);
/**
* Write an array.
*/
public function writeArray(array $array);
/**
* Write a line.
*/
public function writeLine(string $line);
/**
* Write all, i.e. as much as possible.
*/
public function writeAll(string $string);
/**
* Truncate a stream to a given length.
*/
public function truncate(int $size): bool;
}
@@ -0,0 +1,55 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Pathable.
*
* Interface for pathable input/output.
*/
interface StreamPathable extends IStream
{
/**
* Get filename component of path.
*/
public function getBasename(): string;
/**
* Get directory name component of path.
*/
public function getDirname(): string;
}
@@ -0,0 +1,75 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Pointable.
*
* Interface for pointable input/output.
*/
interface StreamPointable extends IStream
{
/**
* Set position equal to $offset bytes.
*/
const SEEK_SET = \SEEK_SET;
/**
* Set position to current location plus $offset.
*/
const SEEK_CURRENT = \SEEK_CUR;
/**
* Set position to end-of-file plus $offset.
*/
const SEEK_END = \SEEK_END;
/**
* Rewind the position of a stream pointer.
*/
public function rewind(): bool;
/**
* Seek on a stream pointer.
*/
public function seek(int $offset, int $whence = self::SEEK_SET): int;
/**
* Get the current position of the stream pointer.
*/
public function tell(): int;
}
@@ -0,0 +1,115 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Statable.
*
* Interface for statable input/output.
*/
interface StreamStatable extends IStream
{
/**
* Size is undefined.
*/
const SIZE_UNDEFINED = -1;
/**
* Get size.
*/
public function getSize(): int;
/**
* Get informations about a file.
*/
public function getStatistic(): array;
/**
* Get last access time of file.
*/
public function getATime(): int;
/**
* Get inode change time of file.
*/
public function getCTime(): int;
/**
* Get file modification time.
*/
public function getMTime(): int;
/**
* Get file group.
*/
public function getGroup(): int;
/**
* Get file owner.
*/
public function getOwner(): int;
/**
* Get file permissions.
*/
public function getPermissions(): int;
/**
* Check if the file is readable.
*/
public function isReadable(): bool;
/**
* Check if the file is writable.
*/
public function isWritable(): bool;
/**
* Check if the file is executable.
*/
public function isExecutable(): bool;
/**
* Clear file status cache.
*/
public function clearStatisticCache();
/**
* Clear all files status cache.
*/
public static function clearAllStatisticCaches();
}
@@ -0,0 +1,110 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Interface \Hoa\Stream\IStream\Touchable.
*
* Interface for touchable input/output.
*/
interface StreamTouchable extends IStream
{
/**
* Overwrite file if already exists.
*/
const OVERWRITE = true;
/**
* Do not overwrite file if already exists.
*/
const DO_NOT_OVERWRITE = false;
/**
* Make directory if does not exist.
*/
const MAKE_DIRECTORY = true;
/**
* Do not make directory if does not exist.
*/
const DO_NOT_MAKE_DIRECTORY = false;
/**
* Set access and modification time of file.
*/
public function touch(int $time = -1, int $atime = -1): bool;
/**
* Copy file.
* Return the destination file path if succeed, false otherwise.
*/
public function copy(string $to, bool $force = self::DO_NOT_OVERWRITE): bool;
/**
* Move a file.
*/
public function move(
string $name,
bool $force = self::DO_NOT_OVERWRITE,
bool $mkdir = self::DO_NOT_MAKE_DIRECTORY
): bool;
/**
* Delete a file.
*/
public function delete(): bool;
/**
* Change file group.
*/
public function changeGroup($group): bool;
/**
* Change file mode.
*/
public function changeMode(int $mode): bool;
/**
* Change file owner.
*/
public function changeOwner($user): bool;
/**
* Change the current umask.
*/
public static function umask(int $umask = null): int;
}
@@ -0,0 +1,143 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* This class represents a UTF-8 string.
* Please, see:
* * http://www.ietf.org/rfc/rfc3454.txt,
* * http://unicode.org/reports/tr9/,
* * http://www.unicode.org/Public/6.0.0/ucd/UnicodeData.txt.
*/
class Ustring
{
/**
* Check if ext/mbstring is available.
*/
public static function checkMbString(): bool
{
return \function_exists('mb_substr');
}
/**
* Get the number of column positions of a wide-character.
*
* This is a PHP implementation of wcwidth() and wcswidth() (defined in IEEE
* Std 1002.1-2001) for Unicode, by Markus Kuhn. Please, see
* http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
*
* The wcwidth(wc) function shall either return 0 (if wc is a null
* wide-character code), or return the number of column positions to be
* occupied by the wide-character code wc, or return -1 (if wc does not
* correspond to a printable wide-character code).
*/
public static function getCharWidth(string $char): int
{
$char = (string) $char;
$c = static::toCode($char);
// Test for 8-bit control characters.
if (0x0 === $c) {
return 0;
}
if (0x20 > $c || (0x7F <= $c && $c < 0xA0)) {
return -1;
}
// Non-spacing characters.
if (0xAD !== $c &&
0 !== \preg_match('#^[\p{Mn}\p{Me}\p{Cf}\x{1160}-\x{11ff}\x{200b}]#u', $char)) {
return 0;
}
// If we arrive here, $c is not a combining C0/C1 control character.
return 1 +
(0x1100 <= $c &&
(0x115F >= $c || // Hangul Jamo init. consonants
0x2329 === $c || 0x232A === $c ||
(0x2E80 <= $c && 0xA4CF >= $c &&
0x303F !== $c) || // CJK…Yi
(0xAC00 <= $c && 0xD7A3 >= $c) || // Hangul Syllables
(0xF900 <= $c && 0xFAFF >= $c) || // CJK Compatibility Ideographs
(0xFE10 <= $c && 0xFE19 >= $c) || // Vertical forms
(0xFE30 <= $c && 0xFE6F >= $c) || // CJK Compatibility Forms
(0xFF00 <= $c && 0xFF60 >= $c) || // Fullwidth Forms
(0xFFE0 <= $c && 0xFFE6 >= $c) ||
(0x20000 <= $c && 0x2FFFD >= $c) ||
(0x30000 <= $c && 0x3FFFD >= $c)));
}
/**
* Check whether the character is printable or not.
*/
public static function isCharPrintable(string $char): bool
{
return 1 <= static::getCharWidth($char);
}
/**
* Get a decimal code representation of a specific character.
*/
public static function toCode(string $char): int
{
$char = (string) $char;
$code = \ord($char[0]);
$bytes = 1;
if (!($code & 0x80)) { // 0xxxxxxx
return $code;
}
if (($code & 0xE0) === 0xC0) { // 110xxxxx
$bytes = 2;
$code = $code & ~0xC0;
} elseif (($code & 0xF0) === 0xE0) { // 1110xxxx
$bytes = 3;
$code = $code & ~0xE0;
} elseif (($code & 0xF8) === 0xF0) { // 11110xxx
$bytes = 4;
$code = $code & ~0xF0;
}
for ($i = 2; $i <= $bytes; $i++) { // 10xxxxxx
$code = ($code << 6) + (\ord($char[$i - 1]) & ~0x80);
}
return $code;
}
}
@@ -0,0 +1,256 @@
<?php
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Psy\Readline\Hoa;
/**
* Build a callable object, i.e. `function`, `class::method`, `object->method` or
* closure. They all have the same behaviour. This callable is an extension of
* native PHP callable (aka callback) to integrate Hoa's structures.
*/
class Xcallable
{
/**
* Callback with the PHP format.
*/
protected $_callback = null;
/**
* Callable hash.
*/
protected $_hash = null;
/**
* Allocates a xcallable based on a callback.
*
* Accepted forms:
* * `'function'`,
* * `'class::method'`,
* * `'class', 'method'`,
* * `$object, 'method'`,
* * `$object, ''`,
* * `function (…) { … }`,
* * `['class', 'method']`,
* * `[$object, 'method']`.
*
* # Examples
*
* ```php
* $toUpper = new Hoa\Consistency\Xcallable('strtoupper');
* assert('FOO' === $toUpper('foo'));
* ```
*
* # Exceptions
*
* A `Hoa\Consistency\Exception` exception is thrown if the callback form
* is invalid.
*
* ```php,must_throw(Hoa\Consistency\Exception)
* new Hoa\Consistency\Xcallable('Foo:');
* ```
*/
public function __construct($call, $able = '')
{
if ($call instanceof \Closure) {
$this->_callback = $call;
return;
}
if (!\is_string($able)) {
throw new Exception('Bad callback form; the able part must be a string.', 0);
}
if ('' === $able) {
if (\is_string($call)) {
if (false === \strpos($call, '::')) {
if (!\function_exists($call)) {
throw new Exception('Bad callback form; function %s does not exist.', 1, $call);
}
$this->_callback = $call;
return;
}
list($call, $able) = \explode('::', $call);
} elseif (\is_object($call)) {
if ($call instanceof StreamOut) {
$able = null;
} elseif (\method_exists($call, '__invoke')) {
$able = '__invoke';
} else {
throw new Exception('Bad callback form; an object but without a known '.'method.', 2);
}
} elseif (\is_array($call) && isset($call[0])) {
if (!isset($call[1])) {
$this->__construct($call[0]);
return;
}
$this->__construct($call[0], $call[1]);
return;
} else {
throw new Exception('Bad callback form.', 3);
}
}
$this->_callback = [$call, $able];
return;
}
/**
* Calls the callable.
*/
public function __invoke(...$arguments)
{
$callback = $this->getValidCallback($arguments);
return $callback(...$arguments);
}
/**
* Returns a valid PHP callback.
*/
public function getValidCallback(array &$arguments = [])
{
$callback = $this->_callback;
$head = null;
if (isset($arguments[0])) {
$head = &$arguments[0];
}
// If method is undetermined, we find it (we understand event bucket and
// stream).
if (null !== $head &&
\is_array($callback) &&
null === $callback[1]) {
if ($head instanceof EventBucket) {
$head = $head->getData();
}
switch ($type = \gettype($head)) {
case 'string':
if (1 === \strlen($head)) {
$method = 'writeCharacter';
} else {
$method = 'writeString';
}
break;
case 'boolean':
case 'integer':
case 'array':
$method = 'write'.\ucfirst($type);
break;
case 'double':
$method = 'writeFloat';
break;
default:
$method = 'writeAll';
$head = $head."\n";
}
$callback[1] = $method;
}
return $callback;
}
/**
* Computes the hash of this callable.
*
* Will produce:
* * `function#…`,
* * `class#…::…`,
* * `object(…)#…::…`,
* * `closure(…)`.
*/
public function getHash(): string
{
if (null !== $this->_hash) {
return $this->_hash;
}
$_ = &$this->_callback;
if (\is_string($_)) {
return $this->_hash = 'function#'.$_;
}
if (\is_array($_)) {
return
$this->_hash =
(\is_object($_[0])
? 'object('.\spl_object_hash($_[0]).')'.
'#'.\get_class($_[0])
: 'class#'.$_[0]).
'::'.
(null !== $_[1]
? $_[1]
: '???');
}
return $this->_hash = 'closure('.\spl_object_hash($_).')';
}
/**
* The string representation of a callable is its hash.
*/
public function __toString(): string
{
return $this->getHash();
}
/**
* Hoa's xcallable() helper.
*/
public static function from($call, $able = '')
{
if ($call instanceof self) {
return $call;
}
return new self($call, $able);
}
}
@@ -0,0 +1,116 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2023 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
use Psy\Util\Str;
/**
* A Libedit-based Readline implementation.
*
* This is largely the same as the Readline implementation, but it emulates
* support for `readline_list_history` since PHP decided it was a good idea to
* ship a fake Readline implementation that is missing history support.
*
* NOTE: As of PHP 7.4, PHP sometimes has history support in the Libedit
* wrapper, so it will use the GNUReadline implementation rather than this one.
*/
class Libedit extends GNUReadline
{
private $hasWarnedOwnership = false;
/**
* Let's emulate GNU Readline by manually reading and parsing the history file!
*/
public static function isSupported(): bool
{
return \function_exists('readline') && !\function_exists('readline_list_history');
}
/**
* {@inheritdoc}
*/
public static function supportsBracketedPaste(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function listHistory(): array
{
$history = \file_get_contents($this->historyFile);
if (!$history) {
return [];
}
// libedit doesn't seem to support non-unix line separators.
$history = \explode("\n", $history);
// remove history signature if it exists
if ($history[0] === '_HiStOrY_V2_') {
\array_shift($history);
}
// decode the line
$history = \array_map([$this, 'parseHistoryLine'], $history);
// filter empty lines & comments
return \array_values(\array_filter($history));
}
/**
* {@inheritdoc}
*/
public function writeHistory(): bool
{
$res = parent::writeHistory();
// Libedit apparently refuses to save history if the history file is not
// owned by the user, even if it is writable. Warn when this happens.
//
// See https://github.com/bobthecow/psysh/issues/552
if ($res === false && !$this->hasWarnedOwnership) {
if (\is_file($this->historyFile) && \is_writable($this->historyFile)) {
$this->hasWarnedOwnership = true;
$msg = \sprintf('Error writing history file, check file ownership: %s', $this->historyFile);
\trigger_error($msg, \E_USER_NOTICE);
}
}
return $res;
}
/**
* From GNUReadline (readline/histfile.c & readline/histexpand.c):
* lines starting with "\0" are comments or timestamps;
* if "\0" is found in an entry,
* everything from it until the next line is a comment.
*
* @param string $line The history line to parse
*
* @return string|null
*/
protected function parseHistoryLine(string $line)
{
// empty line, comment or timestamp
if (!$line || $line[0] === "\0") {
return;
}
// if "\0" is found in an entry, then
// everything from it until the end of line is a comment.
if (($pos = \strpos($line, "\0")) !== false) {
$line = \substr($line, 0, $pos);
}
return ($line !== '') ? Str::unvis($line) : null;
}
}
@@ -0,0 +1,86 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2023 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
/**
* An interface abstracting the various readline_* functions.
*/
interface Readline
{
/**
* @param string|false $historyFile
* @param int|null $historySize
* @param bool|null $eraseDups
*/
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false);
/**
* Check whether this Readline class is supported by the current system.
*/
public static function isSupported(): bool;
/**
* Check whether this Readline class supports bracketed paste.
*/
public static function supportsBracketedPaste(): bool;
/**
* Add a line to the command history.
*
* @param string $line
*
* @return bool Success
*/
public function addHistory(string $line): bool;
/**
* Clear the command history.
*
* @return bool Success
*/
public function clearHistory(): bool;
/**
* List the command history.
*
* @return string[]
*/
public function listHistory(): array;
/**
* Read the command history.
*
* @return bool Success
*/
public function readHistory(): bool;
/**
* Read a single line of input from the user.
*
* @param string|null $prompt
*
* @return false|string
*/
public function readline(string $prompt = null);
/**
* Redraw readline to redraw the display.
*/
public function redisplay();
/**
* Write the command history to a file.
*
* @return bool Success
*/
public function writeHistory(): bool;
}
@@ -0,0 +1,155 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2023 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
use Psy\Exception\BreakException;
/**
* An array-based Readline emulation implementation.
*/
class Transient implements Readline
{
private $history;
private $historySize;
private $eraseDups;
private $stdin;
/**
* Transient Readline is always supported.
*
* {@inheritdoc}
*/
public static function isSupported(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public static function supportsBracketedPaste(): bool
{
return false;
}
/**
* Transient Readline constructor.
*/
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
{
// don't do anything with the history file...
$this->history = [];
$this->historySize = $historySize;
$this->eraseDups = $eraseDups;
}
/**
* {@inheritdoc}
*/
public function addHistory(string $line): bool
{
if ($this->eraseDups) {
if (($key = \array_search($line, $this->history)) !== false) {
unset($this->history[$key]);
}
}
$this->history[] = $line;
if ($this->historySize > 0) {
$histsize = \count($this->history);
if ($histsize > $this->historySize) {
$this->history = \array_slice($this->history, $histsize - $this->historySize);
}
}
$this->history = \array_values($this->history);
return true;
}
/**
* {@inheritdoc}
*/
public function clearHistory(): bool
{
$this->history = [];
return true;
}
/**
* {@inheritdoc}
*/
public function listHistory(): array
{
return $this->history;
}
/**
* {@inheritdoc}
*/
public function readHistory(): bool
{
return true;
}
/**
* {@inheritdoc}
*
* @throws BreakException if user hits Ctrl+D
*
* @return false|string
*/
public function readline(string $prompt = null)
{
echo $prompt;
return \rtrim(\fgets($this->getStdin()), "\n\r");
}
/**
* {@inheritdoc}
*/
public function redisplay()
{
// noop
}
/**
* {@inheritdoc}
*/
public function writeHistory(): bool
{
return true;
}
/**
* Get a STDIN file handle.
*
* @throws BreakException if user hits Ctrl+D
*
* @return resource
*/
private function getStdin()
{
if (!isset($this->stdin)) {
$this->stdin = \fopen('php://stdin', 'r');
}
if (\feof($this->stdin)) {
throw new BreakException('Ctrl+D');
}
return $this->stdin;
}
}
@@ -0,0 +1,165 @@
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2023 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline;
use Psy\Exception\BreakException;
use Psy\Readline\Hoa\Console as HoaConsole;
use Psy\Readline\Hoa\ConsoleCursor as HoaConsoleCursor;
use Psy\Readline\Hoa\ConsoleInput as HoaConsoleInput;
use Psy\Readline\Hoa\ConsoleOutput as HoaConsoleOutput;
use Psy\Readline\Hoa\ConsoleTput as HoaConsoleTput;
use Psy\Readline\Hoa\Readline as HoaReadline;
use Psy\Readline\Hoa\Ustring as HoaUstring;
/**
* Userland Readline implementation.
*/
class Userland implements Readline
{
/** @var HoaReadline */
private $hoaReadline;
/** @var string|null */
private $lastPrompt;
private $tput;
private $input;
private $output;
public static function isSupported(): bool
{
static::bootstrapHoa();
return HoaUstring::checkMbString() && HoaConsoleTput::isSupported();
}
/**
* {@inheritdoc}
*/
public static function supportsBracketedPaste(): bool
{
return false;
}
/**
* Doesn't (currently) support history file, size or erase dupes configs.
*/
public function __construct($historyFile = null, $historySize = 0, $eraseDups = false)
{
static::bootstrapHoa(true);
$this->hoaReadline = new HoaReadline();
$this->hoaReadline->addMapping('\C-l', function () {
$this->redisplay();
return HoaReadline::STATE_NO_ECHO;
});
$this->tput = new HoaConsoleTput();
HoaConsole::setTput($this->tput);
$this->input = new HoaConsoleInput();
HoaConsole::setInput($this->input);
$this->output = new HoaConsoleOutput();
HoaConsole::setOutput($this->output);
}
/**
* Bootstrap some things that Hoa used to do itself.
*/
public static function bootstrapHoa(bool $withTerminalResize = false)
{
// A side effect registers hoa:// stream wrapper
\class_exists('Psy\Readline\Hoa\ProtocolWrapper');
// A side effect registers hoa://Library/Stream
\class_exists('Psy\Readline\Hoa\Stream');
// A side effect binds terminal resize
$withTerminalResize && \class_exists('Psy\Readline\Hoa\ConsoleWindow');
}
/**
* {@inheritdoc}
*/
public function addHistory(string $line): bool
{
$this->hoaReadline->addHistory($line);
return true;
}
/**
* {@inheritdoc}
*/
public function clearHistory(): bool
{
$this->hoaReadline->clearHistory();
return true;
}
/**
* {@inheritdoc}
*/
public function listHistory(): array
{
$i = 0;
$list = [];
while (($item = $this->hoaReadline->getHistory($i++)) !== null) {
$list[] = $item;
}
return $list;
}
/**
* {@inheritdoc}
*/
public function readHistory(): bool
{
return true;
}
/**
* {@inheritdoc}
*
* @throws BreakException if user hits Ctrl+D
*
* @return string
*/
public function readline(string $prompt = null)
{
$this->lastPrompt = $prompt;
return $this->hoaReadline->readLine($prompt);
}
/**
* {@inheritdoc}
*/
public function redisplay()
{
$currentLine = $this->hoaReadline->getLine();
HoaConsoleCursor::clear('all');
echo $this->lastPrompt, $currentLine;
}
/**
* {@inheritdoc}
*/
public function writeHistory(): bool
{
return true;
}
}