添加网站文件
This commit is contained in:
16
vendor/mtdowling/cron-expression/.editorconfig
vendored
Normal file
16
vendor/mtdowling/cron-expression/.editorconfig
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
36
vendor/mtdowling/cron-expression/CHANGELOG.md
vendored
Normal file
36
vendor/mtdowling/cron-expression/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Change Log
|
||||
|
||||
## [1.2.0] - 2017-01-22
|
||||
### Added
|
||||
- Added IDE, CodeSniffer, and StyleCI.IO support
|
||||
|
||||
### Changed
|
||||
- Switched to PSR-4 Autoloading
|
||||
|
||||
### Fixed
|
||||
- 0 step expressions are handled better
|
||||
- Fixed `DayOfMonth` validation to be more strict
|
||||
- Typos
|
||||
|
||||
## [1.1.0] - 2016-01-26
|
||||
### Added
|
||||
- Support for non-hourly offset timezones
|
||||
- Checks for valid expressions
|
||||
|
||||
### Changed
|
||||
- Max Iterations no longer hardcoded for `getRunDate()`
|
||||
- Supports DateTimeImmutable for newer PHP verions
|
||||
|
||||
### Fixed
|
||||
- Fixed looping bug for PHP 7 when determining the last specified weekday of a month
|
||||
|
||||
## [1.0.3] - 2013-11-23
|
||||
### Added
|
||||
- Now supports expressions with any number of extra spaces, tabs, or newlines
|
||||
|
||||
### Changed
|
||||
- Using static instead of self in `CronExpression::factory`
|
||||
|
||||
### Fixed
|
||||
- Fixes issue [#28](https://github.com/mtdowling/cron-expression/issues/28) where PHP increments of ranges were failing due to PHP casting hyphens to 0
|
||||
- Only set default timezone if the given $currentTime is not a DateTime instance ([#34](https://github.com/mtdowling/cron-expression/issues/34))
|
||||
19
vendor/mtdowling/cron-expression/LICENSE
vendored
Normal file
19
vendor/mtdowling/cron-expression/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011 Michael Dowling <mtdowling@gmail.com> and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
71
vendor/mtdowling/cron-expression/README.md
vendored
Normal file
71
vendor/mtdowling/cron-expression/README.md
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
PHP Cron Expression Parser
|
||||
==========================
|
||||
|
||||
[](https://packagist.org/packages/mtdowling/cron-expression) [](https://packagist.org/packages/mtdowling/cron-expression) [](http://travis-ci.org/mtdowling/cron-expression)
|
||||
|
||||
The PHP cron expression parser can parse a CRON expression, determine if it is
|
||||
due to run, calculate the next run date of the expression, and calculate the previous
|
||||
run date of the expression. You can calculate dates far into the future or past by
|
||||
skipping n number of matching dates.
|
||||
|
||||
The parser can handle increments of ranges (e.g. */12, 2-59/3), intervals (e.g. 0-9),
|
||||
lists (e.g. 1,2,3), W to find the nearest weekday for a given day of the month, L to
|
||||
find the last day of the month, L to find the last given weekday of a month, and hash
|
||||
(#) to find the nth weekday of a given month.
|
||||
|
||||
Installing
|
||||
==========
|
||||
|
||||
Add the dependency to your project:
|
||||
|
||||
```bash
|
||||
composer require mtdowling/cron-expression
|
||||
```
|
||||
|
||||
Usage
|
||||
=====
|
||||
```php
|
||||
<?php
|
||||
|
||||
require_once '/vendor/autoload.php';
|
||||
|
||||
// Works with predefined scheduling definitions
|
||||
$cron = Cron\CronExpression::factory('@daily');
|
||||
$cron->isDue();
|
||||
echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
|
||||
echo $cron->getPreviousRunDate()->format('Y-m-d H:i:s');
|
||||
|
||||
// Works with complex expressions
|
||||
$cron = Cron\CronExpression::factory('3-59/15 2,6-12 */15 1 2-5');
|
||||
echo $cron->getNextRunDate()->format('Y-m-d H:i:s');
|
||||
|
||||
// Calculate a run date two iterations into the future
|
||||
$cron = Cron\CronExpression::factory('@daily');
|
||||
echo $cron->getNextRunDate(null, 2)->format('Y-m-d H:i:s');
|
||||
|
||||
// Calculate a run date relative to a specific time
|
||||
$cron = Cron\CronExpression::factory('@monthly');
|
||||
echo $cron->getNextRunDate('2010-01-12 00:00:00')->format('Y-m-d H:i:s');
|
||||
```
|
||||
|
||||
CRON Expressions
|
||||
================
|
||||
|
||||
A CRON expression is a string representing the schedule for a particular command to execute. The parts of a CRON schedule are as follows:
|
||||
|
||||
* * * * * *
|
||||
- - - - - -
|
||||
| | | | | |
|
||||
| | | | | + year [optional]
|
||||
| | | | +----- day of week (0 - 7) (Sunday=0 or 7)
|
||||
| | | +---------- month (1 - 12)
|
||||
| | +--------------- day of month (1 - 31)
|
||||
| +-------------------- hour (0 - 23)
|
||||
+------------------------- min (0 - 59)
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
- PHP 5.3+
|
||||
- PHPUnit is required to run the unit tests
|
||||
- Composer is required to run the unit tests
|
||||
29
vendor/mtdowling/cron-expression/composer.json
vendored
Normal file
29
vendor/mtdowling/cron-expression/composer.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "mtdowling/cron-expression",
|
||||
"type": "library",
|
||||
"description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
|
||||
"keywords": ["cron", "schedule"],
|
||||
"license": "MIT",
|
||||
"authors": [{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}],
|
||||
"require": {
|
||||
"php": ">=5.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0|~5.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Cron\\": "src/Cron/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/Cron/"
|
||||
}
|
||||
},
|
||||
"abandoned": "dragonmantank/cron-expression"
|
||||
}
|
||||
148
vendor/mtdowling/cron-expression/src/Cron/AbstractField.php
vendored
Normal file
148
vendor/mtdowling/cron-expression/src/Cron/AbstractField.php
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
/**
|
||||
* Abstract CRON expression field
|
||||
*/
|
||||
abstract class AbstractField implements FieldInterface
|
||||
{
|
||||
/**
|
||||
* Check to see if a field is satisfied by a value
|
||||
*
|
||||
* @param string $dateValue Date value to check
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSatisfied($dateValue, $value)
|
||||
{
|
||||
if ($this->isIncrementsOfRanges($value)) {
|
||||
return $this->isInIncrementsOfRanges($dateValue, $value);
|
||||
} elseif ($this->isRange($value)) {
|
||||
return $this->isInRange($dateValue, $value);
|
||||
}
|
||||
|
||||
return $value == '*' || $dateValue == $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a range
|
||||
*
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRange($value)
|
||||
{
|
||||
return strpos($value, '-') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is an increments of ranges
|
||||
*
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isIncrementsOfRanges($value)
|
||||
{
|
||||
return strpos($value, '/') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a value is within a range
|
||||
*
|
||||
* @param string $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInRange($dateValue, $value)
|
||||
{
|
||||
$parts = array_map('trim', explode('-', $value, 2));
|
||||
|
||||
return $dateValue >= $parts[0] && $dateValue <= $parts[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a value is within an increments of ranges (offset[-to]/step size)
|
||||
*
|
||||
* @param string $dateValue Set date value
|
||||
* @param string $value Value to test
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInIncrementsOfRanges($dateValue, $value)
|
||||
{
|
||||
$parts = array_map('trim', explode('/', $value, 2));
|
||||
$stepSize = isset($parts[1]) ? (int) $parts[1] : 0;
|
||||
|
||||
if ($stepSize === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($parts[0] == '*' || $parts[0] === '0')) {
|
||||
return (int) $dateValue % $stepSize == 0;
|
||||
}
|
||||
|
||||
$range = explode('-', $parts[0], 2);
|
||||
$offset = $range[0];
|
||||
$to = isset($range[1]) ? $range[1] : $dateValue;
|
||||
// Ensure that the date value is within the range
|
||||
if ($dateValue < $offset || $dateValue > $to) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($dateValue > $offset && 0 === $stepSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for ($i = $offset; $i <= $to; $i+= $stepSize) {
|
||||
if ($i == $dateValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a range of values for the given cron expression
|
||||
*
|
||||
* @param string $expression The expression to evaluate
|
||||
* @param int $max Maximum offset for range
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRangeForExpression($expression, $max)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
if ($this->isRange($expression) || $this->isIncrementsOfRanges($expression)) {
|
||||
if (!$this->isIncrementsOfRanges($expression)) {
|
||||
list ($offset, $to) = explode('-', $expression);
|
||||
$stepSize = 1;
|
||||
}
|
||||
else {
|
||||
$range = array_map('trim', explode('/', $expression, 2));
|
||||
$stepSize = isset($range[1]) ? $range[1] : 0;
|
||||
$range = $range[0];
|
||||
$range = explode('-', $range, 2);
|
||||
$offset = $range[0];
|
||||
$to = isset($range[1]) ? $range[1] : $max;
|
||||
}
|
||||
$offset = $offset == '*' ? 0 : $offset;
|
||||
for ($i = $offset; $i <= $to; $i += $stepSize) {
|
||||
$values[] = $i;
|
||||
}
|
||||
sort($values);
|
||||
}
|
||||
else {
|
||||
$values = array($expression);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
389
vendor/mtdowling/cron-expression/src/Cron/CronExpression.php
vendored
Normal file
389
vendor/mtdowling/cron-expression/src/Cron/CronExpression.php
vendored
Normal file
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* CRON expression parser that can determine whether or not a CRON expression is
|
||||
* due to run, the next run date and previous run date of a CRON expression.
|
||||
* The determinations made by this class are accurate if checked run once per
|
||||
* minute (seconds are dropped from date time comparisons).
|
||||
*
|
||||
* Schedule parts must map to:
|
||||
* minute [0-59], hour [0-23], day of month, month [1-12|JAN-DEC], day of week
|
||||
* [1-7|MON-SUN], and an optional year.
|
||||
*
|
||||
* @link http://en.wikipedia.org/wiki/Cron
|
||||
*/
|
||||
class CronExpression
|
||||
{
|
||||
const MINUTE = 0;
|
||||
const HOUR = 1;
|
||||
const DAY = 2;
|
||||
const MONTH = 3;
|
||||
const WEEKDAY = 4;
|
||||
const YEAR = 5;
|
||||
|
||||
/**
|
||||
* @var array CRON expression parts
|
||||
*/
|
||||
private $cronParts;
|
||||
|
||||
/**
|
||||
* @var FieldFactory CRON field factory
|
||||
*/
|
||||
private $fieldFactory;
|
||||
|
||||
/**
|
||||
* @var int Max iteration count when searching for next run date
|
||||
*/
|
||||
private $maxIterationCount = 1000;
|
||||
|
||||
/**
|
||||
* @var array Order in which to test of cron parts
|
||||
*/
|
||||
private static $order = array(self::YEAR, self::MONTH, self::DAY, self::WEEKDAY, self::HOUR, self::MINUTE);
|
||||
|
||||
/**
|
||||
* Factory method to create a new CronExpression.
|
||||
*
|
||||
* @param string $expression The CRON expression to create. There are
|
||||
* several special predefined values which can be used to substitute the
|
||||
* CRON expression:
|
||||
*
|
||||
* `@yearly`, `@annually` - Run once a year, midnight, Jan. 1 - 0 0 1 1 *
|
||||
* `@monthly` - Run once a month, midnight, first of month - 0 0 1 * *
|
||||
* `@weekly` - Run once a week, midnight on Sun - 0 0 * * 0
|
||||
* `@daily` - Run once a day, midnight - 0 0 * * *
|
||||
* `@hourly` - Run once an hour, first minute - 0 * * * *
|
||||
* @param FieldFactory $fieldFactory Field factory to use
|
||||
*
|
||||
* @return CronExpression
|
||||
*/
|
||||
public static function factory($expression, FieldFactory $fieldFactory = null)
|
||||
{
|
||||
$mappings = array(
|
||||
'@yearly' => '0 0 1 1 *',
|
||||
'@annually' => '0 0 1 1 *',
|
||||
'@monthly' => '0 0 1 * *',
|
||||
'@weekly' => '0 0 * * 0',
|
||||
'@daily' => '0 0 * * *',
|
||||
'@hourly' => '0 * * * *'
|
||||
);
|
||||
|
||||
if (isset($mappings[$expression])) {
|
||||
$expression = $mappings[$expression];
|
||||
}
|
||||
|
||||
return new static($expression, $fieldFactory ?: new FieldFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a CronExpression.
|
||||
*
|
||||
* @param string $expression The CRON expression to validate.
|
||||
*
|
||||
* @return bool True if a valid CRON expression was passed. False if not.
|
||||
* @see \Cron\CronExpression::factory
|
||||
*/
|
||||
public static function isValidExpression($expression)
|
||||
{
|
||||
try {
|
||||
self::factory($expression);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CRON expression
|
||||
*
|
||||
* @param string $expression CRON expression (e.g. '8 * * * *')
|
||||
* @param FieldFactory $fieldFactory Factory to create cron fields
|
||||
*/
|
||||
public function __construct($expression, FieldFactory $fieldFactory)
|
||||
{
|
||||
$this->fieldFactory = $fieldFactory;
|
||||
$this->setExpression($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or change the CRON expression
|
||||
*
|
||||
* @param string $value CRON expression (e.g. 8 * * * *)
|
||||
*
|
||||
* @return CronExpression
|
||||
* @throws \InvalidArgumentException if not a valid CRON expression
|
||||
*/
|
||||
public function setExpression($value)
|
||||
{
|
||||
$this->cronParts = preg_split('/\s/', $value, -1, PREG_SPLIT_NO_EMPTY);
|
||||
if (count($this->cronParts) < 5) {
|
||||
throw new InvalidArgumentException(
|
||||
$value . ' is not a valid CRON expression'
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->cronParts as $position => $part) {
|
||||
$this->setPart($position, $part);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set part of the CRON expression
|
||||
*
|
||||
* @param int $position The position of the CRON expression to set
|
||||
* @param string $value The value to set
|
||||
*
|
||||
* @return CronExpression
|
||||
* @throws \InvalidArgumentException if the value is not valid for the part
|
||||
*/
|
||||
public function setPart($position, $value)
|
||||
{
|
||||
if (!$this->fieldFactory->getField($position)->validate($value)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Invalid CRON field value ' . $value . ' at position ' . $position
|
||||
);
|
||||
}
|
||||
|
||||
$this->cronParts[$position] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set max iteration count for searching next run dates
|
||||
*
|
||||
* @param int $maxIterationCount Max iteration count when searching for next run date
|
||||
*
|
||||
* @return CronExpression
|
||||
*/
|
||||
public function setMaxIterationCount($maxIterationCount)
|
||||
{
|
||||
$this->maxIterationCount = $maxIterationCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a next run date relative to the current date or a specific date
|
||||
*
|
||||
* @param string|\DateTime $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning a
|
||||
* matching next run date. 0, the default, will return the current
|
||||
* date and time if the next run date falls on the current date and
|
||||
* time. Setting this value to 1 will skip the first match and go to
|
||||
* the second match. Setting this value to 2 will skip the first 2
|
||||
* matches and so on.
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the current date if
|
||||
* it matches the cron expression.
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
*/
|
||||
public function getNextRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, false, $allowCurrentDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a previous run date relative to the current date or a specific date
|
||||
*
|
||||
* @param string|\DateTime $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
* @see \Cron\CronExpression::getNextRunDate
|
||||
*/
|
||||
public function getPreviousRunDate($currentTime = 'now', $nth = 0, $allowCurrentDate = false)
|
||||
{
|
||||
return $this->getRunDate($currentTime, $nth, true, $allowCurrentDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple run dates starting at the current date or a specific date
|
||||
*
|
||||
* @param int $total Set the total number of dates to calculate
|
||||
* @param string|\DateTime $currentTime Relative calculation date
|
||||
* @param bool $invert Set to TRUE to retrieve previous dates
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
*
|
||||
* @return array Returns an array of run dates
|
||||
*/
|
||||
public function getMultipleRunDates($total, $currentTime = 'now', $invert = false, $allowCurrentDate = false)
|
||||
{
|
||||
$matches = array();
|
||||
for ($i = 0; $i < max(0, $total); $i++) {
|
||||
try {
|
||||
$matches[] = $this->getRunDate($currentTime, $i, $invert, $allowCurrentDate);
|
||||
} catch (RuntimeException $e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all or part of the CRON expression
|
||||
*
|
||||
* @param string $part Specify the part to retrieve or NULL to get the full
|
||||
* cron schedule string.
|
||||
*
|
||||
* @return string|null Returns the CRON expression, a part of the
|
||||
* CRON expression, or NULL if the part was specified but not found
|
||||
*/
|
||||
public function getExpression($part = null)
|
||||
{
|
||||
if (null === $part) {
|
||||
return implode(' ', $this->cronParts);
|
||||
} elseif (array_key_exists($part, $this->cronParts)) {
|
||||
return $this->cronParts[$part];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to output the full expression.
|
||||
*
|
||||
* @return string Full CRON expression
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the cron is due to run based on the current date or a
|
||||
* specific date. This method assumes that the current number of
|
||||
* seconds are irrelevant, and should be called once per minute.
|
||||
*
|
||||
* @param string|\DateTime $currentTime Relative calculation date
|
||||
*
|
||||
* @return bool Returns TRUE if the cron is due to run or FALSE if not
|
||||
*/
|
||||
public function isDue($currentTime = 'now')
|
||||
{
|
||||
if ('now' === $currentTime) {
|
||||
$currentDate = date('Y-m-d H:i');
|
||||
$currentTime = strtotime($currentDate);
|
||||
} elseif ($currentTime instanceof DateTime) {
|
||||
$currentDate = clone $currentTime;
|
||||
// Ensure time in 'current' timezone is used
|
||||
$currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
||||
$currentDate = $currentDate->format('Y-m-d H:i');
|
||||
$currentTime = strtotime($currentDate);
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
$currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
||||
$currentDate = $currentDate->format('Y-m-d H:i');
|
||||
$currentTime = strtotime($currentDate);
|
||||
} else {
|
||||
$currentTime = new DateTime($currentTime);
|
||||
$currentTime->setTime($currentTime->format('H'), $currentTime->format('i'), 0);
|
||||
$currentDate = $currentTime->format('Y-m-d H:i');
|
||||
$currentTime = $currentTime->getTimeStamp();
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->getNextRunDate($currentDate, 0, true)->getTimestamp() == $currentTime;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next or previous run date of the expression relative to a date
|
||||
*
|
||||
* @param string|\DateTime $currentTime Relative calculation date
|
||||
* @param int $nth Number of matches to skip before returning
|
||||
* @param bool $invert Set to TRUE to go backwards in time
|
||||
* @param bool $allowCurrentDate Set to TRUE to return the
|
||||
* current date if it matches the cron expression
|
||||
*
|
||||
* @return \DateTime
|
||||
* @throws \RuntimeException on too many iterations
|
||||
*/
|
||||
protected function getRunDate($currentTime = null, $nth = 0, $invert = false, $allowCurrentDate = false)
|
||||
{
|
||||
if ($currentTime instanceof DateTime) {
|
||||
$currentDate = clone $currentTime;
|
||||
} elseif ($currentTime instanceof DateTimeImmutable) {
|
||||
$currentDate = DateTime::createFromFormat('U', $currentTime->format('U'));
|
||||
$currentDate->setTimezone($currentTime->getTimezone());
|
||||
} else {
|
||||
$currentDate = new DateTime($currentTime ?: 'now');
|
||||
$currentDate->setTimezone(new DateTimeZone(date_default_timezone_get()));
|
||||
}
|
||||
|
||||
$currentDate->setTime($currentDate->format('H'), $currentDate->format('i'), 0);
|
||||
$nextRun = clone $currentDate;
|
||||
$nth = (int) $nth;
|
||||
|
||||
// We don't have to satisfy * or null fields
|
||||
$parts = array();
|
||||
$fields = array();
|
||||
foreach (self::$order as $position) {
|
||||
$part = $this->getExpression($position);
|
||||
if (null === $part || '*' === $part) {
|
||||
continue;
|
||||
}
|
||||
$parts[$position] = $part;
|
||||
$fields[$position] = $this->fieldFactory->getField($position);
|
||||
}
|
||||
|
||||
// Set a hard limit to bail on an impossible date
|
||||
for ($i = 0; $i < $this->maxIterationCount; $i++) {
|
||||
|
||||
foreach ($parts as $position => $part) {
|
||||
$satisfied = false;
|
||||
// Get the field object used to validate this part
|
||||
$field = $fields[$position];
|
||||
// Check if this is singular or a list
|
||||
if (strpos($part, ',') === false) {
|
||||
$satisfied = $field->isSatisfiedBy($nextRun, $part);
|
||||
} else {
|
||||
foreach (array_map('trim', explode(',', $part)) as $listPart) {
|
||||
if ($field->isSatisfiedBy($nextRun, $listPart)) {
|
||||
$satisfied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the field is not satisfied, then start over
|
||||
if (!$satisfied) {
|
||||
$field->increment($nextRun, $invert, $part);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip this match if needed
|
||||
if ((!$allowCurrentDate && $nextRun == $currentDate) || --$nth > -1) {
|
||||
$this->fieldFactory->getField(0)->increment($nextRun, $invert, isset($parts[0]) ? $parts[0] : null);
|
||||
continue;
|
||||
}
|
||||
|
||||
return $nextRun;
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new RuntimeException('Impossible CRON expression');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
173
vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php
vendored
Normal file
173
vendor/mtdowling/cron-expression/src/Cron/DayOfMonthField.php
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Day of month field. Allows: * , / - ? L W
|
||||
*
|
||||
* 'L' stands for "last" and specifies the last day of the month.
|
||||
*
|
||||
* The 'W' character is used to specify the weekday (Monday-Friday) nearest the
|
||||
* given day. As an example, if you were to specify "15W" as the value for the
|
||||
* day-of-month field, the meaning is: "the nearest weekday to the 15th of the
|
||||
* month". So if the 15th is a Saturday, the trigger will fire on Friday the
|
||||
* 14th. If the 15th is a Sunday, the trigger will fire on Monday the 16th. If
|
||||
* the 15th is a Tuesday, then it will fire on Tuesday the 15th. However if you
|
||||
* specify "1W" as the value for day-of-month, and the 1st is a Saturday, the
|
||||
* trigger will fire on Monday the 3rd, as it will not 'jump' over the boundary
|
||||
* of a month's days. The 'W' character can only be specified when the
|
||||
* day-of-month is a single day, not a range or list of days.
|
||||
*
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class DayOfMonthField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* Get the nearest day of the week for a given day in a month
|
||||
*
|
||||
* @param int $currentYear Current year
|
||||
* @param int $currentMonth Current month
|
||||
* @param int $targetDay Target day of the month
|
||||
*
|
||||
* @return \DateTime Returns the nearest date
|
||||
*/
|
||||
private static function getNearestWeekday($currentYear, $currentMonth, $targetDay)
|
||||
{
|
||||
$tday = str_pad($targetDay, 2, '0', STR_PAD_LEFT);
|
||||
$target = DateTime::createFromFormat('Y-m-d', "$currentYear-$currentMonth-$tday");
|
||||
$currentWeekday = (int) $target->format('N');
|
||||
|
||||
if ($currentWeekday < 6) {
|
||||
return $target;
|
||||
}
|
||||
|
||||
$lastDayOfMonth = $target->format('t');
|
||||
|
||||
foreach (array(-1, 1, -2, 2) as $i) {
|
||||
$adjusted = $targetDay + $i;
|
||||
if ($adjusted > 0 && $adjusted <= $lastDayOfMonth) {
|
||||
$target->setDate($currentYear, $currentMonth, $adjusted);
|
||||
if ($target->format('N') < 6 && $target->format('m') == $currentMonth) {
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
// ? states that the field value is to be skipped
|
||||
if ($value == '?') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$fieldValue = $date->format('d');
|
||||
|
||||
// Check to see if this is the last day of the month
|
||||
if ($value == 'L') {
|
||||
return $fieldValue == $date->format('t');
|
||||
}
|
||||
|
||||
// Check to see if this is the nearest weekday to a particular value
|
||||
if (strpos($value, 'W')) {
|
||||
// Parse the target day
|
||||
$targetDay = substr($value, 0, strpos($value, 'W'));
|
||||
// Find out if the current day is the nearest day of the week
|
||||
return $date->format('j') == self::getNearestWeekday(
|
||||
$date->format('Y'),
|
||||
$date->format('m'),
|
||||
$targetDay
|
||||
)->format('j');
|
||||
}
|
||||
|
||||
return $this->isSatisfied($date->format('d'), $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('previous day');
|
||||
$date->setTime(23, 59);
|
||||
} else {
|
||||
$date->modify('next day');
|
||||
$date->setTime(0, 0);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the value is valid for the Day of the Month field
|
||||
* Days of the month can contain values of 1-31, *, L, or ? by default. This can be augmented with lists via a ',',
|
||||
* ranges via a '-', or with a '[0-9]W' to specify the closest weekday.
|
||||
*
|
||||
* @param string $value
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($value)
|
||||
{
|
||||
// Allow wildcards and a single L
|
||||
if ($value === '?' || $value === '*' || $value === 'L') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If you only contain numbers and are within 1-31
|
||||
if ((bool) preg_match('/^\d{1,2}$/', $value) && ($value >= 1 && $value <= 31)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If you have a -, we will deal with each of your chunks
|
||||
if ((bool) preg_match('/-/', $value)) {
|
||||
// We cannot have a range within a list or vice versa
|
||||
if ((bool) preg_match('/,/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chunks = explode('-', $value);
|
||||
foreach ($chunks as $chunk) {
|
||||
if (!$this->validate($chunk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If you have a comma, we will deal with each value
|
||||
if ((bool) preg_match('/,/', $value)) {
|
||||
// We cannot have a range within a list or vice versa
|
||||
if ((bool) preg_match('/-/', $value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$chunks = explode(',', $value);
|
||||
foreach ($chunks as $chunk) {
|
||||
if (!$this->validate($chunk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// If you contain a /, we'll deal with it
|
||||
if ((bool) preg_match('/\//', $value)) {
|
||||
$chunks = explode('/', $value);
|
||||
foreach ($chunks as $chunk) {
|
||||
if (!$this->validate($chunk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If you end in W, make sure that it has a numeric in front of it
|
||||
if ((bool) preg_match('/^\d{1,2}W$/', $value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
141
vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php
vendored
Normal file
141
vendor/mtdowling/cron-expression/src/Cron/DayOfWeekField.php
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
use InvalidArgumentException;
|
||||
|
||||
|
||||
/**
|
||||
* Day of week field. Allows: * / , - ? L #
|
||||
*
|
||||
* Days of the week can be represented as a number 0-7 (0|7 = Sunday)
|
||||
* or as a three letter string: SUN, MON, TUE, WED, THU, FRI, SAT.
|
||||
*
|
||||
* 'L' stands for "last". It allows you to specify constructs such as
|
||||
* "the last Friday" of a given month.
|
||||
*
|
||||
* '#' is allowed for the day-of-week field, and must be followed by a
|
||||
* number between one and five. It allows you to specify constructs such as
|
||||
* "the second Friday" of a given month.
|
||||
*/
|
||||
class DayOfWeekField extends AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
if ($value == '?') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Convert text day of the week values to integers
|
||||
$value = $this->convertLiterals($value);
|
||||
|
||||
$currentYear = $date->format('Y');
|
||||
$currentMonth = $date->format('m');
|
||||
$lastDayOfMonth = $date->format('t');
|
||||
|
||||
// Find out if this is the last specific weekday of the month
|
||||
if (strpos($value, 'L')) {
|
||||
$weekday = str_replace('7', '0', substr($value, 0, strpos($value, 'L')));
|
||||
$tdate = clone $date;
|
||||
$tdate->setDate($currentYear, $currentMonth, $lastDayOfMonth);
|
||||
while ($tdate->format('w') != $weekday) {
|
||||
$tdateClone = new DateTime();
|
||||
$tdate = $tdateClone
|
||||
->setTimezone($tdate->getTimezone())
|
||||
->setDate($currentYear, $currentMonth, --$lastDayOfMonth);
|
||||
}
|
||||
|
||||
return $date->format('j') == $lastDayOfMonth;
|
||||
}
|
||||
|
||||
// Handle # hash tokens
|
||||
if (strpos($value, '#')) {
|
||||
list($weekday, $nth) = explode('#', $value);
|
||||
|
||||
// 0 and 7 are both Sunday, however 7 matches date('N') format ISO-8601
|
||||
if ($weekday === '0') {
|
||||
$weekday = 7;
|
||||
}
|
||||
|
||||
// Validate the hash fields
|
||||
if ($weekday < 0 || $weekday > 7) {
|
||||
throw new InvalidArgumentException("Weekday must be a value between 0 and 7. {$weekday} given");
|
||||
}
|
||||
if ($nth > 5) {
|
||||
throw new InvalidArgumentException('There are never more than 5 of a given weekday in a month');
|
||||
}
|
||||
// The current weekday must match the targeted weekday to proceed
|
||||
if ($date->format('N') != $weekday) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tdate = clone $date;
|
||||
$tdate->setDate($currentYear, $currentMonth, 1);
|
||||
$dayCount = 0;
|
||||
$currentDay = 1;
|
||||
while ($currentDay < $lastDayOfMonth + 1) {
|
||||
if ($tdate->format('N') == $weekday) {
|
||||
if (++$dayCount >= $nth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$tdate->setDate($currentYear, $currentMonth, ++$currentDay);
|
||||
}
|
||||
|
||||
return $date->format('j') == $currentDay;
|
||||
}
|
||||
|
||||
// Handle day of the week values
|
||||
if (strpos($value, '-')) {
|
||||
$parts = explode('-', $value);
|
||||
if ($parts[0] == '7') {
|
||||
$parts[0] = '0';
|
||||
} elseif ($parts[1] == '0') {
|
||||
$parts[1] = '7';
|
||||
}
|
||||
$value = implode('-', $parts);
|
||||
}
|
||||
|
||||
// Test to see which Sunday to use -- 0 == 7 == Sunday
|
||||
$format = in_array(7, str_split($value)) ? 'N' : 'w';
|
||||
$fieldValue = $date->format($format);
|
||||
|
||||
return $this->isSatisfied($fieldValue, $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('-1 day');
|
||||
$date->setTime(23, 59, 0);
|
||||
} else {
|
||||
$date->modify('+1 day');
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate($value)
|
||||
{
|
||||
$value = $this->convertLiterals($value);
|
||||
|
||||
foreach (explode(',', $value) as $expr) {
|
||||
if (!preg_match('/^(\*|[0-7](L?|#[1-5]))([\/\,\-][0-7]+)*$/', $expr)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function convertLiterals($string)
|
||||
{
|
||||
return str_ireplace(
|
||||
array('SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'),
|
||||
range(0, 6),
|
||||
$string
|
||||
);
|
||||
}
|
||||
}
|
||||
57
vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php
vendored
Normal file
57
vendor/mtdowling/cron-expression/src/Cron/FieldFactory.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CRON field factory implementing a flyweight factory
|
||||
* @link http://en.wikipedia.org/wiki/Cron
|
||||
*/
|
||||
class FieldFactory
|
||||
{
|
||||
/**
|
||||
* @var array Cache of instantiated fields
|
||||
*/
|
||||
private $fields = array();
|
||||
|
||||
/**
|
||||
* Get an instance of a field object for a cron expression position
|
||||
*
|
||||
* @param int $position CRON expression position value to retrieve
|
||||
*
|
||||
* @return FieldInterface
|
||||
* @throws InvalidArgumentException if a position is not valid
|
||||
*/
|
||||
public function getField($position)
|
||||
{
|
||||
if (!isset($this->fields[$position])) {
|
||||
switch ($position) {
|
||||
case 0:
|
||||
$this->fields[$position] = new MinutesField();
|
||||
break;
|
||||
case 1:
|
||||
$this->fields[$position] = new HoursField();
|
||||
break;
|
||||
case 2:
|
||||
$this->fields[$position] = new DayOfMonthField();
|
||||
break;
|
||||
case 3:
|
||||
$this->fields[$position] = new MonthField();
|
||||
break;
|
||||
case 4:
|
||||
$this->fields[$position] = new DayOfWeekField();
|
||||
break;
|
||||
case 5:
|
||||
$this->fields[$position] = new YearField();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidArgumentException(
|
||||
$position . ' is not a valid position'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->fields[$position];
|
||||
}
|
||||
}
|
||||
40
vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php
vendored
Normal file
40
vendor/mtdowling/cron-expression/src/Cron/FieldInterface.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* CRON field interface
|
||||
*/
|
||||
interface FieldInterface
|
||||
{
|
||||
/**
|
||||
* Check if the respective value of a DateTime field satisfies a CRON exp
|
||||
*
|
||||
* @param DateTime $date DateTime object to check
|
||||
* @param string $value CRON expression to test against
|
||||
*
|
||||
* @return bool Returns TRUE if satisfied, FALSE otherwise
|
||||
*/
|
||||
public function isSatisfiedBy(DateTime $date, $value);
|
||||
|
||||
/**
|
||||
* When a CRON expression is not satisfied, this method is used to increment
|
||||
* or decrement a DateTime object by the unit of the cron field
|
||||
*
|
||||
* @param DateTime $date DateTime object to change
|
||||
* @param bool $invert (optional) Set to TRUE to decrement
|
||||
*
|
||||
* @return FieldInterface
|
||||
*/
|
||||
public function increment(DateTime $date, $invert = false);
|
||||
|
||||
/**
|
||||
* Validates a CRON expression for a given field
|
||||
*
|
||||
* @param string $value CRON expression value to validate
|
||||
*
|
||||
* @return bool Returns TRUE if valid, FALSE otherwise
|
||||
*/
|
||||
public function validate($value);
|
||||
}
|
||||
71
vendor/mtdowling/cron-expression/src/Cron/HoursField.php
vendored
Normal file
71
vendor/mtdowling/cron-expression/src/Cron/HoursField.php
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
|
||||
|
||||
/**
|
||||
* Hours field. Allows: * , / -
|
||||
*/
|
||||
class HoursField extends AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('H'), $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false, $parts = null)
|
||||
{
|
||||
// Change timezone to UTC temporarily. This will
|
||||
// allow us to go back or forwards and hour even
|
||||
// if DST will be changed between the hours.
|
||||
if (is_null($parts) || $parts == '*') {
|
||||
$timezone = $date->getTimezone();
|
||||
$date->setTimezone(new DateTimeZone('UTC'));
|
||||
if ($invert) {
|
||||
$date->modify('-1 hour');
|
||||
} else {
|
||||
$date->modify('+1 hour');
|
||||
}
|
||||
$date->setTimezone($timezone);
|
||||
|
||||
$date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||
$hours = array();
|
||||
foreach ($parts as $part) {
|
||||
$hours = array_merge($hours, $this->getRangeForExpression($part, 23));
|
||||
}
|
||||
|
||||
$current_hour = $date->format('H');
|
||||
$position = $invert ? count($hours) - 1 : 0;
|
||||
if (count($hours) > 1) {
|
||||
for ($i = 0; $i < count($hours) - 1; $i++) {
|
||||
if ((!$invert && $current_hour >= $hours[$i] && $current_hour < $hours[$i + 1]) ||
|
||||
($invert && $current_hour > $hours[$i] && $current_hour <= $hours[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hour = $hours[$position];
|
||||
if ((!$invert && $date->format('H') >= $hour) || ($invert && $date->format('H') <= $hour)) {
|
||||
$date->modify(($invert ? '-' : '+') . '1 day');
|
||||
$date->setTime($invert ? 23 : 0, $invert ? 59 : 0);
|
||||
}
|
||||
else {
|
||||
$date->setTime($hour, $invert ? 59 : 0);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
|
||||
}
|
||||
}
|
||||
62
vendor/mtdowling/cron-expression/src/Cron/MinutesField.php
vendored
Normal file
62
vendor/mtdowling/cron-expression/src/Cron/MinutesField.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
|
||||
|
||||
/**
|
||||
* Minutes field. Allows: * , / -
|
||||
*/
|
||||
class MinutesField extends AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('i'), $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false, $parts = null)
|
||||
{
|
||||
if (is_null($parts)) {
|
||||
if ($invert) {
|
||||
$date->modify('-1 minute');
|
||||
} else {
|
||||
$date->modify('+1 minute');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
$parts = strpos($parts, ',') !== false ? explode(',', $parts) : array($parts);
|
||||
$minutes = array();
|
||||
foreach ($parts as $part) {
|
||||
$minutes = array_merge($minutes, $this->getRangeForExpression($part, 59));
|
||||
}
|
||||
|
||||
$current_minute = $date->format('i');
|
||||
$position = $invert ? count($minutes) - 1 : 0;
|
||||
if (count($minutes) > 1) {
|
||||
for ($i = 0; $i < count($minutes) - 1; $i++) {
|
||||
if ((!$invert && $current_minute >= $minutes[$i] && $current_minute < $minutes[$i + 1]) ||
|
||||
($invert && $current_minute > $minutes[$i] && $current_minute <= $minutes[$i + 1])) {
|
||||
$position = $invert ? $i : $i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((!$invert && $current_minute >= $minutes[$position]) || ($invert && $current_minute <= $minutes[$position])) {
|
||||
$date->modify(($invert ? '-' : '+') . '1 hour');
|
||||
$date->setTime($date->format('H'), $invert ? 59 : 0);
|
||||
}
|
||||
else {
|
||||
$date->setTime($date->format('H'), $minutes[$position]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
|
||||
}
|
||||
}
|
||||
44
vendor/mtdowling/cron-expression/src/Cron/MonthField.php
vendored
Normal file
44
vendor/mtdowling/cron-expression/src/Cron/MonthField.php
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Month field. Allows: * , / -
|
||||
*/
|
||||
class MonthField extends AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
// Convert text month values to integers
|
||||
$value = str_ireplace(
|
||||
array(
|
||||
'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
|
||||
'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'
|
||||
),
|
||||
range(1, 12),
|
||||
$value
|
||||
);
|
||||
|
||||
return $this->isSatisfied($date->format('m'), $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('last day of previous month');
|
||||
$date->setTime(23, 59);
|
||||
} else {
|
||||
$date->modify('first day of next month');
|
||||
$date->setTime(0, 0);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/^[\*,\/\-0-9A-Z]+$/', $value);
|
||||
}
|
||||
}
|
||||
37
vendor/mtdowling/cron-expression/src/Cron/YearField.php
vendored
Normal file
37
vendor/mtdowling/cron-expression/src/Cron/YearField.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Cron;
|
||||
|
||||
use DateTime;
|
||||
|
||||
|
||||
/**
|
||||
* Year field. Allows: * , / -
|
||||
*/
|
||||
class YearField extends AbstractField
|
||||
{
|
||||
public function isSatisfiedBy(DateTime $date, $value)
|
||||
{
|
||||
return $this->isSatisfied($date->format('Y'), $value);
|
||||
}
|
||||
|
||||
public function increment(DateTime $date, $invert = false)
|
||||
{
|
||||
if ($invert) {
|
||||
$date->modify('-1 year');
|
||||
$date->setDate($date->format('Y'), 12, 31);
|
||||
$date->setTime(23, 59, 0);
|
||||
} else {
|
||||
$date->modify('+1 year');
|
||||
$date->setDate($date->format('Y'), 1, 1);
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate($value)
|
||||
{
|
||||
return (bool) preg_match('/^[\*,\/\-0-9]+$/', $value);
|
||||
}
|
||||
}
|
||||
86
vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php
vendored
Normal file
86
vendor/mtdowling/cron-expression/tests/Cron/AbstractFieldTest.php
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfWeekField;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class AbstractFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\AbstractField::isRange
|
||||
*/
|
||||
public function testTestsIfRange()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isRange('1-2'));
|
||||
$this->assertFalse($f->isRange('2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\AbstractField::isIncrementsOfRanges
|
||||
*/
|
||||
public function testTestsIfIncrementsOfRanges()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertFalse($f->isIncrementsOfRanges('1-2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('1/2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('*/2'));
|
||||
$this->assertTrue($f->isIncrementsOfRanges('3-12/2'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\AbstractField::isInRange
|
||||
*/
|
||||
public function testTestsIfInRange()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isInRange('1', '1-2'));
|
||||
$this->assertTrue($f->isInRange('2', '1-2'));
|
||||
$this->assertTrue($f->isInRange('5', '4-12'));
|
||||
$this->assertFalse($f->isInRange('3', '4-12'));
|
||||
$this->assertFalse($f->isInRange('13', '4-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\AbstractField::isInIncrementsOfRanges
|
||||
*/
|
||||
public function testTestsIfInIncrementsOfRanges()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('3', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('13', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('15', '3-59/2'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('14', '*/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('2', '3-59/13'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '*/13'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('14', '3-59/2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2-59'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '2'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('3', '*'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('0', '*/0'));
|
||||
$this->assertFalse($f->isInIncrementsOfRanges('1', '*/0'));
|
||||
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('4', '4/10'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('14', '4/10'));
|
||||
$this->assertTrue($f->isInIncrementsOfRanges('34', '4/10'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\AbstractField::isSatisfied
|
||||
*/
|
||||
public function testTestsIfSatisfied()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfied('12', '3-13'));
|
||||
$this->assertTrue($f->isSatisfied('15', '3-59/12'));
|
||||
$this->assertTrue($f->isSatisfied('12', '*'));
|
||||
$this->assertTrue($f->isSatisfied('12', '12'));
|
||||
$this->assertFalse($f->isSatisfied('12', '3-11'));
|
||||
$this->assertFalse($f->isSatisfied('12', '3-59/13'));
|
||||
$this->assertFalse($f->isSatisfied('12', '11'));
|
||||
}
|
||||
}
|
||||
414
vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php
vendored
Normal file
414
vendor/mtdowling/cron-expression/tests/Cron/CronExpressionTest.php
vendored
Normal file
@@ -0,0 +1,414 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\CronExpression;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class CronExpressionTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\CronExpression::factory
|
||||
*/
|
||||
public function testFactoryRecognizesTemplates()
|
||||
{
|
||||
$this->assertEquals('0 0 1 1 *', CronExpression::factory('@annually')->getExpression());
|
||||
$this->assertEquals('0 0 1 1 *', CronExpression::factory('@yearly')->getExpression());
|
||||
$this->assertEquals('0 0 * * 0', CronExpression::factory('@weekly')->getExpression());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::__construct
|
||||
* @covers Cron\CronExpression::getExpression
|
||||
* @covers Cron\CronExpression::__toString
|
||||
*/
|
||||
public function testParsesCronSchedule()
|
||||
{
|
||||
// '2010-09-10 12:00:00'
|
||||
$cron = CronExpression::factory('1 2-4 * 4,5,6 */3');
|
||||
$this->assertEquals('1', $cron->getExpression(CronExpression::MINUTE));
|
||||
$this->assertEquals('2-4', $cron->getExpression(CronExpression::HOUR));
|
||||
$this->assertEquals('*', $cron->getExpression(CronExpression::DAY));
|
||||
$this->assertEquals('4,5,6', $cron->getExpression(CronExpression::MONTH));
|
||||
$this->assertEquals('*/3', $cron->getExpression(CronExpression::WEEKDAY));
|
||||
$this->assertEquals('1 2-4 * 4,5,6 */3', $cron->getExpression());
|
||||
$this->assertEquals('1 2-4 * 4,5,6 */3', (string) $cron);
|
||||
$this->assertNull($cron->getExpression('foo'));
|
||||
|
||||
try {
|
||||
$cron = CronExpression::factory('A 1 2 3 4');
|
||||
$this->fail('Validation exception not thrown');
|
||||
} catch (InvalidArgumentException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::__construct
|
||||
* @covers Cron\CronExpression::getExpression
|
||||
* @dataProvider scheduleWithDifferentSeparatorsProvider
|
||||
*/
|
||||
public function testParsesCronScheduleWithAnySpaceCharsAsSeparators($schedule, array $expected)
|
||||
{
|
||||
$cron = CronExpression::factory($schedule);
|
||||
$this->assertEquals($expected[0], $cron->getExpression(CronExpression::MINUTE));
|
||||
$this->assertEquals($expected[1], $cron->getExpression(CronExpression::HOUR));
|
||||
$this->assertEquals($expected[2], $cron->getExpression(CronExpression::DAY));
|
||||
$this->assertEquals($expected[3], $cron->getExpression(CronExpression::MONTH));
|
||||
$this->assertEquals($expected[4], $cron->getExpression(CronExpression::WEEKDAY));
|
||||
$this->assertEquals($expected[5], $cron->getExpression(CronExpression::YEAR));
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testParsesCronScheduleWithAnySpaceCharsAsSeparators
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function scheduleWithDifferentSeparatorsProvider()
|
||||
{
|
||||
return array(
|
||||
array("*\t*\t*\t*\t*\t*", array('*', '*', '*', '*', '*', '*')),
|
||||
array("* * * * * *", array('*', '*', '*', '*', '*', '*')),
|
||||
array("* \t * \t * \t * \t * \t *", array('*', '*', '*', '*', '*', '*')),
|
||||
array("*\t \t*\t \t*\t \t*\t \t*\t \t*", array('*', '*', '*', '*', '*', '*')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::__construct
|
||||
* @covers Cron\CronExpression::setExpression
|
||||
* @covers Cron\CronExpression::setPart
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidCronsWillFail()
|
||||
{
|
||||
// Only four values
|
||||
$cron = CronExpression::factory('* * * 1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::setPart
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testInvalidPartsWillFail()
|
||||
{
|
||||
// Only four values
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$cron->setPart(1, 'abc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for cron schedule
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function scheduleProvider()
|
||||
{
|
||||
return array(
|
||||
array('*/2 */2 * * *', '2015-08-10 21:47:27', '2015-08-10 22:00:00', false),
|
||||
array('* * * * *', '2015-08-10 21:50:37', '2015-08-10 21:50:00', true),
|
||||
array('* 20,21,22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true),
|
||||
// Handles CSV values
|
||||
array('* 20,22 * * *', '2015-08-10 21:50:00', '2015-08-10 22:00:00', false),
|
||||
// CSV values can be complex
|
||||
array('* 5,21-22 * * *', '2015-08-10 21:50:00', '2015-08-10 21:50:00', true),
|
||||
array('7-9 * */9 * *', '2015-08-10 22:02:33', '2015-08-18 00:07:00', false),
|
||||
// 15th minute, of the second hour, every 15 days, in January, every Friday
|
||||
array('1 * * * 7', '2015-08-10 21:47:27', '2015-08-16 00:01:00', false),
|
||||
// Test with exact times
|
||||
array('47 21 * * *', strtotime('2015-08-10 21:47:30'), '2015-08-10 21:47:00', true),
|
||||
// Test Day of the week (issue #1)
|
||||
// According cron implementation, 0|7 = sunday, 1 => monday, etc
|
||||
array('* * * * 0', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('* * * * 7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('* * * * 1', strtotime('2011-06-15 23:09:00'), '2011-06-20 00:00:00', false),
|
||||
// Should return the sunday date as 7 equals 0
|
||||
array('0 0 * * MON,SUN', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 1,7', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 0-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 7-4', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 4-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 7-3', strtotime('2011-06-15 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 3-7', strtotime('2011-06-15 23:09:00'), '2011-06-16 00:00:00', false),
|
||||
array('0 0 * * 3-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
// Test lists of values and ranges (Abhoryo)
|
||||
array('0 0 * * 2-7', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
|
||||
array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
|
||||
array('0 0 * * 2-7', strtotime('2011-06-18 23:09:00'), '2011-06-19 00:00:00', false),
|
||||
array('0 0 * * 4-7', strtotime('2011-07-19 00:00:00'), '2011-07-21 00:00:00', false),
|
||||
// Test increments of ranges
|
||||
array('0-12/4 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
|
||||
array('4-59/2 * * * *', strtotime('2011-06-20 12:04:00'), '2011-06-20 12:04:00', true),
|
||||
array('4-59/2 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:06:00', true),
|
||||
array('4-59/3 * * * *', strtotime('2011-06-20 12:06:00'), '2011-06-20 12:07:00', false),
|
||||
//array('0 0 * * 0,2-6', strtotime('2011-06-20 23:09:00'), '2011-06-21 00:00:00', false),
|
||||
// Test Day of the Week and the Day of the Month (issue #1)
|
||||
array('0 0 1 1 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
array('0 0 1 JAN 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
array('0 0 1 * 0', strtotime('2011-06-15 23:09:00'), '2012-01-01 00:00:00', false),
|
||||
array('0 0 L * *', strtotime('2011-07-15 00:00:00'), '2011-07-31 00:00:00', false),
|
||||
// Test the W day of the week modifier for day of the month field
|
||||
array('0 0 2W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('0 0 1W * *', strtotime('2011-05-01 00:00:00'), '2011-05-02 00:00:00', false),
|
||||
array('0 0 1W * *', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('0 0 3W * *', strtotime('2011-07-01 00:00:00'), '2011-07-04 00:00:00', false),
|
||||
array('0 0 16W * *', strtotime('2011-07-01 00:00:00'), '2011-07-15 00:00:00', false),
|
||||
array('0 0 28W * *', strtotime('2011-07-01 00:00:00'), '2011-07-28 00:00:00', false),
|
||||
array('0 0 30W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
array('0 0 31W * *', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
// Test the year field
|
||||
array('* * * * * 2012', strtotime('2011-05-01 00:00:00'), '2012-01-01 00:00:00', false),
|
||||
// Test the last weekday of a month
|
||||
array('* * * * 5L', strtotime('2011-07-01 00:00:00'), '2011-07-29 00:00:00', false),
|
||||
array('* * * * 6L', strtotime('2011-07-01 00:00:00'), '2011-07-30 00:00:00', false),
|
||||
array('* * * * 7L', strtotime('2011-07-01 00:00:00'), '2011-07-31 00:00:00', false),
|
||||
array('* * * * 1L', strtotime('2011-07-24 00:00:00'), '2011-07-25 00:00:00', false),
|
||||
array('* * * * TUEL', strtotime('2011-07-24 00:00:00'), '2011-07-26 00:00:00', false),
|
||||
array('* * * 1 5L', strtotime('2011-12-25 00:00:00'), '2012-01-27 00:00:00', false),
|
||||
// Test the hash symbol for the nth weekday of a given month
|
||||
array('* * * * 5#2', strtotime('2011-07-01 00:00:00'), '2011-07-08 00:00:00', false),
|
||||
array('* * * * 5#1', strtotime('2011-07-01 00:00:00'), '2011-07-01 00:00:00', true),
|
||||
array('* * * * 3#4', strtotime('2011-07-01 00:00:00'), '2011-07-27 00:00:00', false),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::isDue
|
||||
* @covers Cron\CronExpression::getNextRunDate
|
||||
* @covers Cron\DayOfMonthField
|
||||
* @covers Cron\DayOfWeekField
|
||||
* @covers Cron\MinutesField
|
||||
* @covers Cron\HoursField
|
||||
* @covers Cron\MonthField
|
||||
* @covers Cron\YearField
|
||||
* @covers Cron\CronExpression::getRunDate
|
||||
* @dataProvider scheduleProvider
|
||||
*/
|
||||
public function testDeterminesIfCronIsDue($schedule, $relativeTime, $nextRun, $isDue)
|
||||
{
|
||||
$relativeTimeString = is_int($relativeTime) ? date('Y-m-d H:i:s', $relativeTime) : $relativeTime;
|
||||
|
||||
// Test next run date
|
||||
$cron = CronExpression::factory($schedule);
|
||||
if (is_string($relativeTime)) {
|
||||
$relativeTime = new DateTime($relativeTime);
|
||||
} elseif (is_int($relativeTime)) {
|
||||
$relativeTime = date('Y-m-d H:i:s', $relativeTime);
|
||||
}
|
||||
$this->assertEquals($isDue, $cron->isDue($relativeTime));
|
||||
$next = $cron->getNextRunDate($relativeTime, 0, true);
|
||||
$this->assertEquals(new DateTime($nextRun), $next);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentDates()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$this->assertTrue($cron->isDue());
|
||||
$this->assertTrue($cron->isDue('now'));
|
||||
$this->assertTrue($cron->isDue(new DateTime('now')));
|
||||
$this->assertTrue($cron->isDue(date('Y-m-d H:i')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::isDue
|
||||
*/
|
||||
public function testIsDueHandlesDifferentTimezones()
|
||||
{
|
||||
$cron = CronExpression::factory('0 15 * * 3'); //Wednesday at 15:00
|
||||
$date = '2014-01-01 15:00'; //Wednesday
|
||||
$utc = new DateTimeZone('UTC');
|
||||
$amsterdam = new DateTimeZone('Europe/Amsterdam');
|
||||
$tokyo = new DateTimeZone('Asia/Tokyo');
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $utc)));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $amsterdam)));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $tokyo)));
|
||||
|
||||
date_default_timezone_set('Europe/Amsterdam');
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $utc)));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $amsterdam)));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $tokyo)));
|
||||
|
||||
date_default_timezone_set('Asia/Tokyo');
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $utc)));
|
||||
$this->assertFalse($cron->isDue(new DateTime($date, $amsterdam)));
|
||||
$this->assertTrue($cron->isDue(new DateTime($date, $tokyo)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getPreviousRunDate
|
||||
*/
|
||||
public function testCanGetPreviousRunDates()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
|
||||
$cron = CronExpression::factory('* */2 * * *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
|
||||
$cron = CronExpression::factory('* * * */2 *');
|
||||
$next = $cron->getNextRunDate('now');
|
||||
$two = $cron->getNextRunDate('now', 1);
|
||||
$this->assertEquals($next, $cron->getPreviousRunDate($two));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getMultipleRunDates
|
||||
*/
|
||||
public function testProvidesMultipleRunDates()
|
||||
{
|
||||
$cron = CronExpression::factory('*/2 * * * *');
|
||||
$this->assertEquals(array(
|
||||
new DateTime('2008-11-09 00:00:00'),
|
||||
new DateTime('2008-11-09 00:02:00'),
|
||||
new DateTime('2008-11-09 00:04:00'),
|
||||
new DateTime('2008-11-09 00:06:00')
|
||||
), $cron->getMultipleRunDates(4, '2008-11-09 00:00:00', false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getMultipleRunDates
|
||||
* @covers Cron\CronExpression::setMaxIterationCount
|
||||
*/
|
||||
public function testProvidesMultipleRunDatesForTheFarFuture() {
|
||||
// Fails with the default 1000 iteration limit
|
||||
$cron = CronExpression::factory('0 0 12 1 * */2');
|
||||
$cron->setMaxIterationCount(2000);
|
||||
$this->assertEquals(array(
|
||||
new DateTime('2016-01-12 00:00:00'),
|
||||
new DateTime('2018-01-12 00:00:00'),
|
||||
new DateTime('2020-01-12 00:00:00'),
|
||||
new DateTime('2022-01-12 00:00:00'),
|
||||
new DateTime('2024-01-12 00:00:00'),
|
||||
new DateTime('2026-01-12 00:00:00'),
|
||||
new DateTime('2028-01-12 00:00:00'),
|
||||
new DateTime('2030-01-12 00:00:00'),
|
||||
new DateTime('2032-01-12 00:00:00'),
|
||||
), $cron->getMultipleRunDates(9, '2015-04-28 00:00:00', false, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression
|
||||
*/
|
||||
public function testCanIterateOverNextRuns()
|
||||
{
|
||||
$cron = CronExpression::factory('@weekly');
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 08:00:00");
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
|
||||
|
||||
// true is cast to 1
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", true, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-16 00:00:00"));
|
||||
|
||||
// You can iterate over them
|
||||
$nextRun = $cron->getNextRunDate($cron->getNextRunDate("2008-11-09 00:00:00", 1, true), 1, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
|
||||
|
||||
// You can skip more than one
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 2, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-23 00:00:00"));
|
||||
$nextRun = $cron->getNextRunDate("2008-11-09 00:00:00", 3, true);
|
||||
$this->assertEquals($nextRun, new DateTime("2008-11-30 00:00:00"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testSkipsCurrentDateByDefault()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$current = new DateTime('now');
|
||||
$next = $cron->getNextRunDate($current);
|
||||
$nextPrev = $cron->getPreviousRunDate($next);
|
||||
$this->assertEquals($current->format('Y-m-d H:i:00'), $nextPrev->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getRunDate
|
||||
* @ticket 7
|
||||
*/
|
||||
public function testStripsForSeconds()
|
||||
{
|
||||
$cron = CronExpression::factory('* * * * *');
|
||||
$current = new DateTime('2011-09-27 10:10:54');
|
||||
$this->assertEquals('2011-09-27 10:11:00', $cron->getNextRunDate($current)->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testFixesPhpBugInDateIntervalMonth()
|
||||
{
|
||||
$cron = CronExpression::factory('0 0 27 JAN *');
|
||||
$this->assertEquals('2011-01-27 00:00:00', $cron->getPreviousRunDate('2011-08-22 00:00:00')->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testIssue29()
|
||||
{
|
||||
$cron = CronExpression::factory('@weekly');
|
||||
$this->assertEquals(
|
||||
'2013-03-10 00:00:00',
|
||||
$cron->getPreviousRunDate('2013-03-17 00:00:00')->format('Y-m-d H:i:s')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/mtdowling/cron-expression/issues/20
|
||||
*/
|
||||
public function testIssue20() {
|
||||
$e = CronExpression::factory('* * * * MON#1');
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-07 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-14 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-21 00:00:00')));
|
||||
|
||||
$e = CronExpression::factory('* * * * SAT#2');
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-05 00:00:00')));
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-12 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-19 00:00:00')));
|
||||
|
||||
$e = CronExpression::factory('* * * * SUN#3');
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-13 00:00:00')));
|
||||
$this->assertTrue($e->isDue(new DateTime('2014-04-20 00:00:00')));
|
||||
$this->assertFalse($e->isDue(new DateTime('2014-04-27 00:00:00')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::getRunDate
|
||||
*/
|
||||
public function testKeepOriginalTime()
|
||||
{
|
||||
$now = new \DateTime;
|
||||
$strNow = $now->format(DateTime::ISO8601);
|
||||
$cron = CronExpression::factory('0 0 * * *');
|
||||
$cron->getPreviousRunDate($now);
|
||||
$this->assertEquals($strNow, $now->format(DateTime::ISO8601));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\CronExpression::__construct
|
||||
* @covers Cron\CronExpression::factory
|
||||
* @covers Cron\CronExpression::isValidExpression
|
||||
* @covers Cron\CronExpression::setExpression
|
||||
* @covers Cron\CronExpression::setPart
|
||||
*/
|
||||
public function testValidationWorks()
|
||||
{
|
||||
// Invalid. Only four values
|
||||
$this->assertFalse(CronExpression::isValidExpression('* * * 1'));
|
||||
// Valid
|
||||
$this->assertTrue(CronExpression::isValidExpression('* * * * 1'));
|
||||
}
|
||||
}
|
||||
61
vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php
vendored
Normal file
61
vendor/mtdowling/cron-expression/tests/Cron/DayOfMonthFieldTest.php
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfMonthField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class DayOfMonthFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\DayOfMonthField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('5W,L'));
|
||||
$this->assertFalse($f->validate('1.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfMonthField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfMonthField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new DayOfMonthField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Day of the month cannot accept a 0 value, it must be between 1 and 31
|
||||
* See Github issue #120
|
||||
*
|
||||
* @since 2017-01-22
|
||||
*/
|
||||
public function testDoesNotAccept0Date()
|
||||
{
|
||||
$f = new DayOfMonthField();
|
||||
$this->assertFalse($f->validate(0));
|
||||
}
|
||||
}
|
||||
117
vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php
vendored
Normal file
117
vendor/mtdowling/cron-expression/tests/Cron/DayOfWeekFieldTest.php
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\DayOfWeekField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class DayOfWeekFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('*/3,1,1-12'));
|
||||
$this->assertTrue($f->validate('SUN-2'));
|
||||
$this->assertFalse($f->validate('1.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testChecksIfSatisfied()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '?'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new DayOfWeekField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-16 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-14 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::isSatisfiedBy
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage Weekday must be a value between 0 and 7. 12 given
|
||||
*/
|
||||
public function testValidatesHashValueWeekday()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '12#1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::isSatisfiedBy
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage There are never more than 5 of a given weekday in a month
|
||||
*/
|
||||
public function testValidatesHashValueNth()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime(), '3#6'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::validate
|
||||
*/
|
||||
public function testValidateWeekendHash()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->validate('MON#1'));
|
||||
$this->assertTrue($f->validate('TUE#2'));
|
||||
$this->assertTrue($f->validate('WED#3'));
|
||||
$this->assertTrue($f->validate('THU#4'));
|
||||
$this->assertTrue($f->validate('FRI#5'));
|
||||
$this->assertTrue($f->validate('SAT#1'));
|
||||
$this->assertTrue($f->validate('SUN#3'));
|
||||
$this->assertTrue($f->validate('MON#1,MON#3'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\DayOfWeekField::isSatisfiedBy
|
||||
*/
|
||||
public function testHandlesZeroAndSevenDayOfTheWeekValues()
|
||||
{
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '0-2'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2011-09-04 00:00:00'), '6-0'));
|
||||
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), 'SUN#3'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '0#3'));
|
||||
$this->assertTrue($f->isSatisfiedBy(new DateTime('2014-04-20 00:00:00'), '7#3'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/mtdowling/cron-expression/issues/47
|
||||
*/
|
||||
public function testIssue47() {
|
||||
$f = new DayOfWeekField();
|
||||
$this->assertFalse($f->validate('mon,'));
|
||||
$this->assertFalse($f->validate('mon-'));
|
||||
$this->assertFalse($f->validate('*/2,'));
|
||||
$this->assertFalse($f->validate('-mon'));
|
||||
$this->assertFalse($f->validate(',1'));
|
||||
$this->assertFalse($f->validate('*-'));
|
||||
$this->assertFalse($f->validate(',-'));
|
||||
}
|
||||
}
|
||||
43
vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php
vendored
Normal file
43
vendor/mtdowling/cron-expression/tests/Cron/FieldFactoryTest.php
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\FieldFactory;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class FieldFactoryTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\FieldFactory::getField
|
||||
*/
|
||||
public function testRetrievesFieldInstances()
|
||||
{
|
||||
$mappings = array(
|
||||
0 => 'Cron\MinutesField',
|
||||
1 => 'Cron\HoursField',
|
||||
2 => 'Cron\DayOfMonthField',
|
||||
3 => 'Cron\MonthField',
|
||||
4 => 'Cron\DayOfWeekField',
|
||||
5 => 'Cron\YearField'
|
||||
);
|
||||
|
||||
$f = new FieldFactory();
|
||||
|
||||
foreach ($mappings as $position => $class) {
|
||||
$this->assertEquals($class, get_class($f->getField($position)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\FieldFactory::getField
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testValidatesFieldPosition()
|
||||
{
|
||||
$f = new FieldFactory();
|
||||
$f->getField(-1);
|
||||
}
|
||||
}
|
||||
75
vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php
vendored
Normal file
75
vendor/mtdowling/cron-expression/tests/Cron/HoursFieldTest.php
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\HoursField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class HoursFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\HoursField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new HoursField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('*/3,1,1-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementsDateWithThirtyMinuteOffsetTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('America/St_Johns');
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\HoursField::increment
|
||||
*/
|
||||
public function testIncrementDateWithFifteenMinuteOffsetTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('Asia/Kathmandu');
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new HoursField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-15 12:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d->setTime(11, 15, 0);
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-15 10:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
}
|
||||
37
vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php
vendored
Normal file
37
vendor/mtdowling/cron-expression/tests/Cron/MinutesFieldTest.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\MinutesField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class MinutesFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\MinutesField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new MinutesField();
|
||||
$this->assertTrue($f->validate('1'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('*/3,1,1-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\MinutesField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new MinutesField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-03-15 11:16:00', $d->format('Y-m-d H:i:s'));
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-03-15 11:15:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
81
vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php
vendored
Normal file
81
vendor/mtdowling/cron-expression/tests/Cron/MonthFieldTest.php
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\MonthField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class MonthFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\MonthField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$this->assertTrue($f->validate('12'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('*/10,2,1-12'));
|
||||
$this->assertFalse($f->validate('1.fix-regexp'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new MonthField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsDateWithThirtyMinuteTimezone()
|
||||
{
|
||||
$tz = date_default_timezone_get();
|
||||
date_default_timezone_set('America/St_Johns');
|
||||
$d = new DateTime('2011-03-31 11:59:59');
|
||||
$f = new MonthField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2011-04-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-02-28 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
date_default_timezone_set($tz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @covers Cron\MonthField::increment
|
||||
*/
|
||||
public function testIncrementsYearAsNeeded()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$d = new DateTime('2011-12-15 00:00:00');
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\MonthField::increment
|
||||
*/
|
||||
public function testDecrementsYearAsNeeded()
|
||||
{
|
||||
$f = new MonthField();
|
||||
$d = new DateTime('2011-01-15 00:00:00');
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2010-12-31 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
37
vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php
vendored
Normal file
37
vendor/mtdowling/cron-expression/tests/Cron/YearFieldTest.php
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Cron\Tests;
|
||||
|
||||
use Cron\YearField;
|
||||
use DateTime;
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
/**
|
||||
* @author Michael Dowling <mtdowling@gmail.com>
|
||||
*/
|
||||
class YearFieldTest extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Cron\YearField::validate
|
||||
*/
|
||||
public function testValidatesField()
|
||||
{
|
||||
$f = new YearField();
|
||||
$this->assertTrue($f->validate('2011'));
|
||||
$this->assertTrue($f->validate('*'));
|
||||
$this->assertTrue($f->validate('*/10,2012,1-12'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Cron\YearField::increment
|
||||
*/
|
||||
public function testIncrementsDate()
|
||||
{
|
||||
$d = new DateTime('2011-03-15 11:15:00');
|
||||
$f = new YearField();
|
||||
$f->increment($d);
|
||||
$this->assertEquals('2012-01-01 00:00:00', $d->format('Y-m-d H:i:s'));
|
||||
$f->increment($d, true);
|
||||
$this->assertEquals('2011-12-31 23:59:00', $d->format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
19
vendor/mtdowling/jmespath.php/LICENSE
vendored
Normal file
19
vendor/mtdowling/jmespath.php/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
123
vendor/mtdowling/jmespath.php/README.rst
vendored
Normal file
123
vendor/mtdowling/jmespath.php/README.rst
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
============
|
||||
jmespath.php
|
||||
============
|
||||
|
||||
JMESPath (pronounced "jaymz path") allows you to declaratively specify how to
|
||||
extract elements from a JSON document. *jmespath.php* allows you to use
|
||||
JMESPath in PHP applications with PHP data structures. It requires PHP 5.4 or
|
||||
greater and can be installed through `Composer <http://getcomposer.org/doc/00-intro.md>`_
|
||||
using the ``mtdowling/jmespath.php`` package.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
$expression = 'foo.*.baz';
|
||||
|
||||
$data = [
|
||||
'foo' => [
|
||||
'bar' => ['baz' => 1],
|
||||
'bam' => ['baz' => 2],
|
||||
'boo' => ['baz' => 3]
|
||||
]
|
||||
];
|
||||
|
||||
JmesPath\search($expression, $data);
|
||||
// Returns: [1, 2, 3]
|
||||
|
||||
- `JMESPath Tutorial <http://jmespath.org/tutorial.html>`_
|
||||
- `JMESPath Grammar <http://jmespath.org/specification.html#grammar>`_
|
||||
- `JMESPath Python library <https://github.com/jmespath/jmespath.py>`_
|
||||
|
||||
PHP Usage
|
||||
=========
|
||||
|
||||
The ``JmesPath\search`` function can be used in most cases when using the
|
||||
library. This function utilizes a JMESPath runtime based on your environment.
|
||||
The runtime utilized can be configured using environment variables and may at
|
||||
some point in the future automatically utilize a C extension if available.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$result = JmesPath\search($expression, $data);
|
||||
|
||||
// or, if you require PSR-4 compliance.
|
||||
$result = JmesPath\Env::search($expression, $data);
|
||||
|
||||
Runtimes
|
||||
--------
|
||||
|
||||
jmespath.php utilizes *runtimes*. There are currently two runtimes:
|
||||
AstRuntime and CompilerRuntime.
|
||||
|
||||
AstRuntime is utilized by ``JmesPath\search()`` and ``JmesPath\Env::search()``
|
||||
by default.
|
||||
|
||||
AstRuntime
|
||||
~~~~~~~~~~
|
||||
|
||||
The AstRuntime will parse an expression, cache the resulting AST in memory,
|
||||
and interpret the AST using an external tree visitor. AstRuntime provides a
|
||||
good general approach for interpreting JMESPath expressions that have a low to
|
||||
moderate level of reuse.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$runtime = new JmesPath\AstRuntime();
|
||||
$runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
|
||||
// > 'baz'
|
||||
|
||||
CompilerRuntime
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
``JmesPath\CompilerRuntime`` provides the most performance for
|
||||
applications that have a moderate to high level of reuse of JMESPath
|
||||
expressions. The CompilerRuntime will walk a JMESPath AST and emit PHP source
|
||||
code, resulting in anywhere from 7x to 60x speed improvements.
|
||||
|
||||
Compiling JMESPath expressions to source code is a slower process than just
|
||||
walking and interpreting a JMESPath AST (via the AstRuntime). However,
|
||||
running the compiled JMESPath code results in much better performance than
|
||||
walking an AST. This essentially means that there is a warm-up period when
|
||||
using the ``CompilerRuntime``, but after the warm-up period, it will provide
|
||||
much better performance.
|
||||
|
||||
Use the CompilerRuntime if you know that you will be executing JMESPath
|
||||
expressions more than once or if you can pre-compile JMESPath expressions
|
||||
before executing them (for example, server-side applications).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// Note: The cache directory argument is optional.
|
||||
$runtime = new JmesPath\CompilerRuntime('/path/to/compile/folder');
|
||||
$runtime('foo.bar', ['foo' => ['bar' => 'baz']]);
|
||||
// > 'baz'
|
||||
|
||||
Environment Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can utilize the CompilerRuntime in ``JmesPath\search()`` by setting
|
||||
the ``JP_PHP_COMPILE`` environment variable to "on" or to a directory
|
||||
on disk used to store cached expressions.
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
A comprehensive list of test cases can be found at
|
||||
https://github.com/jmespath/jmespath.php/tree/master/tests/compliance.
|
||||
These compliance tests are utilized by jmespath.php to ensure consistency with
|
||||
other implementations, and can serve as examples of the language.
|
||||
|
||||
jmespath.php is tested using PHPUnit. In order to run the tests, you need to
|
||||
first install the dependencies using Composer as described in the *Installation*
|
||||
section. Next you just need to run the tests via make:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make test
|
||||
|
||||
You can run a suite of performance tests as well:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make perf
|
||||
74
vendor/mtdowling/jmespath.php/bin/jp.php
vendored
Normal file
74
vendor/mtdowling/jmespath.php/bin/jp.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
|
||||
require __DIR__ . '/../../../autoload.php';
|
||||
} elseif (file_exists(__DIR__ . '/../autoload.php')) {
|
||||
require __DIR__ . '/../autoload.php';
|
||||
} else {
|
||||
throw new RuntimeException('Unable to locate autoload.php file.');
|
||||
}
|
||||
|
||||
use JmesPath\Env;
|
||||
use JmesPath\DebugRuntime;
|
||||
|
||||
$description = <<<EOT
|
||||
Runs a JMESPath expression on the provided input or a test case.
|
||||
|
||||
Provide the JSON input and expression:
|
||||
echo '{}' | jp.php expression
|
||||
|
||||
Or provide the path to a compliance script, a suite, and test case number:
|
||||
jp.php --script path_to_script --suite test_suite_number --case test_case_number [expression]
|
||||
|
||||
EOT;
|
||||
|
||||
$args = [];
|
||||
$currentKey = null;
|
||||
for ($i = 1, $total = count($argv); $i < $total; $i++) {
|
||||
if ($i % 2) {
|
||||
if (substr($argv[$i], 0, 2) == '--') {
|
||||
$currentKey = str_replace('--', '', $argv[$i]);
|
||||
} else {
|
||||
$currentKey = trim($argv[$i]);
|
||||
}
|
||||
} else {
|
||||
$args[$currentKey] = $argv[$i];
|
||||
$currentKey = null;
|
||||
}
|
||||
}
|
||||
|
||||
$expression = $currentKey;
|
||||
|
||||
if (isset($args['file']) || isset($args['suite']) || isset($args['case'])) {
|
||||
if (!isset($args['file']) || !isset($args['suite']) || !isset($args['case'])) {
|
||||
die($description);
|
||||
}
|
||||
// Manually run a compliance test
|
||||
$path = realpath($args['file']);
|
||||
file_exists($path) or die('File not found at ' . $path);
|
||||
$json = json_decode(file_get_contents($path), true);
|
||||
$set = $json[$args['suite']];
|
||||
$data = $set['given'];
|
||||
if (!isset($expression)) {
|
||||
$expression = $set['cases'][$args['case']]['expression'];
|
||||
echo "Expects\n=======\n";
|
||||
if (isset($set['cases'][$args['case']]['result'])) {
|
||||
echo json_encode($set['cases'][$args['case']]['result'], JSON_PRETTY_PRINT) . "\n\n";
|
||||
} elseif (isset($set['cases'][$args['case']]['error'])) {
|
||||
echo "{$set['cases'][$argv['case']]['error']} error\n\n";
|
||||
} else {
|
||||
echo "NULL\n\n";
|
||||
}
|
||||
}
|
||||
} elseif (isset($expression)) {
|
||||
// Pass in an expression and STDIN as a standalone argument
|
||||
$data = json_decode(stream_get_contents(STDIN), true);
|
||||
} else {
|
||||
die($description);
|
||||
}
|
||||
|
||||
$runtime = new DebugRuntime(Env::createRuntime());
|
||||
$runtime($expression, $data);
|
||||
68
vendor/mtdowling/jmespath.php/bin/perf.php
vendored
Normal file
68
vendor/mtdowling/jmespath.php/bin/perf.php
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
} elseif (file_exists(__DIR__ . '/../../../autoload.php')) {
|
||||
require __DIR__ . '/../../../autoload.php';
|
||||
} else {
|
||||
throw new RuntimeException('Unable to locate autoload.php file.');
|
||||
}
|
||||
|
||||
$xdebug = new \Composer\XdebugHandler\XdebugHandler('perf.php');
|
||||
$xdebug->check();
|
||||
unset($xdebug);
|
||||
|
||||
$dir = isset($argv[1]) ? $argv[1] : __DIR__ . '/../tests/compliance/perf';
|
||||
is_dir($dir) or die('Dir not found: ' . $dir);
|
||||
// Warm up the runner
|
||||
\JmesPath\Env::search('foo', []);
|
||||
|
||||
$total = 0;
|
||||
foreach (glob($dir . '/*.json') as $file) {
|
||||
$total += runSuite($file);
|
||||
}
|
||||
echo "\nTotal time: {$total}\n";
|
||||
|
||||
function runSuite($file)
|
||||
{
|
||||
$contents = file_get_contents($file);
|
||||
$json = json_decode($contents, true);
|
||||
$total = 0;
|
||||
foreach ($json as $suite) {
|
||||
foreach ($suite['cases'] as $case) {
|
||||
$total += runCase(
|
||||
$suite['given'],
|
||||
$case['expression'],
|
||||
$case['name']
|
||||
);
|
||||
}
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
function runCase($given, $expression, $name)
|
||||
{
|
||||
$best = 99999;
|
||||
$runtime = \JmesPath\Env::createRuntime();
|
||||
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
$t = microtime(true);
|
||||
$runtime($expression, $given);
|
||||
$tryTime = (microtime(true) - $t) * 1000;
|
||||
if ($tryTime < $best) {
|
||||
$best = $tryTime;
|
||||
}
|
||||
if (!getenv('CACHE')) {
|
||||
$runtime = \JmesPath\Env::createRuntime();
|
||||
// Delete compiled scripts if not caching.
|
||||
if ($runtime instanceof \JmesPath\CompilerRuntime) {
|
||||
array_map('unlink', glob(sys_get_temp_dir() . '/jmespath_*.php'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("time: %07.4fms name: %s\n", $best, $name);
|
||||
|
||||
return $best;
|
||||
}
|
||||
39
vendor/mtdowling/jmespath.php/composer.json
vendored
Normal file
39
vendor/mtdowling/jmespath.php/composer.json
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "mtdowling/jmespath.php",
|
||||
"description": "Declaratively specify how to extract elements from a JSON document",
|
||||
"keywords": ["json", "jsonpath"],
|
||||
"license": "MIT",
|
||||
|
||||
"authors": [
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
}
|
||||
],
|
||||
|
||||
"require": {
|
||||
"php": "^5.4 || ^7.0 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.17"
|
||||
},
|
||||
|
||||
"require-dev": {
|
||||
"composer/xdebug-handler": "^1.4",
|
||||
"phpunit/phpunit": "^4.8.36 || ^7.5.15"
|
||||
},
|
||||
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JmesPath\\": "src/"
|
||||
},
|
||||
"files": ["src/JmesPath.php"]
|
||||
},
|
||||
|
||||
"bin": ["bin/jp.php"],
|
||||
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.6-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
47
vendor/mtdowling/jmespath.php/src/AstRuntime.php
vendored
Normal file
47
vendor/mtdowling/jmespath.php/src/AstRuntime.php
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Uses an external tree visitor to interpret an AST.
|
||||
*/
|
||||
class AstRuntime
|
||||
{
|
||||
private $parser;
|
||||
private $interpreter;
|
||||
private $cache = [];
|
||||
private $cachedCount = 0;
|
||||
|
||||
public function __construct(
|
||||
Parser $parser = null,
|
||||
callable $fnDispatcher = null
|
||||
) {
|
||||
$fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
|
||||
$this->interpreter = new TreeInterpreter($fnDispatcher);
|
||||
$this->parser = $parser ?: new Parser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from the provided input that matches a given JMESPath
|
||||
* expression.
|
||||
*
|
||||
* @param string $expression JMESPath expression to evaluate
|
||||
* @param mixed $data Data to search. This data should be data that
|
||||
* is similar to data returned from json_decode
|
||||
* using associative arrays rather than objects.
|
||||
*
|
||||
* @return mixed Returns the matching data or null
|
||||
*/
|
||||
public function __invoke($expression, $data)
|
||||
{
|
||||
if (!isset($this->cache[$expression])) {
|
||||
// Clear the AST cache when it hits 1024 entries
|
||||
if (++$this->cachedCount > 1024) {
|
||||
$this->cache = [];
|
||||
$this->cachedCount = 0;
|
||||
}
|
||||
$this->cache[$expression] = $this->parser->parse($expression);
|
||||
}
|
||||
|
||||
return $this->interpreter->visit($this->cache[$expression], $data);
|
||||
}
|
||||
}
|
||||
83
vendor/mtdowling/jmespath.php/src/CompilerRuntime.php
vendored
Normal file
83
vendor/mtdowling/jmespath.php/src/CompilerRuntime.php
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Compiles JMESPath expressions to PHP source code and executes it.
|
||||
*
|
||||
* JMESPath file names are stored in the cache directory using the following
|
||||
* logic to determine the filename:
|
||||
*
|
||||
* 1. Start with the string "jmespath_"
|
||||
* 2. Append the MD5 checksum of the expression.
|
||||
* 3. Append ".php"
|
||||
*/
|
||||
class CompilerRuntime
|
||||
{
|
||||
private $parser;
|
||||
private $compiler;
|
||||
private $cacheDir;
|
||||
private $interpreter;
|
||||
|
||||
/**
|
||||
* @param string|null $dir Directory used to store compiled PHP files.
|
||||
* @param Parser|null $parser JMESPath parser to utilize
|
||||
* @throws \RuntimeException if the cache directory cannot be created
|
||||
*/
|
||||
public function __construct($dir = null, Parser $parser = null)
|
||||
{
|
||||
$this->parser = $parser ?: new Parser();
|
||||
$this->compiler = new TreeCompiler();
|
||||
$dir = $dir ?: sys_get_temp_dir();
|
||||
|
||||
if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
|
||||
throw new \RuntimeException("Unable to create cache directory: $dir");
|
||||
}
|
||||
|
||||
$this->cacheDir = realpath($dir);
|
||||
$this->interpreter = new TreeInterpreter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data from the provided input that matches a given JMESPath
|
||||
* expression.
|
||||
*
|
||||
* @param string $expression JMESPath expression to evaluate
|
||||
* @param mixed $data Data to search. This data should be data that
|
||||
* is similar to data returned from json_decode
|
||||
* using associative arrays rather than objects.
|
||||
*
|
||||
* @return mixed Returns the matching data or null
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __invoke($expression, $data)
|
||||
{
|
||||
$functionName = 'jmespath_' . md5($expression);
|
||||
|
||||
if (!function_exists($functionName)) {
|
||||
$filename = "{$this->cacheDir}/{$functionName}.php";
|
||||
if (!file_exists($filename)) {
|
||||
$this->compile($filename, $expression, $functionName);
|
||||
}
|
||||
require $filename;
|
||||
}
|
||||
|
||||
return $functionName($this->interpreter, $data);
|
||||
}
|
||||
|
||||
private function compile($filename, $expression, $functionName)
|
||||
{
|
||||
$code = $this->compiler->visit(
|
||||
$this->parser->parse($expression),
|
||||
$functionName,
|
||||
$expression
|
||||
);
|
||||
|
||||
if (!file_put_contents($filename, $code)) {
|
||||
throw new \RuntimeException(sprintf(
|
||||
'Unable to write the compiled PHP code to: %s (%s)',
|
||||
$filename,
|
||||
var_export(error_get_last(), true)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
109
vendor/mtdowling/jmespath.php/src/DebugRuntime.php
vendored
Normal file
109
vendor/mtdowling/jmespath.php/src/DebugRuntime.php
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Provides CLI debugging information for the AST and Compiler runtimes.
|
||||
*/
|
||||
class DebugRuntime
|
||||
{
|
||||
private $runtime;
|
||||
private $out;
|
||||
private $lexer;
|
||||
private $parser;
|
||||
|
||||
public function __construct(callable $runtime, $output = null)
|
||||
{
|
||||
$this->runtime = $runtime;
|
||||
$this->out = $output ?: STDOUT;
|
||||
$this->lexer = new Lexer();
|
||||
$this->parser = new Parser($this->lexer);
|
||||
}
|
||||
|
||||
public function __invoke($expression, $data)
|
||||
{
|
||||
if ($this->runtime instanceof CompilerRuntime) {
|
||||
return $this->debugCompiled($expression, $data);
|
||||
}
|
||||
|
||||
return $this->debugInterpreted($expression, $data);
|
||||
}
|
||||
|
||||
private function debugInterpreted($expression, $data)
|
||||
{
|
||||
return $this->debugCallback(
|
||||
function () use ($expression, $data) {
|
||||
$runtime = $this->runtime;
|
||||
return $runtime($expression, $data);
|
||||
},
|
||||
$expression,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
private function debugCompiled($expression, $data)
|
||||
{
|
||||
$result = $this->debugCallback(
|
||||
function () use ($expression, $data) {
|
||||
$runtime = $this->runtime;
|
||||
return $runtime($expression, $data);
|
||||
},
|
||||
$expression,
|
||||
$data
|
||||
);
|
||||
$this->dumpCompiledCode($expression);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function dumpTokens($expression)
|
||||
{
|
||||
$lexer = new Lexer();
|
||||
fwrite($this->out, "Tokens\n======\n\n");
|
||||
$tokens = $lexer->tokenize($expression);
|
||||
|
||||
foreach ($tokens as $t) {
|
||||
fprintf(
|
||||
$this->out,
|
||||
"%3d %-13s %s\n", $t['pos'], $t['type'],
|
||||
json_encode($t['value'])
|
||||
);
|
||||
}
|
||||
|
||||
fwrite($this->out, "\n");
|
||||
}
|
||||
|
||||
private function dumpAst($expression)
|
||||
{
|
||||
$parser = new Parser();
|
||||
$ast = $parser->parse($expression);
|
||||
fwrite($this->out, "AST\n========\n\n");
|
||||
fwrite($this->out, json_encode($ast, JSON_PRETTY_PRINT) . "\n");
|
||||
}
|
||||
|
||||
private function dumpCompiledCode($expression)
|
||||
{
|
||||
fwrite($this->out, "Code\n========\n\n");
|
||||
$dir = sys_get_temp_dir();
|
||||
$hash = md5($expression);
|
||||
$functionName = "jmespath_{$hash}";
|
||||
$filename = "{$dir}/{$functionName}.php";
|
||||
fwrite($this->out, "File: {$filename}\n\n");
|
||||
fprintf($this->out, file_get_contents($filename));
|
||||
}
|
||||
|
||||
private function debugCallback(callable $debugFn, $expression, $data)
|
||||
{
|
||||
fprintf($this->out, "Expression\n==========\n\n%s\n\n", $expression);
|
||||
$this->dumpTokens($expression);
|
||||
$this->dumpAst($expression);
|
||||
fprintf($this->out, "\nData\n====\n\n%s\n\n", json_encode($data, JSON_PRETTY_PRINT));
|
||||
$startTime = microtime(true);
|
||||
$result = $debugFn();
|
||||
$total = microtime(true) - $startTime;
|
||||
fprintf($this->out, "\nResult\n======\n\n%s\n\n", json_encode($result, JSON_PRETTY_PRINT));
|
||||
fwrite($this->out, "Time\n====\n\n");
|
||||
fprintf($this->out, "Total time: %f ms\n\n", $total);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
91
vendor/mtdowling/jmespath.php/src/Env.php
vendored
Normal file
91
vendor/mtdowling/jmespath.php/src/Env.php
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Provides a simple environment based search.
|
||||
*
|
||||
* The runtime utilized by the Env class can be customized via environment
|
||||
* variables. If the JP_PHP_COMPILE environment variable is specified, then the
|
||||
* CompilerRuntime will be utilized. If set to "on", JMESPath expressions will
|
||||
* be cached to the system's temp directory. Set the environment variable to
|
||||
* a string to cache expressions to a specific directory.
|
||||
*/
|
||||
final class Env
|
||||
{
|
||||
const COMPILE_DIR = 'JP_PHP_COMPILE';
|
||||
|
||||
/**
|
||||
* Returns data from the input array that matches a JMESPath expression.
|
||||
*
|
||||
* @param string $expression JMESPath expression to evaluate
|
||||
* @param mixed $data JSON-like data to search
|
||||
*
|
||||
* @return mixed Returns the matching data or null
|
||||
*/
|
||||
public static function search($expression, $data)
|
||||
{
|
||||
static $runtime;
|
||||
|
||||
if (!$runtime) {
|
||||
$runtime = Env::createRuntime();
|
||||
}
|
||||
|
||||
return $runtime($expression, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JMESPath runtime based on environment variables and extensions
|
||||
* available on a system.
|
||||
*
|
||||
* @return callable
|
||||
*/
|
||||
public static function createRuntime()
|
||||
{
|
||||
switch ($compileDir = self::getEnvVariable(self::COMPILE_DIR)) {
|
||||
case false: return new AstRuntime();
|
||||
case 'on': return new CompilerRuntime();
|
||||
default: return new CompilerRuntime($compileDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all previously compiled JMESPath files from the JP_COMPILE_DIR
|
||||
* directory or sys_get_temp_dir().
|
||||
*
|
||||
* @return int Returns the number of deleted files.
|
||||
*/
|
||||
public static function cleanCompileDir()
|
||||
{
|
||||
$total = 0;
|
||||
$compileDir = self::getEnvVariable(self::COMPILE_DIR) ?: sys_get_temp_dir();
|
||||
|
||||
foreach (glob("{$compileDir}/jmespath_*.php") as $file) {
|
||||
$total++;
|
||||
unlink($file);
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an environment variable from $_SERVER, $_ENV or via getenv().
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function getEnvVariable($name)
|
||||
{
|
||||
if (array_key_exists($name, $_SERVER)) {
|
||||
return $_SERVER[$name];
|
||||
}
|
||||
|
||||
if (array_key_exists($name, $_ENV)) {
|
||||
return $_ENV[$name];
|
||||
}
|
||||
|
||||
$value = getenv($name);
|
||||
|
||||
return $value === false ? null : $value;
|
||||
}
|
||||
}
|
||||
407
vendor/mtdowling/jmespath.php/src/FnDispatcher.php
vendored
Normal file
407
vendor/mtdowling/jmespath.php/src/FnDispatcher.php
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Dispatches to named JMESPath functions using a single function that has the
|
||||
* following signature:
|
||||
*
|
||||
* mixed $result = fn(string $function_name, array $args)
|
||||
*/
|
||||
class FnDispatcher
|
||||
{
|
||||
/**
|
||||
* Gets a cached instance of the default function implementations.
|
||||
*
|
||||
* @return FnDispatcher
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
static $instance = null;
|
||||
if (!$instance) {
|
||||
$instance = new self();
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fn Function name.
|
||||
* @param array $args Function arguments.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke($fn, array $args)
|
||||
{
|
||||
return $this->{'fn_' . $fn}($args);
|
||||
}
|
||||
|
||||
private function fn_abs(array $args)
|
||||
{
|
||||
$this->validate('abs', $args, [['number']]);
|
||||
return abs($args[0]);
|
||||
}
|
||||
|
||||
private function fn_avg(array $args)
|
||||
{
|
||||
$this->validate('avg', $args, [['array']]);
|
||||
$sum = $this->reduce('avg:0', $args[0], ['number'], function ($a, $b) {
|
||||
return Utils::add($a, $b);
|
||||
});
|
||||
return $args[0] ? ($sum / count($args[0])) : null;
|
||||
}
|
||||
|
||||
private function fn_ceil(array $args)
|
||||
{
|
||||
$this->validate('ceil', $args, [['number']]);
|
||||
return ceil($args[0]);
|
||||
}
|
||||
|
||||
private function fn_contains(array $args)
|
||||
{
|
||||
$this->validate('contains', $args, [['string', 'array'], ['any']]);
|
||||
if (is_array($args[0])) {
|
||||
return in_array($args[1], $args[0]);
|
||||
} elseif (is_string($args[1])) {
|
||||
return mb_strpos($args[0], $args[1], 0, 'UTF-8') !== false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function fn_ends_with(array $args)
|
||||
{
|
||||
$this->validate('ends_with', $args, [['string'], ['string']]);
|
||||
list($search, $suffix) = $args;
|
||||
return $suffix === '' || mb_substr($search, -mb_strlen($suffix, 'UTF-8'), null, 'UTF-8') === $suffix;
|
||||
}
|
||||
|
||||
private function fn_floor(array $args)
|
||||
{
|
||||
$this->validate('floor', $args, [['number']]);
|
||||
return floor($args[0]);
|
||||
}
|
||||
|
||||
private function fn_not_null(array $args)
|
||||
{
|
||||
if (!$args) {
|
||||
throw new \RuntimeException(
|
||||
"not_null() expects 1 or more arguments, 0 were provided"
|
||||
);
|
||||
}
|
||||
|
||||
return array_reduce($args, function ($carry, $item) {
|
||||
return $carry !== null ? $carry : $item;
|
||||
});
|
||||
}
|
||||
|
||||
private function fn_join(array $args)
|
||||
{
|
||||
$this->validate('join', $args, [['string'], ['array']]);
|
||||
$fn = function ($a, $b, $i) use ($args) {
|
||||
return $i ? ($a . $args[0] . $b) : $b;
|
||||
};
|
||||
return $this->reduce('join:0', $args[1], ['string'], $fn);
|
||||
}
|
||||
|
||||
private function fn_keys(array $args)
|
||||
{
|
||||
$this->validate('keys', $args, [['object']]);
|
||||
return array_keys((array) $args[0]);
|
||||
}
|
||||
|
||||
private function fn_length(array $args)
|
||||
{
|
||||
$this->validate('length', $args, [['string', 'array', 'object']]);
|
||||
return is_string($args[0]) ? mb_strlen($args[0], 'UTF-8') : count((array) $args[0]);
|
||||
}
|
||||
|
||||
private function fn_max(array $args)
|
||||
{
|
||||
$this->validate('max', $args, [['array']]);
|
||||
$fn = function ($a, $b) {
|
||||
return $a >= $b ? $a : $b;
|
||||
};
|
||||
return $this->reduce('max:0', $args[0], ['number', 'string'], $fn);
|
||||
}
|
||||
|
||||
private function fn_max_by(array $args)
|
||||
{
|
||||
$this->validate('max_by', $args, [['array'], ['expression']]);
|
||||
$expr = $this->wrapExpression('max_by:1', $args[1], ['number', 'string']);
|
||||
$fn = function ($carry, $item, $index) use ($expr) {
|
||||
return $index
|
||||
? ($expr($carry) >= $expr($item) ? $carry : $item)
|
||||
: $item;
|
||||
};
|
||||
return $this->reduce('max_by:1', $args[0], ['any'], $fn);
|
||||
}
|
||||
|
||||
private function fn_min(array $args)
|
||||
{
|
||||
$this->validate('min', $args, [['array']]);
|
||||
$fn = function ($a, $b, $i) {
|
||||
return $i && $a <= $b ? $a : $b;
|
||||
};
|
||||
return $this->reduce('min:0', $args[0], ['number', 'string'], $fn);
|
||||
}
|
||||
|
||||
private function fn_min_by(array $args)
|
||||
{
|
||||
$this->validate('min_by', $args, [['array'], ['expression']]);
|
||||
$expr = $this->wrapExpression('min_by:1', $args[1], ['number', 'string']);
|
||||
$i = -1;
|
||||
$fn = function ($a, $b) use ($expr, &$i) {
|
||||
return ++$i ? ($expr($a) <= $expr($b) ? $a : $b) : $b;
|
||||
};
|
||||
return $this->reduce('min_by:1', $args[0], ['any'], $fn);
|
||||
}
|
||||
|
||||
private function fn_reverse(array $args)
|
||||
{
|
||||
$this->validate('reverse', $args, [['array', 'string']]);
|
||||
if (is_array($args[0])) {
|
||||
return array_reverse($args[0]);
|
||||
} elseif (is_string($args[0])) {
|
||||
return strrev($args[0]);
|
||||
} else {
|
||||
throw new \RuntimeException('Cannot reverse provided argument');
|
||||
}
|
||||
}
|
||||
|
||||
private function fn_sum(array $args)
|
||||
{
|
||||
$this->validate('sum', $args, [['array']]);
|
||||
$fn = function ($a, $b) {
|
||||
return Utils::add($a, $b);
|
||||
};
|
||||
return $this->reduce('sum:0', $args[0], ['number'], $fn);
|
||||
}
|
||||
|
||||
private function fn_sort(array $args)
|
||||
{
|
||||
$this->validate('sort', $args, [['array']]);
|
||||
$valid = ['string', 'number'];
|
||||
return Utils::stableSort($args[0], function ($a, $b) use ($valid) {
|
||||
$this->validateSeq('sort:0', $valid, $a, $b);
|
||||
return strnatcmp($a, $b);
|
||||
});
|
||||
}
|
||||
|
||||
private function fn_sort_by(array $args)
|
||||
{
|
||||
$this->validate('sort_by', $args, [['array'], ['expression']]);
|
||||
$expr = $args[1];
|
||||
$valid = ['string', 'number'];
|
||||
return Utils::stableSort(
|
||||
$args[0],
|
||||
function ($a, $b) use ($expr, $valid) {
|
||||
$va = $expr($a);
|
||||
$vb = $expr($b);
|
||||
$this->validateSeq('sort_by:0', $valid, $va, $vb);
|
||||
return strnatcmp($va, $vb);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function fn_starts_with(array $args)
|
||||
{
|
||||
$this->validate('starts_with', $args, [['string'], ['string']]);
|
||||
list($search, $prefix) = $args;
|
||||
return $prefix === '' || mb_strpos($search, $prefix, 0, 'UTF-8') === 0;
|
||||
}
|
||||
|
||||
private function fn_type(array $args)
|
||||
{
|
||||
$this->validateArity('type', count($args), 1);
|
||||
return Utils::type($args[0]);
|
||||
}
|
||||
|
||||
private function fn_to_string(array $args)
|
||||
{
|
||||
$this->validateArity('to_string', count($args), 1);
|
||||
$v = $args[0];
|
||||
if (is_string($v)) {
|
||||
return $v;
|
||||
} elseif (is_object($v)
|
||||
&& !($v instanceof \JsonSerializable)
|
||||
&& method_exists($v, '__toString')
|
||||
) {
|
||||
return (string) $v;
|
||||
}
|
||||
|
||||
return json_encode($v);
|
||||
}
|
||||
|
||||
private function fn_to_number(array $args)
|
||||
{
|
||||
$this->validateArity('to_number', count($args), 1);
|
||||
$value = $args[0];
|
||||
$type = Utils::type($value);
|
||||
if ($type == 'number') {
|
||||
return $value;
|
||||
} elseif ($type == 'string' && is_numeric($value)) {
|
||||
return mb_strpos($value, '.', 0, 'UTF-8') ? (float) $value : (int) $value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function fn_values(array $args)
|
||||
{
|
||||
$this->validate('values', $args, [['array', 'object']]);
|
||||
return array_values((array) $args[0]);
|
||||
}
|
||||
|
||||
private function fn_merge(array $args)
|
||||
{
|
||||
if (!$args) {
|
||||
throw new \RuntimeException(
|
||||
"merge() expects 1 or more arguments, 0 were provided"
|
||||
);
|
||||
}
|
||||
|
||||
return call_user_func_array('array_replace', $args);
|
||||
}
|
||||
|
||||
private function fn_to_array(array $args)
|
||||
{
|
||||
$this->validate('to_array', $args, [['any']]);
|
||||
|
||||
return Utils::isArray($args[0]) ? $args[0] : [$args[0]];
|
||||
}
|
||||
|
||||
private function fn_map(array $args)
|
||||
{
|
||||
$this->validate('map', $args, [['expression'], ['any']]);
|
||||
$result = [];
|
||||
foreach ($args[1] as $a) {
|
||||
$result[] = $args[0]($a);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function typeError($from, $msg)
|
||||
{
|
||||
if (mb_strpos($from, ':', 0, 'UTF-8')) {
|
||||
list($fn, $pos) = explode(':', $from);
|
||||
throw new \RuntimeException(
|
||||
sprintf('Argument %d of %s %s', $pos, $fn, $msg)
|
||||
);
|
||||
} else {
|
||||
throw new \RuntimeException(
|
||||
sprintf('Type error: %s %s', $from, $msg)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateArity($from, $given, $expected)
|
||||
{
|
||||
if ($given != $expected) {
|
||||
$err = "%s() expects {$expected} arguments, {$given} were provided";
|
||||
throw new \RuntimeException(sprintf($err, $from));
|
||||
}
|
||||
}
|
||||
|
||||
private function validate($from, $args, $types = [])
|
||||
{
|
||||
$this->validateArity($from, count($args), count($types));
|
||||
foreach ($args as $index => $value) {
|
||||
if (!isset($types[$index]) || !$types[$index]) {
|
||||
continue;
|
||||
}
|
||||
$this->validateType("{$from}:{$index}", $value, $types[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateType($from, $value, array $types)
|
||||
{
|
||||
if ($types[0] == 'any'
|
||||
|| in_array(Utils::type($value), $types)
|
||||
|| ($value === [] && in_array('object', $types))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$msg = 'must be one of the following types: ' . implode(', ', $types)
|
||||
. '. ' . Utils::type($value) . ' found';
|
||||
$this->typeError($from, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates value A and B, ensures they both are correctly typed, and of
|
||||
* the same type.
|
||||
*
|
||||
* @param string $from String of function:argument_position
|
||||
* @param array $types Array of valid value types.
|
||||
* @param mixed $a Value A
|
||||
* @param mixed $b Value B
|
||||
*/
|
||||
private function validateSeq($from, array $types, $a, $b)
|
||||
{
|
||||
$ta = Utils::type($a);
|
||||
$tb = Utils::type($b);
|
||||
|
||||
if ($ta !== $tb) {
|
||||
$msg = "encountered a type mismatch in sequence: {$ta}, {$tb}";
|
||||
$this->typeError($from, $msg);
|
||||
}
|
||||
|
||||
$typeMatch = ($types && $types[0] == 'any') || in_array($ta, $types);
|
||||
if (!$typeMatch) {
|
||||
$msg = 'encountered a type error in sequence. The argument must be '
|
||||
. 'an array of ' . implode('|', $types) . ' types. '
|
||||
. "Found {$ta}, {$tb}.";
|
||||
$this->typeError($from, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces and validates an array of values to a single value using a fn.
|
||||
*
|
||||
* @param string $from String of function:argument_position
|
||||
* @param array $values Values to reduce.
|
||||
* @param array $types Array of valid value types.
|
||||
* @param callable $reduce Reduce function that accepts ($carry, $item).
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function reduce($from, array $values, array $types, callable $reduce)
|
||||
{
|
||||
$i = -1;
|
||||
return array_reduce(
|
||||
$values,
|
||||
function ($carry, $item) use ($from, $types, $reduce, &$i) {
|
||||
if (++$i > 0) {
|
||||
$this->validateSeq($from, $types, $carry, $item);
|
||||
}
|
||||
return $reduce($carry, $item, $i);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the return values of expressions as they are applied.
|
||||
*
|
||||
* @param string $from Function name : position
|
||||
* @param callable $expr Expression function to validate.
|
||||
* @param array $types Array of acceptable return type values.
|
||||
*
|
||||
* @return callable Returns a wrapped function
|
||||
*/
|
||||
private function wrapExpression($from, callable $expr, array $types)
|
||||
{
|
||||
list($fn, $pos) = explode(':', $from);
|
||||
$from = "The expression return value of argument {$pos} of {$fn}";
|
||||
return function ($value) use ($from, $expr, $types) {
|
||||
$value = $expr($value);
|
||||
$this->validateType($from, $value, $types);
|
||||
return $value;
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal Pass function name validation off to runtime */
|
||||
public function __call($name, $args)
|
||||
{
|
||||
$name = str_replace('fn_', '', $name);
|
||||
throw new \RuntimeException("Call to undefined function {$name}");
|
||||
}
|
||||
}
|
||||
17
vendor/mtdowling/jmespath.php/src/JmesPath.php
vendored
Normal file
17
vendor/mtdowling/jmespath.php/src/JmesPath.php
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Returns data from the input array that matches a JMESPath expression.
|
||||
*
|
||||
* @param string $expression Expression to search.
|
||||
* @param mixed $data Data to search.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
if (!function_exists(__NAMESPACE__ . '\search')) {
|
||||
function search($expression, $data)
|
||||
{
|
||||
return Env::search($expression, $data);
|
||||
}
|
||||
}
|
||||
444
vendor/mtdowling/jmespath.php/src/Lexer.php
vendored
Normal file
444
vendor/mtdowling/jmespath.php/src/Lexer.php
vendored
Normal file
@@ -0,0 +1,444 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Tokenizes JMESPath expressions
|
||||
*/
|
||||
class Lexer
|
||||
{
|
||||
const T_DOT = 'dot';
|
||||
const T_STAR = 'star';
|
||||
const T_COMMA = 'comma';
|
||||
const T_COLON = 'colon';
|
||||
const T_CURRENT = 'current';
|
||||
const T_EXPREF = 'expref';
|
||||
const T_LPAREN = 'lparen';
|
||||
const T_RPAREN = 'rparen';
|
||||
const T_LBRACE = 'lbrace';
|
||||
const T_RBRACE = 'rbrace';
|
||||
const T_LBRACKET = 'lbracket';
|
||||
const T_RBRACKET = 'rbracket';
|
||||
const T_FLATTEN = 'flatten';
|
||||
const T_IDENTIFIER = 'identifier';
|
||||
const T_NUMBER = 'number';
|
||||
const T_QUOTED_IDENTIFIER = 'quoted_identifier';
|
||||
const T_UNKNOWN = 'unknown';
|
||||
const T_PIPE = 'pipe';
|
||||
const T_OR = 'or';
|
||||
const T_AND = 'and';
|
||||
const T_NOT = 'not';
|
||||
const T_FILTER = 'filter';
|
||||
const T_LITERAL = 'literal';
|
||||
const T_EOF = 'eof';
|
||||
const T_COMPARATOR = 'comparator';
|
||||
|
||||
const STATE_IDENTIFIER = 0;
|
||||
const STATE_NUMBER = 1;
|
||||
const STATE_SINGLE_CHAR = 2;
|
||||
const STATE_WHITESPACE = 3;
|
||||
const STATE_STRING_LITERAL = 4;
|
||||
const STATE_QUOTED_STRING = 5;
|
||||
const STATE_JSON_LITERAL = 6;
|
||||
const STATE_LBRACKET = 7;
|
||||
const STATE_PIPE = 8;
|
||||
const STATE_LT = 9;
|
||||
const STATE_GT = 10;
|
||||
const STATE_EQ = 11;
|
||||
const STATE_NOT = 12;
|
||||
const STATE_AND = 13;
|
||||
|
||||
/** @var array We know what token we are consuming based on each char */
|
||||
private static $transitionTable = [
|
||||
'<' => self::STATE_LT,
|
||||
'>' => self::STATE_GT,
|
||||
'=' => self::STATE_EQ,
|
||||
'!' => self::STATE_NOT,
|
||||
'[' => self::STATE_LBRACKET,
|
||||
'|' => self::STATE_PIPE,
|
||||
'&' => self::STATE_AND,
|
||||
'`' => self::STATE_JSON_LITERAL,
|
||||
'"' => self::STATE_QUOTED_STRING,
|
||||
"'" => self::STATE_STRING_LITERAL,
|
||||
'-' => self::STATE_NUMBER,
|
||||
'0' => self::STATE_NUMBER,
|
||||
'1' => self::STATE_NUMBER,
|
||||
'2' => self::STATE_NUMBER,
|
||||
'3' => self::STATE_NUMBER,
|
||||
'4' => self::STATE_NUMBER,
|
||||
'5' => self::STATE_NUMBER,
|
||||
'6' => self::STATE_NUMBER,
|
||||
'7' => self::STATE_NUMBER,
|
||||
'8' => self::STATE_NUMBER,
|
||||
'9' => self::STATE_NUMBER,
|
||||
' ' => self::STATE_WHITESPACE,
|
||||
"\t" => self::STATE_WHITESPACE,
|
||||
"\n" => self::STATE_WHITESPACE,
|
||||
"\r" => self::STATE_WHITESPACE,
|
||||
'.' => self::STATE_SINGLE_CHAR,
|
||||
'*' => self::STATE_SINGLE_CHAR,
|
||||
']' => self::STATE_SINGLE_CHAR,
|
||||
',' => self::STATE_SINGLE_CHAR,
|
||||
':' => self::STATE_SINGLE_CHAR,
|
||||
'@' => self::STATE_SINGLE_CHAR,
|
||||
'(' => self::STATE_SINGLE_CHAR,
|
||||
')' => self::STATE_SINGLE_CHAR,
|
||||
'{' => self::STATE_SINGLE_CHAR,
|
||||
'}' => self::STATE_SINGLE_CHAR,
|
||||
'_' => self::STATE_IDENTIFIER,
|
||||
'A' => self::STATE_IDENTIFIER,
|
||||
'B' => self::STATE_IDENTIFIER,
|
||||
'C' => self::STATE_IDENTIFIER,
|
||||
'D' => self::STATE_IDENTIFIER,
|
||||
'E' => self::STATE_IDENTIFIER,
|
||||
'F' => self::STATE_IDENTIFIER,
|
||||
'G' => self::STATE_IDENTIFIER,
|
||||
'H' => self::STATE_IDENTIFIER,
|
||||
'I' => self::STATE_IDENTIFIER,
|
||||
'J' => self::STATE_IDENTIFIER,
|
||||
'K' => self::STATE_IDENTIFIER,
|
||||
'L' => self::STATE_IDENTIFIER,
|
||||
'M' => self::STATE_IDENTIFIER,
|
||||
'N' => self::STATE_IDENTIFIER,
|
||||
'O' => self::STATE_IDENTIFIER,
|
||||
'P' => self::STATE_IDENTIFIER,
|
||||
'Q' => self::STATE_IDENTIFIER,
|
||||
'R' => self::STATE_IDENTIFIER,
|
||||
'S' => self::STATE_IDENTIFIER,
|
||||
'T' => self::STATE_IDENTIFIER,
|
||||
'U' => self::STATE_IDENTIFIER,
|
||||
'V' => self::STATE_IDENTIFIER,
|
||||
'W' => self::STATE_IDENTIFIER,
|
||||
'X' => self::STATE_IDENTIFIER,
|
||||
'Y' => self::STATE_IDENTIFIER,
|
||||
'Z' => self::STATE_IDENTIFIER,
|
||||
'a' => self::STATE_IDENTIFIER,
|
||||
'b' => self::STATE_IDENTIFIER,
|
||||
'c' => self::STATE_IDENTIFIER,
|
||||
'd' => self::STATE_IDENTIFIER,
|
||||
'e' => self::STATE_IDENTIFIER,
|
||||
'f' => self::STATE_IDENTIFIER,
|
||||
'g' => self::STATE_IDENTIFIER,
|
||||
'h' => self::STATE_IDENTIFIER,
|
||||
'i' => self::STATE_IDENTIFIER,
|
||||
'j' => self::STATE_IDENTIFIER,
|
||||
'k' => self::STATE_IDENTIFIER,
|
||||
'l' => self::STATE_IDENTIFIER,
|
||||
'm' => self::STATE_IDENTIFIER,
|
||||
'n' => self::STATE_IDENTIFIER,
|
||||
'o' => self::STATE_IDENTIFIER,
|
||||
'p' => self::STATE_IDENTIFIER,
|
||||
'q' => self::STATE_IDENTIFIER,
|
||||
'r' => self::STATE_IDENTIFIER,
|
||||
's' => self::STATE_IDENTIFIER,
|
||||
't' => self::STATE_IDENTIFIER,
|
||||
'u' => self::STATE_IDENTIFIER,
|
||||
'v' => self::STATE_IDENTIFIER,
|
||||
'w' => self::STATE_IDENTIFIER,
|
||||
'x' => self::STATE_IDENTIFIER,
|
||||
'y' => self::STATE_IDENTIFIER,
|
||||
'z' => self::STATE_IDENTIFIER,
|
||||
];
|
||||
|
||||
/** @var array Valid identifier characters after first character */
|
||||
private $validIdentifier = [
|
||||
'A' => true, 'B' => true, 'C' => true, 'D' => true, 'E' => true,
|
||||
'F' => true, 'G' => true, 'H' => true, 'I' => true, 'J' => true,
|
||||
'K' => true, 'L' => true, 'M' => true, 'N' => true, 'O' => true,
|
||||
'P' => true, 'Q' => true, 'R' => true, 'S' => true, 'T' => true,
|
||||
'U' => true, 'V' => true, 'W' => true, 'X' => true, 'Y' => true,
|
||||
'Z' => true, 'a' => true, 'b' => true, 'c' => true, 'd' => true,
|
||||
'e' => true, 'f' => true, 'g' => true, 'h' => true, 'i' => true,
|
||||
'j' => true, 'k' => true, 'l' => true, 'm' => true, 'n' => true,
|
||||
'o' => true, 'p' => true, 'q' => true, 'r' => true, 's' => true,
|
||||
't' => true, 'u' => true, 'v' => true, 'w' => true, 'x' => true,
|
||||
'y' => true, 'z' => true, '_' => true, '0' => true, '1' => true,
|
||||
'2' => true, '3' => true, '4' => true, '5' => true, '6' => true,
|
||||
'7' => true, '8' => true, '9' => true,
|
||||
];
|
||||
|
||||
/** @var array Valid number characters after the first character */
|
||||
private $numbers = [
|
||||
'0' => true, '1' => true, '2' => true, '3' => true, '4' => true,
|
||||
'5' => true, '6' => true, '7' => true, '8' => true, '9' => true
|
||||
];
|
||||
|
||||
/** @var array Map of simple single character tokens */
|
||||
private $simpleTokens = [
|
||||
'.' => self::T_DOT,
|
||||
'*' => self::T_STAR,
|
||||
']' => self::T_RBRACKET,
|
||||
',' => self::T_COMMA,
|
||||
':' => self::T_COLON,
|
||||
'@' => self::T_CURRENT,
|
||||
'(' => self::T_LPAREN,
|
||||
')' => self::T_RPAREN,
|
||||
'{' => self::T_LBRACE,
|
||||
'}' => self::T_RBRACE,
|
||||
];
|
||||
|
||||
/**
|
||||
* Tokenize the JMESPath expression into an array of tokens hashes that
|
||||
* contain a 'type', 'value', and 'key'.
|
||||
*
|
||||
* @param string $input JMESPath input
|
||||
*
|
||||
* @return array
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
public function tokenize($input)
|
||||
{
|
||||
$tokens = [];
|
||||
|
||||
if ($input === '') {
|
||||
goto eof;
|
||||
}
|
||||
|
||||
$chars = str_split($input);
|
||||
|
||||
while (false !== ($current = current($chars))) {
|
||||
|
||||
// Every character must be in the transition character table.
|
||||
if (!isset(self::$transitionTable[$current])) {
|
||||
$tokens[] = [
|
||||
'type' => self::T_UNKNOWN,
|
||||
'pos' => key($chars),
|
||||
'value' => $current
|
||||
];
|
||||
next($chars);
|
||||
continue;
|
||||
}
|
||||
|
||||
$state = self::$transitionTable[$current];
|
||||
|
||||
if ($state === self::STATE_SINGLE_CHAR) {
|
||||
|
||||
// Consume simple tokens like ".", ",", "@", etc.
|
||||
$tokens[] = [
|
||||
'type' => $this->simpleTokens[$current],
|
||||
'pos' => key($chars),
|
||||
'value' => $current
|
||||
];
|
||||
next($chars);
|
||||
|
||||
} elseif ($state === self::STATE_IDENTIFIER) {
|
||||
|
||||
// Consume identifiers
|
||||
$start = key($chars);
|
||||
$buffer = '';
|
||||
do {
|
||||
$buffer .= $current;
|
||||
$current = next($chars);
|
||||
} while ($current !== false && isset($this->validIdentifier[$current]));
|
||||
$tokens[] = [
|
||||
'type' => self::T_IDENTIFIER,
|
||||
'value' => $buffer,
|
||||
'pos' => $start
|
||||
];
|
||||
|
||||
} elseif ($state === self::STATE_WHITESPACE) {
|
||||
|
||||
// Skip whitespace
|
||||
next($chars);
|
||||
|
||||
} elseif ($state === self::STATE_LBRACKET) {
|
||||
|
||||
// Consume "[", "[?", and "[]"
|
||||
$position = key($chars);
|
||||
$actual = next($chars);
|
||||
if ($actual === ']') {
|
||||
next($chars);
|
||||
$tokens[] = [
|
||||
'type' => self::T_FLATTEN,
|
||||
'pos' => $position,
|
||||
'value' => '[]'
|
||||
];
|
||||
} elseif ($actual === '?') {
|
||||
next($chars);
|
||||
$tokens[] = [
|
||||
'type' => self::T_FILTER,
|
||||
'pos' => $position,
|
||||
'value' => '[?'
|
||||
];
|
||||
} else {
|
||||
$tokens[] = [
|
||||
'type' => self::T_LBRACKET,
|
||||
'pos' => $position,
|
||||
'value' => '['
|
||||
];
|
||||
}
|
||||
|
||||
} elseif ($state === self::STATE_STRING_LITERAL) {
|
||||
|
||||
// Consume raw string literals
|
||||
$t = $this->inside($chars, "'", self::T_LITERAL);
|
||||
$t['value'] = str_replace("\\'", "'", $t['value']);
|
||||
$tokens[] = $t;
|
||||
|
||||
} elseif ($state === self::STATE_PIPE) {
|
||||
|
||||
// Consume pipe and OR
|
||||
$tokens[] = $this->matchOr($chars, '|', '|', self::T_OR, self::T_PIPE);
|
||||
|
||||
} elseif ($state == self::STATE_JSON_LITERAL) {
|
||||
|
||||
// Consume JSON literals
|
||||
$token = $this->inside($chars, '`', self::T_LITERAL);
|
||||
if ($token['type'] === self::T_LITERAL) {
|
||||
$token['value'] = str_replace('\\`', '`', $token['value']);
|
||||
$token = $this->parseJson($token);
|
||||
}
|
||||
$tokens[] = $token;
|
||||
|
||||
} elseif ($state == self::STATE_NUMBER) {
|
||||
|
||||
// Consume numbers
|
||||
$start = key($chars);
|
||||
$buffer = '';
|
||||
do {
|
||||
$buffer .= $current;
|
||||
$current = next($chars);
|
||||
} while ($current !== false && isset($this->numbers[$current]));
|
||||
$tokens[] = [
|
||||
'type' => self::T_NUMBER,
|
||||
'value' => (int)$buffer,
|
||||
'pos' => $start
|
||||
];
|
||||
|
||||
} elseif ($state === self::STATE_QUOTED_STRING) {
|
||||
|
||||
// Consume quoted identifiers
|
||||
$token = $this->inside($chars, '"', self::T_QUOTED_IDENTIFIER);
|
||||
if ($token['type'] === self::T_QUOTED_IDENTIFIER) {
|
||||
$token['value'] = '"' . $token['value'] . '"';
|
||||
$token = $this->parseJson($token);
|
||||
}
|
||||
$tokens[] = $token;
|
||||
|
||||
} elseif ($state === self::STATE_EQ) {
|
||||
|
||||
// Consume equals
|
||||
$tokens[] = $this->matchOr($chars, '=', '=', self::T_COMPARATOR, self::T_UNKNOWN);
|
||||
|
||||
} elseif ($state == self::STATE_AND) {
|
||||
|
||||
$tokens[] = $this->matchOr($chars, '&', '&', self::T_AND, self::T_EXPREF);
|
||||
|
||||
} elseif ($state === self::STATE_NOT) {
|
||||
|
||||
// Consume not equal
|
||||
$tokens[] = $this->matchOr($chars, '!', '=', self::T_COMPARATOR, self::T_NOT);
|
||||
|
||||
} else {
|
||||
|
||||
// either '<' or '>'
|
||||
// Consume less than and greater than
|
||||
$tokens[] = $this->matchOr($chars, $current, '=', self::T_COMPARATOR, self::T_COMPARATOR);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
eof:
|
||||
$tokens[] = [
|
||||
'type' => self::T_EOF,
|
||||
'pos' => mb_strlen($input, 'UTF-8'),
|
||||
'value' => null
|
||||
];
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token based on whether or not the next token matches the
|
||||
* expected value. If it does, a token of "$type" is returned. Otherwise,
|
||||
* a token of "$orElse" type is returned.
|
||||
*
|
||||
* @param array $chars Array of characters by reference.
|
||||
* @param string $current The current character.
|
||||
* @param string $expected Expected character.
|
||||
* @param string $type Expected result type.
|
||||
* @param string $orElse Otherwise return a token of this type.
|
||||
*
|
||||
* @return array Returns a conditional token.
|
||||
*/
|
||||
private function matchOr(array &$chars, $current, $expected, $type, $orElse)
|
||||
{
|
||||
if (next($chars) === $expected) {
|
||||
next($chars);
|
||||
return [
|
||||
'type' => $type,
|
||||
'pos' => key($chars) - 1,
|
||||
'value' => $current . $expected
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => $orElse,
|
||||
'pos' => key($chars) - 1,
|
||||
'value' => $current
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a token the is the result of consuming inside of delimiter
|
||||
* characters. Escaped delimiters will be adjusted before returning a
|
||||
* value. If the token is not closed, "unknown" is returned.
|
||||
*
|
||||
* @param array $chars Array of characters by reference.
|
||||
* @param string $delim The delimiter character.
|
||||
* @param string $type Token type.
|
||||
*
|
||||
* @return array Returns the consumed token.
|
||||
*/
|
||||
private function inside(array &$chars, $delim, $type)
|
||||
{
|
||||
$position = key($chars);
|
||||
$current = next($chars);
|
||||
$buffer = '';
|
||||
|
||||
while ($current !== $delim) {
|
||||
if ($current === '\\') {
|
||||
$buffer .= '\\';
|
||||
$current = next($chars);
|
||||
}
|
||||
if ($current === false) {
|
||||
// Unclosed delimiter
|
||||
return [
|
||||
'type' => self::T_UNKNOWN,
|
||||
'value' => $buffer,
|
||||
'pos' => $position
|
||||
];
|
||||
}
|
||||
$buffer .= $current;
|
||||
$current = next($chars);
|
||||
}
|
||||
|
||||
next($chars);
|
||||
|
||||
return ['type' => $type, 'value' => $buffer, 'pos' => $position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JSON token or sets the token type to "unknown" on error.
|
||||
*
|
||||
* @param array $token Token that needs parsing.
|
||||
*
|
||||
* @return array Returns a token with a parsed value.
|
||||
*/
|
||||
private function parseJson(array $token)
|
||||
{
|
||||
$value = json_decode($token['value'], true);
|
||||
|
||||
if ($error = json_last_error()) {
|
||||
// Legacy support for elided quotes. Try to parse again by adding
|
||||
// quotes around the bad input value.
|
||||
$value = json_decode('"' . $token['value'] . '"', true);
|
||||
if ($error = json_last_error()) {
|
||||
$token['type'] = self::T_UNKNOWN;
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
$token['value'] = $value;
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
519
vendor/mtdowling/jmespath.php/src/Parser.php
vendored
Normal file
519
vendor/mtdowling/jmespath.php/src/Parser.php
vendored
Normal file
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
use JmesPath\Lexer as T;
|
||||
|
||||
/**
|
||||
* JMESPath Pratt parser
|
||||
* @link http://hall.org.ua/halls/wizzard/pdf/Vaughan.Pratt.TDOP.pdf
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
/** @var Lexer */
|
||||
private $lexer;
|
||||
private $tokens;
|
||||
private $token;
|
||||
private $tpos;
|
||||
private $expression;
|
||||
private static $nullToken = ['type' => T::T_EOF];
|
||||
private static $currentNode = ['type' => T::T_CURRENT];
|
||||
|
||||
private static $bp = [
|
||||
T::T_EOF => 0,
|
||||
T::T_QUOTED_IDENTIFIER => 0,
|
||||
T::T_IDENTIFIER => 0,
|
||||
T::T_RBRACKET => 0,
|
||||
T::T_RPAREN => 0,
|
||||
T::T_COMMA => 0,
|
||||
T::T_RBRACE => 0,
|
||||
T::T_NUMBER => 0,
|
||||
T::T_CURRENT => 0,
|
||||
T::T_EXPREF => 0,
|
||||
T::T_COLON => 0,
|
||||
T::T_PIPE => 1,
|
||||
T::T_OR => 2,
|
||||
T::T_AND => 3,
|
||||
T::T_COMPARATOR => 5,
|
||||
T::T_FLATTEN => 9,
|
||||
T::T_STAR => 20,
|
||||
T::T_FILTER => 21,
|
||||
T::T_DOT => 40,
|
||||
T::T_NOT => 45,
|
||||
T::T_LBRACE => 50,
|
||||
T::T_LBRACKET => 55,
|
||||
T::T_LPAREN => 60,
|
||||
];
|
||||
|
||||
/** @var array Acceptable tokens after a dot token */
|
||||
private static $afterDot = [
|
||||
T::T_IDENTIFIER => true, // foo.bar
|
||||
T::T_QUOTED_IDENTIFIER => true, // foo."bar"
|
||||
T::T_STAR => true, // foo.*
|
||||
T::T_LBRACE => true, // foo[1]
|
||||
T::T_LBRACKET => true, // foo{a: 0}
|
||||
T::T_FILTER => true, // foo.[?bar==10]
|
||||
];
|
||||
|
||||
/**
|
||||
* @param Lexer|null $lexer Lexer used to tokenize expressions
|
||||
*/
|
||||
public function __construct(Lexer $lexer = null)
|
||||
{
|
||||
$this->lexer = $lexer ?: new Lexer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a JMESPath expression into an AST
|
||||
*
|
||||
* @param string $expression JMESPath expression to compile
|
||||
*
|
||||
* @return array Returns an array based AST
|
||||
* @throws SyntaxErrorException
|
||||
*/
|
||||
public function parse($expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
$this->tokens = $this->lexer->tokenize($expression);
|
||||
$this->tpos = -1;
|
||||
$this->next();
|
||||
$result = $this->expr();
|
||||
|
||||
if ($this->token['type'] === T::T_EOF) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
throw $this->syntax('Did not reach the end of the token stream');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an expression while rbp < lbp.
|
||||
*
|
||||
* @param int $rbp Right bound precedence
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function expr($rbp = 0)
|
||||
{
|
||||
$left = $this->{"nud_{$this->token['type']}"}();
|
||||
while ($rbp < self::$bp[$this->token['type']]) {
|
||||
$left = $this->{"led_{$this->token['type']}"}($left);
|
||||
}
|
||||
|
||||
return $left;
|
||||
}
|
||||
|
||||
private function nud_identifier()
|
||||
{
|
||||
$token = $this->token;
|
||||
$this->next();
|
||||
return ['type' => 'field', 'value' => $token['value']];
|
||||
}
|
||||
|
||||
private function nud_quoted_identifier()
|
||||
{
|
||||
$token = $this->token;
|
||||
$this->next();
|
||||
$this->assertNotToken(T::T_LPAREN);
|
||||
return ['type' => 'field', 'value' => $token['value']];
|
||||
}
|
||||
|
||||
private function nud_current()
|
||||
{
|
||||
$this->next();
|
||||
return self::$currentNode;
|
||||
}
|
||||
|
||||
private function nud_literal()
|
||||
{
|
||||
$token = $this->token;
|
||||
$this->next();
|
||||
return ['type' => 'literal', 'value' => $token['value']];
|
||||
}
|
||||
|
||||
private function nud_expref()
|
||||
{
|
||||
$this->next();
|
||||
return ['type' => T::T_EXPREF, 'children' => [$this->expr(self::$bp[T::T_EXPREF])]];
|
||||
}
|
||||
|
||||
private function nud_not()
|
||||
{
|
||||
$this->next();
|
||||
return ['type' => T::T_NOT, 'children' => [$this->expr(self::$bp[T::T_NOT])]];
|
||||
}
|
||||
|
||||
private function nud_lparen()
|
||||
{
|
||||
$this->next();
|
||||
$result = $this->expr(0);
|
||||
if ($this->token['type'] !== T::T_RPAREN) {
|
||||
throw $this->syntax('Unclosed `(`');
|
||||
}
|
||||
$this->next();
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function nud_lbrace()
|
||||
{
|
||||
static $validKeys = [T::T_QUOTED_IDENTIFIER => true, T::T_IDENTIFIER => true];
|
||||
$this->next($validKeys);
|
||||
$pairs = [];
|
||||
|
||||
do {
|
||||
$pairs[] = $this->parseKeyValuePair();
|
||||
if ($this->token['type'] == T::T_COMMA) {
|
||||
$this->next($validKeys);
|
||||
}
|
||||
} while ($this->token['type'] !== T::T_RBRACE);
|
||||
|
||||
$this->next();
|
||||
|
||||
return['type' => 'multi_select_hash', 'children' => $pairs];
|
||||
}
|
||||
|
||||
private function nud_flatten()
|
||||
{
|
||||
return $this->led_flatten(self::$currentNode);
|
||||
}
|
||||
|
||||
private function nud_filter()
|
||||
{
|
||||
return $this->led_filter(self::$currentNode);
|
||||
}
|
||||
|
||||
private function nud_star()
|
||||
{
|
||||
return $this->parseWildcardObject(self::$currentNode);
|
||||
}
|
||||
|
||||
private function nud_lbracket()
|
||||
{
|
||||
$this->next();
|
||||
$type = $this->token['type'];
|
||||
if ($type == T::T_NUMBER || $type == T::T_COLON) {
|
||||
return $this->parseArrayIndexExpression();
|
||||
} elseif ($type == T::T_STAR && $this->lookahead() == T::T_RBRACKET) {
|
||||
return $this->parseWildcardArray();
|
||||
} else {
|
||||
return $this->parseMultiSelectList();
|
||||
}
|
||||
}
|
||||
|
||||
private function led_lbracket(array $left)
|
||||
{
|
||||
static $nextTypes = [T::T_NUMBER => true, T::T_COLON => true, T::T_STAR => true];
|
||||
$this->next($nextTypes);
|
||||
switch ($this->token['type']) {
|
||||
case T::T_NUMBER:
|
||||
case T::T_COLON:
|
||||
return [
|
||||
'type' => 'subexpression',
|
||||
'children' => [$left, $this->parseArrayIndexExpression()]
|
||||
];
|
||||
default:
|
||||
return $this->parseWildcardArray($left);
|
||||
}
|
||||
}
|
||||
|
||||
private function led_flatten(array $left)
|
||||
{
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => 'projection',
|
||||
'from' => 'array',
|
||||
'children' => [
|
||||
['type' => T::T_FLATTEN, 'children' => [$left]],
|
||||
$this->parseProjection(self::$bp[T::T_FLATTEN])
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_dot(array $left)
|
||||
{
|
||||
$this->next(self::$afterDot);
|
||||
|
||||
if ($this->token['type'] == T::T_STAR) {
|
||||
return $this->parseWildcardObject($left);
|
||||
}
|
||||
|
||||
return [
|
||||
'type' => 'subexpression',
|
||||
'children' => [$left, $this->parseDot(self::$bp[T::T_DOT])]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_or(array $left)
|
||||
{
|
||||
$this->next();
|
||||
return [
|
||||
'type' => T::T_OR,
|
||||
'children' => [$left, $this->expr(self::$bp[T::T_OR])]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_and(array $left)
|
||||
{
|
||||
$this->next();
|
||||
return [
|
||||
'type' => T::T_AND,
|
||||
'children' => [$left, $this->expr(self::$bp[T::T_AND])]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_pipe(array $left)
|
||||
{
|
||||
$this->next();
|
||||
return [
|
||||
'type' => T::T_PIPE,
|
||||
'children' => [$left, $this->expr(self::$bp[T::T_PIPE])]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_lparen(array $left)
|
||||
{
|
||||
$args = [];
|
||||
$this->next();
|
||||
|
||||
while ($this->token['type'] != T::T_RPAREN) {
|
||||
$args[] = $this->expr(0);
|
||||
if ($this->token['type'] == T::T_COMMA) {
|
||||
$this->next();
|
||||
}
|
||||
}
|
||||
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => 'function',
|
||||
'value' => $left['value'],
|
||||
'children' => $args
|
||||
];
|
||||
}
|
||||
|
||||
private function led_filter(array $left)
|
||||
{
|
||||
$this->next();
|
||||
$expression = $this->expr();
|
||||
if ($this->token['type'] != T::T_RBRACKET) {
|
||||
throw $this->syntax('Expected a closing rbracket for the filter');
|
||||
}
|
||||
|
||||
$this->next();
|
||||
$rhs = $this->parseProjection(self::$bp[T::T_FILTER]);
|
||||
|
||||
return [
|
||||
'type' => 'projection',
|
||||
'from' => 'array',
|
||||
'children' => [
|
||||
$left ?: self::$currentNode,
|
||||
[
|
||||
'type' => 'condition',
|
||||
'children' => [$expression, $rhs]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function led_comparator(array $left)
|
||||
{
|
||||
$token = $this->token;
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => T::T_COMPARATOR,
|
||||
'value' => $token['value'],
|
||||
'children' => [$left, $this->expr(self::$bp[T::T_COMPARATOR])]
|
||||
];
|
||||
}
|
||||
|
||||
private function parseProjection($bp)
|
||||
{
|
||||
$type = $this->token['type'];
|
||||
if (self::$bp[$type] < 10) {
|
||||
return self::$currentNode;
|
||||
} elseif ($type == T::T_DOT) {
|
||||
$this->next(self::$afterDot);
|
||||
return $this->parseDot($bp);
|
||||
} elseif ($type == T::T_LBRACKET || $type == T::T_FILTER) {
|
||||
return $this->expr($bp);
|
||||
}
|
||||
|
||||
throw $this->syntax('Syntax error after projection');
|
||||
}
|
||||
|
||||
private function parseDot($bp)
|
||||
{
|
||||
if ($this->token['type'] == T::T_LBRACKET) {
|
||||
$this->next();
|
||||
return $this->parseMultiSelectList();
|
||||
}
|
||||
|
||||
return $this->expr($bp);
|
||||
}
|
||||
|
||||
private function parseKeyValuePair()
|
||||
{
|
||||
static $validColon = [T::T_COLON => true];
|
||||
$key = $this->token['value'];
|
||||
$this->next($validColon);
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => 'key_val_pair',
|
||||
'value' => $key,
|
||||
'children' => [$this->expr()]
|
||||
];
|
||||
}
|
||||
|
||||
private function parseWildcardObject(array $left = null)
|
||||
{
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => 'projection',
|
||||
'from' => 'object',
|
||||
'children' => [
|
||||
$left ?: self::$currentNode,
|
||||
$this->parseProjection(self::$bp[T::T_STAR])
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function parseWildcardArray(array $left = null)
|
||||
{
|
||||
static $getRbracket = [T::T_RBRACKET => true];
|
||||
$this->next($getRbracket);
|
||||
$this->next();
|
||||
|
||||
return [
|
||||
'type' => 'projection',
|
||||
'from' => 'array',
|
||||
'children' => [
|
||||
$left ?: self::$currentNode,
|
||||
$this->parseProjection(self::$bp[T::T_STAR])
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an array index expression (e.g., [0], [1:2:3]
|
||||
*/
|
||||
private function parseArrayIndexExpression()
|
||||
{
|
||||
static $matchNext = [
|
||||
T::T_NUMBER => true,
|
||||
T::T_COLON => true,
|
||||
T::T_RBRACKET => true
|
||||
];
|
||||
|
||||
$pos = 0;
|
||||
$parts = [null, null, null];
|
||||
$expected = $matchNext;
|
||||
|
||||
do {
|
||||
if ($this->token['type'] == T::T_COLON) {
|
||||
$pos++;
|
||||
$expected = $matchNext;
|
||||
} elseif ($this->token['type'] == T::T_NUMBER) {
|
||||
$parts[$pos] = $this->token['value'];
|
||||
$expected = [T::T_COLON => true, T::T_RBRACKET => true];
|
||||
}
|
||||
$this->next($expected);
|
||||
} while ($this->token['type'] != T::T_RBRACKET);
|
||||
|
||||
// Consume the closing bracket
|
||||
$this->next();
|
||||
|
||||
if ($pos === 0) {
|
||||
// No colons were found so this is a simple index extraction
|
||||
return ['type' => 'index', 'value' => $parts[0]];
|
||||
}
|
||||
|
||||
if ($pos > 2) {
|
||||
throw $this->syntax('Invalid array slice syntax: too many colons');
|
||||
}
|
||||
|
||||
// Sliced array from start (e.g., [2:])
|
||||
return [
|
||||
'type' => 'projection',
|
||||
'from' => 'array',
|
||||
'children' => [
|
||||
['type' => 'slice', 'value' => $parts],
|
||||
$this->parseProjection(self::$bp[T::T_STAR])
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function parseMultiSelectList()
|
||||
{
|
||||
$nodes = [];
|
||||
|
||||
do {
|
||||
$nodes[] = $this->expr();
|
||||
if ($this->token['type'] == T::T_COMMA) {
|
||||
$this->next();
|
||||
$this->assertNotToken(T::T_RBRACKET);
|
||||
}
|
||||
} while ($this->token['type'] !== T::T_RBRACKET);
|
||||
$this->next();
|
||||
|
||||
return ['type' => 'multi_select_list', 'children' => $nodes];
|
||||
}
|
||||
|
||||
private function syntax($msg)
|
||||
{
|
||||
return new SyntaxErrorException($msg, $this->token, $this->expression);
|
||||
}
|
||||
|
||||
private function lookahead()
|
||||
{
|
||||
return (!isset($this->tokens[$this->tpos + 1]))
|
||||
? T::T_EOF
|
||||
: $this->tokens[$this->tpos + 1]['type'];
|
||||
}
|
||||
|
||||
private function next(array $match = null)
|
||||
{
|
||||
if (!isset($this->tokens[$this->tpos + 1])) {
|
||||
$this->token = self::$nullToken;
|
||||
} else {
|
||||
$this->token = $this->tokens[++$this->tpos];
|
||||
}
|
||||
|
||||
if ($match && !isset($match[$this->token['type']])) {
|
||||
throw $this->syntax($match);
|
||||
}
|
||||
}
|
||||
|
||||
private function assertNotToken($type)
|
||||
{
|
||||
if ($this->token['type'] == $type) {
|
||||
throw $this->syntax("Token {$this->tpos} not allowed to be $type");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Handles undefined tokens without paying the cost of validation
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
{
|
||||
$prefix = substr($method, 0, 4);
|
||||
if ($prefix == 'nud_' || $prefix == 'led_') {
|
||||
$token = substr($method, 4);
|
||||
$message = "Unexpected \"$token\" token ($method). Expected one of"
|
||||
. " the following tokens: "
|
||||
. implode(', ', array_map(function ($i) {
|
||||
return '"' . substr($i, 4) . '"';
|
||||
}, array_filter(
|
||||
get_class_methods($this),
|
||||
function ($i) use ($prefix) {
|
||||
return strpos($i, $prefix) === 0;
|
||||
}
|
||||
)));
|
||||
throw $this->syntax($message);
|
||||
}
|
||||
|
||||
throw new \BadMethodCallException("Call to undefined method $method");
|
||||
}
|
||||
}
|
||||
36
vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php
vendored
Normal file
36
vendor/mtdowling/jmespath.php/src/SyntaxErrorException.php
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Syntax errors raise this exception that gives context
|
||||
*/
|
||||
class SyntaxErrorException extends \InvalidArgumentException
|
||||
{
|
||||
/**
|
||||
* @param string $expectedTypesOrMessage Expected array of tokens or message
|
||||
* @param array $token Current token
|
||||
* @param string $expression Expression input
|
||||
*/
|
||||
public function __construct(
|
||||
$expectedTypesOrMessage,
|
||||
array $token,
|
||||
$expression
|
||||
) {
|
||||
$message = "Syntax error at character {$token['pos']}\n"
|
||||
. $expression . "\n" . str_repeat(' ', $token['pos']) . "^\n";
|
||||
$message .= !is_array($expectedTypesOrMessage)
|
||||
? $expectedTypesOrMessage
|
||||
: $this->createTokenMessage($token, $expectedTypesOrMessage);
|
||||
parent::__construct($message);
|
||||
}
|
||||
|
||||
private function createTokenMessage(array $token, array $valid)
|
||||
{
|
||||
return sprintf(
|
||||
'Expected one of the following: %s; found %s "%s"',
|
||||
implode(', ', array_keys($valid)),
|
||||
$token['type'],
|
||||
$token['value']
|
||||
);
|
||||
}
|
||||
}
|
||||
419
vendor/mtdowling/jmespath.php/src/TreeCompiler.php
vendored
Normal file
419
vendor/mtdowling/jmespath.php/src/TreeCompiler.php
vendored
Normal file
@@ -0,0 +1,419 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Tree visitor used to compile JMESPath expressions into native PHP code.
|
||||
*/
|
||||
class TreeCompiler
|
||||
{
|
||||
private $indentation;
|
||||
private $source;
|
||||
private $vars;
|
||||
|
||||
/**
|
||||
* @param array $ast AST to compile.
|
||||
* @param string $fnName The name of the function to generate.
|
||||
* @param string $expr Expression being compiled.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function visit(array $ast, $fnName, $expr)
|
||||
{
|
||||
$this->vars = [];
|
||||
$this->source = $this->indentation = '';
|
||||
$this->write("<?php\n")
|
||||
->write('use JmesPath\\TreeInterpreter as Ti;')
|
||||
->write('use JmesPath\\FnDispatcher as Fd;')
|
||||
->write('use JmesPath\\Utils;')
|
||||
->write('')
|
||||
->write('function %s(Ti $interpreter, $value) {', $fnName)
|
||||
->indent()
|
||||
->dispatch($ast)
|
||||
->write('')
|
||||
->write('return $value;')
|
||||
->outdent()
|
||||
->write('}');
|
||||
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $node
|
||||
* @return mixed
|
||||
*/
|
||||
private function dispatch(array $node)
|
||||
{
|
||||
return $this->{"visit_{$node['type']}"}($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a monotonically incrementing unique variable name by prefix.
|
||||
*
|
||||
* @param string $prefix Variable name prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function makeVar($prefix)
|
||||
{
|
||||
if (!isset($this->vars[$prefix])) {
|
||||
$this->vars[$prefix] = 0;
|
||||
return '$' . $prefix;
|
||||
}
|
||||
|
||||
return '$' . $prefix . ++$this->vars[$prefix];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given line of source code. Pass positional arguments to write
|
||||
* that match the format of sprintf.
|
||||
*
|
||||
* @param string $str String to write
|
||||
* @return $this
|
||||
*/
|
||||
private function write($str)
|
||||
{
|
||||
$this->source .= $this->indentation;
|
||||
if (func_num_args() == 1) {
|
||||
$this->source .= $str . "\n";
|
||||
return $this;
|
||||
}
|
||||
$this->source .= vsprintf($str, array_slice(func_get_args(), 1)) . "\n";
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the indentation level of code being written
|
||||
* @return $this
|
||||
*/
|
||||
private function outdent()
|
||||
{
|
||||
$this->indentation = substr($this->indentation, 0, -4);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the indentation level of code being written
|
||||
* @return $this
|
||||
*/
|
||||
private function indent()
|
||||
{
|
||||
$this->indentation .= ' ';
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function visit_or(array $node)
|
||||
{
|
||||
$a = $this->makeVar('beforeOr');
|
||||
return $this
|
||||
->write('%s = $value;', $a)
|
||||
->dispatch($node['children'][0])
|
||||
->write('if (!$value && $value !== "0" && $value !== 0) {')
|
||||
->indent()
|
||||
->write('$value = %s;', $a)
|
||||
->dispatch($node['children'][1])
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_and(array $node)
|
||||
{
|
||||
$a = $this->makeVar('beforeAnd');
|
||||
return $this
|
||||
->write('%s = $value;', $a)
|
||||
->dispatch($node['children'][0])
|
||||
->write('if ($value || $value === "0" || $value === 0) {')
|
||||
->indent()
|
||||
->write('$value = %s;', $a)
|
||||
->dispatch($node['children'][1])
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_not(array $node)
|
||||
{
|
||||
return $this
|
||||
->write('// Visiting not node')
|
||||
->dispatch($node['children'][0])
|
||||
->write('// Applying boolean not to result of not node')
|
||||
->write('$value = !Utils::isTruthy($value);');
|
||||
}
|
||||
|
||||
private function visit_subexpression(array $node)
|
||||
{
|
||||
return $this
|
||||
->dispatch($node['children'][0])
|
||||
->write('if ($value !== null) {')
|
||||
->indent()
|
||||
->dispatch($node['children'][1])
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_field(array $node)
|
||||
{
|
||||
$arr = '$value[' . var_export($node['value'], true) . ']';
|
||||
$obj = '$value->{' . var_export($node['value'], true) . '}';
|
||||
$this->write('if (is_array($value) || $value instanceof \\ArrayAccess) {')
|
||||
->indent()
|
||||
->write('$value = isset(%s) ? %s : null;', $arr, $arr)
|
||||
->outdent()
|
||||
->write('} elseif ($value instanceof \\stdClass) {')
|
||||
->indent()
|
||||
->write('$value = isset(%s) ? %s : null;', $obj, $obj)
|
||||
->outdent()
|
||||
->write("} else {")
|
||||
->indent()
|
||||
->write('$value = null;')
|
||||
->outdent()
|
||||
->write("}");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function visit_index(array $node)
|
||||
{
|
||||
if ($node['value'] >= 0) {
|
||||
$check = '$value[' . $node['value'] . ']';
|
||||
return $this->write(
|
||||
'$value = (is_array($value) || $value instanceof \\ArrayAccess)'
|
||||
. ' && isset(%s) ? %s : null;',
|
||||
$check, $check
|
||||
);
|
||||
}
|
||||
|
||||
$a = $this->makeVar('count');
|
||||
return $this
|
||||
->write('if (is_array($value) || ($value instanceof \\ArrayAccess && $value instanceof \\Countable)) {')
|
||||
->indent()
|
||||
->write('%s = count($value) + %s;', $a, $node['value'])
|
||||
->write('$value = isset($value[%s]) ? $value[%s] : null;', $a, $a)
|
||||
->outdent()
|
||||
->write('} else {')
|
||||
->indent()
|
||||
->write('$value = null;')
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_literal(array $node)
|
||||
{
|
||||
return $this->write('$value = %s;', var_export($node['value'], true));
|
||||
}
|
||||
|
||||
private function visit_pipe(array $node)
|
||||
{
|
||||
return $this
|
||||
->dispatch($node['children'][0])
|
||||
->dispatch($node['children'][1]);
|
||||
}
|
||||
|
||||
private function visit_multi_select_list(array $node)
|
||||
{
|
||||
return $this->visit_multi_select_hash($node);
|
||||
}
|
||||
|
||||
private function visit_multi_select_hash(array $node)
|
||||
{
|
||||
$listVal = $this->makeVar('list');
|
||||
$value = $this->makeVar('prev');
|
||||
$this->write('if ($value !== null) {')
|
||||
->indent()
|
||||
->write('%s = [];', $listVal)
|
||||
->write('%s = $value;', $value);
|
||||
|
||||
$first = true;
|
||||
foreach ($node['children'] as $child) {
|
||||
if (!$first) {
|
||||
$this->write('$value = %s;', $value);
|
||||
}
|
||||
$first = false;
|
||||
if ($node['type'] == 'multi_select_hash') {
|
||||
$this->dispatch($child['children'][0]);
|
||||
$key = var_export($child['value'], true);
|
||||
$this->write('%s[%s] = $value;', $listVal, $key);
|
||||
} else {
|
||||
$this->dispatch($child);
|
||||
$this->write('%s[] = $value;', $listVal);
|
||||
}
|
||||
}
|
||||
|
||||
return $this
|
||||
->write('$value = %s;', $listVal)
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_function(array $node)
|
||||
{
|
||||
$value = $this->makeVar('val');
|
||||
$args = $this->makeVar('args');
|
||||
$this->write('%s = $value;', $value)
|
||||
->write('%s = [];', $args);
|
||||
|
||||
foreach ($node['children'] as $arg) {
|
||||
$this->dispatch($arg);
|
||||
$this->write('%s[] = $value;', $args)
|
||||
->write('$value = %s;', $value);
|
||||
}
|
||||
|
||||
return $this->write(
|
||||
'$value = Fd::getInstance()->__invoke("%s", %s);',
|
||||
$node['value'], $args
|
||||
);
|
||||
}
|
||||
|
||||
private function visit_slice(array $node)
|
||||
{
|
||||
return $this
|
||||
->write('$value = !is_string($value) && !Utils::isArray($value)')
|
||||
->write(' ? null : Utils::slice($value, %s, %s, %s);',
|
||||
var_export($node['value'][0], true),
|
||||
var_export($node['value'][1], true),
|
||||
var_export($node['value'][2], true)
|
||||
);
|
||||
}
|
||||
|
||||
private function visit_current(array $node)
|
||||
{
|
||||
return $this->write('// Visiting current node (no-op)');
|
||||
}
|
||||
|
||||
private function visit_expref(array $node)
|
||||
{
|
||||
$child = var_export($node['children'][0], true);
|
||||
return $this->write('$value = function ($value) use ($interpreter) {')
|
||||
->indent()
|
||||
->write('return $interpreter->visit(%s, $value);', $child)
|
||||
->outdent()
|
||||
->write('};');
|
||||
}
|
||||
|
||||
private function visit_flatten(array $node)
|
||||
{
|
||||
$this->dispatch($node['children'][0]);
|
||||
$merged = $this->makeVar('merged');
|
||||
$val = $this->makeVar('val');
|
||||
|
||||
$this
|
||||
->write('// Visiting merge node')
|
||||
->write('if (!Utils::isArray($value)) {')
|
||||
->indent()
|
||||
->write('$value = null;')
|
||||
->outdent()
|
||||
->write('} else {')
|
||||
->indent()
|
||||
->write('%s = [];', $merged)
|
||||
->write('foreach ($value as %s) {', $val)
|
||||
->indent()
|
||||
->write('if (is_array(%s) && isset(%s[0])) {', $val, $val)
|
||||
->indent()
|
||||
->write('%s = array_merge(%s, %s);', $merged, $merged, $val)
|
||||
->outdent()
|
||||
->write('} elseif (%s !== []) {', $val)
|
||||
->indent()
|
||||
->write('%s[] = %s;', $merged, $val)
|
||||
->outdent()
|
||||
->write('}')
|
||||
->outdent()
|
||||
->write('}')
|
||||
->write('$value = %s;', $merged)
|
||||
->outdent()
|
||||
->write('}');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function visit_projection(array $node)
|
||||
{
|
||||
$val = $this->makeVar('val');
|
||||
$collected = $this->makeVar('collected');
|
||||
$this->write('// Visiting projection node')
|
||||
->dispatch($node['children'][0])
|
||||
->write('');
|
||||
|
||||
if (!isset($node['from'])) {
|
||||
$this->write('if (!is_array($value) || !($value instanceof \stdClass)) { $value = null; }');
|
||||
} elseif ($node['from'] == 'object') {
|
||||
$this->write('if (!Utils::isObject($value)) { $value = null; }');
|
||||
} elseif ($node['from'] == 'array') {
|
||||
$this->write('if (!Utils::isArray($value)) { $value = null; }');
|
||||
}
|
||||
|
||||
$this->write('if ($value !== null) {')
|
||||
->indent()
|
||||
->write('%s = [];', $collected)
|
||||
->write('foreach ((array) $value as %s) {', $val)
|
||||
->indent()
|
||||
->write('$value = %s;', $val)
|
||||
->dispatch($node['children'][1])
|
||||
->write('if ($value !== null) {')
|
||||
->indent()
|
||||
->write('%s[] = $value;', $collected)
|
||||
->outdent()
|
||||
->write('}')
|
||||
->outdent()
|
||||
->write('}')
|
||||
->write('$value = %s;', $collected)
|
||||
->outdent()
|
||||
->write('}');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function visit_condition(array $node)
|
||||
{
|
||||
$value = $this->makeVar('beforeCondition');
|
||||
return $this
|
||||
->write('%s = $value;', $value)
|
||||
->write('// Visiting condition node')
|
||||
->dispatch($node['children'][0])
|
||||
->write('// Checking result of condition node')
|
||||
->write('if (Utils::isTruthy($value)) {')
|
||||
->indent()
|
||||
->write('$value = %s;', $value)
|
||||
->dispatch($node['children'][1])
|
||||
->outdent()
|
||||
->write('} else {')
|
||||
->indent()
|
||||
->write('$value = null;')
|
||||
->outdent()
|
||||
->write('}');
|
||||
}
|
||||
|
||||
private function visit_comparator(array $node)
|
||||
{
|
||||
$value = $this->makeVar('val');
|
||||
$a = $this->makeVar('left');
|
||||
$b = $this->makeVar('right');
|
||||
|
||||
$this
|
||||
->write('// Visiting comparator node')
|
||||
->write('%s = $value;', $value)
|
||||
->dispatch($node['children'][0])
|
||||
->write('%s = $value;', $a)
|
||||
->write('$value = %s;', $value)
|
||||
->dispatch($node['children'][1])
|
||||
->write('%s = $value;', $b);
|
||||
|
||||
if ($node['value'] == '==') {
|
||||
$this->write('$value = Utils::isEqual(%s, %s);', $a, $b);
|
||||
} elseif ($node['value'] == '!=') {
|
||||
$this->write('$value = !Utils::isEqual(%s, %s);', $a, $b);
|
||||
} else {
|
||||
$this->write(
|
||||
'$value = (is_int(%s) || is_float(%s)) && (is_int(%s) || is_float(%s)) && %s %s %s;',
|
||||
$a, $a, $b, $b, $a, $node['value'], $b
|
||||
);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function __call($method, $args)
|
||||
{
|
||||
throw new \RuntimeException(
|
||||
sprintf('Invalid node encountered: %s', json_encode($args[0]))
|
||||
);
|
||||
}
|
||||
}
|
||||
235
vendor/mtdowling/jmespath.php/src/TreeInterpreter.php
vendored
Normal file
235
vendor/mtdowling/jmespath.php/src/TreeInterpreter.php
vendored
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
/**
|
||||
* Tree visitor used to evaluates JMESPath AST expressions.
|
||||
*/
|
||||
class TreeInterpreter
|
||||
{
|
||||
/** @var callable */
|
||||
private $fnDispatcher;
|
||||
|
||||
/**
|
||||
* @param callable|null $fnDispatcher Function dispatching function that accepts
|
||||
* a function name argument and an array of
|
||||
* function arguments and returns the result.
|
||||
*/
|
||||
public function __construct(callable $fnDispatcher = null)
|
||||
{
|
||||
$this->fnDispatcher = $fnDispatcher ?: FnDispatcher::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits each node in a JMESPath AST and returns the evaluated result.
|
||||
*
|
||||
* @param array $node JMESPath AST node
|
||||
* @param mixed $data Data to evaluate
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function visit(array $node, $data)
|
||||
{
|
||||
return $this->dispatch($node, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively traverses an AST using depth-first, pre-order traversal.
|
||||
* The evaluation logic for each node type is embedded into a large switch
|
||||
* statement to avoid the cost of "double dispatch".
|
||||
* @return mixed
|
||||
*/
|
||||
private function dispatch(array $node, $value)
|
||||
{
|
||||
$dispatcher = $this->fnDispatcher;
|
||||
|
||||
switch ($node['type']) {
|
||||
|
||||
case 'field':
|
||||
if (is_array($value) || $value instanceof \ArrayAccess) {
|
||||
return isset($value[$node['value']]) ? $value[$node['value']] : null;
|
||||
} elseif ($value instanceof \stdClass) {
|
||||
return isset($value->{$node['value']}) ? $value->{$node['value']} : null;
|
||||
}
|
||||
return null;
|
||||
|
||||
case 'subexpression':
|
||||
return $this->dispatch(
|
||||
$node['children'][1],
|
||||
$this->dispatch($node['children'][0], $value)
|
||||
);
|
||||
|
||||
case 'index':
|
||||
if (!Utils::isArray($value)) {
|
||||
return null;
|
||||
}
|
||||
$idx = $node['value'] >= 0
|
||||
? $node['value']
|
||||
: $node['value'] + count($value);
|
||||
return isset($value[$idx]) ? $value[$idx] : null;
|
||||
|
||||
case 'projection':
|
||||
$left = $this->dispatch($node['children'][0], $value);
|
||||
switch ($node['from']) {
|
||||
case 'object':
|
||||
if (!Utils::isObject($left)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (!Utils::isArray($left)) {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!is_array($left) || !($left instanceof \stdClass)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$collected = [];
|
||||
foreach ((array) $left as $val) {
|
||||
$result = $this->dispatch($node['children'][1], $val);
|
||||
if ($result !== null) {
|
||||
$collected[] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $collected;
|
||||
|
||||
case 'flatten':
|
||||
static $skipElement = [];
|
||||
$value = $this->dispatch($node['children'][0], $value);
|
||||
|
||||
if (!Utils::isArray($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$merged = [];
|
||||
foreach ($value as $values) {
|
||||
// Only merge up arrays lists and not hashes
|
||||
if (is_array($values) && isset($values[0])) {
|
||||
$merged = array_merge($merged, $values);
|
||||
} elseif ($values !== $skipElement) {
|
||||
$merged[] = $values;
|
||||
}
|
||||
}
|
||||
|
||||
return $merged;
|
||||
|
||||
case 'literal':
|
||||
return $node['value'];
|
||||
|
||||
case 'current':
|
||||
return $value;
|
||||
|
||||
case 'or':
|
||||
$result = $this->dispatch($node['children'][0], $value);
|
||||
return Utils::isTruthy($result)
|
||||
? $result
|
||||
: $this->dispatch($node['children'][1], $value);
|
||||
|
||||
case 'and':
|
||||
$result = $this->dispatch($node['children'][0], $value);
|
||||
return Utils::isTruthy($result)
|
||||
? $this->dispatch($node['children'][1], $value)
|
||||
: $result;
|
||||
|
||||
case 'not':
|
||||
return !Utils::isTruthy(
|
||||
$this->dispatch($node['children'][0], $value)
|
||||
);
|
||||
|
||||
case 'pipe':
|
||||
return $this->dispatch(
|
||||
$node['children'][1],
|
||||
$this->dispatch($node['children'][0], $value)
|
||||
);
|
||||
|
||||
case 'multi_select_list':
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$collected = [];
|
||||
foreach ($node['children'] as $node) {
|
||||
$collected[] = $this->dispatch($node, $value);
|
||||
}
|
||||
|
||||
return $collected;
|
||||
|
||||
case 'multi_select_hash':
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$collected = [];
|
||||
foreach ($node['children'] as $node) {
|
||||
$collected[$node['value']] = $this->dispatch(
|
||||
$node['children'][0],
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
return $collected;
|
||||
|
||||
case 'comparator':
|
||||
$left = $this->dispatch($node['children'][0], $value);
|
||||
$right = $this->dispatch($node['children'][1], $value);
|
||||
if ($node['value'] == '==') {
|
||||
return Utils::isEqual($left, $right);
|
||||
} elseif ($node['value'] == '!=') {
|
||||
return !Utils::isEqual($left, $right);
|
||||
} else {
|
||||
return self::relativeCmp($left, $right, $node['value']);
|
||||
}
|
||||
|
||||
case 'condition':
|
||||
return Utils::isTruthy($this->dispatch($node['children'][0], $value))
|
||||
? $this->dispatch($node['children'][1], $value)
|
||||
: null;
|
||||
|
||||
case 'function':
|
||||
$args = [];
|
||||
foreach ($node['children'] as $arg) {
|
||||
$args[] = $this->dispatch($arg, $value);
|
||||
}
|
||||
return $dispatcher($node['value'], $args);
|
||||
|
||||
case 'slice':
|
||||
return is_string($value) || Utils::isArray($value)
|
||||
? Utils::slice(
|
||||
$value,
|
||||
$node['value'][0],
|
||||
$node['value'][1],
|
||||
$node['value'][2]
|
||||
) : null;
|
||||
|
||||
case 'expref':
|
||||
$apply = $node['children'][0];
|
||||
return function ($value) use ($apply) {
|
||||
return $this->visit($apply, $value);
|
||||
};
|
||||
|
||||
default:
|
||||
throw new \RuntimeException("Unknown node type: {$node['type']}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private static function relativeCmp($left, $right, $cmp)
|
||||
{
|
||||
if (!(is_int($left) || is_float($left)) || !(is_int($right) || is_float($right))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($cmp) {
|
||||
case '>': return $left > $right;
|
||||
case '>=': return $left >= $right;
|
||||
case '<': return $left < $right;
|
||||
case '<=': return $left <= $right;
|
||||
default: throw new \RuntimeException("Invalid comparison: $cmp");
|
||||
}
|
||||
}
|
||||
}
|
||||
258
vendor/mtdowling/jmespath.php/src/Utils.php
vendored
Normal file
258
vendor/mtdowling/jmespath.php/src/Utils.php
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
namespace JmesPath;
|
||||
|
||||
class Utils
|
||||
{
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely add together two values.
|
||||
*
|
||||
* @param mixed $a First value to add
|
||||
* @param mixed $b Second value to add
|
||||
*
|
||||
* @return int|float
|
||||
*/
|
||||
public static function add($a, $b)
|
||||
{
|
||||
if (is_numeric($a)) {
|
||||
if (is_numeric($b)) {
|
||||
return $a + $b;
|
||||
} else {
|
||||
return $a;
|
||||
}
|
||||
} else {
|
||||
if (is_numeric($b)) {
|
||||
return $b;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' ? mb_strlen($subject, 'UTF-8') : 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user