Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ php:
- 5.6
- 7.0
- 7.1
- 7.2

matrix:
include:
Expand All @@ -18,8 +19,14 @@ matrix:
before_script:
- php -m
- php --info | grep -i 'intl\|icu\|pcre'
- stty size
- composer install --no-interaction --prefer-source

script: phpunit --debug
script: vendor/bin/phpunit

cache:
directories:
- $HOME/.composer/cache

notifications:
email:
Expand Down
43 changes: 43 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
build: off
clone_folder: C:\projects\php-cli-tools
platform: x64
shallow_clone: true
skip_tags: true
version: '{build}'

branches:
only:
- windows_phpunit

cache:
- '%LOCALAPPDATA%\Composer\files'
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
- C:\projects\php-cli-tools-bin -> appveyor.yml

init:
- set PATH=C:\projects\php-cli-tools-bin;C:\Program Files\Git\bin;C:\cygwin64\bin;C:\Program Files\Java\jdk1.8.0\bin;C:\Program Files\MySQL\MySQL Server 5.7\bin;%PATH%
- setx COMPOSER_NO_INTERACTION 1
- git config --global core.autocrlf input
- ps: Update-AppveyorBuild -Version "$($env:APPVEYOR_REPO_BRANCH) - $($env:APPVEYOR_REPO_COMMIT)"

# BEFORE services are launched.
install:
# Install PHP via WebPI.
- cInst webpicommandline -y
- WebPICMD /Install /Products:"PHP70" /AcceptEULA

# Install Composer.
- ps: mkdir C:\projects\php-cli-tools-bin -Force
- cd C:\projects\php-cli-tools-bin
- ps: Invoke-WebRequest https://getcomposer.org/composer.phar -Outfile composer.phar
- ps: Add-Content composer.bat "@php C:\projects\php-cli-tools-bin\composer.phar %*"
- RefreshEnv

# Install php-cli-tools dependencies.
- cd "%APPVEYOR_BUILD_FOLDER%"
- composer install --no-interaction --prefer-source

