├── .github
└── dependabot.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── _config.yml
├── composer.json
├── composer.lock
├── docker-compose.yml
├── package-lock.json
├── package.json
├── phpstan.neon
├── phpunit.xml
├── psalm.xml
├── src
├── Exceptions
│ ├── MessageToLongException.php
│ ├── QueueAlreadyExistsException.php
│ ├── QueueNotFoundException.php
│ └── QueueParametersValidationException.php
├── ExecutorInterface.php
├── Message.php
├── QueueAttributes.php
├── QueueWorker.php
├── RSMQClient.php
├── RSMQClientInterface.php
├── WorkerSleepProvider.php
├── functions.php
└── functions_include.php
└── tests
├── FunctionsTest.php
├── QueueWorkerTest.php
└── RSMQTest.php
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "composer"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor/
3 | coverage/
4 | .phpunit.result.cache
5 | coverage.xml
6 | clover.xml
7 | node_modules/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | services:
3 | - redis
4 |
5 | php:
6 | - 7.4
7 | - 8.0
8 | - nightly
9 |
10 | matrix:
11 | allow_failures:
12 | - php: nightly
13 | fast_finish: true
14 |
15 | install:
16 | - composer install -n
17 |
18 | script:
19 | - composer test -- --coverage-clover=clover.xml
20 | - composer phpstan
21 | - composer psalm
22 |
23 | cache:
24 | directories:
25 | - $HOME/.composer/cache/files
26 |
27 | after_success:
28 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 eislambey
4 | Copyright (c) 2020 Andrew Breksa
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Redis Simple Message Queue
2 | --------------------------
3 | [](https://travis-ci.com/abreksa4/php-rsmq)
4 | [](https://codecov.io/gh/abreksa4/php-rsmq)
5 | [](//packagist.org/packages/andrewbreksa/rsmq)
6 | [](https://github.com/abreksa4/php-rsmq/issues)
7 | [](//packagist.org/packages/andrewbreksa/rsmq)
8 | [](//packagist.org/packages/andrewbreksa/rsmq)
9 | [](//packagist.org/packages/andrewbreksa/rsmq)
10 | [](//packagist.org/packages/andrewbreksa/rsmq)
11 | [](https://github.com/abreksa4/php-rsmq/stargazers)
12 | [](//packagist.org/packages/andrewbreksa/rsmq)
13 |
14 | A lightweight message queue for PHP that requires no dedicated queue server. Just a Redis server. See
15 | [smrchy/rsmq](https://github.com/smrchy/rsmq) for more information.
16 |
17 | This is a fork of [eislambey/php-rsmq](https://github.com/eislambey/php-rsmq) with the following changes:
18 |
19 | - Uses [predis](https://github.com/nrk/predis) instead of the Redis extension
20 | - Has some OO wrappers for QueueAttributes and Message
21 | - Provides a simple [QueueWorker](./src/QueueWorker.php)
22 |
23 | # Table of Contents
24 |
25 |
26 |
27 | - [Installation](#installation)
28 | - [Methods](#methods)
29 | * [Construct](#construct)
30 | * [Queue](#queue)
31 | + [createQueue](#createqueue)
32 | + [listQueues](#listqueues)
33 | + [deleteQueue](#deletequeue)
34 | + [getQueueAttributes](#getqueueattributes)
35 | + [setQueueAttributes](#setqueueattributes)
36 | * [Messages](#messages)
37 | + [sendMessage](#sendmessage)
38 | + [receiveMessage](#receivemessage)
39 | + [deleteMessage](#deletemessage)
40 | + [popMessage](#popmessage)
41 | + [changeMessageVisibility](#changemessagevisibility)
42 | * [Realtime](#realtime)
43 | - [QueueWorker](#queueworker)
44 | - [LICENSE](#license)
45 |
46 |
47 |
48 | # Installation
49 |
50 | composer require andrewbreksa/rsmq
51 |
52 | # Methods
53 |
54 | ## Construct
55 |
56 | Creates a new instance of RSMQ.
57 |
58 | Parameters:
59 |
60 | * `$predis` (\Predis\ClientInterface): *required The Predis instance
61 | * `$ns` (string): *optional (Default: "rsmq")* The namespace prefix used for all keys created by RSMQ
62 | * `$realtime` (Boolean): *optional (Default: false)* Enable realtime PUBLISH of new messages
63 |
64 | Example:
65 |
66 | ```php
67 | '127.0.0.1',
74 | 'port' => 6379
75 | ]
76 | );
77 | $this->rsmq = new RSMQClient($predis);
78 | ```
79 |
80 | ## Queue
81 |
82 | ### createQueue
83 |
84 | Create a new queue.
85 |
86 | Parameters:
87 |
88 | * `$name` (string): The Queue name. Maximum 160 characters; alphanumeric characters, hyphens (-), and underscores (_)
89 | are allowed.
90 | * `$vt` (int): *optional* *(Default: 30)* The length of time, in seconds, that a message received from a queue will be
91 | invisible to other receiving components when they ask to receive messages. Allowed values: 0-9999999 (around 115 days)
92 | * `$delay` (int): *optional* *(Default: 0)* The time in seconds that the delivery of all new messages in the queue will
93 | be delayed. Allowed values: 0-9999999 (around 115 days)
94 | * `$maxsize` (int): *optional* *(Default: 65536)* The maximum message size in bytes. Allowed values: 1024-65536 and -1
95 | (for unlimited size)
96 |
97 | Returns:
98 |
99 | * `true` (Bool)
100 |
101 | Throws:
102 |
103 | * `\AndrewBreksa\RSMQ\Exceptions\QueueAlreadyExistsException`
104 |
105 | Example:
106 |
107 | ```php
108 | createQueue('myqueue');
114 | ```
115 |
116 | ### listQueues
117 |
118 | List all queues
119 |
120 | Returns an array:
121 |
122 | * `["qname1", "qname2"]`
123 |
124 | Example:
125 |
126 | ```php
127 | listQueues();
133 | ```
134 |
135 | ### deleteQueue
136 |
137 | Deletes a queue and all messages.
138 |
139 | Parameters:
140 |
141 | * `$name` (string): The Queue name.
142 |
143 | Returns:
144 |
145 | * `true` (Bool)
146 |
147 | Throws:
148 |
149 | * `\AndrewBreksa\RSMQ\Exceptions\QueueNotFoundException`
150 |
151 | Example:
152 |
153 | ```php
154 | deleteQueue('myqueue');
160 | ```
161 |
162 | ### getQueueAttributes
163 |
164 | Get queue attributes, counter and stats
165 |
166 | Parameters:
167 |
168 | * `$queue` (string): The Queue name.
169 |
170 | Returns a `\AndrewBreksa\RSMQ\QueueAttributes` object with the following properties:
171 |
172 | * `vt` (int): The visibility timeout for the queue in seconds
173 | * `delay` (int): The delay for new messages in seconds
174 | * `maxSize` (int): The maximum size of a message in bytes
175 | * `totalReceived` (int): Total number of messages received from the queue
176 | * `totalSent` (int): Total number of messages sent to the queue
177 | * `created` (float): Timestamp (epoch in seconds) when the queue was created
178 | * `modified` (float): Timestamp (epoch in seconds) when the queue was last modified with `setQueueAttributes`
179 | * `messageCount` (int): Current number of messages in the queue
180 | * `hiddenMessageCount` (int): Current number of hidden / not visible messages. A message can be hidden while "in flight"
181 | due to a `vt` parameter or when sent with a `delay`
182 |
183 | Example:
184 |
185 | ```php
186 | getQueueAttributes('myqueue');
192 | echo "visibility timeout: ", $attributes->getVt(), "\n";
193 | echo "delay for new messages: ", $attributes->getDelay(), "\n";
194 | echo "max size in bytes: ", $attributes->getMaxSize(), "\n";
195 | echo "total received messages: ", $attributes->getTotalReceived(), "\n";
196 | echo "total sent messages: ", $attributes->getTotalSent(), "\n";
197 | echo "created: ", $attributes->getCreated(), "\n";
198 | echo "last modified: ", $attributes->getModified(), "\n";
199 | echo "current n of messages: ", $attributes->getMessageCount(), "\n";
200 | echo "hidden messages: ", $attributes->getHiddenMessageCount(), "\n";
201 | ```
202 |
203 | ### setQueueAttributes
204 |
205 | Sets queue parameters.
206 |
207 | Parameters:
208 |
209 | * `$queue` (string): The Queue name.
210 | * `$vt` (int): *optional* * The length of time, in seconds, that a message received from a queue will be invisible to
211 | other receiving components when they ask to receive messages. Allowed values: 0-9999999 (around 115 days)
212 | * `$delay` (int): *optional* The time in seconds that the delivery of all new messages in the queue will be delayed.
213 | Allowed values: 0-9999999 (around 115 days)
214 | * `$maxsize` (int): *optional* The maximum message size in bytes. Allowed values: 1024-65536 and -1 (for unlimited size)
215 |
216 | Note: At least one attribute (vt, delay, maxsize) must be supplied. Only attributes that are supplied will be modified.
217 |
218 | Returns a `\AndrewBreksa\RSMQ\QueueAttributes` object with the following properties:
219 |
220 | * `vt` (int): The visibility timeout for the queue in seconds
221 | * `delay` (int): The delay for new messages in seconds
222 | * `maxSize` (int): The maximum size of a message in bytes
223 | * `totalReceived` (int): Total number of messages received from the queue
224 | * `totalSent` (int): Total number of messages sent to the queue
225 | * `created` (float): Timestamp (epoch in seconds) when the queue was created
226 | * `modified` (float): Timestamp (epoch in seconds) when the queue was last modified with `setQueueAttributes`
227 | * `messageCount` (int): Current number of messages in the queue
228 | * `hiddenMessageCount` (int): Current number of hidden / not visible messages. A message can be hidden while "in flight"
229 | due to a `vt` parameter or when sent with a `delay`
230 |
231 | Throws:
232 |
233 | * `\AndrewBreksa\RSMQ\QueueAttributes`
234 | * `\AndrewBreksa\RSMQ\QueueParametersValidationException`
235 | * `\AndrewBreksa\RSMQ\QueueNotFoundException`
236 |
237 | Example:
238 |
239 | ```php
240 | setQueueAttributes($queue, $vt, $delay, $maxsize);
250 | ```
251 |
252 | ## Messages
253 |
254 | ### sendMessage
255 |
256 | Sends a new message.
257 |
258 | Parameters:
259 |
260 | * `$queue` (string)
261 | * `$message` (string)
262 | * `$delay` (int): *optional* *(Default: queue settings)* The time in seconds that the delivery of the message will be
263 | delayed. Allowed values: 0-9999999 (around 115 days)
264 |
265 | Returns:
266 |
267 | * `$id` (string): The internal message id.
268 |
269 | Throws:
270 |
271 | * `\AndrewBreksa\RSMQ\Exceptions\MessageToLongException`
272 | * `\AndrewBreksa\RSMQ\Exceptions\QueueNotFoundException`
273 | * `\AndrewBreksa\RSMQ\Exceptions\QueueParametersValidationException`
274 |
275 | Example:
276 |
277 | ```php
278 | sendMessage('myqueue', 'a message');
284 | echo "Message Sent. ID: ", $id;
285 | ```
286 |
287 | ### receiveMessage
288 |
289 | Receive the next message from the queue.
290 |
291 | Parameters:
292 |
293 | * `$queue` (string): The Queue name.
294 | * `$vt` (int): *optional* *(Default: queue settings)* The length of time, in seconds, that the received message will be
295 | invisible to others. Allowed values: 0-9999999 (around 115 days)
296 |
297 | Returns a `\AndrewBreksa\RSMQ\Message` object with the following properties:
298 |
299 | * `message` (string): The message's contents.
300 | * `id` (string): The internal message id.
301 | * `sent` (int): Timestamp of when this message was sent / created.
302 | * `firstReceived` (int): Timestamp of when this message was first received.
303 | * `receiveCount` (int): Number of times this message was received.
304 |
305 | Note: Will return an empty array if no message is there
306 |
307 | Throws:
308 |
309 | * `\AndrewBreksa\RSMQ\Exceptions\QueueNotFoundException`
310 | * `\AndrewBreksa\RSMQ\Exceptions\QueueParametersValidationException`
311 |
312 | Example:
313 |
314 | ```php
315 | receiveMessage('myqueue');
321 | echo "Message ID: ", $message->getId();
322 | echo "Message: ", $message->getMessage();
323 | ```
324 |
325 | ### deleteMessage
326 |
327 | Parameters:
328 |
329 | * `$queue` (string): The Queue name.
330 | * `$id` (string): message id to delete.
331 |
332 | Returns:
333 |
334 | * `true` if successful, `false` if the message was not found (bool).
335 |
336 | Throws:
337 |
338 | * `\AndrewBreksa\RSMQ\Exceptions\QueueParametersValidationException`
339 |
340 | Example:
341 |
342 | ```php
343 | sendMessage('queue', 'a message');
349 | $rsmq->deleteMessage('queue', $id);
350 | ```
351 |
352 | ### popMessage
353 |
354 | Receive the next message from the queue **and delete it**.
355 |
356 | **Important:** This method deletes the message it receives right away. There is no way to receive the message again if
357 | something goes wrong while working on the message.
358 |
359 | Parameters:
360 |
361 | * `$queue` (string): The Queue name.
362 |
363 | Returns a `\AndrewBreksa\RSMQ\Message` object with the following properties:
364 |
365 | * `message` (string): The message's contents.
366 | * `id` (string): The internal message id.
367 | * `sent` (int): Timestamp of when this message was sent / created.
368 | * `firstReceived` (int): Timestamp of when this message was first received.
369 | * `receiveCount` (int): Number of times this message was received.
370 |
371 | Note: Will return an empty object if no message is there
372 |
373 | Throws:
374 |
375 | * `\AndrewBreksa\RSMQ\Exceptions\QueueNotFoundException`
376 | * `\AndrewBreksa\RSMQ\Exceptions\QueueParametersValidationException`
377 |
378 | Example:
379 |
380 | ```php
381 | popMessage('myqueue');
387 | echo "Message ID: ", $message->getId();
388 | echo "Message: ", $message->getMessage();
389 | ```
390 |
391 | ### changeMessageVisibility
392 |
393 | Change the visibility timer of a single message. The time when the message will be visible again is calculated from the
394 | current time (now) + `vt`.
395 |
396 | Parameters:
397 |
398 | * `qname` (string): The Queue name.
399 | * `id` (string): The message id.
400 | * `vt` (int): The length of time, in seconds, that this message will not be visible. Allowed values: 0-9999999 (around
401 | 115 days)
402 |
403 | Returns:
404 |
405 | * `true` if successful, `false` if the message was not found (bool).
406 |
407 | Throws:
408 |
409 | * `\AndrewBreksa\RSMQ\Exceptions\QueueParametersValidationException`
410 | * `\AndrewBreksa\RSMQ\Exceptions\QueueNotFoundException`
411 |
412 | Example:
413 |
414 | ```php
415 | sendMessage($queue, 'a message');
422 | if($rsmq->changeMessageVisibility($queue, $id, 60)) {
423 | echo "Message hidden for 60 secs";
424 | }
425 | ```
426 |
427 | ## Realtime
428 |
429 | When creating an instance of `AndrewBreksa\RSMQ\RSMQClient`, you can enable the realtime `PUBLISH` for new messages by
430 | passing `true` for the `$realtime` argument of `\AndrewBreksa\RSMQ\RSMQClient::__construct`. On every new message that
431 | is sent via `sendMessage`, a Redis `PUBLISH` will be issued to `{rsmq.ns}:rt:{qname}`.
432 |
433 | Example for RSMQ with default settings:
434 |
435 | * The queue `testQueue` already contains 5 messages.
436 | * A new message is being sent to the queue `testQueue`.
437 | * The following Redis command will be issued: `PUBLISH rsmq:rt:testQueue 6`
438 |
439 | The realtime option enables sending a `PUBLISH` when a new message is sent to RSMQ, however no further functionality is
440 | built on this feature. Your app could use the Redis `SUBSCRIBE` command to be notified of new messages and then attempt
441 | to poll from the queue, however due to how the Redis pub/sub system works,
442 | [all listeners will be notified of the new message](https://redis.io/docs/manual/pubsub/), this method doesn't lend
443 | itself to driving message handling in environments with more than one subscribed process.
444 |
445 | # QueueWorker
446 |
447 | The QueueWorker class provides an easy way to consume RSMQ messages, to use it:
448 |
449 | ```php
450 | work(); // here we can optionally pass true to only process one message
481 | ```
482 |
483 | # LICENSE
484 |
485 | The MIT LICENSE. See [LICENSE](./LICENSE)
486 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-hacker
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "andrewbreksa/rsmq",
3 | "description": "Redis Simple Message Queue.",
4 | "type": "library",
5 | "license": "MIT",
6 | "version": "2.0.2",
7 | "authors": [
8 | {
9 | "name": "emre can islambey",
10 | "email": "eislambey@gmail.com"
11 | },
12 | {
13 | "name": "Andrew Breksa",
14 | "email": "andrew@andrewbreksa.com"
15 | }
16 | ],
17 | "require": {
18 | "php": "^7.4|^8.0",
19 | "ext-mbstring": "*",
20 | "predis/predis": "^1.1"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "AndrewBreksa\\RSMQ\\": "src/"
25 | },
26 | "files": [
27 | "./src/functions_include.php"
28 | ]
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^8.3 || ^9.0",
32 | "phpstan/phpstan": "^0.11.12 || ^0.12.0 || ^1.0.0",
33 | "roave/security-advisories": "dev-master",
34 | "vimeo/psalm": "^3.12 || ^4.0",
35 | "squizlabs/php_codesniffer": "^3.5",
36 | "mockery/mockery": "^1.3"
37 | },
38 | "autoload-dev": {
39 | "classmap": [
40 | "tests/"
41 | ]
42 | },
43 | "scripts": {
44 | "test": "XDEBUG_MODE=coverage phpunit",
45 | "local-test": "XDEBUG_MODE=coverage php ./vendor/bin/phpunit --coverage-text --coverage-clover coverage.xml --coverage-html ./coverage",
46 | "phpstan": "phpstan analyse -c phpstan.neon",
47 | "cbf": "php ./vendor/bin/phpcbf tests src",
48 | "psalm": "php ./vendor/bin/psalm --show-info=true",
49 | "toc": "node ./node_modules/markdown-toc/cli.js README.md -i",
50 | "all-the-things": [
51 | "composer local-test",
52 | "composer phpstan",
53 | "composer cbf",
54 | "composer psalm",
55 | "composer toc"
56 | ]
57 | },
58 | "minimum-stability": "dev",
59 | "prefer-stable": true
60 | }
61 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.0"
2 | services:
3 | redis:
4 | image: redis:4
5 | ports:
6 | - 6379:6379
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-rsmq",
3 | "lockfileVersion": 2,
4 | "requires": true,
5 | "packages": {
6 | "": {
7 | "devDependencies": {
8 | "markdown-toc": "^1.2.0"
9 | }
10 | },
11 | "node_modules/ansi-red": {
12 | "version": "0.1.1",
13 | "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
14 | "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=",
15 | "dev": true,
16 | "dependencies": {
17 | "ansi-wrap": "0.1.0"
18 | },
19 | "engines": {
20 | "node": ">=0.10.0"
21 | }
22 | },
23 | "node_modules/ansi-wrap": {
24 | "version": "0.1.0",
25 | "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
26 | "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
27 | "dev": true,
28 | "engines": {
29 | "node": ">=0.10.0"
30 | }
31 | },
32 | "node_modules/argparse": {
33 | "version": "1.0.10",
34 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
35 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
36 | "dev": true,
37 | "dependencies": {
38 | "sprintf-js": "~1.0.2"
39 | }
40 | },
41 | "node_modules/autolinker": {
42 | "version": "0.28.1",
43 | "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz",
44 | "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=",
45 | "dev": true,
46 | "dependencies": {
47 | "gulp-header": "^1.7.1"
48 | }
49 | },
50 | "node_modules/buffer-from": {
51 | "version": "1.1.1",
52 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
53 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
54 | "dev": true
55 | },
56 | "node_modules/coffee-script": {
57 | "version": "1.12.7",
58 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
59 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==",
60 | "deprecated": "CoffeeScript on NPM has moved to \"coffeescript\" (no hyphen)",
61 | "dev": true,
62 | "bin": {
63 | "cake": "bin/cake",
64 | "coffee": "bin/coffee"
65 | },
66 | "engines": {
67 | "node": ">=0.8.0"
68 | }
69 | },
70 | "node_modules/concat-stream": {
71 | "version": "1.6.2",
72 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
73 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
74 | "dev": true,
75 | "engines": [
76 | "node >= 0.8"
77 | ],
78 | "dependencies": {
79 | "buffer-from": "^1.0.0",
80 | "inherits": "^2.0.3",
81 | "readable-stream": "^2.2.2",
82 | "typedarray": "^0.0.6"
83 | }
84 | },
85 | "node_modules/concat-with-sourcemaps": {
86 | "version": "1.1.0",
87 | "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
88 | "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
89 | "dev": true,
90 | "dependencies": {
91 | "source-map": "^0.6.1"
92 | }
93 | },
94 | "node_modules/core-util-is": {
95 | "version": "1.0.2",
96 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
97 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
98 | "dev": true
99 | },
100 | "node_modules/diacritics-map": {
101 | "version": "0.1.0",
102 | "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz",
103 | "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=",
104 | "dev": true,
105 | "engines": {
106 | "node": ">=0.8.0"
107 | }
108 | },
109 | "node_modules/esprima": {
110 | "version": "4.0.1",
111 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
112 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
113 | "dev": true,
114 | "bin": {
115 | "esparse": "bin/esparse.js",
116 | "esvalidate": "bin/esvalidate.js"
117 | },
118 | "engines": {
119 | "node": ">=4"
120 | }
121 | },
122 | "node_modules/expand-range": {
123 | "version": "1.8.2",
124 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
125 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
126 | "dev": true,
127 | "dependencies": {
128 | "fill-range": "^2.1.0"
129 | },
130 | "engines": {
131 | "node": ">=0.10.0"
132 | }
133 | },
134 | "node_modules/extend-shallow": {
135 | "version": "2.0.1",
136 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
137 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
138 | "dev": true,
139 | "dependencies": {
140 | "is-extendable": "^0.1.0"
141 | },
142 | "engines": {
143 | "node": ">=0.10.0"
144 | }
145 | },
146 | "node_modules/fill-range": {
147 | "version": "2.2.4",
148 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
149 | "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
150 | "dev": true,
151 | "dependencies": {
152 | "is-number": "^2.1.0",
153 | "isobject": "^2.0.0",
154 | "randomatic": "^3.0.0",
155 | "repeat-element": "^1.1.2",
156 | "repeat-string": "^1.5.2"
157 | },
158 | "engines": {
159 | "node": ">=0.10.0"
160 | }
161 | },
162 | "node_modules/for-in": {
163 | "version": "1.0.2",
164 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
165 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
166 | "dev": true,
167 | "engines": {
168 | "node": ">=0.10.0"
169 | }
170 | },
171 | "node_modules/gray-matter": {
172 | "version": "2.1.1",
173 | "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz",
174 | "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=",
175 | "dev": true,
176 | "dependencies": {
177 | "ansi-red": "^0.1.1",
178 | "coffee-script": "^1.12.4",
179 | "extend-shallow": "^2.0.1",
180 | "js-yaml": "^3.8.1",
181 | "toml": "^2.3.2"
182 | },
183 | "engines": {
184 | "node": ">=0.10.0"
185 | }
186 | },
187 | "node_modules/gulp-header": {
188 | "version": "1.8.12",
189 | "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
190 | "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==",
191 | "deprecated": "Removed event-stream from gulp-header",
192 | "dev": true,
193 | "dependencies": {
194 | "concat-with-sourcemaps": "*",
195 | "lodash.template": "^4.4.0",
196 | "through2": "^2.0.0"
197 | }
198 | },
199 | "node_modules/inherits": {
200 | "version": "2.0.4",
201 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
202 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
203 | "dev": true
204 | },
205 | "node_modules/is-buffer": {
206 | "version": "1.1.6",
207 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
208 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
209 | "dev": true
210 | },
211 | "node_modules/is-extendable": {
212 | "version": "0.1.1",
213 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
214 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
215 | "dev": true,
216 | "engines": {
217 | "node": ">=0.10.0"
218 | }
219 | },
220 | "node_modules/is-number": {
221 | "version": "2.1.0",
222 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
223 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
224 | "dev": true,
225 | "dependencies": {
226 | "kind-of": "^3.0.2"
227 | },
228 | "engines": {
229 | "node": ">=0.10.0"
230 | }
231 | },
232 | "node_modules/is-plain-object": {
233 | "version": "2.0.4",
234 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
235 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
236 | "dev": true,
237 | "dependencies": {
238 | "isobject": "^3.0.1"
239 | },
240 | "engines": {
241 | "node": ">=0.10.0"
242 | }
243 | },
244 | "node_modules/is-plain-object/node_modules/isobject": {
245 | "version": "3.0.1",
246 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
247 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
248 | "dev": true,
249 | "engines": {
250 | "node": ">=0.10.0"
251 | }
252 | },
253 | "node_modules/isarray": {
254 | "version": "1.0.0",
255 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
256 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
257 | "dev": true
258 | },
259 | "node_modules/isobject": {
260 | "version": "2.1.0",
261 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
262 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
263 | "dev": true,
264 | "dependencies": {
265 | "isarray": "1.0.0"
266 | },
267 | "engines": {
268 | "node": ">=0.10.0"
269 | }
270 | },
271 | "node_modules/js-yaml": {
272 | "version": "3.14.0",
273 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
274 | "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
275 | "dev": true,
276 | "dependencies": {
277 | "argparse": "^1.0.7",
278 | "esprima": "^4.0.0"
279 | },
280 | "bin": {
281 | "js-yaml": "bin/js-yaml.js"
282 | }
283 | },
284 | "node_modules/kind-of": {
285 | "version": "3.2.2",
286 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
287 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
288 | "dev": true,
289 | "dependencies": {
290 | "is-buffer": "^1.1.5"
291 | },
292 | "engines": {
293 | "node": ">=0.10.0"
294 | }
295 | },
296 | "node_modules/lazy-cache": {
297 | "version": "2.0.2",
298 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz",
299 | "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=",
300 | "dev": true,
301 | "dependencies": {
302 | "set-getter": "^0.1.0"
303 | },
304 | "engines": {
305 | "node": ">=0.10.0"
306 | }
307 | },
308 | "node_modules/list-item": {
309 | "version": "1.1.1",
310 | "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz",
311 | "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=",
312 | "dev": true,
313 | "dependencies": {
314 | "expand-range": "^1.8.1",
315 | "extend-shallow": "^2.0.1",
316 | "is-number": "^2.1.0",
317 | "repeat-string": "^1.5.2"
318 | },
319 | "engines": {
320 | "node": ">=0.10.0"
321 | }
322 | },
323 | "node_modules/lodash._reinterpolate": {
324 | "version": "3.0.0",
325 | "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
326 | "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
327 | "dev": true
328 | },
329 | "node_modules/lodash.template": {
330 | "version": "4.5.0",
331 | "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
332 | "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
333 | "dev": true,
334 | "dependencies": {
335 | "lodash._reinterpolate": "^3.0.0",
336 | "lodash.templatesettings": "^4.0.0"
337 | }
338 | },
339 | "node_modules/lodash.templatesettings": {
340 | "version": "4.2.0",
341 | "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
342 | "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
343 | "dev": true,
344 | "dependencies": {
345 | "lodash._reinterpolate": "^3.0.0"
346 | }
347 | },
348 | "node_modules/markdown-link": {
349 | "version": "0.1.1",
350 | "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz",
351 | "integrity": "sha1-MsXGUZmmRXMWMi0eQinRNAfIx88=",
352 | "dev": true,
353 | "engines": {
354 | "node": ">=0.10.0"
355 | }
356 | },
357 | "node_modules/markdown-toc": {
358 | "version": "1.2.0",
359 | "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz",
360 | "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==",
361 | "dev": true,
362 | "dependencies": {
363 | "concat-stream": "^1.5.2",
364 | "diacritics-map": "^0.1.0",
365 | "gray-matter": "^2.1.0",
366 | "lazy-cache": "^2.0.2",
367 | "list-item": "^1.1.1",
368 | "markdown-link": "^0.1.1",
369 | "minimist": "^1.2.0",
370 | "mixin-deep": "^1.1.3",
371 | "object.pick": "^1.2.0",
372 | "remarkable": "^1.7.1",
373 | "repeat-string": "^1.6.1",
374 | "strip-color": "^0.1.0"
375 | },
376 | "bin": {
377 | "markdown-toc": "cli.js"
378 | },
379 | "engines": {
380 | "node": ">=0.10.0"
381 | }
382 | },
383 | "node_modules/math-random": {
384 | "version": "1.0.4",
385 | "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
386 | "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
387 | "dev": true
388 | },
389 | "node_modules/minimist": {
390 | "version": "1.2.6",
391 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
392 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
393 | "dev": true
394 | },
395 | "node_modules/mixin-deep": {
396 | "version": "1.3.2",
397 | "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
398 | "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
399 | "dev": true,
400 | "dependencies": {
401 | "for-in": "^1.0.2",
402 | "is-extendable": "^1.0.1"
403 | },
404 | "engines": {
405 | "node": ">=0.10.0"
406 | }
407 | },
408 | "node_modules/mixin-deep/node_modules/is-extendable": {
409 | "version": "1.0.1",
410 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
411 | "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
412 | "dev": true,
413 | "dependencies": {
414 | "is-plain-object": "^2.0.4"
415 | },
416 | "engines": {
417 | "node": ">=0.10.0"
418 | }
419 | },
420 | "node_modules/object.pick": {
421 | "version": "1.3.0",
422 | "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
423 | "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
424 | "dev": true,
425 | "dependencies": {
426 | "isobject": "^3.0.1"
427 | },
428 | "engines": {
429 | "node": ">=0.10.0"
430 | }
431 | },
432 | "node_modules/object.pick/node_modules/isobject": {
433 | "version": "3.0.1",
434 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
435 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
436 | "dev": true,
437 | "engines": {
438 | "node": ">=0.10.0"
439 | }
440 | },
441 | "node_modules/process-nextick-args": {
442 | "version": "2.0.1",
443 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
444 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
445 | "dev": true
446 | },
447 | "node_modules/randomatic": {
448 | "version": "3.1.1",
449 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
450 | "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
451 | "dev": true,
452 | "dependencies": {
453 | "is-number": "^4.0.0",
454 | "kind-of": "^6.0.0",
455 | "math-random": "^1.0.1"
456 | },
457 | "engines": {
458 | "node": ">= 0.10.0"
459 | }
460 | },
461 | "node_modules/randomatic/node_modules/is-number": {
462 | "version": "4.0.0",
463 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
464 | "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
465 | "dev": true,
466 | "engines": {
467 | "node": ">=0.10.0"
468 | }
469 | },
470 | "node_modules/randomatic/node_modules/kind-of": {
471 | "version": "6.0.3",
472 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
473 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
474 | "dev": true,
475 | "engines": {
476 | "node": ">=0.10.0"
477 | }
478 | },
479 | "node_modules/readable-stream": {
480 | "version": "2.3.7",
481 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
482 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
483 | "dev": true,
484 | "dependencies": {
485 | "core-util-is": "~1.0.0",
486 | "inherits": "~2.0.3",
487 | "isarray": "~1.0.0",
488 | "process-nextick-args": "~2.0.0",
489 | "safe-buffer": "~5.1.1",
490 | "string_decoder": "~1.1.1",
491 | "util-deprecate": "~1.0.1"
492 | }
493 | },
494 | "node_modules/remarkable": {
495 | "version": "1.7.4",
496 | "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz",
497 | "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==",
498 | "dev": true,
499 | "dependencies": {
500 | "argparse": "^1.0.10",
501 | "autolinker": "~0.28.0"
502 | },
503 | "bin": {
504 | "remarkable": "bin/remarkable.js"
505 | },
506 | "engines": {
507 | "node": ">= 0.10.0"
508 | }
509 | },
510 | "node_modules/repeat-element": {
511 | "version": "1.1.3",
512 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
513 | "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
514 | "dev": true,
515 | "engines": {
516 | "node": ">=0.10.0"
517 | }
518 | },
519 | "node_modules/repeat-string": {
520 | "version": "1.6.1",
521 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
522 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
523 | "dev": true,
524 | "engines": {
525 | "node": ">=0.10"
526 | }
527 | },
528 | "node_modules/safe-buffer": {
529 | "version": "5.1.2",
530 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
531 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
532 | "dev": true
533 | },
534 | "node_modules/set-getter": {
535 | "version": "0.1.1",
536 | "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz",
537 | "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==",
538 | "dev": true,
539 | "dependencies": {
540 | "to-object-path": "^0.3.0"
541 | },
542 | "engines": {
543 | "node": ">=0.10.0"
544 | }
545 | },
546 | "node_modules/source-map": {
547 | "version": "0.6.1",
548 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
549 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
550 | "dev": true,
551 | "engines": {
552 | "node": ">=0.10.0"
553 | }
554 | },
555 | "node_modules/sprintf-js": {
556 | "version": "1.0.3",
557 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
558 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
559 | "dev": true
560 | },
561 | "node_modules/string_decoder": {
562 | "version": "1.1.1",
563 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
564 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
565 | "dev": true,
566 | "dependencies": {
567 | "safe-buffer": "~5.1.0"
568 | }
569 | },
570 | "node_modules/strip-color": {
571 | "version": "0.1.0",
572 | "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz",
573 | "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=",
574 | "dev": true,
575 | "engines": {
576 | "node": ">=0.10.0"
577 | }
578 | },
579 | "node_modules/through2": {
580 | "version": "2.0.5",
581 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
582 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
583 | "dev": true,
584 | "dependencies": {
585 | "readable-stream": "~2.3.6",
586 | "xtend": "~4.0.1"
587 | }
588 | },
589 | "node_modules/to-object-path": {
590 | "version": "0.3.0",
591 | "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
592 | "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
593 | "dev": true,
594 | "dependencies": {
595 | "kind-of": "^3.0.2"
596 | },
597 | "engines": {
598 | "node": ">=0.10.0"
599 | }
600 | },
601 | "node_modules/toml": {
602 | "version": "2.3.6",
603 | "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz",
604 | "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==",
605 | "dev": true
606 | },
607 | "node_modules/typedarray": {
608 | "version": "0.0.6",
609 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
610 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
611 | "dev": true
612 | },
613 | "node_modules/util-deprecate": {
614 | "version": "1.0.2",
615 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
616 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
617 | "dev": true
618 | },
619 | "node_modules/xtend": {
620 | "version": "4.0.2",
621 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
622 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
623 | "dev": true,
624 | "engines": {
625 | "node": ">=0.4"
626 | }
627 | }
628 | },
629 | "dependencies": {
630 | "ansi-red": {
631 | "version": "0.1.1",
632 | "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz",
633 | "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=",
634 | "dev": true,
635 | "requires": {
636 | "ansi-wrap": "0.1.0"
637 | }
638 | },
639 | "ansi-wrap": {
640 | "version": "0.1.0",
641 | "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
642 | "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=",
643 | "dev": true
644 | },
645 | "argparse": {
646 | "version": "1.0.10",
647 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
648 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
649 | "dev": true,
650 | "requires": {
651 | "sprintf-js": "~1.0.2"
652 | }
653 | },
654 | "autolinker": {
655 | "version": "0.28.1",
656 | "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz",
657 | "integrity": "sha1-BlK0kYgYefB3XazgzcoyM5QqTkc=",
658 | "dev": true,
659 | "requires": {
660 | "gulp-header": "^1.7.1"
661 | }
662 | },
663 | "buffer-from": {
664 | "version": "1.1.1",
665 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
666 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
667 | "dev": true
668 | },
669 | "coffee-script": {
670 | "version": "1.12.7",
671 | "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
672 | "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==",
673 | "dev": true
674 | },
675 | "concat-stream": {
676 | "version": "1.6.2",
677 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
678 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
679 | "dev": true,
680 | "requires": {
681 | "buffer-from": "^1.0.0",
682 | "inherits": "^2.0.3",
683 | "readable-stream": "^2.2.2",
684 | "typedarray": "^0.0.6"
685 | }
686 | },
687 | "concat-with-sourcemaps": {
688 | "version": "1.1.0",
689 | "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz",
690 | "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==",
691 | "dev": true,
692 | "requires": {
693 | "source-map": "^0.6.1"
694 | }
695 | },
696 | "core-util-is": {
697 | "version": "1.0.2",
698 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
699 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
700 | "dev": true
701 | },
702 | "diacritics-map": {
703 | "version": "0.1.0",
704 | "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz",
705 | "integrity": "sha1-bfwP+dAQAKLt8oZTccrDFulJd68=",
706 | "dev": true
707 | },
708 | "esprima": {
709 | "version": "4.0.1",
710 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
711 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
712 | "dev": true
713 | },
714 | "expand-range": {
715 | "version": "1.8.2",
716 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
717 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
718 | "dev": true,
719 | "requires": {
720 | "fill-range": "^2.1.0"
721 | }
722 | },
723 | "extend-shallow": {
724 | "version": "2.0.1",
725 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
726 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
727 | "dev": true,
728 | "requires": {
729 | "is-extendable": "^0.1.0"
730 | }
731 | },
732 | "fill-range": {
733 | "version": "2.2.4",
734 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
735 | "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
736 | "dev": true,
737 | "requires": {
738 | "is-number": "^2.1.0",
739 | "isobject": "^2.0.0",
740 | "randomatic": "^3.0.0",
741 | "repeat-element": "^1.1.2",
742 | "repeat-string": "^1.5.2"
743 | }
744 | },
745 | "for-in": {
746 | "version": "1.0.2",
747 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
748 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
749 | "dev": true
750 | },
751 | "gray-matter": {
752 | "version": "2.1.1",
753 | "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz",
754 | "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=",
755 | "dev": true,
756 | "requires": {
757 | "ansi-red": "^0.1.1",
758 | "coffee-script": "^1.12.4",
759 | "extend-shallow": "^2.0.1",
760 | "js-yaml": "^3.8.1",
761 | "toml": "^2.3.2"
762 | }
763 | },
764 | "gulp-header": {
765 | "version": "1.8.12",
766 | "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz",
767 | "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==",
768 | "dev": true,
769 | "requires": {
770 | "concat-with-sourcemaps": "*",
771 | "lodash.template": "^4.4.0",
772 | "through2": "^2.0.0"
773 | }
774 | },
775 | "inherits": {
776 | "version": "2.0.4",
777 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
778 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
779 | "dev": true
780 | },
781 | "is-buffer": {
782 | "version": "1.1.6",
783 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
784 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
785 | "dev": true
786 | },
787 | "is-extendable": {
788 | "version": "0.1.1",
789 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
790 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
791 | "dev": true
792 | },
793 | "is-number": {
794 | "version": "2.1.0",
795 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
796 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
797 | "dev": true,
798 | "requires": {
799 | "kind-of": "^3.0.2"
800 | }
801 | },
802 | "is-plain-object": {
803 | "version": "2.0.4",
804 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
805 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
806 | "dev": true,
807 | "requires": {
808 | "isobject": "^3.0.1"
809 | },
810 | "dependencies": {
811 | "isobject": {
812 | "version": "3.0.1",
813 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
814 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
815 | "dev": true
816 | }
817 | }
818 | },
819 | "isarray": {
820 | "version": "1.0.0",
821 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
822 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
823 | "dev": true
824 | },
825 | "isobject": {
826 | "version": "2.1.0",
827 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
828 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
829 | "dev": true,
830 | "requires": {
831 | "isarray": "1.0.0"
832 | }
833 | },
834 | "js-yaml": {
835 | "version": "3.14.0",
836 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
837 | "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
838 | "dev": true,
839 | "requires": {
840 | "argparse": "^1.0.7",
841 | "esprima": "^4.0.0"
842 | }
843 | },
844 | "kind-of": {
845 | "version": "3.2.2",
846 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
847 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
848 | "dev": true,
849 | "requires": {
850 | "is-buffer": "^1.1.5"
851 | }
852 | },
853 | "lazy-cache": {
854 | "version": "2.0.2",
855 | "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz",
856 | "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=",
857 | "dev": true,
858 | "requires": {
859 | "set-getter": "^0.1.0"
860 | }
861 | },
862 | "list-item": {
863 | "version": "1.1.1",
864 | "resolved": "https://registry.npmjs.org/list-item/-/list-item-1.1.1.tgz",
865 | "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=",
866 | "dev": true,
867 | "requires": {
868 | "expand-range": "^1.8.1",
869 | "extend-shallow": "^2.0.1",
870 | "is-number": "^2.1.0",
871 | "repeat-string": "^1.5.2"
872 | }
873 | },
874 | "lodash._reinterpolate": {
875 | "version": "3.0.0",
876 | "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
877 | "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=",
878 | "dev": true
879 | },
880 | "lodash.template": {
881 | "version": "4.5.0",
882 | "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
883 | "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
884 | "dev": true,
885 | "requires": {
886 | "lodash._reinterpolate": "^3.0.0",
887 | "lodash.templatesettings": "^4.0.0"
888 | }
889 | },
890 | "lodash.templatesettings": {
891 | "version": "4.2.0",
892 | "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
893 | "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
894 | "dev": true,
895 | "requires": {
896 | "lodash._reinterpolate": "^3.0.0"
897 | }
898 | },
899 | "markdown-link": {
900 | "version": "0.1.1",
901 | "resolved": "https://registry.npmjs.org/markdown-link/-/markdown-link-0.1.1.tgz",
902 | "integrity": "sha1-MsXGUZmmRXMWMi0eQinRNAfIx88=",
903 | "dev": true
904 | },
905 | "markdown-toc": {
906 | "version": "1.2.0",
907 | "resolved": "https://registry.npmjs.org/markdown-toc/-/markdown-toc-1.2.0.tgz",
908 | "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==",
909 | "dev": true,
910 | "requires": {
911 | "concat-stream": "^1.5.2",
912 | "diacritics-map": "^0.1.0",
913 | "gray-matter": "^2.1.0",
914 | "lazy-cache": "^2.0.2",
915 | "list-item": "^1.1.1",
916 | "markdown-link": "^0.1.1",
917 | "minimist": "^1.2.0",
918 | "mixin-deep": "^1.1.3",
919 | "object.pick": "^1.2.0",
920 | "remarkable": "^1.7.1",
921 | "repeat-string": "^1.6.1",
922 | "strip-color": "^0.1.0"
923 | }
924 | },
925 | "math-random": {
926 | "version": "1.0.4",
927 | "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
928 | "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
929 | "dev": true
930 | },
931 | "minimist": {
932 | "version": "1.2.6",
933 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
934 | "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
935 | "dev": true
936 | },
937 | "mixin-deep": {
938 | "version": "1.3.2",
939 | "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
940 | "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
941 | "dev": true,
942 | "requires": {
943 | "for-in": "^1.0.2",
944 | "is-extendable": "^1.0.1"
945 | },
946 | "dependencies": {
947 | "is-extendable": {
948 | "version": "1.0.1",
949 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
950 | "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
951 | "dev": true,
952 | "requires": {
953 | "is-plain-object": "^2.0.4"
954 | }
955 | }
956 | }
957 | },
958 | "object.pick": {
959 | "version": "1.3.0",
960 | "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
961 | "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
962 | "dev": true,
963 | "requires": {
964 | "isobject": "^3.0.1"
965 | },
966 | "dependencies": {
967 | "isobject": {
968 | "version": "3.0.1",
969 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
970 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
971 | "dev": true
972 | }
973 | }
974 | },
975 | "process-nextick-args": {
976 | "version": "2.0.1",
977 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
978 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
979 | "dev": true
980 | },
981 | "randomatic": {
982 | "version": "3.1.1",
983 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
984 | "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
985 | "dev": true,
986 | "requires": {
987 | "is-number": "^4.0.0",
988 | "kind-of": "^6.0.0",
989 | "math-random": "^1.0.1"
990 | },
991 | "dependencies": {
992 | "is-number": {
993 | "version": "4.0.0",
994 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
995 | "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
996 | "dev": true
997 | },
998 | "kind-of": {
999 | "version": "6.0.3",
1000 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
1001 | "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
1002 | "dev": true
1003 | }
1004 | }
1005 | },
1006 | "readable-stream": {
1007 | "version": "2.3.7",
1008 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
1009 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
1010 | "dev": true,
1011 | "requires": {
1012 | "core-util-is": "~1.0.0",
1013 | "inherits": "~2.0.3",
1014 | "isarray": "~1.0.0",
1015 | "process-nextick-args": "~2.0.0",
1016 | "safe-buffer": "~5.1.1",
1017 | "string_decoder": "~1.1.1",
1018 | "util-deprecate": "~1.0.1"
1019 | }
1020 | },
1021 | "remarkable": {
1022 | "version": "1.7.4",
1023 | "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz",
1024 | "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==",
1025 | "dev": true,
1026 | "requires": {
1027 | "argparse": "^1.0.10",
1028 | "autolinker": "~0.28.0"
1029 | }
1030 | },
1031 | "repeat-element": {
1032 | "version": "1.1.3",
1033 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
1034 | "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
1035 | "dev": true
1036 | },
1037 | "repeat-string": {
1038 | "version": "1.6.1",
1039 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
1040 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
1041 | "dev": true
1042 | },
1043 | "safe-buffer": {
1044 | "version": "5.1.2",
1045 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1046 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
1047 | "dev": true
1048 | },
1049 | "set-getter": {
1050 | "version": "0.1.1",
1051 | "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz",
1052 | "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==",
1053 | "dev": true,
1054 | "requires": {
1055 | "to-object-path": "^0.3.0"
1056 | }
1057 | },
1058 | "source-map": {
1059 | "version": "0.6.1",
1060 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
1061 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1062 | "dev": true
1063 | },
1064 | "sprintf-js": {
1065 | "version": "1.0.3",
1066 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1067 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1068 | "dev": true
1069 | },
1070 | "string_decoder": {
1071 | "version": "1.1.1",
1072 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1073 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1074 | "dev": true,
1075 | "requires": {
1076 | "safe-buffer": "~5.1.0"
1077 | }
1078 | },
1079 | "strip-color": {
1080 | "version": "0.1.0",
1081 | "resolved": "https://registry.npmjs.org/strip-color/-/strip-color-0.1.0.tgz",
1082 | "integrity": "sha1-EG9l09PmotlAHKwOsM6LinArT3s=",
1083 | "dev": true
1084 | },
1085 | "through2": {
1086 | "version": "2.0.5",
1087 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
1088 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
1089 | "dev": true,
1090 | "requires": {
1091 | "readable-stream": "~2.3.6",
1092 | "xtend": "~4.0.1"
1093 | }
1094 | },
1095 | "to-object-path": {
1096 | "version": "0.3.0",
1097 | "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
1098 | "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
1099 | "dev": true,
1100 | "requires": {
1101 | "kind-of": "^3.0.2"
1102 | }
1103 | },
1104 | "toml": {
1105 | "version": "2.3.6",
1106 | "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.6.tgz",
1107 | "integrity": "sha512-gVweAectJU3ebq//Ferr2JUY4WKSDe5N+z0FvjDncLGyHmIDoxgY/2Ie4qfEIDm4IS7OA6Rmdm7pdEEdMcV/xQ==",
1108 | "dev": true
1109 | },
1110 | "typedarray": {
1111 | "version": "0.0.6",
1112 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1113 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
1114 | "dev": true
1115 | },
1116 | "util-deprecate": {
1117 | "version": "1.0.2",
1118 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1119 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
1120 | "dev": true
1121 | },
1122 | "xtend": {
1123 | "version": "4.0.2",
1124 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1125 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
1126 | "dev": true
1127 | }
1128 | }
1129 | }
1130 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "devDependencies": {
4 | "markdown-toc": "^1.2.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 1
3 | paths:
4 | - src
5 | - tests
6 |
7 | reportUnmatchedIgnoredErrors: false
8 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | src
8 |
9 |
10 | ./src/functions_include.php
11 |
12 |
13 |
14 |
15 | tests
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Exceptions/MessageToLongException.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class MessageToLongException extends Exception
16 | {
17 |
18 | }
--------------------------------------------------------------------------------
/src/Exceptions/QueueAlreadyExistsException.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class QueueAlreadyExistsException extends Exception
16 | {
17 |
18 | }
--------------------------------------------------------------------------------
/src/Exceptions/QueueNotFoundException.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class QueueNotFoundException extends Exception
16 | {
17 |
18 | }
--------------------------------------------------------------------------------
/src/Exceptions/QueueParametersValidationException.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class QueueParametersValidationException extends Exception
16 | {
17 |
18 | }
--------------------------------------------------------------------------------
/src/ExecutorInterface.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface ExecutorInterface
13 | {
14 |
15 | /**
16 | * Handle the message, retuning true will "ack" the message, false will not ack (causing the message to become
17 | * visible as per the queue's vt setting)
18 | *
19 | * @param Message $message
20 | * @return bool
21 | */
22 | public function __invoke(Message $message): bool;
23 | }
24 |
--------------------------------------------------------------------------------
/src/Message.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class Message
13 | {
14 |
15 | /**
16 | * @var string
17 | */
18 | protected $id;
19 |
20 | /**
21 | * @var string
22 | */
23 | protected $message;
24 |
25 | /**
26 | * @var int
27 | */
28 | protected $receiveCount;
29 |
30 | /**
31 | * @var int
32 | */
33 | protected $firstReceived;
34 |
35 | /**
36 | * @var float
37 | */
38 | protected $sent;
39 |
40 | /**
41 | * Message constructor.
42 | *
43 | * @param string $id
44 | * @param string $message
45 | * @param int $receiveCount
46 | * @param int $firstReceived
47 | * @param float $sent
48 | */
49 | public function __construct(string $id, string $message, int $receiveCount, int $firstReceived, float $sent)
50 | {
51 | $this->id = $id;
52 | $this->message = $message;
53 | $this->receiveCount = $receiveCount;
54 | $this->firstReceived = $firstReceived;
55 | $this->sent = $sent;
56 | }
57 |
58 | /**
59 | * @return string
60 | */
61 | public function getId(): string
62 | {
63 | return $this->id;
64 | }
65 |
66 | /**
67 | * @return string
68 | */
69 | public function getMessage(): string
70 | {
71 | return $this->message;
72 | }
73 |
74 | /**
75 | * @return int
76 | */
77 | public function getReceiveCount(): int
78 | {
79 | return $this->receiveCount;
80 | }
81 |
82 | /**
83 | * @return int
84 | */
85 | public function getFirstReceived(): int
86 | {
87 | return $this->firstReceived;
88 | }
89 |
90 | /**
91 | * @return float
92 | */
93 | public function getSent(): float
94 | {
95 | return $this->sent;
96 | }
97 | }
--------------------------------------------------------------------------------
/src/QueueAttributes.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class QueueAttributes
13 | {
14 |
15 | /**
16 | * @var int
17 | */
18 | protected $vt;
19 |
20 | /**
21 | * @var int
22 | */
23 | protected $delay;
24 |
25 | /**
26 | * @var int
27 | */
28 | protected $maxSize;
29 |
30 | /**
31 | * @var int
32 | */
33 | protected $totalReceived;
34 |
35 | /**
36 | * @var int
37 | */
38 | protected $totalSent;
39 |
40 | /**
41 | * @var int
42 | */
43 | protected $created;
44 |
45 | /**
46 | * @var int
47 | */
48 | protected $modified;
49 |
50 | /**
51 | * @var int
52 | */
53 | protected $messageCount;
54 |
55 | /**
56 | * @var int
57 | */
58 | protected $hiddenMessageCount;
59 |
60 | /**
61 | * QueueAttributes constructor.
62 | *
63 | * @param int $vt
64 | * @param int $delay
65 | * @param int $maxSize
66 | * @param int $totalReceived
67 | * @param int $totalSent
68 | * @param int $created
69 | * @param int $modified
70 | * @param int $messageCount
71 | * @param int $hiddenMessageCount
72 | */
73 | public function __construct(
74 | int $vt,
75 | int $delay,
76 | int $maxSize,
77 | int $totalReceived,
78 | int $totalSent,
79 | int $created,
80 | int $modified,
81 | int $messageCount,
82 | int $hiddenMessageCount
83 | ) {
84 | $this->vt = $vt;
85 | $this->delay = $delay;
86 | $this->maxSize = $maxSize;
87 | $this->totalReceived = $totalReceived;
88 | $this->totalSent = $totalSent;
89 | $this->created = $created;
90 | $this->modified = $modified;
91 | $this->messageCount = $messageCount;
92 | $this->hiddenMessageCount = $hiddenMessageCount;
93 | }
94 |
95 | /**
96 | * @return int
97 | */
98 | public function getVt(): int
99 | {
100 | return $this->vt;
101 | }
102 |
103 | /**
104 | * @return int
105 | */
106 | public function getDelay(): int
107 | {
108 | return $this->delay;
109 | }
110 |
111 | /**
112 | * @return int
113 | */
114 | public function getMaxSize(): int
115 | {
116 | return $this->maxSize;
117 | }
118 |
119 | /**
120 | * @return int
121 | */
122 | public function getTotalReceived(): int
123 | {
124 | return $this->totalReceived;
125 | }
126 |
127 | /**
128 | * @return int
129 | */
130 | public function getTotalSent(): int
131 | {
132 | return $this->totalSent;
133 | }
134 |
135 | /**
136 | * @return int
137 | */
138 | public function getCreated(): int
139 | {
140 | return $this->created;
141 | }
142 |
143 | /**
144 | * @return int
145 | */
146 | public function getModified(): int
147 | {
148 | return $this->modified;
149 | }
150 |
151 | /**
152 | * @return int
153 | */
154 | public function getMessageCount(): int
155 | {
156 | return $this->messageCount;
157 | }
158 |
159 | /**
160 | * @return int
161 | */
162 | public function getHiddenMessageCount(): int
163 | {
164 | return $this->hiddenMessageCount;
165 | }
166 |
167 | }
--------------------------------------------------------------------------------
/src/QueueWorker.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class QueueWorker
13 | {
14 |
15 | /**
16 | * @var RSMQClientInterface
17 | */
18 | protected $rsmq;
19 |
20 | /**
21 | * @var ExecutorInterface
22 | */
23 | protected $executor;
24 |
25 | /**
26 | * @var string
27 | */
28 | protected $queue;
29 |
30 | /**
31 | * @var WorkerSleepProvider
32 | */
33 | protected $sleepProvider;
34 |
35 | /**
36 | * @var int
37 | */
38 | protected $received = 0;
39 |
40 | /**
41 | * @var int
42 | */
43 | protected $failed = 0;
44 |
45 | /**
46 | * @var int
47 | */
48 | protected $successful = 0;
49 |
50 | /**
51 | * QueueWorker constructor.
52 | *
53 | * @param RSMQClientInterface $rsmq
54 | * @param ExecutorInterface $executor
55 | * @param WorkerSleepProvider $sleepProvider
56 | * @param string $queue
57 | */
58 | public function __construct(
59 | RSMQClientInterface $rsmq,
60 | ExecutorInterface $executor,
61 | WorkerSleepProvider $sleepProvider,
62 | string $queue
63 | ) {
64 | $this->rsmq = $rsmq;
65 | $this->executor = $executor;
66 | $this->sleepProvider = $sleepProvider;
67 | $this->queue = $queue;
68 | }
69 |
70 |
71 | /**
72 | * @param bool $processOne
73 | * @throws Exceptions\QueueNotFoundException
74 | * @throws Exceptions\QueueParametersValidationException
75 | */
76 | public function work(bool $processOne = false): void
77 | {
78 | while (true) {
79 | $sleep = $this->sleepProvider->getSleep();
80 | if ($sleep === null || $sleep < 0) {
81 | break;
82 | }
83 | $message = $this->rsmq->receiveMessage($this->queue);
84 | if (!($message instanceof Message)) {
85 | sleep($sleep);
86 | continue;
87 | }
88 | $this->received++;
89 | $result = $this->executor->__invoke($message);
90 | if ($result === true) {
91 | $this->successful++;
92 | $this->rsmq->deleteMessage($this->queue, $message->getId());
93 | } else {
94 | $this->failed++;
95 | }
96 | if ($processOne && $this->getProcessedCount() === 1) {
97 | break;
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * @return int
104 | */
105 | public function getProcessedCount(): int
106 | {
107 | return $this->successful + $this->failed;
108 | }
109 |
110 | /**
111 | * @return int
112 | */
113 | public function getReceived(): int
114 | {
115 | return $this->received;
116 | }
117 |
118 | /**
119 | * @return int
120 | */
121 | public function getFailed(): int
122 | {
123 | return $this->failed;
124 | }
125 |
126 | /**
127 | * @return int
128 | */
129 | public function getSuccessful(): int
130 | {
131 | return $this->successful;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/RSMQClient.php:
--------------------------------------------------------------------------------
1 |
17 | * @author emre can islambey
18 | */
19 | class RSMQClient implements RSMQClientInterface
20 | {
21 | const MAX_DELAY = 9999999;
22 | const MIN_MESSAGE_SIZE = 1024;
23 | const MAX_PAYLOAD_SIZE = 65536;
24 |
25 | /**
26 | * @var ClientInterface
27 | */
28 | private ClientInterface $predis;
29 |
30 | /**
31 | * @var string
32 | */
33 | private string $ns;
34 |
35 | /**
36 | * @var bool
37 | */
38 | private bool $realtime;
39 |
40 | /**
41 | * @var string
42 | */
43 | private string $receiveMessageSha1;
44 |
45 | /**
46 | * @var string
47 | */
48 | private string $popMessageSha1;
49 |
50 | /**
51 | * @var string
52 | */
53 | private string $changeMessageVisibilitySha1;
54 |
55 | /**
56 | * RSMQ constructor.
57 | *
58 | * @param ClientInterface $predis
59 | * @param string $ns
60 | * @param bool $realtime
61 | */
62 | public function __construct(ClientInterface $predis, string $ns = 'rsmq', bool $realtime = false)
63 | {
64 | $this->predis = $predis;
65 | $this->ns = "$ns:";
66 | $this->realtime = $realtime;
67 |
68 | $this->initScripts();
69 | }
70 |
71 |
72 | private function initScripts(): void
73 | {
74 | $receiveMessageScript = 'local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1")
75 | if #msg == 0 then
76 | return {}
77 | end
78 | redis.call("ZADD", KEYS[1], KEYS[3], msg[1])
79 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1)
80 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1])
81 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1)
82 | local o = {msg[1], mbody, rc}
83 | if rc==1 then
84 | redis.call("hset", KEYS[1] .. ":Q", msg[1] .. ":fr", KEYS[2])
85 | table.insert(o, KEYS[2])
86 | else
87 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr")
88 | table.insert(o, fr)
89 | end
90 | return o';
91 |
92 | $popMessageScript = 'local msg = redis.call("ZRANGEBYSCORE", KEYS[1], "-inf", KEYS[2], "LIMIT", "0", "1")
93 | if #msg == 0 then
94 | return {}
95 | end
96 | redis.call("HINCRBY", KEYS[1] .. ":Q", "totalrecv", 1)
97 | local mbody = redis.call("HGET", KEYS[1] .. ":Q", msg[1])
98 | local rc = redis.call("HINCRBY", KEYS[1] .. ":Q", msg[1] .. ":rc", 1)
99 | local o = {msg[1], mbody, rc}
100 | if rc==1 then
101 | table.insert(o, KEYS[2])
102 | else
103 | local fr = redis.call("HGET", KEYS[1] .. ":Q", msg[1] .. ":fr")
104 | table.insert(o, fr)
105 | end
106 | redis.call("zrem", KEYS[1], msg[1])
107 | redis.call("hdel", KEYS[1] .. ":Q", msg[1], msg[1] .. ":rc", msg[1] .. ":fr")
108 | return o';
109 |
110 | $changeMessageVisibilityScript = 'local msg = redis.call("ZSCORE", KEYS[1], KEYS[2])
111 | if not msg then
112 | return 0
113 | end
114 | redis.call("ZADD", KEYS[1], KEYS[3], KEYS[2])
115 | return 1';
116 |
117 | $this->receiveMessageSha1 = $this->predis->script('load', $receiveMessageScript);
118 | $this->popMessageSha1 = $this->predis->script('load', $popMessageScript);
119 | $this->changeMessageVisibilitySha1 = $this->predis->script('load', $changeMessageVisibilityScript);
120 | }
121 |
122 | /**
123 | * @param string $name
124 | * @param int $vt
125 | * @param int $delay
126 | * @param int $maxSize
127 | * @return bool
128 | * @throws QueueAlreadyExistsException
129 | */
130 | public function createQueue(string $name, int $vt = 30, int $delay = 0, int $maxSize = 65536): bool
131 | {
132 | $this->validate(
133 | [
134 | 'queue' => $name,
135 | 'vt' => $vt,
136 | 'delay' => $delay,
137 | 'maxsize' => $maxSize,
138 | ]
139 | );
140 |
141 | $key = "{$this->ns}$name:Q";
142 |
143 | $resp = $this->predis->time();
144 | $this->predis->multi();
145 | $this->predis->hsetnx($key, 'vt', (string)$vt);
146 | $this->predis->hsetnx($key, 'delay', (string)$delay);
147 | $this->predis->hsetnx($key, 'maxsize', (string)$maxSize);
148 | $this->predis->hsetnx($key, 'created', $resp[0]);
149 | $this->predis->hsetnx($key, 'modified', $resp[0]);
150 | $resp = $this->predis->exec() ?? [];
151 |
152 | if (!$resp[0]) {
153 | throw new QueueAlreadyExistsException('Queue already exists.');
154 | }
155 |
156 | return (bool)$this->predis->sadd("{$this->ns}QUEUES", [$name]);
157 | }
158 |
159 | /**
160 | * @param array $params
161 | * @throws QueueParametersValidationException
162 | */
163 | public function validate(array $params): void
164 | {
165 | if (isset($params['queue']) && !preg_match('/^([a-zA-Z0-9_-]){1,160}$/', (string)$params['queue'])) {
166 | throw new QueueParametersValidationException('Invalid queue name');
167 | }
168 |
169 | if (isset($params['id']) && !preg_match('/^([a-zA-Z0-9:]){32}$/', (string)$params['id'])) {
170 | throw new QueueParametersValidationException('Invalid message id');
171 | }
172 |
173 | if (isset($params['vt']) && ($params['vt'] < 0 || $params['vt'] > self::MAX_DELAY)) {
174 | throw new QueueParametersValidationException('Visibility time must be between 0 and ' . self::MAX_DELAY);
175 | }
176 |
177 | if (isset($params['delay']) && ($params['delay'] < 0 || $params['delay'] > self::MAX_DELAY)) {
178 | throw new QueueParametersValidationException('Delay must be between 0 and ' . self::MAX_DELAY);
179 | }
180 |
181 | if (isset($params['maxsize'])
182 | && $params['maxsize'] !== -1 && ($params['maxsize'] < self::MIN_MESSAGE_SIZE || $params['maxsize'] > self::MAX_PAYLOAD_SIZE)
183 | ) {
184 | $message = "Maximum message size must be between %d and %d";
185 | throw new QueueParametersValidationException(
186 | sprintf(
187 | $message, self::MIN_MESSAGE_SIZE,
188 | self::MAX_PAYLOAD_SIZE
189 | )
190 | );
191 | }
192 | }
193 |
194 | /**
195 | * @return array
196 | */
197 | public function listQueues(): array
198 | {
199 | return $this->predis->smembers("{$this->ns}QUEUES");
200 | }
201 |
202 | /**
203 | * @param string $name
204 | * @throws QueueNotFoundException
205 | */
206 | public function deleteQueue(string $name): void
207 | {
208 | $this->validate(
209 | [
210 | 'queue' => $name,
211 | ]
212 | );
213 |
214 | $key = "{$this->ns}$name";
215 | $this->predis->multi();
216 | $this->predis->del(["$key:Q", $key]);
217 | $this->predis->srem("{$this->ns}QUEUES", $name);
218 | $resp = $this->predis->exec() ?? [];
219 |
220 | if (!$resp[0]) {
221 | throw new QueueNotFoundException('Queue not found.');
222 | }
223 | }
224 |
225 | /**
226 | * @param string $queue
227 | * @param int|null $vt
228 | * @param int|null $delay
229 | * @param int|null $maxSize
230 | * @return QueueAttributes
231 | * @throws QueueParametersValidationException
232 | * @throws QueueNotFoundException
233 | */
234 | public function setQueueAttributes(
235 | string $queue,
236 | int $vt = null,
237 | int $delay = null,
238 | int $maxSize = null
239 | ): QueueAttributes {
240 | $this->validate(
241 | [
242 | 'vt' => $vt,
243 | 'delay' => $delay,
244 | 'maxsize' => $maxSize,
245 | ]
246 | );
247 | $this->getQueue($queue);
248 |
249 | $time = $this->predis->time();
250 | $this->predis->multi();
251 |
252 | $this->predis->hset("{$this->ns}$queue:Q", 'modified', $time[0]);
253 | if ($vt !== null) {
254 | $this->predis->hset("{$this->ns}$queue:Q", 'vt', (string)$vt);
255 | }
256 |
257 | if ($delay !== null) {
258 | $this->predis->hset("{$this->ns}$queue:Q", 'delay', (string)$delay);
259 | }
260 |
261 | if ($maxSize !== null) {
262 | $this->predis->hset("{$this->ns}$queue:Q", 'maxsize', (string)$maxSize);
263 | }
264 |
265 | $this->predis->exec();
266 |
267 | return $this->getQueueAttributes($queue);
268 | }
269 |
270 | /**
271 | * @param string $name
272 | * @param bool $generateUid
273 | * @return array|int[]
274 | * @throws QueueNotFoundException
275 | */
276 | private function getQueue(string $name, bool $generateUid = false): array
277 | {
278 | $this->validate(
279 | [
280 | 'queue' => $name,
281 | ]
282 | );
283 |
284 | /**
285 | * @psalm-suppress UndefinedMagicMethod
286 | */
287 | $transaction = $this->predis->transaction();
288 | $transaction->hmget("{$this->ns}$name:Q", ['vt', 'delay', 'maxsize']);
289 | $transaction->time();
290 | $resp = $transaction->execute();
291 |
292 | if (!isset($resp[0][0])) {
293 | throw new QueueNotFoundException('Queue not found.');
294 | }
295 |
296 | $ms = formatZeroPad((int)$resp[1][1], 6);
297 |
298 |
299 | $queue = [
300 | 'vt' => (int)$resp[0][0],
301 | 'delay' => (int)$resp[0][1],
302 | 'maxsize' => (int)$resp[0][2],
303 | 'ts' => (int)($resp[1][0] . substr($ms, 0, 3)),
304 | ];
305 |
306 | if ($generateUid) {
307 | $queue['uid'] = base_convert(($resp[1][0] . $ms), 10, 36) . makeID(22);
308 | }
309 |
310 | return $queue;
311 | }
312 |
313 | /**
314 | * @param string $queue
315 | * @return QueueAttributes
316 | * @throws QueueNotFoundException
317 | * @throws QueueParametersValidationException
318 | */
319 | public function getQueueAttributes(string $queue): QueueAttributes
320 | {
321 | $this->validate(
322 | [
323 | 'queue' => $queue,
324 | ]
325 | );
326 |
327 | $key = "{$this->ns}$queue";
328 | $resp = $this->predis->time();
329 |
330 | /**
331 | * @psalm-suppress UndefinedMagicMethod
332 | */
333 | $transaction = $this->predis->transaction();
334 | $transaction->hmget("$key:Q", ['vt', 'delay', 'maxsize', 'totalrecv', 'totalsent', 'created', 'modified']);
335 | $transaction->zcard($key);
336 | $transaction->zcount($key, $resp[0] . '0000', "+inf");
337 | $resp = $transaction->execute();
338 |
339 | if (!isset($resp[0][0])) {
340 | throw new QueueNotFoundException('Queue not found.');
341 | }
342 |
343 | return new QueueAttributes(
344 | (int)$resp[0][0],
345 | (int)$resp[0][1],
346 | (int)$resp[0][2],
347 | (int)$resp[0][3],
348 | (int)$resp[0][4],
349 | (int)$resp[0][5],
350 | (int)$resp[0][6],
351 | (int)$resp[1],
352 | (int)$resp[2]
353 | );
354 | }
355 |
356 | /**
357 | * @param string $queue
358 | * @param string $message
359 | * @param int|null $delay
360 | * @return string
361 | * @throws MessageToLongException
362 | * @throws QueueNotFoundException
363 | * @throws QueueParametersValidationException
364 | */
365 | public function sendMessage(string $queue, string $message, int $delay = null): string
366 | {
367 | $this->validate(
368 | [
369 | 'queue' => $queue,
370 | ]
371 | );
372 |
373 | $q = $this->getQueue($queue, true);
374 | if ($delay === null) {
375 | $delay = $q['delay'];
376 | }
377 |
378 | if ($q['maxsize'] !== -1 && mb_strlen($message) > $q['maxsize']) {
379 | throw new MessageToLongException('Message too long');
380 | }
381 |
382 | $key = "{$this->ns}$queue";
383 |
384 | $this->predis->multi();
385 | $this->predis->zadd($key, [$q['uid'] => $q['ts'] + $delay * 1000]);
386 | $this->predis->hset("$key:Q", $q['uid'], $message);
387 | $this->predis->hincrby("$key:Q", 'totalsent', 1);
388 |
389 | if ($this->realtime) {
390 | $this->predis->zcard($key);
391 | }
392 |
393 | $resp = $this->predis->exec() ?? [];
394 |
395 | if ($this->realtime) {
396 | $this->predis->publish("{$this->ns}rt:$$queue", $resp[3]);
397 | }
398 |
399 | return $q['uid'];
400 | }
401 |
402 | /**
403 | * @param string $queue
404 | * @param array $options
405 | * @return Message|null
406 | * @throws QueueNotFoundException
407 | * @throws QueueParametersValidationException
408 | */
409 | public function receiveMessage(string $queue, array $options = []): ?Message
410 | {
411 | $this->validate(
412 | [
413 | 'queue' => $queue,
414 | ]
415 | );
416 |
417 | $q = $this->getQueue($queue);
418 | $vt = $options['vt'] ?? $q['vt'];
419 |
420 | $resp = $this->predis->evalsha(
421 | $this->receiveMessageSha1,
422 | 3,
423 | "{$this->ns}$queue", $q['ts'],
424 | $q['ts'] + $vt * 1000
425 | );
426 | if (empty($resp)) {
427 | return null;
428 | }
429 |
430 | return new Message(
431 | (string)$resp[0],
432 | (string)$resp[1],
433 | (int)$resp[2],
434 | (int)$resp[3],
435 | (float)base_convert(substr($resp[0], 0, 10), 36, 10) / 1000
436 | );
437 | }
438 |
439 | /**
440 | * @param string $queue
441 | * @return Message|null
442 | * @throws QueueNotFoundException
443 | * @throws QueueParametersValidationException
444 | */
445 | public function popMessage(string $queue): ?Message
446 | {
447 | $this->validate(
448 | [
449 | 'queue' => $queue,
450 | ]
451 | );
452 |
453 | $q = $this->getQueue($queue);
454 |
455 | $resp = $this->predis->evalsha($this->popMessageSha1, 2, "{$this->ns}$queue", $q['ts']);
456 | if (empty($resp)) {
457 | return null;
458 | }
459 | return new Message(
460 | (string)$resp[0],
461 | (string)$resp[1],
462 | (int)$resp[2],
463 | (int)$resp[3],
464 | (float)base_convert(substr($resp[0], 0, 10), 36, 10) / 1000
465 | );
466 | }
467 |
468 | /**
469 | * @param string $queue
470 | * @param string $id
471 | * @return bool
472 | * @throws QueueParametersValidationException
473 | */
474 | public function deleteMessage(string $queue, string $id): bool
475 | {
476 | $this->validate(
477 | [
478 | 'queue' => $queue,
479 | 'id' => $id,
480 | ]
481 | );
482 |
483 | $key = "{$this->ns}$queue";
484 | $this->predis->multi();
485 | $this->predis->zrem($key, $id);
486 | $this->predis->hdel("$key:Q", [$id, "$id:rc", "$id:fr"]);
487 | $resp = $this->predis->exec() ?? [];
488 |
489 | return $resp[0] === 1 && $resp[1] > 0;
490 | }
491 |
492 | /**
493 | * @param string $queue
494 | * @param string $id
495 | * @param int $vt
496 | * @return bool
497 | * @throws QueueParametersValidationException
498 | * @throws QueueNotFoundException
499 | */
500 | public function changeMessageVisibility(string $queue, string $id, int $vt): bool
501 | {
502 | $this->validate(
503 | [
504 | 'queue' => $queue,
505 | 'id' => $id,
506 | 'vt' => $vt,
507 | ]
508 | );
509 |
510 | $q = $this->getQueue($queue, true);
511 |
512 | $resp = $this->predis->evalsha(
513 | $this->changeMessageVisibilitySha1,
514 | 3,
515 | "{$this->ns}$queue",
516 | $id,
517 | $q['ts'] + $vt * 1000
518 | );
519 |
520 | return (bool)$resp;
521 | }
522 | }
523 |
--------------------------------------------------------------------------------
/src/RSMQClientInterface.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | interface RSMQClientInterface
19 | {
20 |
21 | /**
22 | * @param string $name
23 | * @param int $vt
24 | * @param int $delay
25 | * @param int $maxSize
26 | * @return bool
27 | * @throws QueueAlreadyExistsException
28 | */
29 | public function createQueue(string $name, int $vt = 30, int $delay = 0, int $maxSize = 65536): bool;
30 |
31 | /**
32 | * @param string $queue
33 | * @param array $options
34 | * @return Message|null
35 | * @throws QueueNotFoundException
36 | * @throws QueueParametersValidationException
37 | */
38 | public function receiveMessage(string $queue, array $options = []): ?Message;
39 |
40 | /**
41 | * @param string $queue
42 | * @param string $id
43 | * @param int $vt
44 | * @return bool
45 | * @throws QueueParametersValidationException
46 | * @throws QueueNotFoundException
47 | */
48 | public function changeMessageVisibility(string $queue, string $id, int $vt): bool;
49 |
50 | /**
51 | * @param string $queue
52 | * @return QueueAttributes
53 | * @throws QueueNotFoundException
54 | * @throws QueueParametersValidationException
55 | */
56 | public function getQueueAttributes(string $queue): QueueAttributes;
57 |
58 | /**
59 | * @param string $queue
60 | * @param int|null $vt
61 | * @param int|null $delay
62 | * @param int|null $maxSize
63 | * @return QueueAttributes
64 | * @throws QueueParametersValidationException
65 | * @throws QueueNotFoundException
66 | */
67 | public function setQueueAttributes(
68 | string $queue,
69 | int $vt = null,
70 | int $delay = null,
71 | int $maxSize = null
72 | ): QueueAttributes;
73 |
74 | /**
75 | * @param string $name
76 | * @throws QueueNotFoundException
77 | */
78 | public function deleteQueue(string $name): void;
79 |
80 | /**
81 | * @param string $queue
82 | * @return Message|null
83 | * @throws QueueNotFoundException
84 | * @throws QueueParametersValidationException
85 | */
86 | public function popMessage(string $queue): ?Message;
87 |
88 | /**
89 | * @param array $params
90 | * @throws QueueParametersValidationException
91 | */
92 | public function validate(array $params): void;
93 |
94 | /**
95 | * @param string $queue
96 | * @param string $id
97 | * @return bool
98 | * @throws QueueParametersValidationException
99 | */
100 | public function deleteMessage(string $queue, string $id): bool;
101 |
102 | /**
103 | * @return array
104 | */
105 | public function listQueues(): array;
106 |
107 | /**
108 | * @param string $queue
109 | * @param string $message
110 | * @param int|null $delay
111 | * @return string
112 | * @throws MessageToLongException
113 | * @throws QueueNotFoundException
114 | * @throws QueueParametersValidationException
115 | */
116 | public function sendMessage(string $queue, string $message, int $delay = null): string;
117 |
118 | }
--------------------------------------------------------------------------------
/src/WorkerSleepProvider.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | interface WorkerSleepProvider
13 | {
14 |
15 | /**
16 | * Return the number of seconds that the worker should sleep for before grabbing the next message.
17 | * Returning null or a value less than zero will cause the worker to exit.
18 | *
19 | * Note: this method is called _before_ the receiveMessage method is called.
20 | *
21 | * @return positive-int|null
22 | */
23 | public function getSleep(): ?int;
24 | }
25 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 | assertSame($size, strlen(makeID($size)));
13 | }
14 |
15 | /**
16 | * @param string $expected
17 | * @param int $num
18 | * @param int $count
19 | * @dataProvider providerFormatZeroPad
20 | */
21 | public function testFormatZeroPad($expected, $num, $count): void
22 | {
23 | $this->assertSame($expected, formatZeroPad($num, $count));
24 | }
25 |
26 | /**
27 | * @return array
28 | */
29 | public function providerFormatZeroPad(): array
30 | {
31 | return [
32 | ['01', 1, 2],
33 | ['001', 1, 3],
34 | ['0001', 1, 4],
35 | ['00001', 1, 5],
36 | ['000001', 1, 6],
37 | ['000451', 451, 6],
38 | ['123456', 123456, 6],
39 | ['0000123456', 123456, 10],
40 | ];
41 | }
42 | }
--------------------------------------------------------------------------------
/tests/QueueWorkerTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class QueueWorkerTest extends TestCase
16 | {
17 |
18 |
19 | public function testEjectFromSleepProvider()
20 | {
21 | $rsmq = Mockery::mock(RSMQClientInterface::class);
22 | $executor = Mockery::mock(ExecutorInterface::class);
23 | $sleepProvider = Mockery::mock(WorkerSleepProvider::class);
24 | $sleepProvider->shouldReceive('getSleep')
25 | ->andReturn(null)
26 | ->once();
27 | $worker = new QueueWorker($rsmq, $executor, $sleepProvider, 'test');
28 |
29 | $worker->work(true);
30 | self::assertEquals(0, $worker->getProcessedCount());
31 | }
32 |
33 | public function testProcessOneAvilableFailed()
34 | {
35 | $message = Mockery::mock(Message::class);
36 | $rsmq = Mockery::mock(RSMQClientInterface::class);
37 | $rsmq->shouldReceive('receiveMessage')
38 | ->with('test')
39 | ->andReturn($message)
40 | ->once();
41 | $executor = Mockery::mock(ExecutorInterface::class);
42 | $executor->shouldReceive('__invoke')
43 | ->with($message)
44 | ->andReturn(false)
45 | ->once();
46 | $sleepProvider = Mockery::mock(WorkerSleepProvider::class);
47 | $sleepProvider->shouldReceive('getSleep')
48 | ->andReturn(0)
49 | ->once();
50 | $worker = new QueueWorker($rsmq, $executor, $sleepProvider, 'test');
51 |
52 | $worker->work(true);
53 | self::assertEquals(1, $worker->getProcessedCount());
54 | self::assertEquals(0, $worker->getSuccessful());
55 | self::assertEquals(1, $worker->getFailed());
56 | }
57 |
58 | public function testProcessOneNotAvilableSuccessful()
59 | {
60 | $message = Mockery::mock(Message::class);
61 | $message->shouldReceive('getId')
62 | ->andReturn('test_id')
63 | ->once();
64 | $rsmq = Mockery::mock(RSMQClientInterface::class);
65 | $rsmq->shouldReceive('receiveMessage')
66 | ->with('test')
67 | ->andReturn(null, $message)
68 | ->twice();
69 | $rsmq->shouldReceive('deleteMessage')
70 | ->with('test', 'test_id')
71 | ->once();
72 | $executor = Mockery::mock(ExecutorInterface::class);
73 | $executor->shouldReceive('__invoke')
74 | ->with($message)
75 | ->andReturn(true)
76 | ->once();
77 | $sleepProvider = Mockery::mock(WorkerSleepProvider::class);
78 | $sleepProvider->shouldReceive('getSleep')
79 | ->andReturn(0)
80 | ->twice();
81 | $worker = new QueueWorker($rsmq, $executor, $sleepProvider, 'test');
82 |
83 | $worker->work(true);
84 | self::assertEquals(1, $worker->getProcessedCount());
85 | self::assertEquals(1, $worker->getSuccessful());
86 | self::assertEquals(0, $worker->getFailed());
87 | }
88 |
89 | public function testProcessThreeAndExit()
90 | {
91 | $message = Mockery::mock(Message::class);
92 | $message->shouldReceive('getId')
93 | ->andReturn('test_id')
94 | ->twice();
95 | $rsmq = Mockery::mock(RSMQClientInterface::class);
96 | $rsmq->shouldReceive('receiveMessage')
97 | ->with('test')
98 | ->andReturn($message, $message, $message)
99 | ->times(3);
100 | $rsmq->shouldReceive('deleteMessage')
101 | ->with('test', 'test_id')
102 | ->twice();
103 | $executor = Mockery::mock(ExecutorInterface::class);
104 | $executor->shouldReceive('__invoke')
105 | ->with($message)
106 | ->andReturn(false, true, true)
107 | ->times(3);
108 | $sleepProvider = Mockery::mock(WorkerSleepProvider::class);
109 | $sleepProvider->shouldReceive('getSleep')
110 | ->andReturn(0, 0, 0, null)
111 | ->times(4);
112 | $worker = new QueueWorker($rsmq, $executor, $sleepProvider, 'test');
113 |
114 | $worker->work();
115 | self::assertEquals(3, $worker->getProcessedCount());
116 | self::assertEquals(2, $worker->getSuccessful());
117 | self::assertEquals(1, $worker->getFailed());
118 | self::assertEquals(3, $worker->getReceived());
119 | }
120 |
121 | public function tearDown(): void
122 | {
123 | Mockery::close();
124 | parent::tearDown();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/tests/RSMQTest.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1',
20 | 'port' => 6379
21 | ]
22 | );
23 | $this->rsmq = new RSMQClient($redis);
24 | }
25 |
26 | public function testScriptsShouldInitialized(): void
27 | {
28 | $reflection = new ReflectionClass($this->rsmq);
29 |
30 | $recvMsgRef = $reflection->getProperty('receiveMessageSha1');
31 | $recvMsgRef->setAccessible(true);
32 |
33 | $this->assertSame(40, strlen($recvMsgRef->getValue($this->rsmq)));
34 |
35 | $popMsgRef = $reflection->getProperty('popMessageSha1');
36 | $popMsgRef->setAccessible(true);
37 |
38 | $this->assertSame(40, strlen($popMsgRef->getValue($this->rsmq)));
39 | }
40 |
41 | public function testCreateQueue(): void
42 | {
43 | $this->assertTrue($this->rsmq->createQueue('foo'));
44 | }
45 |
46 | public function testCreateQueueWithInvalidName(): void
47 | {
48 | $this->expectException(QueueParametersValidationException::class);
49 | $this->expectExceptionMessage('Invalid queue name');
50 | $this->rsmq->createQueue(' sad');
51 | }
52 |
53 | public function testCreateQueueWithBigVt(): void
54 | {
55 | $this->expectException(QueueParametersValidationException::class);
56 | $this->expectExceptionMessage('Visibility time must be between');
57 | $this->rsmq->createQueue('foo', PHP_INT_MAX);
58 | }
59 |
60 | public function testCreateQueueWithNegativeVt(): void
61 | {
62 | $this->expectException(QueueParametersValidationException::class);
63 | $this->expectExceptionMessage('Visibility time must be between');
64 | $this->rsmq->createQueue('foo', -1);
65 | }
66 |
67 | public function testCreateQueueWithBigDelay(): void
68 | {
69 | $this->expectException(QueueParametersValidationException::class);
70 | $this->expectExceptionMessage('Delay must be between');
71 | $this->rsmq->createQueue('foo', 30, PHP_INT_MAX);
72 | }
73 |
74 | public function testCreateQueueWithNegativeDelay(): void
75 | {
76 | $this->expectException(QueueParametersValidationException::class);
77 | $this->expectExceptionMessage('Delay must be between');
78 | $this->rsmq->createQueue('foo', 30, -1);
79 | }
80 |
81 | public function testCreateQueueWithBigMaxSize(): void
82 | {
83 | $this->expectException(QueueParametersValidationException::class);
84 | $this->expectExceptionMessage('Maximum message size must be between');
85 | $this->rsmq->createQueue('foo', 30, 0, PHP_INT_MAX);
86 | }
87 |
88 | public function testCreateQueueWithSmallMaxSize(): void
89 | {
90 | $this->expectException(QueueParametersValidationException::class);
91 | $this->expectExceptionMessage('Maximum message size must be between');
92 | $this->rsmq->createQueue('foo', 30, 0, 1023);
93 | }
94 |
95 | public function testGetQueueAttributes(): void
96 | {
97 | $vt = 40;
98 | $delay = 60;
99 | $maxSize = 1024;
100 | $this->rsmq->createQueue('foo', $vt, $delay, $maxSize);
101 |
102 | $attributes = $this->rsmq->getQueueAttributes('foo');
103 |
104 | $this->assertSame($vt, $attributes->getVt());
105 | $this->assertSame($delay, $attributes->getDelay());
106 | $this->assertSame($maxSize, $attributes->getMaxSize());
107 | $this->assertSame(0, $attributes->getMessageCount());
108 | $this->assertSame(0, $attributes->getHiddenMessageCount());
109 | $this->assertSame(0, $attributes->getTotalReceived());
110 | $this->assertSame(0, $attributes->getTotalSent());
111 | $this->assertNotEmpty($attributes->getCreated());
112 | $this->assertNotEmpty($attributes->getModified());
113 | }
114 |
115 | public function testGetQueueAttributesThatDoesNotExists(): void
116 | {
117 | $this->expectExceptionMessage('Queue not found.');
118 | $this->rsmq->getQueueAttributes('not_existent_queue');
119 | }
120 |
121 | public function testCreateQueueMustThrowExceptionWhenQueueExists(): void
122 | {
123 | $this->expectException(Exception::class);
124 | $this->expectExceptionMessage('Queue already exists.');
125 |
126 | $this->rsmq->createQueue('foo');
127 | $this->rsmq->createQueue('foo');
128 | }
129 |
130 | public function testListQueues(): void
131 | {
132 | $this->assertEmpty($this->rsmq->listQueues());
133 |
134 | $this->rsmq->createQueue('foo');
135 | $this->assertSame(['foo'], $this->rsmq->listQueues());
136 | }
137 |
138 | public function testValidateWithInvalidQueueName(): void
139 | {
140 | $this->expectExceptionMessage('Invalid queue name');
141 | $this->invokeMethod(
142 | $this->rsmq, 'validate', [
143 | ['queue' => ' foo']
144 | ]
145 | );
146 |
147 | }
148 |
149 | /**
150 | * @param object $object
151 | * @param string $methodName
152 | * @param array $parameters
153 | * @return mixed
154 | * @throws ReflectionException
155 | */
156 | public function invokeMethod(object &$object, string $methodName, array $parameters = array())
157 | {
158 | $reflection = new ReflectionClass(get_class($object));
159 | $method = $reflection->getMethod($methodName);
160 | $method->setAccessible(true);
161 |
162 | return $method->invokeArgs($object, $parameters);
163 | }
164 |
165 | public function testValidateWithInvalidVt(): void
166 | {
167 | $this->expectExceptionMessage('Visibility time must be');
168 | $this->invokeMethod(
169 | $this->rsmq, 'validate', [
170 | ['vt' => '-1']
171 | ]
172 | );
173 | }
174 |
175 | public function testValidateWithInvalidId(): void
176 | {
177 | $this->expectExceptionMessage('Invalid message id');
178 | $this->invokeMethod(
179 | $this->rsmq, 'validate', [
180 | ['id' => '123456']
181 | ]
182 | );
183 | }
184 |
185 | public function testValidateWithInvalidDelay(): void
186 | {
187 | $this->expectExceptionMessage('Delay must be');
188 | $this->invokeMethod(
189 | $this->rsmq, 'validate', [
190 | ['delay' => 99999999]
191 | ]
192 | );
193 | }
194 |
195 | public function testValidateWithInvalidMaxSize(): void
196 | {
197 | $this->expectExceptionMessage('Maximum message size must be');
198 | $this->invokeMethod(
199 | $this->rsmq,
200 | 'validate',
201 | [
202 | ['maxsize' => 512]
203 | ]
204 | );
205 | }
206 |
207 | public function testSendMessage(): void
208 | {
209 | $this->rsmq->createQueue('foo');
210 | $id = $this->rsmq->sendMessage('foo', 'foobar');
211 | $this->assertSame(32, strlen($id));
212 | $attributes = $this->rsmq->getQueueAttributes('foo');
213 | $this->assertSame(1, $attributes->getMessageCount());
214 | $this->assertSame(0, $attributes->getHiddenMessageCount());
215 | $this->assertSame(0, $attributes->getTotalReceived());
216 | $this->assertSame(1, $attributes->getTotalSent());
217 | }
218 |
219 | public function testSendMessageRealtime(): void
220 | {
221 | $rsmq = new RSMQClient(new Client(['host' => '127.0.0.1', 'port' => 6379]), 'rsmq', true);
222 | $rsmq->createQueue('foo');
223 | $id = $rsmq->sendMessage('foo', 'foobar');
224 | $this->assertSame(32, strlen($id));
225 | }
226 |
227 | public function testSendMessageWithBigMessage(): void
228 | {
229 | $this->rsmq->createQueue('foo');
230 | $bigStr = str_repeat(bin2hex(random_bytes(512)), 100);
231 |
232 | $this->expectExceptionMessage('Message too long');
233 | $this->rsmq->sendMessage('foo', $bigStr);
234 | }
235 |
236 | public function testDeleteMessage(): void
237 | {
238 | $this->rsmq->createQueue('foo');
239 | $id = $this->rsmq->sendMessage('foo', 'bar');
240 | $this->assertTrue($this->rsmq->deleteMessage('foo', $id));
241 | }
242 |
243 | public function testReceiveMessage(): void
244 | {
245 | $queue = 'foo';
246 | $message = 'Hello World';
247 | $this->rsmq->createQueue($queue);
248 | $id = $this->rsmq->sendMessage($queue, $message);
249 | $received = $this->rsmq->receiveMessage($queue);
250 |
251 | $this->assertSame($message, $received->getMessage());
252 | $this->assertSame($id, $received->getId());
253 | $this->assertNotEmpty($received->getFirstReceived());
254 | $this->assertNotEmpty($received->getSent());
255 | $this->assertSame(1, $received->getReceiveCount());
256 | }
257 |
258 | public function testReceiveMessageWhenNoMessageExists(): void
259 | {
260 | $queue = 'foo';
261 | $this->rsmq->createQueue($queue);
262 | $received = $this->rsmq->receiveMessage($queue);
263 |
264 | $this->assertEmpty($received);
265 | }
266 |
267 | public function testChangeMessageVisibility(): void
268 | {
269 | $queue = 'foo';
270 | $this->rsmq->createQueue($queue);
271 | $id = $this->rsmq->sendMessage($queue, 'bar');
272 | $this->assertTrue($this->rsmq->changeMessageVisibility($queue, $id, 60));
273 |
274 | }
275 |
276 | public function testGetQueue(): void
277 | {
278 | $queueName = 'foo';
279 | $vt = 30;
280 | $delay = 0;
281 | $maxSize = 65536;
282 | $this->rsmq->createQueue($queueName, $vt, $delay, $maxSize);
283 | $queue = $this->invokeMethod($this->rsmq, 'getQueue', [$queueName, true]);
284 |
285 | $this->assertSame($vt, $queue['vt']);
286 | $this->assertSame($delay, $queue['delay']);
287 | $this->assertSame($maxSize, $queue['maxsize']);
288 | $this->assertArrayHasKey('uid', $queue);
289 | $this->assertSame(32, strlen($queue['uid']));
290 | }
291 |
292 | public function testGetQueueNotFound(): void
293 | {
294 | $this->expectExceptionMessage('Queue not found');
295 | $this->invokeMethod($this->rsmq, 'getQueue', ['notfound']);
296 | }
297 |
298 | public function testPopMessage(): void
299 | {
300 | $queue = 'foo';
301 | $message = 'bar';
302 | $this->rsmq->createQueue($queue);
303 |
304 | $id = $this->rsmq->sendMessage($queue, $message);
305 | $received = $this->rsmq->popMessage($queue);
306 |
307 | $this->assertSame($id, $received->getId());
308 | $this->assertSame($message, $received->getMessage());
309 | }
310 |
311 | public function testPopMessageWhenNoMessageExists(): void
312 | {
313 | $queue = 'foo';
314 | $this->rsmq->createQueue($queue);
315 |
316 | $received = $this->rsmq->popMessage($queue);
317 |
318 | $this->assertEmpty($received);
319 |
320 | }
321 |
322 | public function testSetQueueAttributes(): void
323 | {
324 | $queue = 'foo';
325 | $vt = 100;
326 | $delay = 10;
327 | $maxsize = 2048;
328 | $this->rsmq->createQueue($queue);
329 | $attrs = $this->rsmq->setQueueAttributes($queue, $vt, $delay, $maxsize);
330 |
331 | $this->assertSame($vt, $attrs->getVt());
332 | $this->assertSame($delay, $attrs->getDelay());
333 | $this->assertSame($maxsize, $attrs->getMaxSize());
334 | }
335 |
336 | public function tearDown(): void
337 | {
338 | try {
339 | $this->rsmq->deleteQueue('foo');
340 | } catch (Exception $_) {
341 |
342 | }
343 |
344 | }
345 | }
--------------------------------------------------------------------------------