| 
<?phpnamespace JmesPath;
 
 class Utils
 {
 static $typeMap = [
 'boolean' => 'boolean',
 'string'  => 'string',
 'NULL'    => 'null',
 'double'  => 'number',
 'float'   => 'number',
 'integer' => 'number'
 ];
 
 /**
 * Returns true if the value is truthy
 *
 * @param mixed $value Value to check
 *
 * @return bool
 */
 public static function isTruthy($value)
 {
 if (!$value) {
 return $value === 0 || $value === '0';
 } elseif ($value instanceof \stdClass) {
 return (bool) get_object_vars($value);
 } else {
 return true;
 }
 }
 
 /**
 * Gets the JMESPath type equivalent of a PHP variable.
 *
 * @param mixed $arg PHP variable
 * @return string Returns the JSON data type
 * @throws \InvalidArgumentException when an unknown type is given.
 */
 public static function type($arg)
 {
 $type = gettype($arg);
 if (isset(self::$typeMap[$type])) {
 return self::$typeMap[$type];
 } elseif ($type === 'array') {
 if (empty($arg)) {
 return 'array';
 }
 reset($arg);
 return key($arg) === 0 ? 'array' : 'object';
 } elseif ($arg instanceof \stdClass) {
 return 'object';
 } elseif ($arg instanceof \Closure) {
 return 'expression';
 } elseif ($arg instanceof \ArrayAccess
 && $arg instanceof \Countable
 ) {
 return count($arg) == 0 || $arg->offsetExists(0)
 ? 'array'
 : 'object';
 } elseif (method_exists($arg, '__toString')) {
 return 'string';
 }
 
 throw new \InvalidArgumentException(
 'Unable to determine JMESPath type from ' . get_class($arg)
 );
 }
 
 /**
 * Determine if the provided value is a JMESPath compatible object.
 *
 * @param mixed $value
 *
 * @return bool
 */
 public static function isObject($value)
 {
 if (is_array($value)) {
 return !$value || array_keys($value)[0] !== 0;
 }
 
 // Handle array-like values. Must be empty or offset 0 does not exist
 return $value instanceof \Countable && $value instanceof \ArrayAccess
 ? count($value) == 0 || !$value->offsetExists(0)
 : $value instanceof \stdClass;
 }
 
 /**
 * Determine if the provided value is a JMESPath compatible array.
 *
 * @param mixed $value
 *
 * @return bool
 */
 public static function isArray($value)
 {
 if (is_array($value)) {
 return !$value || array_keys($value)[0] === 0;
 }
 
 // Handle array-like values. Must be empty or offset 0 exists.
 return $value instanceof \Countable && $value instanceof \ArrayAccess
 ? count($value) == 0 || $value->offsetExists(0)
 : false;
 }
 
 /**
 * JSON aware value comparison function.
 *
 * @param mixed $a First value to compare
 * @param mixed $b Second value to compare
 *
 * @return bool
 */
 public static function isEqual($a, $b)
 {
 if ($a === $b) {
 return true;
 } elseif ($a instanceof \stdClass) {
 return self::isEqual((array) $a, $b);
 } elseif ($b instanceof \stdClass) {
 return self::isEqual($a, (array) $b);
 } else {
 return false;
 }
 }
 
 /**
 * JMESPath requires a stable sorting algorithm, so here we'll implement
 * a simple Schwartzian transform that uses array index positions as tie
 * breakers.
 *
 * @param array    $data   List or map of data to sort
 * @param callable $sortFn Callable used to sort values
 *
 * @return array Returns the sorted array
 * @link http://en.wikipedia.org/wiki/Schwartzian_transform
 */
 public static function stableSort(array $data, callable $sortFn)
 {
 // Decorate each item by creating an array of [value, index]
 array_walk($data, function (&$v, $k) { $v = [$v, $k]; });
 // Sort by the sort function and use the index as a tie-breaker
 uasort($data, function ($a, $b) use ($sortFn) {
 return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
 });
 
 // Undecorate each item and return the resulting sorted array
 return array_map(function ($v) { return $v[0]; }, array_values($data));
 }
 
 /**
 * Creates a Python-style slice of a string or array.
 *
 * @param array|string $value Value to slice
 * @param int|null     $start Starting position
 * @param int|null     $stop  Stop position
 * @param int          $step  Step (1, 2, -1, -2, etc.)
 *
 * @return array|string
 * @throws \InvalidArgumentException
 */
 public static function slice($value, $start = null, $stop = null, $step = 1)
 {
 if (!is_array($value) && !is_string($value)) {
 throw new \InvalidArgumentException('Expects string or array');
 }
 
 return self::sliceIndices($value, $start, $stop, $step);
 }
 
 private static function adjustEndpoint($length, $endpoint, $step)
 {
 if ($endpoint < 0) {
 $endpoint += $length;
 if ($endpoint < 0) {
 $endpoint = $step < 0 ? -1 : 0;
 }
 } elseif ($endpoint >= $length) {
 $endpoint = $step < 0 ? $length - 1 : $length;
 }
 
 return $endpoint;
 }
 
 private static function adjustSlice($length, $start, $stop, $step)
 {
 if ($step === null) {
 $step = 1;
 } elseif ($step === 0) {
 throw new \RuntimeException('step cannot be 0');
 }
 
 if ($start === null) {
 $start = $step < 0 ? $length - 1 : 0;
 } else {
 $start = self::adjustEndpoint($length, $start, $step);
 }
 
 if ($stop === null) {
 $stop = $step < 0 ? -1 : $length;
 } else {
 $stop = self::adjustEndpoint($length, $stop, $step);
 }
 
 return [$start, $stop, $step];
 }
 
 private static function sliceIndices($subject, $start, $stop, $step)
 {
 $type = gettype($subject);
 $len = $type == 'string' ? strlen($subject) : count($subject);
 list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);
 
 $result = [];
 if ($step > 0) {
 for ($i = $start; $i < $stop; $i += $step) {
 $result[] = $subject[$i];
 }
 } else {
 for ($i = $start; $i > $stop; $i += $step) {
 $result[] = $subject[$i];
 }
 }
 
 return $type == 'string' ? implode($result, '') : $result;
 }
 }
 
 |