test_script:
# Run phpunit.
- vendor\bin\phpunit
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"require": {
"php": ">= 5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">=4"
},
"autoload": {
"psr-0": {
"cli": "lib/"
Expand Down
47 changes: 27 additions & 20 deletions lib/cli/Shell.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,25 @@ static public function columns() {
$columns = null;
}
if ( null === $columns ) {
if ( function_exists( 'exec' ) ) {
if ( self::is_windows() ) {
// Cater for shells such as Cygwin and Git bash where `mode CON` returns an incorrect value for columns.
if ( ( $shell = getenv( 'SHELL' ) ) && preg_match( '/(?:bash|zsh)(?:\.exe)?$/', $shell ) && getenv( 'TERM' ) ) {
$columns = (int) exec( 'tput cols' );
}
if ( ! $columns ) {
$return_var = -1;
$output = array();
exec( 'mode CON', $output, $return_var );
if ( 0 === $return_var && $output ) {
// Look for second line ending in ": <number>" (searching for "Columns:" will fail on non-English locales).
if ( preg_match( '/:\s*[0-9]+\n[^:]+:\s*([0-9]+)\n/', implode( "\n", $output ), $matches ) ) {
$columns = (int) $matches[1];
if ( ! ( $columns = (int) getenv( 'COLUMNS' ) ) ) {
if ( function_exists( 'exec' ) ) {
if ( self::is_windows() ) {
// Cater for shells such as Cygwin and Git bash where `mode CON` returns an incorrect value for columns.
if ( self::is_bashlike() && getenv( 'TERM' ) ) {
$columns = (int) exec( 'tput cols' );
}
if ( ! $columns ) {
$return_var = -1;
$output = array();
exec( 'mode CON', $output, $return_var );
if ( 0 === $return_var && $output ) {
// Look for second line ending in ": <number>" (searching for "Columns:" will fail on non-English locales).
if ( preg_match( '/:\s*[0-9]+\n[^:]+:\s*([0-9]+)\n/', implode( "\n", $output ), $matches ) ) {
$columns = (int) $matches[1];
}
}
}
}
} else {
if ( ! ( $columns = (int) getenv( 'COLUMNS' ) ) ) {
} else {
$size = exec( '/usr/bin/env stty size 2>/dev/null' );
if ( '' !== $size && preg_match( '/[0-9]+ ([0-9]+)/', $size, $matches ) ) {
$columns = (int) $matches[1];
Expand Down Expand Up @@ -103,15 +103,22 @@ static public function hide($hidden = true) {
system( 'stty ' . ( $hidden? '-echo' : 'echo' ) );
}

/**
* Is the shell bash-like (bash or zsh)? Useful to tell if running on Cygwin or Git bash under Windows.
*
* @return bool
*/
static public function is_bashlike() {
return ( $shell = getenv( 'SHELL' ) ) && preg_match( '/(?:bash|zsh)(?:\.exe)?$/', $shell );
}

/**
* Is this shell in Windows?
*
* @return bool
*/
static private function is_windows() {
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
return '\\' === DIRECTORY_SEPARATOR;
}

}

?>
16 changes: 10 additions & 6 deletions lib/cli/Streams.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ static function _call( $func, $args ) {
}

static public function isTty() {
return (function_exists('posix_isatty') && posix_isatty(static::$out));
// Use Shell version so `SHELL_PIPE` environment variable is respected.
return ! Shell::isPiped();
}

/**
Expand Down Expand Up @@ -257,23 +258,26 @@ public static function menu( $items, $default = null, $title = 'Choose an item'
* - 'err' (default: STDERR)
*
* Any custom streams will be closed for you on shutdown, so please don't close stream
* resources used with this method.
* resources used with this method, unless you set `$own_close`.
*
* @param string $whichStream The stream property to update
* @param resource $stream The new stream resource to use
* @param bool $own_close Optional. If set, stream won't be closed on shutdown. Default false.
* @return void
* @throws \Exception Thrown if $stream is not a resource of the 'stream' type.
*/
public static function setStream( $whichStream, $stream ) {
public static function setStream( $whichStream, $stream, $own_close = false ) {
if( !is_resource( $stream ) || get_resource_type( $stream ) !== 'stream' ) {
throw new \Exception( 'Invalid resource type!' );
}
if( property_exists( __CLASS__, $whichStream ) ) {
static::${$whichStream} = $stream;
}
register_shutdown_function( function() use ($stream) {
fclose( $stream );
} );
if ( ! $own_close ) {
register_shutdown_function( function() use ($stream) {
fclose( $stream );
} );
}
}

}
2 changes: 1 addition & 1 deletion lib/cli/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function getDisplayLines() {

foreach ($this->_rows as $row) {
$row = $this->_renderer->row($row);
$row = explode( PHP_EOL, $row );
$row = explode( "\n", normalize_eols( $row ) );
$out = array_merge( $out, $row );
}

Expand Down
10 changes: 10 additions & 0 deletions lib/cli/cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,13 @@ function get_unicode_regexs( $idx = null ) {

return array( $eaw_regex, $m_regex, );
}

/**
* Convert Windows EOLs to *nix.
*
* @param string $str String to convert.
* @return string String with carriage return / newline pairs reduced to newlines.
*/
function normalize_eols( $str ) {
return str_replace( "\r\n", "\n", $str );
}
2 changes: 1 addition & 1 deletion lib/cli/table/Ascii.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public function row( array $row ) {

foreach( $row as $col => $value ) {

$value = str_replace( array( "\r\n", "\n" ), ' ', $value );
$value = str_replace( "\n", ' ', \cli\normalize_eols( $value ) );

$col_width = $this->_widths[ $col ];
$encoding = function_exists( 'mb_detect_encoding' ) ? mb_detect_encoding( $value, null, true /*strict*/ ) : false;
Expand Down
4 changes: 3 additions & 1 deletion tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

require dirname( dirname( __FILE__ ) ) . '/lib/cli/cli.php';
if ( ! function_exists( '\cli\render' ) ) {
require dirname( dirname( __FILE__ ) ) . '/lib/cli/cli.php';
}

/**
* Compatibility with PHPUnit 6+
Expand Down
8 changes: 8 additions & 0 deletions tests/test-cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,12 @@ function test_safe_strlen() {
mb_detect_order( $mb_detect_order );
}
}

function test_normalize_eols() {
$this->assertSame( "blah\n", \cli\normalize_eols( "blah\r\n" ) );
$this->assertSame( "blah\nblah\nblah\n", \cli\normalize_eols( "blah\r\nblah\r\nblah\r\n" ) );
$this->assertSame( "blah\nblah\nblah\n", \cli\normalize_eols( "blah\r\nblah\nblah\r\n" ) );
$this->assertSame( "blah\rblah\nblah\n", \cli\normalize_eols( "blah\rblah\r\nblah\r\n" ) );
$this->assertSame( "blah\r\nblah\nblah\n", \cli\normalize_eols( "blah\r\r\nblah\r\nblah\r\n" ) );
}
}
97 changes: 87 additions & 10 deletions tests/test-shell.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ function testColumns() {
// Save.
$env_term = getenv( 'TERM' );
$env_columns = getenv( 'COLUMNS' );
$env_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' );
$env_shell_columns_reset = getenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' );

putenv( 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET=1' );
Expand All @@ -24,11 +23,6 @@ function testColumns() {
putenv( 'TERM' );
putenv( 'COLUMNS=80' );

putenv( 'WP_CLI_TEST_IS_WINDOWS=0' );
$columns = cli\Shell::columns();
$this->assertSame( 80, $columns );

putenv( 'WP_CLI_TEST_IS_WINDOWS=1' );
$columns = cli\Shell::columns();
$this->assertSame( 80, $columns );

Expand All @@ -37,18 +31,101 @@ function testColumns() {
putenv( 'TERM=vt100' );
putenv( 'COLUMNS=100' );

putenv( 'WP_CLI_TEST_IS_WINDOWS=0' );
$columns = cli\Shell::columns();
$this->assertSame( 100, $columns );

putenv( 'WP_CLI_TEST_IS_WINDOWS=1' );
// No TERM or COLUMNS could return anything so just check > 0.

putenv( 'TERM' );
putenv( 'COLUMNS' );

$columns = cli\Shell::columns();
$this->assertSame( 100, $columns );
$this->assertTrue( $columns > 0 );


// TERM and no COLUMNS could return anything so just check > 0.

putenv( 'TERM=vt100' );
putenv( 'COLUMNS' );

$columns = cli\Shell::columns();
$this->assertTrue( $columns > 0 );

// Restore.
putenv( false === $env_term ? 'TERM' : "TERM=$env_term" );
putenv( false === $env_columns ? 'COLUMNS' : "COLUMNS=$env_columns" );
putenv( false === $env_is_windows ? 'WP_CLI_TEST_IS_WINDOWS' : "WP_CLI_TEST_IS_WINDOWS=$env_is_windows" );
putenv( false === $env_shell_columns_reset ? 'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' : "PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET=$env_shell_columns_reset" );
}

/**
* Test whether STDOUT is piped or not.
*/
function testIsPiped() {
// Save.
$env_shell_pipe = getenv( 'SHELL_PIPE' );

$php = "require '" . dirname( __DIR__ ) . "/lib/cli/Shell.php'; exit( (int) \\cli\\Shell::isPiped() );";

putenv( 'SHELL_PIPE' );

// No `posix_isatty()` on Windows so only do real test on *nix.
if ( '\\' !== DIRECTORY_SEPARATOR ) {
exec( 'php -r ' . escapeshellarg( $php ) . ' 1>&0', $output, $return_var ); // Redirect STDOUT to STDIN to get tty in `exec()`.
$this->assertTrue( 0 === $return_var );

exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var ); // `exec()` pipes STDOUT.
$this->assertTrue( 1 === $return_var );

exec( 'php -r ' . escapeshellarg( $php ) . ' 1>/dev/null', $output, $return_var ); // Redirect STDOUT to null device.
$this->assertTrue( 1 === $return_var );
}

putenv( 'SHELL_PIPE=0' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var ); // `exec()` pipes STDOUT.
$this->assertTrue( 0 === $return_var );

putenv( 'SHELL_PIPE=1' );
exec( 'php -r ' . escapeshellarg( $php ) . ' 1>&0', $output, $return_var ); // Redirect STDOUT to STDIN to get tty in `exec()`.
$this->assertTrue( 1 === $return_var );

// Restore.
putenv( false === $env_shell_pipe ? 'SHELL_PIPE' : "SHELL_PIPE=$env_shell_pipe" );
}

/**
* Test whether shell is bash-like.
*/
function testIsBashlike() {
// Save.
$env_shell = getenv( 'SHELL' );

$php = "require '" . dirname( __DIR__ ) . "/lib/cli/Shell.php'; exit( (int) \\cli\\Shell::is_bashlike() );";

putenv( 'SHELL' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 0 === $return_var );

putenv( 'SHELL=/bin/sh' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 0 === $return_var );

putenv( 'SHELL=/bin/bash' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 1 === $return_var );

putenv( 'SHELL=/usr/bin/zsh' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 1 === $return_var );

putenv( 'SHELL=C:\\cygwin64\\bin\\bash.exe' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 1 === $return_var );

putenv( 'SHELL=C:\\Program Files\\Git\\usr\\bin\\bash.exe' );
exec( 'php -r ' . escapeshellarg( $php ), $output, $return_var );
$this->assertTrue( 1 === $return_var );

// Restore.
putenv( false === $env_shell ? 'SHELL' : "SHELL=$env_shell" );
}
}
Loading