diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7d38942
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,44 @@
+# Autodetect text files (convert CRLF => LF)
+* text=auto
+
+# ...Unless the name matches the following overriding patterns
+
+# Definitively text files
+*.c text
+*.clj text
+*.cpp text
+*.css text
+*.h text
+*.htm text
+*.html text
+*.java text
+*.js text
+*.json text
+*.less text
+*.lisp text
+*.php text
+*.py text
+*.rb text
+*.script text
+*.sql text
+*.txt text
+*.xml text
+*.yml text
+
+# Ensure those won't be messed up with
+*.gif binary
+*.jpg binary
+*.png binary
+
+*.jar binary
+
+*.doc binary
+*.DOC binary
+*.docx binary
+*.DOCX binary
+*.dot binary
+*.DOT binary
+*.pdf binary
+*.PDF binary
+*.rtf binary
+*.RTF binary
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..778247a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,14 @@
+# See: http://about.travis-ci.org/docs/user/build-configuration/
+
+language: php
+php:
+ - 5.3
+
+before_script:
+ - cp phpunit-dist.xml phpunit.xml
+ - composer install
+script: phpunit --configuration phpunit.xml
+
+notifications:
+ on_success: always
+ on_failure: always
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..cbc32d7
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,6 @@
+ChangeLog
+=========
+
+## Version 1.1.0 (2013-05-19)
+
+First release on Github.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..02bbb60
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c8e8cd2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,260 @@
+# Helpers
+[![Build Status](https://secure.travis-ci.org/geoffroy-aubry/Helpers.png?branch=stable)](http://travis-ci.org/geoffroy-aubry/Helpers)
+
+Some helpers used in several personal packages.
+
+## Description
+Static methods of `Helpers` class:
+
+* [arrayMergeRecursiveDistinct](#desc.arrayMergeRecursiveDistinct)
+* [exec](#desc.exec)
+* [flattenArray](#desc.flattenArray)
+* [intToMultiple](#desc.intToMultiple)
+* [numberFormat](#desc.numberFormat)
+* [isAssociativeArray](#desc.isAssociativeArray)
+* [round](#desc.round)
+* [stripBashColors](#desc.stripBashColors)
+* [strPutCSV](#desc.strPutCSV)
+* [ucwordWithDelimiters](#desc.ucwordWithDelimiters)
+* [utf8Encode](#desc.utf8Encode)
+
+
+### arrayMergeRecursiveDistinct()
+```php
+/**
+ * array_merge_recursive() does indeed merge arrays, but it converts values with duplicate
+ * keys to arrays rather than overwriting the value in the first array with the duplicate
+ * value in the second array, as array_merge does. I.e., with array_merge_recursive(),
+ * this happens (documented behavior):
+ *
+ * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
+ * ⇒ array('key' => array('org value', 'new value'));
+ *
+ * arrayMergeRecursiveDistinct() does not change the datatypes of the values in the arrays.
+ * Matching keys' values in the second array overwrite those in the first array, as is the
+ * case with array_merge, i.e.:
+ *
+ * arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
+ * ⇒ array('key' => array('new value'));
+ *
+ * EVO on indexed arrays:
+ * Before:
+ * arrayMergeRecursiveDistinct(array('a', 'b'), array('c')) => array('c', 'b')
+ * Now:
+ * ⇒ array('c')
+ *
+ * @param array $aArray1
+ * @param array $aArray2
+ * @return array An array of values resulted from strictly merging the arguments together.
+ * @author Daniel
+ * @author Gabriel Sobrinho
+ * @author Geoffroy Aubry
+ * @see http://fr2.php.net/manual/en/function.array-merge-recursive.php#89684
+ */
+public static function arrayMergeRecursiveDistinct (array $aArray1, array $aArray2);
+```
+
+
+### exec()
+```php
+/**
+ * Executes the given shell command and returns an array filled with every line of output from the command.
+ * Trailing whitespace, such as \n, is not included in this array.
+ * On shell error (error code <> 0), throws a RuntimeException with error message..
+ *
+ * @param string $sCmd shell command
+ * @param string $sOutputPath optional redirection of standard output
+ * @return array array filled with every line of output from the command
+ * @throws RuntimeException if shell error
+ */
+public static function exec ($sCmd, $sOutputPath='');
+```
+
+
+### flattenArray()
+```php
+/**
+ * Flatten a multidimensional array (keys are ignored).
+ *
+ * @param array $aArray
+ * @return array a one dimensional array.
+ * @see http://stackoverflow.com/a/1320156/1813519
+ */
+public static function flattenArray (array $aArray);
+```
+Example:
+```php
+$a = array(
+ 1,
+ 'a' => array(
+ 'b' => array('c', 2),
+ 'd'
+ )
+);
+print_r(Helpers::flattenArray($a));
+```
+⇒
+```php
+Array(
+ [0] => 1
+ [1] => 'c'
+ [2] => 2
+ [3] => 'd'
+)
+```
+
+
+### intToMultiple()
+```php
+/**
+ * Returns specified value in the most appropriate unit, with that unit.
+ * If $bBinaryPrefix is FALSE then use SI units (i.e. k, M, G, T),
+ * else use IED units (i.e. Ki, Mi, Gi, Ti).
+ * @see http://en.wikipedia.org/wiki/Binary_prefix
+ *
+ * @param int $iValue
+ * @param bool $bBinaryPrefix
+ * @return array a pair constituted by specified value in the most appropriate unit and that unit
+ */
+public static function intToMultiple ($iValue, $bBinaryPrefix=false);
+```
+Example:
+```php
+print_r(Helpers::intToMultiple(17825792, false));
+print_r(Helpers::intToMultiple(17825792, true));
+```
+⇒
+```php
+Array(
+ [0] => 17.825792
+ [1] => 'M'
+)
+Array(
+ [0] => 17
+ [1] => 'Mi'
+)
+```
+
+
+### numberFormat()
+```php
+/**
+ * Format a number with grouped thousands.
+ * It is an extended version of number_format() that allows do not specify $decimals.
+ *
+ * @param float $fNumber The number being formatted.
+ * @param string $sDecPoint Sets the separator for the decimal point.
+ * @param string $sThousandsSep Sets the thousands separator. Only the first character of $thousands_sep is used.
+ * @param int $iDecimals Sets the number of decimal points.
+ * @return string A formatted version of $number.
+ */
+public static function numberFormat ($fNumber, $sDecPoint='.', $sThousandsSep=',', $iDecimals=null);
+```
+
+
+### isAssociativeArray()
+```php
+/**
+ * Returns TRUE iff the specified array is associative.
+ * Returns FALSE if the specified array is empty.
+ *
+ * http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential
+ *
+ * @param array $aArray
+ * @return bool true ssi iff the specified array is associative
+ */
+public static function isAssociativeArray (array $aArray);
+```
+
+
+### round()
+```php
+/**
+ * Rounds specified value with precision $iPrecision as native round() function, but keep trailing zeros.
+ *
+ * @param float $fValue value to round
+ * @param int $iPrecision the optional number of decimal digits to round to (can also be negative)
+ * @return string
+ */
+public static function round ($fValue, $iPrecision=0);
+```
+
+
+### stripBashColors()
+```php
+/**
+ * Remove all Bash color sequences from the specified string.
+ *
+ * @param string $sMsg
+ * @return string specified string without any Bash color sequence.
+ */
+public static function stripBashColors ($sMsg);
+```
+
+
+### strPutCSV()
+```php
+/**
+ * Formats a line passed as a fields array as CSV and return it, without the trailing newline.
+ * Inspiration: http://www.php.net/manual/en/function.str-getcsv.php#88773
+ *
+ * @param array $aInput
+ * @param string $sDelimiter
+ * @param string $sEnclosure
+ * @return string specified array converted into CSV format string
+ */
+public static function strPutCSV ($aInput, $sDelimiter = ',', $sEnclosure = '"');
+```
+
+
+### ucwordWithDelimiters()
+```php
+/**
+ * Returns a string with the first character of each word in specified string capitalized,
+ * if that character is alphabetic.
+ * Additionally, each character that is immediately after one of $aDelimiters will be capitalized too.
+ *
+ * @param string $sString
+ * @param array $aDelimiters
+ * @return string
+ */
+public static function ucwordWithDelimiters ($sString, array $aDelimiters=array());
+```
+Eaxmple:
+```php
+echo Helpers::ucwordWithDelimiters("hel-lo wo'rld", array('-', "'"));
+```
+⇒
+```php
+"Hel-Lo Wo'Rld"
+```
+
+
+### utf8Encode()
+```php
+/**
+ * Returns the UTF-8 translation of the specified string, only if not already in UTF-8.
+ *
+ * @param string $s
+ * @return string the UTF-8 translation of the specified string, only if not already in UTF-8.
+ */
+public static function utf8Encode ($s);
+```
+
+## Copyrights & licensing
+Licensed under the GNU Lesser General Public License v3 (LGPL version 3).
+See [LICENSE](https://github.com/geoffroy-aubry/Helpers/blob/stable/LICENSE) file for details.
+
+## ChangeLog
+See [CHANGELOG](https://github.com/geoffroy-aubry/Helpers/blob/stable/CHANGELOG.md) file for details.
+
+## Running tests
+To run the test suite, simply:
+
+```bash
+$ cp phpunit-dist.php phpunit.php # and adapt, if necessary
+$ phpunit -c phpunit.xml
+```
+
+## Git branching model
+The git branching model used for development is the one described and assisted by `twgit` tool: [https://github.com/Twenga/twgit](https://github.com/Twenga/twgit).
diff --git a/composer.json b/composer.json
index f9795f6..e4e1d31 100644
--- a/composer.json
+++ b/composer.json
@@ -1,19 +1,19 @@
{
- "name": "geoffroy-aubry/tools",
- "description": "bla bla",
- "keywords": ["bla"],
+ "name": "geoffroy-aubry/helpers",
+ "description": "Some helpers used in several personal packages.",
+ "keywords": ["helpers"],
"type": "library",
"license": "LGPL-3.0+",
"authors": [
{
"name": "Geoffroy Aubry",
- "email": "gaubry@hi-media.com"
+ "email": "geoffroy.aubry@free.fr"
}
],
"require": {
"php": ">=5.3.3"
},
"autoload": {
- "psr-0": {"GAubry\\Tools": "src/"}
+ "psr-0": {"GAubry\\Helpers": "src/"}
}
}
diff --git a/phpunit-dist.xml b/phpunit-dist.xml
index 570b6a1..9708f90 100644
--- a/phpunit-dist.xml
+++ b/phpunit-dist.xml
@@ -9,7 +9,7 @@
convertWarningsToExceptions="true"
syntaxCheck="true"
processIsolation="false"
- colors="true"
+ colors="false"
strict="true"
verbose="true"
stopOnFailure="false"
@@ -27,7 +27,7 @@
-
+
tests
@@ -35,7 +35,7 @@
+ * Licensed under the GNU Lesser General Public License v3 (LGPL version 3).
+ *
+ * @copyright 2013 Geoffroy Aubry
+ * @license http://www.gnu.org/licenses/lgpl.html
+ */
+class Helpers
+{
+ private function __construct()
+ {
+ }
+
+ /**
+ * Flatten a multidimensional array (keys are ignored).
+ *
+ * @param array $aArray
+ * @return array a one dimensional array.
+ * @see http://stackoverflow.com/a/1320156/1813519
+ */
+ public static function flattenArray (array $aArray) {
+ $aFlattened = array();
+ array_walk_recursive($aArray, function($a) use (&$aFlattened) {$aFlattened[] = $a;});
+ return $aFlattened;
+ }
+
+ /**
+ * Returns the UTF-8 translation of the specified string, only if not already in UTF-8.
+ *
+ * @param string $s
+ * @return string the UTF-8 translation of the specified string, only if not already in UTF-8.
+ */
+ public static function utf8Encode ($s)
+ {
+ return (utf8_encode(utf8_decode($s)) == $s ? $s : utf8_encode($s));
+ }
+
+ /**
+ * Executes the given shell command and returns an array filled with every line of output from the command.
+ * Trailing whitespace, such as \n, is not included in this array.
+ * On shell error (error code <> 0), throws a RuntimeException with error message..
+ *
+ * @param string $sCmd shell command
+ * @param string $sOutputPath optional redirection of standard output
+ * @return array array filled with every line of output from the command
+ * @throws RuntimeException if shell error
+ */
+ public static function exec ($sCmd, $sOutputPath='')
+ {
+ if (empty($sOutputPath)) {
+ $sFullCmd = '( ' . $sCmd . ' ) 2>&1';
+ } else {
+ $sFullCmd = "( $sCmd ) 1>$sOutputPath 2>&1 & echo $!";
+ }
+ exec($sFullCmd, $aResult, $iReturnCode);
+ if ($iReturnCode !== 0) {
+ throw new RuntimeException(
+ "Exit code not null: $iReturnCode. Result: '" . implode("\n", $aResult) . "'",
+ $iReturnCode
+ );
+ }
+ return $aResult;
+ }
+
+ /**
+ * Remove all Bash color sequences from the specified string.
+ *
+ * @param string $sMsg
+ * @return string specified string without any Bash color sequence.
+ */
+ public static function stripBashColors ($sMsg)
+ {
+ return preg_replace('/\x1B\[([0-9]{1,2}(;[0-9]{1,2}){0,2})?[m|K]/', '', $sMsg);
+ }
+
+ /**
+ * Rounds specified value with precision $iPrecision as native round() function, but keep trailing zeros.
+ *
+ * @param float $fValue value to round
+ * @param int $iPrecision the optional number of decimal digits to round to (can also be negative)
+ * @return string
+ */
+ public static function round ($fValue, $iPrecision=0)
+ {
+ $sPrintfPrecision = max(0, $iPrecision);
+ return sprintf("%01.{$sPrintfPrecision}f", round($fValue, $iPrecision));
+ }
+
+ /**
+ * Returns a string with the first character of each word in specified string capitalized,
+ * if that character is alphabetic.
+ * Additionally, each character that is immediately after one of $aDelimiters will be capitalized too.
+ *
+ * @param string $sString
+ * @param array $aDelimiters
+ * @return string
+ */
+ public static function ucwordWithDelimiters ($sString, array $aDelimiters=array())
+ {
+ $sReturn = ucwords($sString);
+ foreach ($aDelimiters as $sDelimiter) {
+ if (strpos($sReturn, $sDelimiter) !== false) {
+ $sReturn = implode($sDelimiter, array_map('ucfirst', explode($sDelimiter, $sReturn)));
+ }
+ }
+ return $sReturn;
+ }
+
+ /**
+ * Returns specified value in the most appropriate unit, with that unit.
+ * If $bBinaryPrefix is FALSE then use SI units (i.e. k, M, G, T),
+ * else use IED units (i.e. Ki, Mi, Gi, Ti).
+ * @see http://en.wikipedia.org/wiki/Binary_prefix
+ *
+ * @param int $iValue
+ * @param bool $bBinaryPrefix
+ * @return array a pair constituted by specified value in the most appropriate unit and that unit
+ */
+ public static function intToMultiple ($iValue, $bBinaryPrefix=false)
+ {
+ static $aAllPrefixes = array(
+ 10 => array(12 => 'T', 9 => 'G', 6 => 'M', 3 => 'k', 0 => ''),
+ 2 => array(40 => 'Ti', 30 => 'Gi', 20 => 'Mi', 10 => 'Ki', 0 => ''),
+ );
+
+ $iBase = ($bBinaryPrefix ? 2 : 10);
+ $aPrefixes = $aAllPrefixes[$iBase];
+ $m = 0;
+ foreach (array_keys($aPrefixes) as $iMultiple) {
+ if ($iValue >= pow($iBase, $iMultiple)) {
+ $m = $iMultiple;
+ break;
+ }
+ }
+
+ return array($iValue / pow($iBase, $m), $aPrefixes[$m]);
+ }
+
+ /**
+ * Format a number with grouped thousands.
+ * It is an extended version of number_format() that allows do not specify $decimals.
+ *
+ * @param float $fNumber The number being formatted.
+ * @param string $sDecPoint Sets the separator for the decimal point.
+ * @param string $sThousandsSep Sets the thousands separator. Only the first character of $thousands_sep is used.
+ * @param int $iDecimals Sets the number of decimal points.
+ * @return string A formatted version of $number.
+ */
+ public static function numberFormat ($fNumber, $sDecPoint='.', $sThousandsSep=',', $iDecimals=null)
+ {
+ if ($iDecimals !== null) {
+ return number_format($fNumber, $iDecimals, $sDecPoint, $sThousandsSep);
+ } else {
+ $tmp = explode('.', $fNumber);
+ $out = number_format($tmp[0], 0, $sDecPoint, $sThousandsSep);
+ if (isset($tmp[1])) {
+ $out .= $sDecPoint.$tmp[1];
+ }
+ return $out;
+ }
+ }
+
+ /**
+ * Formats a line passed as a fields array as CSV and return it, without the trailing newline.
+ * Inspiration: http://www.php.net/manual/en/function.str-getcsv.php#88773
+ *
+ * @param array $aInput
+ * @param string $sDelimiter
+ * @param string $sEnclosure
+ * @return string specified array converted into CSV format string
+ */
+ public static function strPutCSV ($aInput, $sDelimiter = ',', $sEnclosure = '"')
+ {
+ // Open a memory "file" for read/write...
+ $fp = fopen('php://temp', 'r+');
+ fputcsv($fp, $aInput, $sDelimiter, $sEnclosure);
+ // ... rewind the "file" so we can read what we just wrote...
+ rewind($fp);
+ $sData = fgets($fp);
+ fclose($fp);
+ // ... and return the $data to the caller, with the trailing newline from fgets() removed.
+ return rtrim($sData, "\n");
+ }
+
+ /**
+ * array_merge_recursive() does indeed merge arrays, but it converts values with duplicate
+ * keys to arrays rather than overwriting the value in the first array with the duplicate
+ * value in the second array, as array_merge does. I.e., with array_merge_recursive(),
+ * this happens (documented behavior):
+ *
+ * array_merge_recursive(array('key' => 'org value'), array('key' => 'new value'));
+ * ⇒ array('key' => array('org value', 'new value'));
+ *
+ * arrayMergeRecursiveDistinct() does not change the datatypes of the values in the arrays.
+ * Matching keys' values in the second array overwrite those in the first array, as is the
+ * case with array_merge, i.e.:
+ *
+ * arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value'));
+ * ⇒ array('key' => array('new value'));
+ *
+ * EVO on indexed arrays:
+ * Before:
+ * arrayMergeRecursiveDistinct(array('a', 'b'), array('c')) ⇒ array('c', 'b')
+ * Now:
+ * ⇒ array('c')
+ *
+ * @param array $aArray1
+ * @param array $aArray2
+ * @return array An array of values resulted from strictly merging the arguments together.
+ * @author Daniel
+ * @author Gabriel Sobrinho
+ * @author Geoffroy Aubry
+ * @see http://fr2.php.net/manual/en/function.array-merge-recursive.php#89684
+ */
+ public static function arrayMergeRecursiveDistinct (array $aArray1, array $aArray2)
+ {
+ $aMerged = $aArray1;
+ if (self::isAssociativeArray($aMerged)) {
+ foreach ($aArray2 as $key => &$value) {
+ if (is_array($value) && isset($aMerged[$key]) && is_array($aMerged[$key])) {
+ $aMerged[$key] = self::arrayMergeRecursiveDistinct($aMerged[$key], $value);
+ } else {
+ $aMerged[$key] = $value;
+ }
+ }
+ } else {
+ $aMerged = $aArray2;
+ }
+ return $aMerged;
+ }
+
+ /**
+ * Returns TRUE iff the specified array is associative.
+ * Returns FALSE if the specified array is empty.
+ *
+ * http://stackoverflow.com/questions/173400/php-arrays-a-good-way-to-check-if-an-array-is-associative-or-sequential
+ *
+ * @param array $aArray
+ * @return bool true ssi iff the specified array is associative
+ */
+ public static function isAssociativeArray (array $aArray) {
+ foreach (array_keys($aArray) as $key) {
+ if ( ! is_int($key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/GAubry/Tools/Tools.php b/src/GAubry/Tools/Tools.php
deleted file mode 100644
index 740b24d..0000000
--- a/src/GAubry/Tools/Tools.php
+++ /dev/null
@@ -1,196 +0,0 @@
- 0), lance une exception incluant le message d'erreur.
- *
- * @param string $sCmd
- * @return array tableau indexé du flux de sortie shell découpé par ligne
- * @throws RuntimeException en cas d'erreur shell
- */
- public static function exec ($sCmd, $sOutputPath='')
- {
- if (empty($sOutputPath)) {
- $sFullCmd = '( ' . $sCmd . ' ) 2>&1';
- } else {
- $sFullCmd = "( $sCmd ) 1>$sOutputPath 2>&1 & echo $!";
- }
- exec($sFullCmd, $aResult, $iReturnCode);
- if ($iReturnCode !== 0) {
- throw new RuntimeException(
- "Exit code not null: $iReturnCode. Result: '" . implode("\n", $aResult) . "'",
- $iReturnCode
- );
- }
- return $aResult;
- }
-
- public static function stripBashColors ($sMsg)
- {
- return preg_replace('/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/', '', $sMsg);
- }
-
- // Allow negative precision.
- public static function round ($fValue, $iPrecision=0)
- {
- $sPrintfPrecision = max(0, $iPrecision);
- return sprintf("%01.{$sPrintfPrecision}f", round($fValue, $iPrecision));
- }
-
- public static function ucwordWithDelimiters ($str, array $aDelimiters=array("'", '-')){
- $sReturn = ucwords(strtolower($str));
- foreach ($aDelimiters as $sDelimiter) {
- if (strpos($sReturn, $sDelimiter) !== false) {
- $sReturn = implode($sDelimiter, array_map('ucfirst', explode($sDelimiter, $sReturn)));
- }
- }
- return $sReturn;
- }
-
- public static function intToSI ($iValue)
- {
- $prefixes = array(12 => 'T', 9 => 'G', 6 => 'M', 3 => 'k', 0 => '');
-
- $m = 0;
- foreach ($prefixes as $multiple => $s) {
- if ($iValue >= pow(10, $multiple)) {
- $m = $multiple;
- break;
- }
- }
-
- //$decimals = ($m > 0 && $val/pow(10, $m) < 100 ? 1 : 0);
- $decimals = ($m === 0 ? 0 : 1);
- return array(round($iValue / pow(10, $m), $decimals), $prefixes[$m]);
- }
-
- /**
- * Format a number with grouped thousands.
- * It is an extended version of number_format() that allow do not specify $decimals.
- *
- * @param float $fNumber The number being formatted.
- * @param string $sDecPoint Sets the separator for the decimal point.
- * @param string $sThousandsSep Sets the thousands separator. Only the first character of $thousands_sep is used.
- * @param int $iDecimals Sets the number of decimal points.
- * @return string A formatted version of $number.
- */
- public static function numberFormat ($fNumber, $sDecPoint='.', $sThousandsSep=',', $iDecimals=NULL)
- {
- if ($iDecimals !== NULL) {
- return number_format($fNumber, $iDecimals, $sDecPoint, $sThousandsSep);
- } else {
- $tmp = explode('.', $fNumber);
- $out = number_format($tmp[0], 0, $sDecPoint, $sThousandsSep);
- if (isset($tmp[1])) {
- $out .= $sDecPoint.$tmp[1];
- }
- return $out;
- }
- }
-
- /**
- * Retourne un couple comprenant d'une part le nombre d'octets contenus dans la plus grande unité informatique
- * inférieure à la taille spécifiée, et d'autre part le nom de cette unité.
- *
- * Par exemple, si $iFileSize vaut 2000, alors le résultat sera : array(1024, 'Kio').
- *
- * @param int $iFileSize taille en octets à changer d'unité
- * @return array tableau (int, string) comprenant d'une part le nombre d'octets contenus dans la plus grande
- * unité inférieure à la taille spécifiée, et d'autre part le nom de cette unité.
- */
- public static function getFileSizeUnit ($iFileSize)
- {
- if ($iFileSize < 1024) {
- $iUnit = 1;
- $sUnit = 'o';
- } else if ($iFileSize < 1024*1024) {
- $iUnit = 1024;
- $sUnit = 'Kio';
- } else {
- $iUnit = 1024*1024;
- $sUnit = 'Mio';
- }
- return array($iUnit, $sUnit);
- }
-
- /**
- * Retourne un couple comprenant d'une part la taille spécifiée arrondie,
- * et d'autre part l'unité dans laquelle la taille a été arrondie.
- *
- * Le second paramètre, si <> de 0, permet de spécifier une taille de référence pour le calcul de l'unité.
- *
- * Par exemple :
- * (100, 0) => ('100', 'o')
- * (100, 2000000) => ('<1', 'Mio')
- * (200, 0) => ('2', 'Kio')
- *
- * @param int $iSize taille à convertir
- * @param int $iRefSize référentiel de conversion, si différent de 0
- * @return array un couple comprenant d'une part la taille spécifiée arrondie,
- * et d'autre part l'unité dans laquelle la taille a été arrondie.
- */
- public static function convertFileSize2String ($iSize, $iRefSize=0)
- {
- if ($iRefSize === 0) {
- $iRefSize = $iSize;
- }
- list($iUnit, $sUnit) = self::getFileSizeUnit($iRefSize);
-
- $sFileSize = round($iSize/$iUnit);
- if ($sFileSize == 0 && $iSize > 0) {
- $sFileSize = '<1';
- }
- return array($sFileSize, $sUnit);
- }
-
- public static function strPutCSV ($input, $delimiter = ',', $enclosure = '"') {
- // Open a memory "file" for read/write...
- $fp = fopen('php://temp', 'r+');
- // ... write the $input array to the "file" using fputcsv()...
- fputcsv($fp, $input, $delimiter, $enclosure);
- // ... rewind the "file" so we can read what we just wrote...
- rewind($fp);
- // ... read the entire line into a variable...
- $data = fgets($fp);
- // ... close the "file"...
- fclose($fp);
- // ... and return the $data to the caller, with the trailing newline from fgets() removed.
- return rtrim( $data, "\n" );
- }
-}
diff --git a/tests/GAubry/Helpers/HelpersTest.php b/tests/GAubry/Helpers/HelpersTest.php
new file mode 100644
index 0000000..cfce02e
--- /dev/null
+++ b/tests/GAubry/Helpers/HelpersTest.php
@@ -0,0 +1,239 @@
+
+ */
+class HelpersTest extends \PHPUnit_Framework_TestCase
+{
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::arrayMergeRecursiveDistinct
+ * @dataProvider dataProvider_testArrayMergeRecursiveDistinct
+ *
+ * @param array $aArray1
+ * @param array $aArray2
+ * @param array $aResult
+ */
+ public function testArrayMergeRecursiveDistinct (array $aArray1, array $aArray2, array $aExpected)
+ {
+ $this->assertEquals($aExpected, Helpers::arrayMergeRecursiveDistinct($aArray1, $aArray2));
+ }
+
+ /**
+ * Data provider pour testArrayMergeRecursiveDistinct()
+ */
+ public function dataProvider_testArrayMergeRecursiveDistinct ()
+ {
+ $aArray1 = array('a' => 'b', 'c' => array('d' => 'e'));
+ return array(
+ array(array(), array(), array()),
+ array($aArray1, array(), $aArray1),
+ array(array(), $aArray1, $aArray1),
+
+ array(array(1, 2), array(3), array(3)),
+ array(array(3), array(1, 2), array(1, 2)),
+
+ array(array('a', 'b'), array('c'), array('c')),
+ array(array('c'), array('a', 'b'), array('a', 'b')),
+
+ array(array(3 => 'a', 'b' => 'c'), array(3 => null), array(3 => null, 'b' => 'c')),
+ array(array(3 => null), array(3 => 'a', 'b' => 'c'), array(3 => 'a', 'b' => 'c')),
+
+ array(array('a' => 'b'), array('a' => array(1, 2)), array('a' => array(1, 2))),
+ array(array('a' => array(1, 2)), array('a' => 'b'), array('a' => 'b')),
+ array(array('a' => array(1, 2)), array('a' => array(3)), array('a' => array(3))),
+
+ array($aArray1, array('a' => 'x'), array('a' => 'x', 'c' => array('d' => 'e'))),
+ array(array('a' => 'x'), $aArray1, array('a' => 'b', 'c' => array('d' => 'e'))),
+
+ array(
+ $aArray1,
+ array('c' => array('d' => 'x'), 'y' => 'z'),
+ array('a' => 'b', 'c' => array('d' => 'x'), 'y' => 'z')
+ ),
+ array(
+ array('c' => array('d' => 'x'), 'y' => 'z'),
+ $aArray1,
+ array('a' => 'b', 'c' => array('d' => 'e'), 'y' => 'z')
+ ),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::isAssociativeArray
+ * @dataProvider dataProvider_testIsAssociativeArray
+ *
+ * @param array $aArray
+ * @param bool $bResult
+ */
+ public function testIsAssociativeArray ($aArray, $bExpected)
+ {
+ $this->assertEquals($bExpected, Helpers::isAssociativeArray($aArray));
+ }
+
+ /**
+ * Data provider pour testIsAssociativeArray()
+ */
+ public function dataProvider_testIsAssociativeArray ()
+ {
+ return array(
+ array(array(), false),
+ array(array('a'), false),
+ array(array('a' => 1), true),
+ array(array(1, 2), false),
+ array(array(1, 'a' => 2, 3), true),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::stripBashColors
+ * @dataProvider dataProvider_testStripBashColors
+ *
+ * @param string $sSource
+ * @param string $sExpected
+ */
+ public function testStripBashColors ($sSource, $sExpected)
+ {
+ $this->assertEquals($sExpected, Helpers::stripBashColors($sSource));
+ }
+
+ /**
+ * Data provider pour testStripBashColors()
+ */
+ public function dataProvider_testStripBashColors ()
+ {
+ return array(
+ array('', ''),
+ array('a', 'a'),
+ array("\033[0m", ''),
+ array("\033[1;34m", ''),
+ array("\033[5;32;47m", ''),
+ array("\033[1;34mxyz", 'xyz'),
+ array("xyz\033[1;34m", 'xyz'),
+ array("x\033[1;34my\033[0mz", 'xyz'),
+ array("x\x1B[1;34my\x1B[0mz", 'xyz'),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::flattenArray
+ * @dataProvider dataProvider_testFlattenArray
+ *
+ * @param array $aSource
+ * @param array $aExpected
+ */
+ public function testFlattenArray (array $aSource, array $aExpected)
+ {
+ $this->assertEquals($aExpected, Helpers::flattenArray($aSource));
+ }
+
+ /**
+ * Data provider pour testFlattenArray()
+ */
+ public function dataProvider_testFlattenArray ()
+ {
+ return array(
+ array(array(), array()),
+ array(array(1), array(1)),
+ array(array('a' => 'b'), array('b')),
+ array(array(1, 'a' => 'b'), array(1, 'b')),
+ array(array(array('a' => 'b')), array('b')),
+ array(array(1, 'a' => array('b' => array('c', 2), 'd')), array(1, 'c', 2, 'd')),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::intToMultiple
+ * @dataProvider dataProvider_testIntToMultiple
+ *
+ * @param int $iValue
+ * @param array $aExpected
+ */
+ public function testIntToMultiple ($iValue, $bBinaryPrefix, array $aExpected)
+ {
+ $this->assertEquals($aExpected, Helpers::intToMultiple($iValue, $bBinaryPrefix));
+ }
+
+ /**
+ * Data provider pour testIntToMultiple()
+ */
+ public function dataProvider_testIntToMultiple ()
+ {
+ return array(
+ array(0, false, array(0, '')),
+ array(10, false, array(10, '')),
+ array(1024, false, array(1.024, 'k')),
+ array(17825792, false, array(17.825792, 'M')),
+ array(1073741824, false, array(1.073741824, 'G')),
+
+ array(0, true, array(0, '')),
+ array(10, true, array(10, '')),
+ array(1024, true, array(1, 'Ki')),
+ array(17825792, true, array(17, 'Mi')),
+ array(1073741824, true, array(1, 'Gi')),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::round
+ * @dataProvider dataProvider_testRound
+ *
+ * @param float $fValue
+ * @param int $iPrecision
+ * @param string $sExpected
+ */
+ public function testRound ($fValue, $iPrecision, $sExpected)
+ {
+ $this->assertEquals($sExpected, Helpers::round($fValue, $iPrecision));
+ }
+
+ /**
+ * Data provider pour testRound()
+ */
+ public function dataProvider_testRound ()
+ {
+ return array(
+ array(0, 0, '0'),
+ array(1, 3, '1.000'),
+ array(156.789, 0, '157'),
+ array(156.789, 2, '156.79'),
+ array(156.789, 4, '156.7890'),
+ array(156.789, -1, '160'),
+ array(156.789, -2, '200'),
+ array(156.789, -3, '0'),
+ );
+ }
+
+ /**
+ * @covers \GAubry\Helpers\Helpers::ucwordWithDelimiters
+ * @dataProvider dataProvider_testUcwordWithDelimiters
+ *
+ * @param string $sString
+ * @param array $aDelimiters
+ * @param string $sExpected
+ */
+ public function testUcwordWithDelimiters ($sString, array $aDelimiters, $sExpected)
+ {
+ $this->assertEquals($sExpected, Helpers::ucwordWithDelimiters($sString, $aDelimiters));
+ }
+
+ /**
+ * Data provider pour testRound()
+ */
+ public function dataProvider_testUcwordWithDelimiters ()
+ {
+ return array(
+ array('hello world', array(), 'Hello World'),
+ array('HELLO world', array(), 'HELLO World'),
+ array('hel-lo world', array(), 'Hel-lo World'),
+ array('hel-lo world', array('-'), 'Hel-Lo World'),
+ array("hel-lo wo'rld", array('-', "'"), "Hel-Lo Wo'Rld"),
+ );
+ }
+}
diff --git a/tests/GAubry/Tools/ToolsTest.php b/tests/GAubry/Tools/ToolsTest.php
deleted file mode 100644
index c4b00ec..0000000
--- a/tests/GAubry/Tools/ToolsTest.php
+++ /dev/null
@@ -1,68 +0,0 @@
-
- */
-class ToolsTest extends \PHPUnit_Framework_TestCase
-{
-
- /**
- * @covers \GAubry\Tools\Tools::getFileSizeUnit
- * @dataProvider dataProvider_testGetFileSizeUnit
- * @param int $iFileSize taille en octets à changer d'unité
- * @param array $aExpected tableau (int, string) comprenant d'une part le nombre d'octets contenus dans la plus grande
- * unité inférieure à la taille spécifiée, et d'autre part le nom de cette unité.
- */
- public function testGetFileSizeUnit ($iFileSize, $aExpected)
- {
- $aResult = Tools::getFileSizeUnit($iFileSize);
- $this->assertEquals($aExpected, $aResult);
- }
-
- /**
- * Data provider pour testGetFileSizeUnit()
- */
- public static function dataProvider_testGetFileSizeUnit ()
- {
- return array(
- array(0, array(1, 'o')),
- array(100, array(1, 'o')),
- array(2000, array(1024, 'Kio')),
- array(2000000, array(1024*1024, 'Mio')),
- );
- }
-
- /**
- * @covers \GAubry\Tools\Tools::convertFileSize2String
- * @dataProvider dataProvider_testConvertFileSize2String
- * @param int $iSize taille à convertir
- * @param int $iRefSize référentiel de conversion, si différent de 0
- * @param array $aExpected un couple comprenant d'une part la taille spécifiée arrondie,
- * et d'autre part l'unité dans laquelle la taille a été arrondie.
- */
- public function testConvertFileSize2String ($iSize, $iRefSize, $aExpected)
- {
- $aResult = Tools::convertFileSize2String($iSize, $iRefSize);
- $this->assertEquals($aExpected, $aResult);
- }
-
- /**
- * Data provider pour testConvertFileSize2String()
- */
- public static function dataProvider_testConvertFileSize2String ()
- {
- return array(
- array(0, 0, array('0', 'o')),
- array(100, 0, array('100', 'o')),
- array(100, 2000000, array('<1', 'Mio')),
- array(2000, 0, array('2', 'Kio')),
- array(2000000, 0, array('2', 'Mio')),
- );
- }
-}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 77e6cb7..3ad8db8 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -12,5 +12,4 @@
exit(1);
}
-$oLoader = require __DIR__ . '/../vendor/autoload.php';
-$oLoader->add('GAubry\Tools\Tests', __DIR__);
+require __DIR__ . '/../vendor/autoload.php';