@ -1,2 +1,6 @@ |
|||
# thoughts |
|||
micro blogging platform (server api + client side) fullstack |
|||
|
|||
MEAN fullstack |
|||
backend: nodejs + express + mongodb |
|||
frontend: javascript + angular + bootstrap |
@ -0,0 +1,6 @@ |
|||
module.exports = { |
|||
|
|||
'secret': 'secretfortoken', |
|||
'database': 'mongodb://localhost/thoughts' |
|||
|
|||
}; |
@ -0,0 +1,22 @@ |
|||
{ |
|||
"evil": true, |
|||
"regexdash": true, |
|||
"browser": true, |
|||
"wsh": true, |
|||
"trailing": true, |
|||
"sub": true, |
|||
"unused": true, |
|||
"undef": true, |
|||
"laxcomma": true, |
|||
"node": true, |
|||
"browser": false, |
|||
"esnext": true, |
|||
"globals": { |
|||
"describe": true, |
|||
"it": true, |
|||
"require": true, |
|||
"atob": false, |
|||
"escape": true, |
|||
"before": true |
|||
} |
|||
} |
@ -0,0 +1,2 @@ |
|||
node_modules |
|||
.DS_Store |
@ -0,0 +1,6 @@ |
|||
language: node_js |
|||
before_install: npm i -g npm@1.4.28 |
|||
node_js: |
|||
- "5" |
|||
- "4" |
|||
- "0.12" |
@ -0,0 +1,266 @@ |
|||
# Change Log |
|||
|
|||
|
|||
All notable changes to this project will be documented in this file starting from version **v4.0.0**. |
|||
This project adheres to [Semantic Versioning](http://semver.org/). |
|||
|
|||
## 7.1.2 - 2016-07-12 |
|||
|
|||
- do not stringify the payload when signing async - closes #224 ([084f537d3dfbcef2bea411cc0a1515899cc8aa21](https://github.com/auth0/node-jsonwebtoken/commit/084f537d3dfbcef2bea411cc0a1515899cc8aa21)), closes [#224](https://github.com/auth0/node-jsonwebtoken/issues/224) |
|||
|
|||
## 7.1.1 - 2016-07-12 |
|||
|
|||
- do not mutate options in jwt.verify, closes #227 ([63263a28a268624dab0927b9ad86fffa44a10f84](https://github.com/auth0/node-jsonwebtoken/commit/63263a28a268624dab0927b9ad86fffa44a10f84)), closes [#227](https://github.com/auth0/node-jsonwebtoken/issues/227) |
|||
- refactor into multiple files ([e11d505207fa33501298300c9accbfb809d8748d](https://github.com/auth0/node-jsonwebtoken/commit/e11d505207fa33501298300c9accbfb809d8748d)) |
|||
|
|||
## 7.1.0 - 2016-07-12 |
|||
|
|||
- Exp calculated based on iat. fix #217 ([757a16e0e35ad19f9e456820f55d5d9f3fc76aee](https://github.com/auth0/node-jsonwebtoken/commit/757a16e0e35ad19f9e456820f55d5d9f3fc76aee)), closes [#217](https://github.com/auth0/node-jsonwebtoken/issues/217) |
|||
|
|||
## 7.0.0 - 2016-05-19 |
|||
|
|||
- change jwt.sign to return errors on callback instead of throwing errors ([1e46c5a42aa3dab8478efa4081d8f8f5c5485d56](https://github.com/auth0/node-jsonwebtoken/commit/1e46c5a42aa3dab8478efa4081d8f8f5c5485d56)) |
|||
|
|||
## 6.2.0 - 2016-04-29 |
|||
|
|||
- add support for `options.clockTolerance` to `jwt.verify` ([65ddea934f226bf06bc9d6a55be9587515cfc38d](https://github.com/auth0/node-jsonwebtoken/commit/65ddea934f226bf06bc9d6a55be9587515cfc38d)) |
|||
|
|||
## 6.1.2 - 2016-04-29 |
|||
|
|||
- fix sign method for node.js 0.12. closes #193 ([9c38374142d3929be3c9314b5e9bc5d963c5955f](https://github.com/auth0/node-jsonwebtoken/commit/9c38374142d3929be3c9314b5e9bc5d963c5955f)), closes [#193](https://github.com/auth0/node-jsonwebtoken/issues/193) |
|||
- improve async test ([7b0981380ddc40a5f1208df520631785b5ffb85a](https://github.com/auth0/node-jsonwebtoken/commit/7b0981380ddc40a5f1208df520631785b5ffb85a)) |
|||
|
|||
## 6.1.0 - 2016-04-27 |
|||
|
|||
- verify unsigned tokens ([ec880791c10ed5ef7c8df7bf28ebb95c810479ed](https://github.com/auth0/node-jsonwebtoken/commit/ec880791c10ed5ef7c8df7bf28ebb95c810479ed)) |
|||
|
|||
## 6.0.1 - 2016-04-27 |
|||
|
|||
This was an immediate change after publishing 6.0.0. |
|||
|
|||
- throw error on invalid options when the payload is not an object ([304f1b33075f79ed66f784e27dc4f5307aa39e27](https://github.com/auth0/node-jsonwebtoken/commit/304f1b33075f79ed66f784e27dc4f5307aa39e27)) |
|||
|
|||
## 6.0.0 - 2016-04-27 |
|||
|
|||
- Change .sign to standard async callback ([50873c7d45d2733244d5da8afef3d1872e657a60](https://github.com/auth0/node-jsonwebtoken/commit/50873c7d45d2733244d5da8afef3d1872e657a60)) |
|||
- Improved the options for the `sign` method ([53c3987b3cc34e95eb396b26fc9b051276e2f6f9](https://github.com/auth0/node-jsonwebtoken/commit/53c3987b3cc34e95eb396b26fc9b051276e2f6f9)) |
|||
|
|||
- throw error on invalid options like `expiresIn` when the payload is not an object ([304f1b33075f79ed66f784e27dc4f5307aa39e27](https://github.com/auth0/node-jsonwebtoken/commit/304f1b33075f79ed66f784e27dc4f5307aa39e27)) |
|||
- `expiresInMinutes` and `expiresInSeconds` are deprecated and no longer supported. |
|||
- `notBeforeInMinutes` and `notBeforeInSeconds` are deprecated and no longer supported. |
|||
- `options` are strongly validated. |
|||
- `options.expiresIn`, `options.notBefore`, `options.audience`, `options.issuer`, `options.subject` and `options.jwtid` are mutually exclusive with `payload.exp`, `payload.nbf`, `payload.aud`, `payload.iss` |
|||
- `options.algorithm` is properly validated. |
|||
- `options.headers` is renamed to `options.header`. |
|||
|
|||
- update CHANGELOG to reflect most of the changes. closes #136 ([b87a1a8d2e2533fbfab518765a54f00077918eb7](https://github.com/auth0/node-jsonwebtoken/commit/b87a1a8d2e2533fbfab518765a54f00077918eb7)), closes [#136](https://github.com/auth0/node-jsonwebtoken/issues/136) |
|||
- update readme ([53a88ecf4494e30e1d62a1cf3cc354650349f486](https://github.com/auth0/node-jsonwebtoken/commit/53a88ecf4494e30e1d62a1cf3cc354650349f486)) |
|||
|
|||
## 5.7.0 - 2016-02-16 |
|||
|
|||
|
|||
- add support for validating multiples issuers. closes #163 ([39d9309ae05648dbd72e5fd1993df064ad0e8fa5](https://github.com/auth0/node-jsonwebtoken/commit/39d9309ae05648dbd72e5fd1993df064ad0e8fa5)), closes [#163](https://github.com/auth0/node-jsonwebtoken/issues/163) |
|||
|
|||
|
|||
## 5.6.1 - 2016-02-16 |
|||
|
|||
|
|||
- 5.6.1 ([06d8209d499dbc9a8dd978ab6cbb9c6818fde203](https://github.com/auth0/node-jsonwebtoken/commit/06d8209d499dbc9a8dd978ab6cbb9c6818fde203)) |
|||
- fix wrong error when setting expiration on non-object payload. closes #153 ([7f7d76edfd918d6afc7c7cead888caa42ccaceb4](https://github.com/auth0/node-jsonwebtoken/commit/7f7d76edfd918d6afc7c7cead888caa42ccaceb4)), closes [#153](https://github.com/auth0/node-jsonwebtoken/issues/153) |
|||
|
|||
|
|||
|
|||
## 5.6.0 - 2016-02-16 |
|||
|
|||
|
|||
- added missing validations of sub and jti ([a1affe960d0fc52e9042bcbdedb65734f8855580](https://github.com/auth0/node-jsonwebtoken/commit/a1affe960d0fc52e9042bcbdedb65734f8855580)) |
|||
- Fix tests in jwt.rs.tests.js which causes 4 to fail ([8aedf2b1f575b0d9575c1fc9f2ac7bc868f75ff1](https://github.com/auth0/node-jsonwebtoken/commit/8aedf2b1f575b0d9575c1fc9f2ac7bc868f75ff1)) |
|||
- Update README.md ([349b7cd00229789b138928ca060d3ef015aedaf9](https://github.com/auth0/node-jsonwebtoken/commit/349b7cd00229789b138928ca060d3ef015aedaf9)) |
|||
|
|||
|
|||
|
|||
## 5.5.4 - 2016-01-04 |
|||
|
|||
|
|||
- minor ([46552e7c45025c76e3f647680d7539a66bfac612](https://github.com/auth0/node-jsonwebtoken/commit/46552e7c45025c76e3f647680d7539a66bfac612)) |
|||
|
|||
|
|||
|
|||
## 5.5.3 - 2016-01-04 |
|||
|
|||
|
|||
- add a console.warn on invalid options for string payloads ([71200f14deba0533d3261266348338fac2d14661](https://github.com/auth0/node-jsonwebtoken/commit/71200f14deba0533d3261266348338fac2d14661)) |
|||
- minor ([65b1f580382dc58dd3da6f47a52713776fd7cdf2](https://github.com/auth0/node-jsonwebtoken/commit/65b1f580382dc58dd3da6f47a52713776fd7cdf2)) |
|||
|
|||
|
|||
|
|||
## 5.5.2 - 2016-01-04 |
|||
|
|||
|
|||
- fix signing method with sealed objects, do not modify the params object. closes #147 ([be9c09af83b09c9e72da8b2c6166fa51d92aeab6](https://github.com/auth0/node-jsonwebtoken/commit/be9c09af83b09c9e72da8b2c6166fa51d92aeab6)), closes [#147](https://github.com/auth0/node-jsonwebtoken/issues/147) |
|||
|
|||
|
|||
|
|||
## 5.5.1 - 2016-01-04 |
|||
|
|||
|
|||
- fix nbf verification. fix #152 ([786d37b299c67771b5e71a2ca476666ab0f97d98](https://github.com/auth0/node-jsonwebtoken/commit/786d37b299c67771b5e71a2ca476666ab0f97d98)), closes [#152](https://github.com/auth0/node-jsonwebtoken/issues/152) |
|||
|
|||
|
|||
|
|||
## 5.5.0 - 2015-12-28 |
|||
|
|||
|
|||
- improvements to nbf and jti claims ([46372e928f6d2e7398f9b88022ca617d2a3b0699](https://github.com/auth0/node-jsonwebtoken/commit/46372e928f6d2e7398f9b88022ca617d2a3b0699)) |
|||
- Remove duplicate payload line (fix bug in IE strict mode) ([8163d698e0c5ad8c44817a5dcd42a15d7e9c6bc8](https://github.com/auth0/node-jsonwebtoken/commit/8163d698e0c5ad8c44817a5dcd42a15d7e9c6bc8)) |
|||
- Remove duplicate require('ms') line ([7c00bcbcbf8f7503a1070b394a165eccd41de66f](https://github.com/auth0/node-jsonwebtoken/commit/7c00bcbcbf8f7503a1070b394a165eccd41de66f)) |
|||
- Update README to reflect addition of async sign ([d661d4b6f68eb417834c99b36769444723041ccf](https://github.com/auth0/node-jsonwebtoken/commit/d661d4b6f68eb417834c99b36769444723041ccf)) |
|||
|
|||
|
|||
|
|||
## 5.4.0 - 2015-10-02 |
|||
|
|||
|
|||
- deprecate expireInMinutes and expireInSeconds - in favor of expiresIn ([39ecc6f8f310f8462e082f1d53de0b4222b29b6f](https://github.com/auth0/node-jsonwebtoken/commit/39ecc6f8f310f8462e082f1d53de0b4222b29b6f)) |
|||
|
|||
|
|||
## 5.3.0 - 2015-10-02 |
|||
|
|||
|
|||
- 5.3.0 ([5d559ced3fbf10c1adae2e5792deda06ea89bcd3](https://github.com/auth0/node-jsonwebtoken/commit/5d559ced3fbf10c1adae2e5792deda06ea89bcd3)) |
|||
- minor ([6e81ff87a3799b0e56db09cbae42a97e784716c4](https://github.com/auth0/node-jsonwebtoken/commit/6e81ff87a3799b0e56db09cbae42a97e784716c4)) |
|||
|
|||
|
|||
|
|||
## 5.1.0 - 2015-10-02 |
|||
|
|||
|
|||
- added async signing ([9414fbcb15a1f9cf4fe147d070e9424c547dabba](https://github.com/auth0/node-jsonwebtoken/commit/9414fbcb15a1f9cf4fe147d070e9424c547dabba)) |
|||
- Update README.md ([40b2aaaa843442dfb8ee7b574f0a788177e7c904](https://github.com/auth0/node-jsonwebtoken/commit/40b2aaaa843442dfb8ee7b574f0a788177e7c904)) |
|||
|
|||
|
|||
|
|||
## 5.0.5 - 2015-08-19 |
|||
|
|||
|
|||
- add ms dep to package.json ([f13b3fb7f29dff787e7c91ebe2eb5adeeb05f251](https://github.com/auth0/node-jsonwebtoken/commit/f13b3fb7f29dff787e7c91ebe2eb5adeeb05f251)) |
|||
- add note to explain, related to #96 #101 #6 ([dd8969e0e6ed0bcb9cae905d2b1a96476bd85da3](https://github.com/auth0/node-jsonwebtoken/commit/dd8969e0e6ed0bcb9cae905d2b1a96476bd85da3)) |
|||
- add tests for options.headers ([7787dd74e705787c39a871ca29c75a2e0a3948ac](https://github.com/auth0/node-jsonwebtoken/commit/7787dd74e705787c39a871ca29c75a2e0a3948ac)) |
|||
- add tests for verify expires ([d7c5793d98c300603440ab460c11665f661ad3a0](https://github.com/auth0/node-jsonwebtoken/commit/d7c5793d98c300603440ab460c11665f661ad3a0)) |
|||
- add verify option maxAge (with tests) ([49d54e54f7e70b1c53a2e4ee67e116c907d75319](https://github.com/auth0/node-jsonwebtoken/commit/49d54e54f7e70b1c53a2e4ee67e116c907d75319)) |
|||
- fix spelling error in error message ([8078b11b224fa05ac9003ca5aa2c85e9f0128cfb](https://github.com/auth0/node-jsonwebtoken/commit/8078b11b224fa05ac9003ca5aa2c85e9f0128cfb)) |
|||
- Fix typo options.header is not a documented option + ([5feaa5b962ccbddeff054817a410f7b0c1e6ce7f](https://github.com/auth0/node-jsonwebtoken/commit/5feaa5b962ccbddeff054817a410f7b0c1e6ce7f)) |
|||
- update JWT spec link. closes #112 ([f5fa50f797456a12240589161835c7ea30807195](https://github.com/auth0/node-jsonwebtoken/commit/f5fa50f797456a12240589161835c7ea30807195)), closes [#112](https://github.com/auth0/node-jsonwebtoken/issues/112) |
|||
|
|||
|
|||
## 5.0.3 - 2015-07-15 |
|||
|
|||
- Added nbf support ([f26ba4e2fa197a20497632b63ffcd13ae93aacc4](https://github.com/auth0/node-jsonwebtoken/commit/f26ba4e2fa197a20497632b63ffcd13ae93aacc4)) |
|||
- Added support for subject and jwt id ([ab76ec5bc554e2d1e25376ddb7cea711d86af651](https://github.com/auth0/node-jsonwebtoken/commit/ab76ec5bc554e2d1e25376ddb7cea711d86af651)) |
|||
- Fix `this` referring to the global object instead of `module.exports` in `verify()` ([93f554312e37129027fcf4916f48cb8d1b53588c](https://github.com/auth0/node-jsonwebtoken/commit/93f554312e37129027fcf4916f48cb8d1b53588c)) |
|||
- Fix typo, line 139 README, complete option for .decode. ([59c110aeb8c7c1847ef2ffd77702d13627c89e10](https://github.com/auth0/node-jsonwebtoken/commit/59c110aeb8c7c1847ef2ffd77702d13627c89e10)) |
|||
- minor ([61ff1172272b582902313e958058ff22413494af](https://github.com/auth0/node-jsonwebtoken/commit/61ff1172272b582902313e958058ff22413494af)) |
|||
|
|||
|
|||
|
|||
## 5.0.2 - 2015-06-15 |
|||
|
|||
|
|||
- fix typo in docs . closes #86 ([3d3413221f36acef4dfd1cbed87f1f3565cd6f84](https://github.com/auth0/node-jsonwebtoken/commit/3d3413221f36acef4dfd1cbed87f1f3565cd6f84)), closes [#86](https://github.com/auth0/node-jsonwebtoken/issues/86) |
|||
|
|||
|
|||
|
|||
## 5.0.1 - 2015-05-15 |
|||
|
|||
|
|||
- Add option to return header and payload when decoding. ([7254e011b59f892d1947e6c11819281adac7069d](https://github.com/auth0/node-jsonwebtoken/commit/7254e011b59f892d1947e6c11819281adac7069d)) |
|||
- Avoid uncaught "SyntaxError: Unexpected token ͧ" error. ([0dc59cd6ee15d83a606acffa7909ee76176ae186](https://github.com/auth0/node-jsonwebtoken/commit/0dc59cd6ee15d83a606acffa7909ee76176ae186)) |
|||
- Document complete option in README. ([ec32b20241a74d9681ea26e1a7024b4642468c00](https://github.com/auth0/node-jsonwebtoken/commit/ec32b20241a74d9681ea26e1a7024b4642468c00)) |
|||
- Fix example in README, silence verbose logging. ([ba3174d10033c41e9c211a38f1cc67f74fbd7f69](https://github.com/auth0/node-jsonwebtoken/commit/ba3174d10033c41e9c211a38f1cc67f74fbd7f69)) |
|||
- Fix link to auth0.com in README ([1b3c5ff72c9bc25e9271646e679f3080f2a042a0](https://github.com/auth0/node-jsonwebtoken/commit/1b3c5ff72c9bc25e9271646e679f3080f2a042a0)) |
|||
- Immediate return if not decoded. ([851bda2b10168f3269c3da6e74d310742f31a193](https://github.com/auth0/node-jsonwebtoken/commit/851bda2b10168f3269c3da6e74d310742f31a193)) |
|||
- Prevent throw on undefined/null secret ([0fdf78d4dbf609455f3277d6169a987aef0384d4](https://github.com/auth0/node-jsonwebtoken/commit/0fdf78d4dbf609455f3277d6169a987aef0384d4)) |
|||
- Removed path from test ([d6240e24186732d368bffe21143becf44c38f0d6](https://github.com/auth0/node-jsonwebtoken/commit/d6240e24186732d368bffe21143becf44c38f0d6)) |
|||
- Simplified checking for missing key ([f1cffd033bffc44f20558eda4a797c3fa2f4ee05](https://github.com/auth0/node-jsonwebtoken/commit/f1cffd033bffc44f20558eda4a797c3fa2f4ee05)) |
|||
- Typo ([ffe68dbe0219bab535c1018448eb4c0b22f1f902](https://github.com/auth0/node-jsonwebtoken/commit/ffe68dbe0219bab535c1018448eb4c0b22f1f902)) |
|||
- Update CHANGELOG.md ([927cce0dad1bc9aad75aeef53e276cf4cfc0d776](https://github.com/auth0/node-jsonwebtoken/commit/927cce0dad1bc9aad75aeef53e276cf4cfc0d776)) |
|||
- Update CHANGELOG.md ([6879e0fdde222995c70a3a69a4af94993d9c667e](https://github.com/auth0/node-jsonwebtoken/commit/6879e0fdde222995c70a3a69a4af94993d9c667e)) |
|||
- Update CHANGELOG.md ([c5596c10e8705727fa13e0394184a606083078bc](https://github.com/auth0/node-jsonwebtoken/commit/c5596c10e8705727fa13e0394184a606083078bc)) |
|||
- Update CHANGELOG.md ([07541f0315f26d179e1cde92732b6124d6869b6f](https://github.com/auth0/node-jsonwebtoken/commit/07541f0315f26d179e1cde92732b6124d6869b6f)) |
|||
- Update CHANGELOG.md ([e6465d48ddd1dc2c3297229b28c78fd5490a2ba9](https://github.com/auth0/node-jsonwebtoken/commit/e6465d48ddd1dc2c3297229b28c78fd5490a2ba9)) |
|||
|
|||
## [5.0.0] - 2015-04-11 |
|||
|
|||
### Changed |
|||
|
|||
- [sign] Only set defautl `iat` if the user does not specify that argument. |
|||
|
|||
https://github.com/auth0/node-jsonwebtoken/commit/e900282a8d2dff1d4dec815f7e6aa7782e867d91 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/35036b188b4ee6b42df553bbb93bc8a6b19eae9d |
|||
https://github.com/auth0/node-jsonwebtoken/commit/954bd7a312934f03036b6bb6f00edd41f29e54d9 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/24a370080e0b75f11d4717cd2b11b2949d95fc2e |
|||
https://github.com/auth0/node-jsonwebtoken/commit/a77df6d49d4ec688dfd0a1cc723586bffe753516 |
|||
|
|||
### Security |
|||
|
|||
- [verify] Update to jws@^3.0.0 and renaming `header.alg` mismatch exception to `invalid algorithm` and adding more mismatch tests. |
|||
|
|||
As `jws@3.0.0` changed the verify method signature to be `jws.verify(signature, algorithm, secretOrKey)`, the token header must be decoded first in order to make sure that the `alg` field matches one of the allowed `options.algorithms`. After that, the now validated `header.alg` is passed to `jws.verify` |
|||
|
|||
As the order of steps has changed, the error that was thrown when the JWT was invalid is no longer the `jws` one: |
|||
``` |
|||
{ [Error: Invalid token: no header in signature 'a.b.c'] code: 'MISSING_HEADER', signature: 'a.b.c' } |
|||
``` |
|||
|
|||
That old error (removed from jws) has been replaced by a `JsonWebTokenError` with message `invalid token`. |
|||
|
|||
> Important: versions >= 4.2.2 this library are safe to use but we decided to deprecate everything `< 5.0.0` to prevent security warnings from library `node-jws` when doing `npm install`. |
|||
|
|||
https://github.com/auth0/node-jsonwebtoken/commit/634b8ed0ff5267dc25da5c808634208af109824e |
|||
https://github.com/auth0/node-jsonwebtoken/commit/9f24ffd5791febb449d4d03ff58d7807da9b9b7e |
|||
https://github.com/auth0/node-jsonwebtoken/commit/19e6cc6a1f2fd90356f89b074223b9665f2aa8a2 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/1e4623420159c6410616f02a44ed240f176287a9 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/954bd7a312934f03036b6bb6f00edd41f29e54d9 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/24a370080e0b75f11d4717cd2b11b2949d95fc2e |
|||
https://github.com/auth0/node-jsonwebtoken/commit/a77df6d49d4ec688dfd0a1cc723586bffe753516 |
|||
|
|||
## [4.2.2] - 2015-03-26 |
|||
### Fixed |
|||
|
|||
- [asymmetric-keys] Fix verify for RSAPublicKey formated keys (`jfromaniello - awlayton`) |
|||
https://github.com/auth0/node-jsonwebtoken/commit/402794663b9521bf602fcc6f2e811e7d3912f9dc |
|||
https://github.com/auth0/node-jsonwebtoken/commit/8df6aabbc7e1114c8fb3917931078254eb52c222 |
|||
|
|||
## [4.2.1] - 2015-03-17 |
|||
### Fixed |
|||
|
|||
- [asymmetric-keys] Fixed issue when public key starts with BEING PUBLIC KEY (https://github.com/auth0/node-jsonwebtoken/issues/70) (`jfromaniello`) |
|||
https://github.com/auth0/node-jsonwebtoken/commit/7017e74db9b194448ff488b3e16468ada60c4ee5 |
|||
|
|||
## [4.2.0] - 2015-03-16 |
|||
### Security |
|||
|
|||
- [asymmetric-keys] Making sure a token signed with an asymmetric key will be verified using a asymmetric key. |
|||
When the verification part was expecting a token digitally signed with an asymmetric key (RS/ES family) of algorithms an attacker could send a token signed with a symmetric algorithm (HS* family). |
|||
|
|||
The issue was caused because the same signature was used to verify both type of tokens (`verify` method parameter: `secretOrPublicKey`). |
|||
|
|||
This change adds a new parameter to the verify called `algorithms`. This can be used to specify a list of supported algorithms, but the default value depends on the secret used: if the secretOrPublicKey contains the string `BEGIN CERTIFICATE` the default is `[ 'RS256','RS384','RS512','ES256','ES384','ES512' ]` otherwise is `[ 'HS256','HS384','HS512' ]`. (`jfromaniello`) |
|||
https://github.com/auth0/node-jsonwebtoken/commit/c2bf7b2cd7e8daf66298c2d168a008690bc4bdd3 |
|||
https://github.com/auth0/node-jsonwebtoken/commit/1bb584bc382295eeb7ee8c4452a673a77a68b687 |
|||
|
|||
## [4.1.0] - 2015-03-10 |
|||
### Changed |
|||
- Assume the payload is JSON even when there is no `typ` property. [5290db1](https://github.com/auth0/node-jsonwebtoken/commit/5290db1bd74f74cd38c90b19e2355ef223a4d931) |
|||
|
|||
## [4.0.0] - 2015-03-06 |
|||
### Changed |
|||
- The default encoding is now utf8 instead of binary. [92d33bd](https://github.com/auth0/node-jsonwebtoken/commit/92d33bd99a3416e9e5a8897d9ad8ff7d70a00bfd) |
|||
- Add `encoding` as a new option to `sign`. [1fc385e](https://github.com/auth0/node-jsonwebtoken/commit/1fc385ee10bd0018cd1441552dce6c2e5a16375f) |
|||
- Add `ignoreExpiration` to `verify`. [8d4da27](https://github.com/auth0/node-jsonwebtoken/commit/8d4da279e1b351ac71ace276285c9255186d549f) |
|||
- Add `expiresInSeconds` to `sign`. [dd156cc](https://github.com/auth0/node-jsonwebtoken/commit/dd156cc30f17028744e60aec0502897e34609329) |
|||
|
|||
### Fixed |
|||
- Fix wrong error message when the audience doesn't match. [44e3c8d](https://github.com/auth0/node-jsonwebtoken/commit/44e3c8d757e6b4e2a57a69a035f26b4abec3e327) |
|||
- Fix wrong error message when the issuer doesn't match. [44e3c8d](https://github.com/auth0/node-jsonwebtoken/commit/44e3c8d757e6b4e2a57a69a035f26b4abec3e327) |
|||
- Fix wrong `iat` and `exp` values when signing with `noTimestamp`. [331b7bc](https://github.com/auth0/node-jsonwebtoken/commit/331b7bc9cc335561f8806f2c4558e105cb53e0a6) |
@ -0,0 +1,21 @@ |
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2015 Auth0, Inc. <support@auth0.com> (http://auth0.com) |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,260 @@ |
|||
# jsonwebtoken [![Build Status](https://secure.travis-ci.org/auth0/node-jsonwebtoken.svg?branch=master)](http://travis-ci.org/auth0/node-jsonwebtoken)[![Dependency Status](https://david-dm.org/auth0/node-jsonwebtoken.svg)](https://david-dm.org/auth0/node-jsonwebtoken) |
|||
|
|||
|
|||
An implementation of [JSON Web Tokens](https://tools.ietf.org/html/rfc7519). |
|||
|
|||
This was developed against `draft-ietf-oauth-json-web-token-08`. It makes use of [node-jws](https://github.com/brianloveswords/node-jws) |
|||
|
|||
# Install |
|||
|
|||
```bash |
|||
$ npm install jsonwebtoken |
|||
``` |
|||
|
|||
# Usage |
|||
|
|||
### jwt.sign(payload, secretOrPrivateKey, options, [callback]) |
|||
|
|||
(Asynchronous) If a callback is supplied, callback is called with the `err` or the JWT. |
|||
|
|||
(Synchronous) Returns the JsonWebToken as string |
|||
|
|||
`payload` could be an object literal, buffer or string. *Please note that* `exp` is only set if the payload is an object literal. |
|||
|
|||
`secretOrPrivateKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM |
|||
encoded private key for RSA and ECDSA. |
|||
|
|||
`options`: |
|||
|
|||
* `algorithm` (default: `HS256`) |
|||
* `expiresIn`: expressed in seconds or a string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"` |
|||
* `notBefore`: expressed in seconds or a string describing a time span [rauchg/ms](https://github.com/rauchg/ms.js). Eg: `60`, `"2 days"`, `"10h"`, `"7d"` |
|||
* `audience` |
|||
* `issuer` |
|||
* `jwtid` |
|||
* `subject` |
|||
* `noTimestamp` |
|||
* `header` |
|||
|
|||
If `payload` is not a buffer or a string, it will be coerced into a string using `JSON.stringify`. |
|||
|
|||
There are no default values for `expiresIn`, `notBefore`, `audience`, `subject`, `issuer`. These claims can also be provided in the payload directly with `exp`, `nbf`, `aud` and `sub` respectively, but you can't include in both places. |
|||
|
|||
|
|||
The header can be customized via the `option.header` object. |
|||
|
|||
Generated jwts will include an `iat` (issued at) claim by default unless `noTimestamp` is specified. If `iat` is inserted in the payload, it will be used instead of the real timestamp for calculating other things like `exp` given a timespan in `options.expiresIn`. |
|||
|
|||
Example |
|||
|
|||
```js |
|||
// sign with default (HMAC SHA256) |
|||
var jwt = require('jsonwebtoken'); |
|||
var token = jwt.sign({ foo: 'bar' }, 'shhhhh'); |
|||
//backdate a jwt 30 seconds |
|||
var older_token = jwt.sign({ foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh'); |
|||
|
|||
// sign with RSA SHA256 |
|||
var cert = fs.readFileSync('private.key'); // get private key |
|||
var token = jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256'}); |
|||
|
|||
// sign asynchronously |
|||
jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256' }, function(err, token) { |
|||
console.log(token); |
|||
}); |
|||
``` |
|||
|
|||
### jwt.verify(token, secretOrPublicKey, [options, callback]) |
|||
|
|||
(Asynchronous) If a callback is supplied, function acts asynchronously. Callback passed the payload decoded if the signature (and optionally expiration, audience, issuer) are valid. If not, it will be passed the error. |
|||
|
|||
(Synchronous) If a callback is not supplied, function acts synchronously. Returns the payload decoded if the signature (and optionally expiration, audience, issuer) are valid. If not, it will throw the error. |
|||
|
|||
`token` is the JsonWebToken string |
|||
|
|||
`secretOrPublicKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM |
|||
encoded public key for RSA and ECDSA. |
|||
|
|||
`options` |
|||
|
|||
* `algorithms`: List of strings with the names of the allowed algorithms. For instance, `["HS256", "HS384"]`. |
|||
* `audience`: if you want to check audience (`aud`), provide a value here |
|||
* `issuer` (optional): string or array of strings of valid values for the `iss` field. |
|||
* `ignoreExpiration`: if `true` do not validate the expiration of the token. |
|||
* `ignoreNotBefore`... |
|||
* `subject`: if you want to check subject (`sub`), provide a value here |
|||
* `clockTolerance`: number of second to tolerate when checking the `nbf` and `exp` claims, to deal with small clock differences among different servers |
|||
|
|||
|
|||
```js |
|||
// verify a token symmetric - synchronous |
|||
var decoded = jwt.verify(token, 'shhhhh'); |
|||
console.log(decoded.foo) // bar |
|||
|
|||
// verify a token symmetric |
|||
jwt.verify(token, 'shhhhh', function(err, decoded) { |
|||
console.log(decoded.foo) // bar |
|||
}); |
|||
|
|||
// invalid token - synchronous |
|||
try { |
|||
var decoded = jwt.verify(token, 'wrong-secret'); |
|||
} catch(err) { |
|||
// err |
|||
} |
|||
|
|||
// invalid token |
|||
jwt.verify(token, 'wrong-secret', function(err, decoded) { |
|||
// err |
|||
// decoded undefined |
|||
}); |
|||
|
|||
// verify a token asymmetric |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, function(err, decoded) { |
|||
console.log(decoded.foo) // bar |
|||
}); |
|||
|
|||
// verify audience |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, { audience: 'urn:foo' }, function(err, decoded) { |
|||
// if audience mismatch, err == invalid audience |
|||
}); |
|||
|
|||
// verify issuer |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(err, decoded) { |
|||
// if issuer mismatch, err == invalid issuer |
|||
}); |
|||
|
|||
// verify jwt id |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid' }, function(err, decoded) { |
|||
// if jwt id mismatch, err == invalid jwt id |
|||
}); |
|||
|
|||
// verify subject |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer', jwtid: 'jwtid', subject: 'subject' }, function(err, decoded) { |
|||
// if subject mismatch, err == invalid subject |
|||
}); |
|||
|
|||
// alg mismatch |
|||
var cert = fs.readFileSync('public.pem'); // get public key |
|||
jwt.verify(token, cert, { algorithms: ['RS256'] }, function (err, payload) { |
|||
// if token alg != RS256, err == invalid signature |
|||
}); |
|||
|
|||
``` |
|||
|
|||
### jwt.decode(token [, options]) |
|||
|
|||
(Synchronous) Returns the decoded payload without verifying if the signature is valid. |
|||
|
|||
__Warning:__ This will __not__ verify whether the signature is valid. You should __not__ use this for untrusted messages. You most likely want to use `jwt.verify` instead. |
|||
|
|||
`token` is the JsonWebToken string |
|||
|
|||
`options`: |
|||
|
|||
* `json`: force JSON.parse on the payload even if the header doesn't contain `"typ":"JWT"`. |
|||
* `complete`: return an object with the decoded payload and header. |
|||
|
|||
Example |
|||
|
|||
```js |
|||
// get the decoded payload ignoring signature, no secretOrPrivateKey needed |
|||
var decoded = jwt.decode(token); |
|||
|
|||
// get the decoded payload and header |
|||
var decoded = jwt.decode(token, {complete: true}); |
|||
console.log(decoded.header); |
|||
console.log(decoded.payload) |
|||
``` |
|||
|
|||
## Errors & Codes |
|||
Possible thrown errors during verification. |
|||
Error is the first argument of the verification callback. |
|||
|
|||
### TokenExpiredError |
|||
|
|||
Thrown error if the token is expired. |
|||
|
|||
Error object: |
|||
|
|||
* name: 'TokenExpiredError' |
|||
* message: 'jwt expired' |
|||
* expiredAt: [ExpDate] |
|||
|
|||
```js |
|||
jwt.verify(token, 'shhhhh', function(err, decoded) { |
|||
if (err) { |
|||
/* |
|||
err = { |
|||
name: 'TokenExpiredError', |
|||
message: 'jwt expired', |
|||
expiredAt: 1408621000 |
|||
} |
|||
*/ |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
### JsonWebTokenError |
|||
Error object: |
|||
|
|||
* name: 'JsonWebTokenError' |
|||
* message: |
|||
* 'jwt malformed' |
|||
* 'jwt signature is required' |
|||
* 'invalid signature' |
|||
* 'jwt audience invalid. expected: [OPTIONS AUDIENCE]' |
|||
* 'jwt issuer invalid. expected: [OPTIONS ISSUER]' |
|||
* 'jwt id invalid. expected: [OPTIONS JWT ID]' |
|||
* 'jwt subject invalid. expected: [OPTIONS SUBJECT]' |
|||
|
|||
```js |
|||
jwt.verify(token, 'shhhhh', function(err, decoded) { |
|||
if (err) { |
|||
/* |
|||
err = { |
|||
name: 'JsonWebTokenError', |
|||
message: 'jwt malformed' |
|||
} |
|||
*/ |
|||
} |
|||
}); |
|||
``` |
|||
|
|||
## Algorithms supported |
|||
|
|||
Array of supported algorithms. The following algorithms are currently supported. |
|||
|
|||
alg Parameter Value | Digital Signature or MAC Algorithm |
|||
----------------|---------------------------- |
|||
HS256 | HMAC using SHA-256 hash algorithm |
|||
HS384 | HMAC using SHA-384 hash algorithm |
|||
HS512 | HMAC using SHA-512 hash algorithm |
|||
RS256 | RSASSA using SHA-256 hash algorithm |
|||
RS384 | RSASSA using SHA-384 hash algorithm |
|||
RS512 | RSASSA using SHA-512 hash algorithm |
|||
ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm |
|||
ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm |
|||
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm |
|||
none | No digital signature or MAC value included |
|||
|
|||
# TODO |
|||
|
|||
* X.509 certificate chain is not checked |
|||
|
|||
## Issue Reporting |
|||
|
|||
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues. |
|||
|
|||
## Author |
|||
|
|||
[Auth0](https://auth0.com) |
|||
|
|||
## License |
|||
|
|||
This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info. |
@ -0,0 +1,28 @@ |
|||
#!/usr/bin/env node |
|||
|
|||
var changelog = require('conventional-changelog'); |
|||
var semver_regex = /\bv?(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?\b/ig; |
|||
|
|||
const commitPartial = ` - {{header}} |
|||
|
|||
{{~!-- commit hash --}} {{#if @root.linkReferences}}([{{hash}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}/{{@root.commit}}/{{hash}})){{else}}{{hash~}}{{/if}} |
|||
|
|||
{{~!-- commit references --}}{{#if references}}, closes{{~#each references}} {{#if @root.linkReferences}}[{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}]({{#if @root.host}}{{@root.host}}/{{/if}}{{#if this.repository}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}{{else}}{{#if @root.owner}}{{@root.owner}}/{{/if}}{{@root.repository}}{{/if}}/{{@root.issue}}/{{this.issue}}){{else}}{{#if this.owner}}{{this.owner}}/{{/if}}{{this.repository}}#{{this.issue}}{{/if}}{{/each}}{{/if}} |
|||
`; |
|||
|
|||
const headerPartial = `## {{version}}{{#if title}} "{{title}}"{{/if}}{{#if date}} - {{date}}{{/if}} |
|||
`; |
|||
|
|||
changelog({ |
|||
releaseCount: 19, |
|||
// preset: 'jshint' |
|||
}, null, null, null, { |
|||
transform: function (commit) { |
|||
if (commit.header && semver_regex.exec(commit.header)) { |
|||
return null; |
|||
} |
|||
return commit; |
|||
}, |
|||
commitPartial: commitPartial, |
|||
headerPartial: headerPartial |
|||
}).pipe(process.stdout); |
@ -0,0 +1,30 @@ |
|||
var jws = require('jws'); |
|||
|
|||
module.exports = function (jwt, options) { |
|||
options = options || {}; |
|||
var decoded = jws.decode(jwt, options); |
|||
if (!decoded) { return null; } |
|||
var payload = decoded.payload; |
|||
|
|||
//try parse the payload
|
|||
if(typeof payload === 'string') { |
|||
try { |
|||
var obj = JSON.parse(payload); |
|||
if(typeof obj === 'object') { |
|||
payload = obj; |
|||
} |
|||
} catch (e) { } |
|||
} |
|||
|
|||
//return header if `complete` option is enabled. header includes claims
|
|||
//such as `kid` and `alg` used to select the key within a JWKS needed to
|
|||
//verify the signature
|
|||
if (options.complete === true) { |
|||
return { |
|||
header: decoded.header, |
|||
payload: payload, |
|||
signature: decoded.signature |
|||
}; |
|||
} |
|||
return payload; |
|||
}; |
@ -0,0 +1,8 @@ |
|||
module.exports = { |
|||
decode: require('./decode'), |
|||
verify: require('./verify'), |
|||
sign: require('./sign'), |
|||
JsonWebTokenError: require('./lib/JsonWebTokenError'), |
|||
NotBeforeError: require('./lib/NotBeforeError'), |
|||
TokenExpiredError: require('./lib/TokenExpiredError'), |
|||
}; |
@ -0,0 +1,12 @@ |
|||
var JsonWebTokenError = function (message, error) { |
|||
Error.call(this, message); |
|||
Error.captureStackTrace(this, this.constructor); |
|||
this.name = 'JsonWebTokenError'; |
|||
this.message = message; |
|||
if (error) this.inner = error; |
|||
}; |
|||
|
|||
JsonWebTokenError.prototype = Object.create(Error.prototype); |
|||
JsonWebTokenError.prototype.constructor = JsonWebTokenError; |
|||
|
|||
module.exports = JsonWebTokenError; |
@ -0,0 +1,13 @@ |
|||
var JsonWebTokenError = require('./JsonWebTokenError'); |
|||
|
|||
var NotBeforeError = function (message, date) { |
|||
JsonWebTokenError.call(this, message); |
|||
this.name = 'NotBeforeError'; |
|||
this.date = date; |
|||
}; |
|||
|
|||
NotBeforeError.prototype = Object.create(JsonWebTokenError.prototype); |
|||
|
|||
NotBeforeError.prototype.constructor = NotBeforeError; |
|||
|
|||
module.exports = NotBeforeError; |
@ -0,0 +1,13 @@ |
|||
var JsonWebTokenError = require('./JsonWebTokenError'); |
|||
|
|||
var TokenExpiredError = function (message, expiredAt) { |
|||
JsonWebTokenError.call(this, message); |
|||
this.name = 'TokenExpiredError'; |
|||
this.expiredAt = expiredAt; |
|||
}; |
|||
|
|||
TokenExpiredError.prototype = Object.create(JsonWebTokenError.prototype); |
|||
|
|||
TokenExpiredError.prototype.constructor = TokenExpiredError; |
|||
|
|||
module.exports = TokenExpiredError; |
@ -0,0 +1,18 @@ |
|||
var ms = require('ms'); |
|||
|
|||
module.exports = function (time, iat) { |
|||
var timestamp = iat || Math.floor(Date.now() / 1000); |
|||
|
|||
if (typeof time === 'string') { |
|||
var milliseconds = ms(time); |
|||
if (typeof milliseconds === 'undefined') { |
|||
return; |
|||
} |
|||
return Math.floor(timestamp + milliseconds / 1000); |
|||
} else if (typeof time === 'number') { |
|||
return timestamp + time; |
|||
} else { |
|||
return; |
|||
} |
|||
|
|||
}; |
@ -0,0 +1,2 @@ |
|||
*.DS_Store |
|||
node_modules |
@ -0,0 +1,6 @@ |
|||
.PHONY: test |
|||
|
|||
MOCHA = ./node_modules/mocha/bin/mocha |
|||
|
|||
test: |
|||
$(MOCHA) -R list |
@ -0,0 +1,95 @@ |
|||
# cb() |
|||
|
|||
A minimal node.js utility for handling common (but often overlooked) callback scenarios. |
|||
|
|||
##Features |
|||
|
|||
* `.timeout()`: Simple callback timeouts |
|||
* `.error()`: Explicit error handling |
|||
* `.once()`: Once-and-only-once callback semantics |
|||
* Guaranteed asynchronous callback execution (protects against code that breaks this assumption) |
|||
|
|||
## Installation |
|||
|
|||
$ npm install cb |
|||
|
|||
## Usage |
|||
|
|||
### Basic Usage |
|||
|
|||
The most basic usage of `cb` consists of passing in your own function reference. In this example, `cb` will do nothing other |
|||
than insure the once-and-only-once, asynchronous invocation of the callback. |
|||
|
|||
doAsync(cb(function(err, res) { |
|||
console.log(res); |
|||
})); |
|||
|
|||
### Timeout Handling |
|||
|
|||
Timeouts are specified through the `.timeout()` method, and are specified in milliseconds. If a timeout does occur, the error |
|||
passed to the callback will be an instance of `cb.TimeoutError`. |
|||
|
|||
doReallySlowAsync(cb(function(err, res) { |
|||
assert(err instanceof cb.TimeoutError); |
|||
}).timeout(50)); |
|||
|
|||
*Note: once a timeout has occured, any tardy attempts to invoke the callback will be ignored.* |
|||
|
|||
### Explicit Error Handling |
|||
|
|||
In situations where it is convenient to separate the code that runs on success or failure, this can easily be accomplished |
|||
with `.error()`. If an 'errback' handler has been provided to `.error()`, then it is assumed that the error-first parameter |
|||
to the success handler is no longer required. To illustrate, |
|||
|
|||
doAsync(cb(function(err, res) { |
|||
if (err) { |
|||
console.error(err); |
|||
} else { |
|||
console.log(res); |
|||
} |
|||
})); |
|||
|
|||
Can be rewritten as: |
|||
|
|||
doAsync(cb(console.log).error(console.error)); |
|||
|
|||
### Force Once-and-only-once Callback Execution |
|||
|
|||
Sometimes it's necessary to ensure that a callback is invoked once, and no more. Once-and-only-once execution semantics can be |
|||
enforced by using `.once()`. |
|||
|
|||
function runTwice(callback) { |
|||
process.nextTick(function() { |
|||
callback(); |
|||
callback(); |
|||
}); |
|||
} |
|||
|
|||
runTwice(cb(function() { |
|||
console.log('I will only run once'); |
|||
}).once()); |
|||
|
|||
*Note: technically, `.once()` simply enforces at-most-once semantics. However, when combined with `.timeout()`, once-and-only-once |
|||
is achieved.* |
|||
|
|||
### Combining Features |
|||
|
|||
The `cb` API is fully chainable, and any arrangement of the features is valid. For example: |
|||
|
|||
doAsync(cb(console.log).error(console.error).timeout(50).once()); |
|||
|
|||
## Running the Tests |
|||
|
|||
$ make test |
|||
|
|||
## License |
|||
|
|||
The MIT License (MIT) |
|||
|
|||
Copyright (c) 2012 Jeremy Martin |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,39 @@ |
|||
module.exports = function(callback) { |
|||
|
|||
var cb = function() { |
|||
if (timedout || (once && count)) return; |
|||
count += 1; |
|||
tid && clearTimeout(tid); |
|||
|
|||
var args = Array.prototype.slice.call(arguments); |
|||
process.nextTick(function() { |
|||
if (!errback) return callback.apply(this, args); |
|||
args[0] ? errback(args[0]) : callback.apply(this, args.slice(1)); |
|||
}); |
|||
|
|||
}, count = 0, once = false, timedout = false, errback, tid; |
|||
|
|||
cb.timeout = function(ms) { |
|||
tid && clearTimeout(tid); |
|||
tid = setTimeout(function() { |
|||
cb(new TimeoutError(ms)); |
|||
timedout = true; |
|||
}, ms); |
|||
return cb; |
|||
}; |
|||
|
|||
cb.error = function(func) { errback = func; return cb; }; |
|||
|
|||
cb.once = function() { once = true; return cb; }; |
|||
|
|||
return cb; |
|||
|
|||
}; |
|||
|
|||
var TimeoutError = module.exports.TimeoutError = function TimeoutError(ms) { |
|||
this.message = 'Specified timeout of ' + ms + 'ms was reached'; |
|||
Error.captureStackTrace(this, this.constructor); |
|||
}; |
|||
TimeoutError.prototype = new Error; |
|||
TimeoutError.prototype.constructor = TimeoutError; |
|||
TimeoutError.prototype.name = 'TimeoutError'; |
@ -0,0 +1,46 @@ |
|||
{ |
|||
"author": { |
|||
"name": "Jeremy Martin", |
|||
"email": "jmar777@gmail.com", |
|||
"url": "http://twitter.com/jmar777" |
|||
}, |
|||
"name": "cb", |
|||
"description": "Super simple callback mechanism with support for timeouts and explicit error handling", |
|||
"version": "0.1.0", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git://github.com/jmar777/cb.git" |
|||
}, |
|||
"main": "lib/cb.js", |
|||
"devDependencies": { |
|||
"mocha": ">=0.3.6" |
|||
}, |
|||
"engines": { |
|||
"node": ">=0.6.0" |
|||
}, |
|||
"_npmUser": { |
|||
"name": "jmar777", |
|||
"email": "jmar777@gmail.com" |
|||
}, |
|||
"_id": "cb@0.1.0", |
|||
"dependencies": {}, |
|||
"optionalDependencies": {}, |
|||
"_engineSupported": true, |
|||
"_npmVersion": "1.1.0-2", |
|||
"_nodeVersion": "v0.6.8", |
|||
"_defaultsLoaded": true, |
|||
"dist": { |
|||
"shasum": "26f7e740f2808facc83cef7b20392e4d881b5203", |
|||
"tarball": "https://registry.npmjs.org/cb/-/cb-0.1.0.tgz" |
|||
}, |
|||
"maintainers": [ |
|||
{ |
|||
"name": "jmar777", |
|||
"email": "jmar777@gmail.com" |
|||
} |
|||
], |
|||
"directories": {}, |
|||
"_shasum": "26f7e740f2808facc83cef7b20392e4d881b5203", |
|||
"_resolved": "https://registry.npmjs.org/cb/-/cb-0.1.0.tgz", |
|||
"_from": "cb@>=0.1.0 <0.2.0" |
|||
} |
@ -0,0 +1,125 @@ |
|||
var assert = require('assert'), |
|||
cb = require('../'); |
|||
|
|||
function invokeAsync(callback) { |
|||
setTimeout(function() { |
|||
callback(null, 'foo'); |
|||
}, 100); |
|||
} |
|||
|
|||
function invokeAsyncError(callback) { |
|||
setTimeout(function() { |
|||
callback(new Error()); |
|||
}, 100); |
|||
} |
|||
|
|||
function invokeAsyncTwice(callback) { |
|||
setTimeout(function() { |
|||
callback(null, 'foo'); |
|||
callback(null, 'foo'); |
|||
}, 100); |
|||
} |
|||
|
|||
describe('cb(callback)', function() { |
|||
|
|||
it('should invoke the provided callback', function(done) { |
|||
invokeAsync(cb(function(err, res) { |
|||
assert.strictEqual(res, 'foo'); |
|||
done(); |
|||
})); |
|||
}); |
|||
|
|||
it('shouldn\'t mess with errors', function(done) { |
|||
invokeAsyncError(cb(function(err, res) { |
|||
assert(err); |
|||
done(); |
|||
})); |
|||
}); |
|||
|
|||
it('should allow multiple executions', function(done) { |
|||
var count = 0; |
|||
invokeAsyncTwice(cb(function(err, res) { |
|||
count++; |
|||
if (count === 2) done(); |
|||
})); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('cb(callback).timeout(ms)', function() { |
|||
|
|||
it('should complete successfully within timeout period', function(done) { |
|||
invokeAsync(cb(function(err, res) { |
|||
assert.strictEqual(res, 'foo'); |
|||
done(); |
|||
}).timeout(200)); |
|||
}); |
|||
|
|||
it('should complete with an error after timeout period', function(done) { |
|||
invokeAsync(cb(function(err, res) { |
|||
assert(err); |
|||
done(); |
|||
}).timeout(50)); |
|||
}); |
|||
|
|||
it('error resulting from a timeout should be instanceof cb.TimeoutError', function(done) { |
|||
invokeAsync(cb(function(err, res) { |
|||
assert(err instanceof cb.TimeoutError); |
|||
done(); |
|||
}).timeout(50)); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('cb(callback).error(errback)', function() { |
|||
|
|||
it('should skip the err argument when invoking callback', function(done) { |
|||
invokeAsync(cb(function(res) { |
|||
assert.strictEqual(res, 'foo'); |
|||
done(); |
|||
}).error(assert.ifError)); |
|||
}); |
|||
|
|||
it('should pass errors to provided errback', function(done) { |
|||
invokeAsyncError(cb(function(res) { |
|||
throw new Error('should not be invoked'); |
|||
}).error(function(err) { |
|||
assert(err); |
|||
done(); |
|||
})); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('cb(callback).error(errback).timeout(ms)', function() { |
|||
|
|||
it('should skip the err argument when invoking callback', function(done) { |
|||
invokeAsync(cb(function(res) { |
|||
assert.strictEqual(res, 'foo'); |
|||
done(); |
|||
}).error(assert.ifError).timeout(200)); |
|||
}); |
|||
|
|||
it('should pass timeout error to provided errback', function(done) { |
|||
invokeAsyncError(cb(function(res) { |
|||
throw new Error('should not be invoked'); |
|||
}).error(function(err) { |
|||
assert(err); |
|||
done(); |
|||
}).timeout(50)); |
|||
}); |
|||
|
|||
}); |
|||
|
|||
describe('cb(callback).once()', function() { |
|||
|
|||
it('should allow multiple executions', function(done) { |
|||
var count = 0; |
|||
invokeAsyncTwice(cb(function(err, res) { |
|||
count++; |
|||
assert.notEqual(count, 2); |
|||
setTimeout(done, 100); |
|||
}).once()); |
|||
}); |
|||
|
|||
}); |
@ -0,0 +1,2 @@ |
|||
examples |
|||
sandbox.js |
@ -0,0 +1,21 @@ |
|||
.c9 |
|||
.idea |
|||
.project |
|||
*.iml |
|||
npm-debug.log |
|||
dump.rdb |
|||
node_modules |
|||
results.tap |
|||
results.xml |
|||
npm-shrinkwrap.json |
|||
config.json |
|||
.DS_Store |
|||
*/.DS_Store |
|||
*/*/.DS_Store |
|||
._* |
|||
*/._* |
|||
*/*/._* |
|||
coverage.* |
|||
lib-cov |
|||
complexity.md |
|||
sandbox.js |
@ -0,0 +1,9 @@ |
|||
language: node_js |
|||
|
|||
node_js: |
|||
- "0.10" |
|||
- "4.0" |
|||
- "4" |
|||
- "5" |
|||
|
|||
sudo: false |
@ -0,0 +1,14 @@ |
|||
# How to contribute |
|||
We welcome contributions from the community and are pleased to have them. Please follow this guide when logging issues or making code changes. |
|||
|
|||
## Logging Issues |
|||
All issues should be created using the [new issue form](https://github.com/hapijs/joi/issues/new). Clearly describe the issue including steps to reproduce if there are any. Also, make sure to indicate the earliest version that has the issue being reported. |
|||
|
|||
## Patching Code |
|||
Code changes are welcome and should follow the guidelines below. |
|||
|
|||
* Fork the repository on GitHub. |
|||
* Fix the issue ensuring that your code follows the [style guide](https://github.com/hapijs/contrib/blob/master/Style.md). |
|||
* Add tests for your new code ensuring that you have 100% code coverage (we can help you reach 100% but will not merge without it). |
|||
* Run `npm test` to generate a report of test coverage |
|||
* [Pull requests](http://help.github.com/send-pull-requests/) should be made to the [master branch](https://github.com/hapijs/joi/tree/master). |
@ -0,0 +1,28 @@ |
|||
Copyright (c) 2012-2014, Walmart and other contributors. |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
* The names of any contributors may not be used to endorse or promote |
|||
products derived from this software without specific prior written |
|||
permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY |
|||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
* * * |
|||
|
|||
The complete list of contributors can be found at: https://github.com/hapijs/joi/graphs/contributors |
@ -0,0 +1,90 @@ |
|||
![joi Logo](https://raw.github.com/hapijs/joi/master/images/joi.png) |
|||
|
|||
Object schema description language and validator for JavaScript objects. |
|||
|
|||
[![npm version](https://badge.fury.io/js/joi.svg)](http://badge.fury.io/js/joi) |
|||
[![Build Status](https://secure.travis-ci.org/hapijs/joi.svg)](http://travis-ci.org/hapijs/joi) |
|||
[![Dependencies Status](https://david-dm.org/hapijs/joi.svg)](https://david-dm.org/hapijs/joi) |
|||
[![DevDependencies Status](https://david-dm.org/hapijs/joi/dev-status.svg)](https://david-dm.org/hapijs/joi#info=devDependencies) |
|||
|
|||
[![Join the chat at https://gitter.im/hapijs/joi](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hapijs/joi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) |
|||
|
|||
Lead Maintainer: [Nicolas Morel](https://github.com/marsup) |
|||
|
|||
# Example |
|||
|
|||
```javascript |
|||
var Joi = require('joi'); |
|||
|
|||
var schema = Joi.object().keys({ |
|||
username: Joi.string().alphanum().min(3).max(30).required(), |
|||
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/), |
|||
access_token: [Joi.string(), Joi.number()], |
|||
birthyear: Joi.number().integer().min(1900).max(2013), |
|||
email: Joi.string().email() |
|||
}).with('username', 'birthyear').without('password', 'access_token'); |
|||
|
|||
Joi.validate({ username: 'abc', birthyear: 1994 }, schema, function (err, value) { }); // err === null -> valid |
|||
``` |
|||
|
|||
The above schema defines the following constraints: |
|||
* `username` |
|||
* a required string |
|||
* must contain only alphanumeric characters |
|||
* at least 3 characters long but no more than 30 |
|||
* must be accompanied by `birthyear` |
|||
* `password` |
|||
* an optional string |
|||
* must satisfy the custom regex |
|||
* cannot appear together with `access_token` |
|||
* `access_token` |
|||
* an optional, unconstrained string or number |
|||
* `birthyear` |
|||
* an integer between 1900 and 2013 |
|||
* `email` |
|||
* a valid email address string |
|||
|
|||
# Usage |
|||
|
|||
Usage is a two steps process. First, a schema is constructed using the provided types and constraints: |
|||
|
|||
```javascript |
|||
var schema = { |
|||
a: Joi.string() |
|||
}; |
|||
``` |
|||
|
|||
Note that **joi** schema objects are immutable which means every additional rule added (e.g. `.min(5)`) will return a |
|||
new schema object. |
|||
|
|||
Then the value is validated against the schema: |
|||
|
|||
```javascript |
|||
Joi.validate({ a: 'a string' }, schema, function (err, value) { }); |
|||
``` |
|||
|
|||
If the value is valid, `null` is returned, otherwise an `Error` object. |
|||
|
|||
The schema can be a plain JavaScript object where every key is assigned a **joi** type, or it can be a **joi** type directly: |
|||
|
|||
```javascript |
|||
var schema = Joi.string().min(10); |
|||
``` |
|||
|
|||
If the schema is a **joi** type, the `schema.validate(value, callback)` can be called directly on the type. When passing a non-type schema object, |
|||
the module converts it internally to an object() type equivalent to: |
|||
|
|||
```javascript |
|||
var schema = Joi.object().keys({ |
|||
a: Joi.string() |
|||
}); |
|||
``` |
|||
|
|||
When validating a schema: |
|||
|
|||
* Keys are optional by default. |
|||
* Strings are utf-8 encoded by default. |
|||
* Rules are defined in an additive fashion and evaluated in order after whitelist and blacklist checks. |
|||
|
|||
# API |
|||
See the [API Reference](API.md). |
@ -0,0 +1,43 @@ |
|||
// This is an example of a survey to obtain the reputation of Parisians
|
|||
// It contains examples of how to conditionally require keys based on values of other keys
|
|||
|
|||
var Joi = require('../'); |
|||
|
|||
// This is a valid value for integer rating 1 - 5
|
|||
var intRating = Joi.number().integer().min(1).max(5); |
|||
|
|||
var schema = Joi.object().keys({ |
|||
// Do you know any French people? yes or no (required)
|
|||
q1: Joi.boolean().required(), |
|||
// Do you know any Parisians? yes or no (required if answered yes in q1)
|
|||
q2: Joi.boolean() |
|||
.when('q1', { is: true, then: Joi.required() }), |
|||
// How many french in paris do you know? 1-6, 6-10, 11-50 or 50+ (required if answered yes in q2)
|
|||
q3: Joi.string() |
|||
.when('q2', { is: true, then: Joi.valid('1-5', '6-10', '11-50', '50+').required() }), |
|||
// Rate 20% of most friendly Parisians, from how many people you know answered in q3, individually on 1-5 rating
|
|||
q4: Joi.array() |
|||
.when('q3', {is: '1-5', then: Joi.array().min(0).max(1).items(intRating).required() }) |
|||
.when('q3', {is: '6-10', then: Joi.array().min(1).max(2).items(intRating).required() }) |
|||
.when('q3', {is: '11-50', then: Joi.array().min(2).max(10).items(intRating).required() }) |
|||
.when('q3', {is: '50+', then: Joi.array().min(10).items(intRating).required() }), |
|||
// Rate remaining 80% of Parisians, from how many people you know answered in q3, individually on 1-5 rating
|
|||
q5: Joi.array() |
|||
.when('q3', {is: '1-5', then: Joi.array().min(1).max(4).items(intRating).required() }) |
|||
.when('q3', {is: '6-10', then: Joi.array().min(4).max(8).items(intRating).required() }) |
|||
.when('q3', {is: '11-50', then: Joi.array().min(8).max(40).items(intRating).required() }) |
|||
.when('q3', {is: '50+', then: Joi.array().min(40).items(intRating).required().required() }), |
|||
// Rate the reputation of Parisians in general, 1-5 rating
|
|||
q6: intRating.required() |
|||
}); |
|||
|
|||
var response = { |
|||
q1: true, |
|||
q2: true, |
|||
q3: '1-5', |
|||
q4: [5], |
|||
q5: [1], |
|||
q6: 2 |
|||
}; |
|||
|
|||
Joi.assert(response, schema); |
@ -0,0 +1,22 @@ |
|||
var Joi = require('../'); |
|||
|
|||
|
|||
var schema = Joi.object().options({ abortEarly: false }).keys({ |
|||
email: Joi.string().email().required().label('User Email'), |
|||
password: Joi.string().min(8).required(), |
|||
password_confirmation: Joi.any().valid(Joi.ref('password')).required().options({ language: { any: { allowOnly: 'must match password' }, label: 'Password Confirmation' } }).label('This label is not used because language.label takes precedence'), |
|||
first_name: Joi.string().required(), |
|||
last_name: Joi.string().required(), |
|||
company: Joi.string().optional() |
|||
}); |
|||
|
|||
|
|||
var data = { |
|||
email: 'not_a_valid_email_to_show_custom_label', |
|||
password: 'abcd1234', |
|||
password_confirmation: 'abc1', |
|||
first_name: 'Joe', |
|||
last_name: 'Doe' |
|||
}; |
|||
|
|||
Joi.assert(data, schema); |
@ -0,0 +1,17 @@ |
|||
var Joi = require('../'); |
|||
|
|||
|
|||
var schema = { |
|||
type: Joi.string().required(), |
|||
subtype: Joi.alternatives() |
|||
.when('type', {is: 'video', then: Joi.valid('mp4', 'wav')}) |
|||
.when('type', {is: 'audio', then: Joi.valid('mp3')}) |
|||
.when('type', {is: 'image', then: Joi.valid('jpg', 'png')}) |
|||
.when('type', {is: 'pdf' , then: Joi.valid('document')}) |
|||
}; |
|||
|
|||
|
|||
Joi.assert({ type: 'video', subtype: 'mp4' }, schema); // Pass
|
|||
Joi.assert({ type: 'video', subtype: 'wav' }, schema); // Pass
|
|||
Joi.assert({ type: 'other', subtype: 'something' }, schema); // Fail
|
|||
Joi.assert({ type: 'audio', subtype: 'mp4' }, schema); // Fail
|
@ -0,0 +1,21 @@ |
|||
var Toc = require('markdown-toc'); |
|||
var Fs = require('fs'); |
|||
var Package = require('./package.json'); |
|||
|
|||
var filename = './API.md'; |
|||
|
|||
var api = Fs.readFileSync(filename, 'utf8'); |
|||
var tocOptions = { |
|||
bullets: '-', |
|||
slugify: function (text) { |
|||
|
|||
return text.toLowerCase() |
|||
.replace(/\s/g, '-') |
|||
.replace(/[^\w-]/g, ''); |
|||
} |
|||
}; |
|||
|
|||
var output = Toc.insert(api, tocOptions) |
|||
.replace(/<!-- version -->(.|\n)*<!-- versionstop -->/, '<!-- version -->\n# ' + Package.version + ' API Reference\n<!-- versionstop -->'); |
|||
|
|||
Fs.writeFileSync(filename, output); |
@ -0,0 +1,152 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
var Any = require('./any'); |
|||
var Cast = require('./cast'); |
|||
var Ref = require('./ref'); |
|||
var Errors = require('./errors'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.Alternatives = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'alternatives'; |
|||
this._invalids.remove(null); |
|||
|
|||
this._inner.matches = []; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Alternatives, Any); |
|||
|
|||
|
|||
internals.Alternatives.prototype._base = function (value, state, options) { |
|||
|
|||
var errors = []; |
|||
for (var i = 0, il = this._inner.matches.length; i < il; ++i) { |
|||
var item = this._inner.matches[i]; |
|||
var schema = item.schema; |
|||
if (!schema) { |
|||
var failed = item.is._validate(item.ref(state.parent, options), null, options, state.parent).errors; |
|||
schema = failed ? item.otherwise : item.then; |
|||
if (!schema) { |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
var result = schema._validate(value, state, options); |
|||
if (!result.errors) { // Found a valid match
|
|||
return result; |
|||
} |
|||
|
|||
errors = errors.concat(result.errors); |
|||
} |
|||
|
|||
return { errors: errors.length ? errors : Errors.create('alternatives.base', null, state, options) }; |
|||
}; |
|||
|
|||
|
|||
internals.Alternatives.prototype.try = function (/* schemas */) { |
|||
|
|||
|
|||
var schemas = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema'); |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
for (var i = 0, il = schemas.length; i < il; ++i) { |
|||
var cast = Cast.schema(schemas[i]); |
|||
if (cast._refs.length) { |
|||
obj._refs = obj._refs.concat(cast._refs); |
|||
} |
|||
obj._inner.matches.push({ schema: cast }); |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Alternatives.prototype.when = function (ref, options) { |
|||
|
|||
Hoek.assert(Ref.isRef(ref) || typeof ref === 'string', 'Invalid reference:', ref); |
|||
Hoek.assert(options, 'Missing options'); |
|||
Hoek.assert(typeof options === 'object', 'Invalid options'); |
|||
Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive'); |
|||
Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); |
|||
|
|||
var obj = this.clone(); |
|||
var is = Cast.schema(options.is); |
|||
|
|||
if (options.is === null || !options.is.isJoi) { |
|||
|
|||
// Only apply required if this wasn't already a schema, we'll suppose people know what they're doing
|
|||
is = is.required(); |
|||
} |
|||
|
|||
var item = { |
|||
ref: Cast.ref(ref), |
|||
is: is, |
|||
then: options.then !== undefined ? Cast.schema(options.then) : undefined, |
|||
otherwise: options.otherwise !== undefined ? Cast.schema(options.otherwise) : undefined |
|||
}; |
|||
|
|||
Ref.push(obj._refs, item.ref); |
|||
obj._refs = obj._refs.concat(item.is._refs); |
|||
|
|||
if (item.then && item.then._refs) { |
|||
obj._refs = obj._refs.concat(item.then._refs); |
|||
} |
|||
|
|||
if (item.otherwise && item.otherwise._refs) { |
|||
obj._refs = obj._refs.concat(item.otherwise._refs); |
|||
} |
|||
|
|||
obj._inner.matches.push(item); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Alternatives.prototype.describe = function () { |
|||
|
|||
var description = Any.prototype.describe.call(this); |
|||
var alternatives = []; |
|||
for (var i = 0, il = this._inner.matches.length; i < il; ++i) { |
|||
var item = this._inner.matches[i]; |
|||
if (item.schema) { |
|||
|
|||
// try()
|
|||
|
|||
alternatives.push(item.schema.describe()); |
|||
} |
|||
else { |
|||
|
|||
// when()
|
|||
|
|||
var when = { |
|||
ref: item.ref.toString(), |
|||
is: item.is.describe() |
|||
}; |
|||
|
|||
if (item.then) { |
|||
when.then = item.then.describe(); |
|||
} |
|||
|
|||
if (item.otherwise) { |
|||
when.otherwise = item.otherwise.describe(); |
|||
} |
|||
|
|||
alternatives.push(when); |
|||
} |
|||
} |
|||
|
|||
description.alternatives = alternatives; |
|||
return description; |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Alternatives(); |
@ -0,0 +1,899 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
var Ref = require('./ref'); |
|||
var Errors = require('./errors'); |
|||
var Alternatives = null; // Delay-loaded to prevent circular dependencies
|
|||
var Cast = null; |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.defaults = { |
|||
abortEarly: true, |
|||
convert: true, |
|||
allowUnknown: false, |
|||
skipFunctions: false, |
|||
stripUnknown: false, |
|||
language: {}, |
|||
presence: 'optional', |
|||
raw: false, |
|||
strip: false, |
|||
noDefaults: false |
|||
|
|||
// context: null
|
|||
}; |
|||
|
|||
|
|||
internals.checkOptions = function (options) { |
|||
|
|||
var optionType = { |
|||
abortEarly: 'boolean', |
|||
convert: 'boolean', |
|||
allowUnknown: 'boolean', |
|||
skipFunctions: 'boolean', |
|||
stripUnknown: 'boolean', |
|||
language: 'object', |
|||
presence: ['string', 'required', 'optional', 'forbidden', 'ignore'], |
|||
raw: 'boolean', |
|||
context: 'object', |
|||
strip: 'boolean', |
|||
noDefaults: 'boolean' |
|||
}; |
|||
|
|||
var keys = Object.keys(options); |
|||
for (var k = 0, kl = keys.length; k < kl; ++k) { |
|||
var key = keys[k]; |
|||
var opt = optionType[key]; |
|||
var type = opt; |
|||
var values = null; |
|||
|
|||
if (Array.isArray(opt)) { |
|||
type = opt[0]; |
|||
values = opt.slice(1); |
|||
} |
|||
|
|||
Hoek.assert(type, 'unknown key ' + key); |
|||
Hoek.assert(typeof options[key] === type, key + ' should be of type ' + type); |
|||
if (values) { |
|||
Hoek.assert(values.indexOf(options[key]) >= 0, key + ' should be one of ' + values.join(', ')); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
|
|||
module.exports = internals.Any = function () { |
|||
|
|||
Cast = Cast || require('./cast'); |
|||
|
|||
this.isJoi = true; |
|||
this._type = 'any'; |
|||
this._settings = null; |
|||
this._valids = new internals.Set(); |
|||
this._invalids = new internals.Set(); |
|||
this._tests = []; |
|||
this._refs = []; |
|||
this._flags = { /* |
|||
presence: 'optional', // optional, required, forbidden, ignore
|
|||
allowOnly: false, |
|||
allowUnknown: undefined, |
|||
default: undefined, |
|||
forbidden: false, |
|||
encoding: undefined, |
|||
insensitive: false, |
|||
trim: false, |
|||
case: undefined, // upper, lower
|
|||
empty: undefined, |
|||
func: false |
|||
*/ }; |
|||
|
|||
this._description = null; |
|||
this._unit = null; |
|||
this._notes = []; |
|||
this._tags = []; |
|||
this._examples = []; |
|||
this._meta = []; |
|||
|
|||
this._inner = {}; // Hash of arrays of immutable objects
|
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects
|
|||
|
|||
|
|||
internals.Any.prototype.clone = function () { |
|||
|
|||
var obj = Object.create(Object.getPrototypeOf(this)); |
|||
|
|||
obj.isJoi = true; |
|||
obj._type = this._type; |
|||
obj._settings = internals.concatSettings(this._settings); |
|||
obj._valids = Hoek.clone(this._valids); |
|||
obj._invalids = Hoek.clone(this._invalids); |
|||
obj._tests = this._tests.slice(); |
|||
obj._refs = this._refs.slice(); |
|||
obj._flags = Hoek.clone(this._flags); |
|||
|
|||
obj._description = this._description; |
|||
obj._unit = this._unit; |
|||
obj._notes = this._notes.slice(); |
|||
obj._tags = this._tags.slice(); |
|||
obj._examples = this._examples.slice(); |
|||
obj._meta = this._meta.slice(); |
|||
|
|||
obj._inner = {}; |
|||
var inners = Object.keys(this._inner); |
|||
for (var i = 0, il = inners.length; i < il; ++i) { |
|||
var key = inners[i]; |
|||
obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null; |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.concat = function (schema) { |
|||
|
|||
Hoek.assert(schema && schema.isJoi, 'Invalid schema object'); |
|||
Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type); |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
if (this._type === 'any' && schema._type !== 'any') { |
|||
|
|||
// Reset values as if we were "this"
|
|||
var tmpObj = schema.clone(); |
|||
var keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit', |
|||
'_notes', '_tags', '_examples', '_meta', '_inner']; |
|||
|
|||
for (var j = 0, jl = keysToRestore.length; j < jl; ++j) { |
|||
tmpObj[keysToRestore[j]] = obj[keysToRestore[j]]; |
|||
} |
|||
|
|||
obj = tmpObj; |
|||
} |
|||
|
|||
obj._settings = obj._settings ? internals.concatSettings(obj._settings, schema._settings) : schema._settings; |
|||
obj._valids.merge(schema._valids, schema._invalids); |
|||
obj._invalids.merge(schema._invalids, schema._valids); |
|||
obj._tests = obj._tests.concat(schema._tests); |
|||
obj._refs = obj._refs.concat(schema._refs); |
|||
Hoek.merge(obj._flags, schema._flags); |
|||
|
|||
obj._description = schema._description || obj._description; |
|||
obj._unit = schema._unit || obj._unit; |
|||
obj._notes = obj._notes.concat(schema._notes); |
|||
obj._tags = obj._tags.concat(schema._tags); |
|||
obj._examples = obj._examples.concat(schema._examples); |
|||
obj._meta = obj._meta.concat(schema._meta); |
|||
|
|||
var inners = Object.keys(schema._inner); |
|||
var isObject = obj._type === 'object'; |
|||
for (var i = 0, il = inners.length; i < il; ++i) { |
|||
var key = inners[i]; |
|||
var source = schema._inner[key]; |
|||
if (source) { |
|||
var target = obj._inner[key]; |
|||
if (target) { |
|||
if (isObject && key === 'children') { |
|||
var keys = {}; |
|||
|
|||
for (var k = 0, kl = target.length; k < kl; ++k) { |
|||
keys[target[k].key] = k; |
|||
} |
|||
|
|||
for (k = 0, kl = source.length; k < kl; ++k) { |
|||
var sourceKey = source[k].key; |
|||
if (keys[sourceKey] >= 0) { |
|||
target[keys[sourceKey]] = { |
|||
key: sourceKey, |
|||
schema: target[keys[sourceKey]].schema.concat(source[k].schema) |
|||
}; |
|||
} |
|||
else { |
|||
target.push(source[k]); |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
obj._inner[key] = obj._inner[key].concat(source); |
|||
} |
|||
} |
|||
else { |
|||
obj._inner[key] = source.slice(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype._test = function (name, arg, func) { |
|||
|
|||
Hoek.assert(!this._flags.allowOnly, 'Cannot define rules when valid values specified'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._tests.push({ func: func, name: name, arg: arg }); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.options = function (options) { |
|||
|
|||
Hoek.assert(!options.context, 'Cannot override context'); |
|||
internals.checkOptions(options); |
|||
|
|||
var obj = this.clone(); |
|||
obj._settings = internals.concatSettings(obj._settings, options); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.strict = function (isStrict) { |
|||
|
|||
var obj = this.clone(); |
|||
obj._settings = obj._settings || {}; |
|||
obj._settings.convert = isStrict === undefined ? false : !isStrict; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.raw = function (isRaw) { |
|||
|
|||
var obj = this.clone(); |
|||
obj._settings = obj._settings || {}; |
|||
obj._settings.raw = isRaw === undefined ? true : isRaw; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype._allow = function () { |
|||
|
|||
var values = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
for (var i = 0, il = values.length; i < il; ++i) { |
|||
var value = values[i]; |
|||
|
|||
Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); |
|||
this._invalids.remove(value); |
|||
this._valids.add(value, this._refs); |
|||
} |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.allow = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._allow.apply(obj, arguments); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.valid = internals.Any.prototype.only = internals.Any.prototype.equal = function () { |
|||
|
|||
Hoek.assert(!this._tests.length, 'Cannot set valid values when rules specified'); |
|||
|
|||
var obj = this.allow.apply(this, arguments); |
|||
obj._flags.allowOnly = true; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.invalid = internals.Any.prototype.disallow = internals.Any.prototype.not = function (value) { |
|||
|
|||
var obj = this.clone(); |
|||
var values = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
for (var i = 0, il = values.length; i < il; ++i) { |
|||
value = values[i]; |
|||
|
|||
Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined'); |
|||
obj._valids.remove(value); |
|||
obj._invalids.add(value, this._refs); |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.required = internals.Any.prototype.exist = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.presence = 'required'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.optional = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.presence = 'optional'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.forbidden = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.presence = 'forbidden'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.strip = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.strip = true; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.applyFunctionToChildren = function (children, fn, args, root) { |
|||
|
|||
children = [].concat(children); |
|||
|
|||
if (children.length !== 1 || children[0] !== '') { |
|||
root = root ? (root + '.') : ''; |
|||
|
|||
var extraChildren = (children[0] === '' ? children.slice(1) : children).map(function (child) { |
|||
|
|||
return root + child; |
|||
}); |
|||
|
|||
throw new Error('unknown key(s) ' + extraChildren.join(', ')); |
|||
} |
|||
|
|||
return this[fn].apply(this, args); |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.default = function (value, description) { |
|||
|
|||
if (typeof value === 'function' && |
|||
!Ref.isRef(value)) { |
|||
|
|||
if (!value.description && |
|||
description) { |
|||
|
|||
value.description = description; |
|||
} |
|||
|
|||
if (!this._flags.func) { |
|||
Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function'); |
|||
} |
|||
} |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.default = value; |
|||
Ref.push(obj._refs, value); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.empty = function (schema) { |
|||
|
|||
var obj; |
|||
if (schema === undefined) { |
|||
obj = this.clone(); |
|||
obj._flags.empty = undefined; |
|||
} |
|||
else { |
|||
schema = Cast.schema(schema); |
|||
|
|||
obj = this.clone(); |
|||
obj._flags.empty = schema; |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.when = function (ref, options) { |
|||
|
|||
Hoek.assert(options && typeof options === 'object', 'Invalid options'); |
|||
Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"'); |
|||
|
|||
var then = options.then ? this.concat(Cast.schema(options.then)) : this; |
|||
var otherwise = options.otherwise ? this.concat(Cast.schema(options.otherwise)) : this; |
|||
|
|||
Alternatives = Alternatives || require('./alternatives'); |
|||
var obj = Alternatives.when(ref, { is: options.is, then: then, otherwise: otherwise }); |
|||
obj._flags.presence = 'ignore'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.description = function (desc) { |
|||
|
|||
Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._description = desc; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.notes = function (notes) { |
|||
|
|||
Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._notes = obj._notes.concat(notes); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.tags = function (tags) { |
|||
|
|||
Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._tags = obj._tags.concat(tags); |
|||
return obj; |
|||
}; |
|||
|
|||
internals.Any.prototype.meta = function (meta) { |
|||
|
|||
Hoek.assert(meta !== undefined, 'Meta cannot be undefined'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._meta = obj._meta.concat(meta); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.example = function (value) { |
|||
|
|||
Hoek.assert(arguments.length, 'Missing example'); |
|||
var result = this._validate(value, null, internals.defaults); |
|||
Hoek.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value)); |
|||
|
|||
var obj = this.clone(); |
|||
obj._examples = obj._examples.concat(value); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.unit = function (name) { |
|||
|
|||
Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._unit = name; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals._try = function (fn, arg) { |
|||
|
|||
var err; |
|||
var result; |
|||
|
|||
try { |
|||
result = fn.call(null, arg); |
|||
} catch (e) { |
|||
err = e; |
|||
} |
|||
|
|||
return { |
|||
value: result, |
|||
error: err |
|||
}; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype._validate = function (value, state, options, reference) { |
|||
|
|||
var self = this; |
|||
var originalValue = value; |
|||
|
|||
// Setup state and settings
|
|||
|
|||
state = state || { key: '', path: '', parent: null, reference: reference }; |
|||
|
|||
if (this._settings) { |
|||
options = internals.concatSettings(options, this._settings); |
|||
} |
|||
|
|||
var errors = []; |
|||
var finish = function () { |
|||
|
|||
var finalValue; |
|||
|
|||
if (!self._flags.strip) { |
|||
if (value !== undefined) { |
|||
finalValue = options.raw ? originalValue : value; |
|||
} |
|||
else if (options.noDefaults) { |
|||
finalValue = originalValue; |
|||
} |
|||
else if (Ref.isRef(self._flags.default)) { |
|||
finalValue = self._flags.default(state.parent, options); |
|||
} |
|||
else if (typeof self._flags.default === 'function' && |
|||
!(self._flags.func && !self._flags.default.description)) { |
|||
|
|||
var arg; |
|||
|
|||
if (state.parent !== null && |
|||
self._flags.default.length > 0) { |
|||
|
|||
arg = Hoek.clone(state.parent); |
|||
} |
|||
|
|||
var defaultValue = internals._try(self._flags.default, arg); |
|||
finalValue = defaultValue.value; |
|||
if (defaultValue.error) { |
|||
errors.push(Errors.create('any.default', defaultValue.error, state, options)); |
|||
} |
|||
} |
|||
else { |
|||
finalValue = Hoek.clone(self._flags.default); |
|||
} |
|||
} |
|||
|
|||
return { |
|||
value: finalValue, |
|||
errors: errors.length ? errors : null |
|||
}; |
|||
}; |
|||
|
|||
// Check presence requirements
|
|||
|
|||
var presence = this._flags.presence || options.presence; |
|||
if (presence === 'optional') { |
|||
if (value === undefined) { |
|||
var isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined; |
|||
if (isDeepDefault && this._type === 'object') { |
|||
value = {}; |
|||
} |
|||
else { |
|||
return finish(); |
|||
} |
|||
} |
|||
} |
|||
else if (presence === 'required' && |
|||
value === undefined) { |
|||
|
|||
errors.push(Errors.create('any.required', null, state, options)); |
|||
return finish(); |
|||
} |
|||
else if (presence === 'forbidden') { |
|||
if (value === undefined) { |
|||
return finish(); |
|||
} |
|||
|
|||
errors.push(Errors.create('any.unknown', null, state, options)); |
|||
return finish(); |
|||
} |
|||
|
|||
if (this._flags.empty && !this._flags.empty._validate(value, null, internals.defaults).errors) { |
|||
value = undefined; |
|||
return finish(); |
|||
} |
|||
|
|||
// Check allowed and denied values using the original value
|
|||
|
|||
if (this._valids.has(value, state, options, this._flags.insensitive)) { |
|||
return finish(); |
|||
} |
|||
|
|||
if (this._invalids.has(value, state, options, this._flags.insensitive)) { |
|||
errors.push(Errors.create(value === '' ? 'any.empty' : 'any.invalid', null, state, options)); |
|||
if (options.abortEarly || |
|||
value === undefined) { // No reason to keep validating missing value
|
|||
|
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
// Convert value and validate type
|
|||
|
|||
if (this._base) { |
|||
var base = this._base.call(this, value, state, options); |
|||
if (base.errors) { |
|||
value = base.value; |
|||
errors = errors.concat(base.errors); |
|||
return finish(); // Base error always aborts early
|
|||
} |
|||
|
|||
if (base.value !== value) { |
|||
value = base.value; |
|||
|
|||
// Check allowed and denied values using the converted value
|
|||
|
|||
if (this._valids.has(value, state, options, this._flags.insensitive)) { |
|||
return finish(); |
|||
} |
|||
|
|||
if (this._invalids.has(value, state, options, this._flags.insensitive)) { |
|||
errors.push(Errors.create('any.invalid', null, state, options)); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Required values did not match
|
|||
|
|||
if (this._flags.allowOnly) { |
|||
errors.push(Errors.create('any.allowOnly', { valids: this._valids.values({ stripUndefined: true }) }, state, options)); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
// Helper.validate tests
|
|||
|
|||
for (var i = 0, il = this._tests.length; i < il; ++i) { |
|||
var test = this._tests[i]; |
|||
var err = test.func.call(this, value, state, options); |
|||
if (err) { |
|||
errors.push(err); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return finish(); |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype._validateWithOptions = function (value, options, callback) { |
|||
|
|||
if (options) { |
|||
internals.checkOptions(options); |
|||
} |
|||
|
|||
var settings = internals.concatSettings(internals.defaults, options); |
|||
var result = this._validate(value, null, settings); |
|||
var errors = Errors.process(result.errors, value); |
|||
|
|||
if (callback) { |
|||
return callback(errors, result.value); |
|||
} |
|||
|
|||
return { error: errors, value: result.value }; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.validate = function (value, callback) { |
|||
|
|||
var result = this._validate(value, null, internals.defaults); |
|||
var errors = Errors.process(result.errors, value); |
|||
|
|||
if (callback) { |
|||
return callback(errors, result.value); |
|||
} |
|||
|
|||
return { error: errors, value: result.value }; |
|||
}; |
|||
|
|||
|
|||
internals.Any.prototype.describe = function () { |
|||
|
|||
var description = { |
|||
type: this._type |
|||
}; |
|||
|
|||
var flags = Object.keys(this._flags); |
|||
if (flags.length) { |
|||
if (this._flags.empty) { |
|||
description.flags = {}; |
|||
for (var f = 0, fl = flags.length; f < fl; ++f) { |
|||
var flag = flags[f]; |
|||
description.flags[flag] = flag === 'empty' ? this._flags[flag].describe() : this._flags[flag]; |
|||
} |
|||
} |
|||
else { |
|||
description.flags = this._flags; |
|||
} |
|||
} |
|||
|
|||
if (this._description) { |
|||
description.description = this._description; |
|||
} |
|||
|
|||
if (this._notes.length) { |
|||
description.notes = this._notes; |
|||
} |
|||
|
|||
if (this._tags.length) { |
|||
description.tags = this._tags; |
|||
} |
|||
|
|||
if (this._meta.length) { |
|||
description.meta = this._meta; |
|||
} |
|||
|
|||
if (this._examples.length) { |
|||
description.examples = this._examples; |
|||
} |
|||
|
|||
if (this._unit) { |
|||
description.unit = this._unit; |
|||
} |
|||
|
|||
var valids = this._valids.values(); |
|||
if (valids.length) { |
|||
description.valids = valids; |
|||
} |
|||
|
|||
var invalids = this._invalids.values(); |
|||
if (invalids.length) { |
|||
description.invalids = invalids; |
|||
} |
|||
|
|||
description.rules = []; |
|||
|
|||
for (var i = 0, il = this._tests.length; i < il; ++i) { |
|||
var validator = this._tests[i]; |
|||
var item = { name: validator.name }; |
|||
if (validator.arg !== void 0) { |
|||
item.arg = validator.arg; |
|||
} |
|||
description.rules.push(item); |
|||
} |
|||
|
|||
if (!description.rules.length) { |
|||
delete description.rules; |
|||
} |
|||
|
|||
var label = Hoek.reach(this._settings, 'language.label'); |
|||
if (label) { |
|||
description.label = label; |
|||
} |
|||
|
|||
return description; |
|||
}; |
|||
|
|||
internals.Any.prototype.label = function (name) { |
|||
|
|||
Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string'); |
|||
|
|||
var obj = this.clone(); |
|||
var options = { language: { label: name } }; |
|||
|
|||
// If language.label is set, it should override this label
|
|||
obj._settings = internals.concatSettings(options, obj._settings); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
// Set
|
|||
|
|||
internals.Set = function () { |
|||
|
|||
this._set = []; |
|||
}; |
|||
|
|||
|
|||
internals.Set.prototype.add = function (value, refs) { |
|||
|
|||
Hoek.assert(value === null || value === undefined || value instanceof Date || Buffer.isBuffer(value) || Ref.isRef(value) || (typeof value !== 'function' && typeof value !== 'object'), 'Value cannot be an object or function'); |
|||
|
|||
if (typeof value !== 'function' && |
|||
this.has(value, null, null, false)) { |
|||
|
|||
return; |
|||
} |
|||
|
|||
Ref.push(refs, value); |
|||
this._set.push(value); |
|||
}; |
|||
|
|||
|
|||
internals.Set.prototype.merge = function (add, remove) { |
|||
|
|||
for (var i = 0, il = add._set.length; i < il; ++i) { |
|||
this.add(add._set[i]); |
|||
} |
|||
|
|||
for (i = 0, il = remove._set.length; i < il; ++i) { |
|||
this.remove(remove._set[i]); |
|||
} |
|||
}; |
|||
|
|||
|
|||
internals.Set.prototype.remove = function (value) { |
|||
|
|||
this._set = this._set.filter(function (item) { |
|||
|
|||
return value !== item; |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Set.prototype.has = function (value, state, options, insensitive) { |
|||
|
|||
for (var i = 0, il = this._set.length; i < il; ++i) { |
|||
var items = this._set[i]; |
|||
|
|||
if (Ref.isRef(items)) { |
|||
items = items(state.reference || state.parent, options); |
|||
} |
|||
|
|||
if (!Array.isArray(items)) { |
|||
items = [items]; |
|||
} |
|||
|
|||
for (var j = 0, jl = items.length; j < jl; ++j) { |
|||
var item = items[j]; |
|||
if (typeof value !== typeof item) { |
|||
continue; |
|||
} |
|||
|
|||
if (value === item || |
|||
(value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) || |
|||
(insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) || |
|||
(Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) { |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
}; |
|||
|
|||
|
|||
internals.Set.prototype.values = function (options) { |
|||
|
|||
if (options && options.stripUndefined) { |
|||
var values = []; |
|||
|
|||
for (var i = 0, il = this._set.length; i < il; ++i) { |
|||
var item = this._set[i]; |
|||
if (item !== undefined) { |
|||
values.push(item); |
|||
} |
|||
} |
|||
|
|||
return values; |
|||
} |
|||
|
|||
return this._set.slice(); |
|||
}; |
|||
|
|||
|
|||
internals.concatSettings = function (target, source) { |
|||
|
|||
// Used to avoid cloning context
|
|||
|
|||
if (!target && |
|||
!source) { |
|||
|
|||
return null; |
|||
} |
|||
|
|||
var key, obj = {}; |
|||
|
|||
if (target) { |
|||
var tKeys = Object.keys(target); |
|||
for (var i = 0, il = tKeys.length; i < il; ++i) { |
|||
key = tKeys[i]; |
|||
obj[key] = target[key]; |
|||
} |
|||
} |
|||
|
|||
if (source) { |
|||
var sKeys = Object.keys(source); |
|||
for (var j = 0, jl = sKeys.length; j < jl; ++j) { |
|||
key = sKeys[j]; |
|||
if (key !== 'language' || |
|||
!obj.hasOwnProperty(key)) { |
|||
|
|||
obj[key] = source[key]; |
|||
} |
|||
else { |
|||
obj[key] = Hoek.applyToDefaults(obj[key], source[key]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return obj; |
|||
}; |
@ -0,0 +1,517 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Cast = require('./cast'); |
|||
var Errors = require('./errors'); |
|||
var Hoek = require('hoek'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.fastSplice = function (arr, i) { |
|||
|
|||
var il = arr.length; |
|||
var pos = i; |
|||
|
|||
while (pos < il) { |
|||
arr[pos++] = arr[pos]; |
|||
} |
|||
|
|||
--arr.length; |
|||
}; |
|||
|
|||
|
|||
internals.Array = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'array'; |
|||
this._inner.items = []; |
|||
this._inner.ordereds = []; |
|||
this._inner.inclusions = []; |
|||
this._inner.exclusions = []; |
|||
this._inner.requireds = []; |
|||
this._flags.sparse = false; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Array, Any); |
|||
|
|||
|
|||
internals.Array.prototype._base = function (value, state, options) { |
|||
|
|||
var result = { |
|||
value: value |
|||
}; |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
try { |
|||
var converted = JSON.parse(value); |
|||
if (Array.isArray(converted)) { |
|||
result.value = converted; |
|||
} |
|||
} |
|||
catch (e) { } |
|||
} |
|||
|
|||
var isArray = Array.isArray(result.value); |
|||
var wasArray = isArray; |
|||
if (options.convert && this._flags.single && !isArray) { |
|||
result.value = [result.value]; |
|||
isArray = true; |
|||
} |
|||
|
|||
if (!isArray) { |
|||
result.errors = Errors.create('array.base', null, state, options); |
|||
return result; |
|||
} |
|||
|
|||
if (this._inner.inclusions.length || |
|||
this._inner.exclusions.length || |
|||
!this._flags.sparse) { |
|||
|
|||
// Clone the array so that we don't modify the original
|
|||
if (wasArray) { |
|||
result.value = result.value.slice(0); |
|||
} |
|||
|
|||
result.errors = internals.checkItems.call(this, result.value, wasArray, state, options); |
|||
|
|||
if (result.errors && wasArray && options.convert && this._flags.single) { |
|||
|
|||
// Attempt a 2nd pass by putting the array inside one.
|
|||
var previousErrors = result.errors; |
|||
|
|||
result.value = [result.value]; |
|||
result.errors = internals.checkItems.call(this, result.value, wasArray, state, options); |
|||
|
|||
if (result.errors) { |
|||
|
|||
// Restore previous errors and value since this didn't validate either.
|
|||
result.errors = previousErrors; |
|||
result.value = result.value[0]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
internals.checkItems = function (items, wasArray, state, options) { |
|||
|
|||
var errors = []; |
|||
var errored; |
|||
|
|||
var requireds = this._inner.requireds.slice(); |
|||
var ordereds = this._inner.ordereds.slice(); |
|||
var inclusions = this._inner.inclusions.concat(requireds); |
|||
|
|||
for (var v = 0, vl = items.length; v < vl; ++v) { |
|||
errored = false; |
|||
var item = items[v]; |
|||
var isValid = false; |
|||
var localState = { key: v, path: (state.path ? state.path + '.' : '') + v, parent: items, reference: state.reference }; |
|||
var res; |
|||
|
|||
// Sparse
|
|||
|
|||
if (!this._flags.sparse && item === undefined) { |
|||
errors.push(Errors.create('array.sparse', null, { key: state.key, path: localState.path }, options)); |
|||
|
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
|
|||
continue; |
|||
} |
|||
|
|||
// Exclusions
|
|||
|
|||
for (var i = 0, il = this._inner.exclusions.length; i < il; ++i) { |
|||
res = this._inner.exclusions[i]._validate(item, localState, {}); // Not passing options to use defaults
|
|||
|
|||
if (!res.errors) { |
|||
errors.push(Errors.create(wasArray ? 'array.excludes' : 'array.excludesSingle', { pos: v, value: item }, { key: state.key, path: localState.path }, options)); |
|||
errored = true; |
|||
|
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (errored) { |
|||
continue; |
|||
} |
|||
|
|||
// Ordered
|
|||
if (this._inner.ordereds.length) { |
|||
if (ordereds.length > 0) { |
|||
var ordered = ordereds.shift(); |
|||
res = ordered._validate(item, localState, options); |
|||
if (!res.errors) { |
|||
if (ordered._flags.strip) { |
|||
internals.fastSplice(items, v); |
|||
--v; |
|||
--vl; |
|||
} |
|||
else { |
|||
items[v] = res.value; |
|||
} |
|||
} |
|||
else { |
|||
errors.push(Errors.create('array.ordered', { pos: v, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options)); |
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
} |
|||
continue; |
|||
} |
|||
else if (!this._inner.items.length) { |
|||
errors.push(Errors.create('array.orderedLength', { pos: v, limit: this._inner.ordereds.length }, { key: state.key, path: localState.path }, options)); |
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
// Requireds
|
|||
|
|||
var requiredChecks = []; |
|||
for (i = 0, il = requireds.length; i < il; ++i) { |
|||
res = requiredChecks[i] = requireds[i]._validate(item, localState, options); |
|||
if (!res.errors) { |
|||
items[v] = res.value; |
|||
isValid = true; |
|||
internals.fastSplice(requireds, i); |
|||
--i; |
|||
--il; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (isValid) { |
|||
continue; |
|||
} |
|||
|
|||
// Inclusions
|
|||
|
|||
for (i = 0, il = inclusions.length; i < il; ++i) { |
|||
var inclusion = inclusions[i]; |
|||
|
|||
// Avoid re-running requireds that already didn't match in the previous loop
|
|||
var previousCheck = requireds.indexOf(inclusion); |
|||
if (previousCheck !== -1) { |
|||
res = requiredChecks[previousCheck]; |
|||
} |
|||
else { |
|||
res = inclusion._validate(item, localState, options); |
|||
|
|||
if (!res.errors) { |
|||
if (inclusion._flags.strip) { |
|||
internals.fastSplice(items, v); |
|||
--v; |
|||
--vl; |
|||
} |
|||
else { |
|||
items[v] = res.value; |
|||
} |
|||
isValid = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// Return the actual error if only one inclusion defined
|
|||
if (il === 1) { |
|||
if (options.stripUnknown) { |
|||
internals.fastSplice(items, v); |
|||
--v; |
|||
--vl; |
|||
isValid = true; |
|||
break; |
|||
} |
|||
|
|||
errors.push(Errors.create(wasArray ? 'array.includesOne' : 'array.includesOneSingle', { pos: v, reason: res.errors, value: item }, { key: state.key, path: localState.path }, options)); |
|||
errored = true; |
|||
|
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (errored) { |
|||
continue; |
|||
} |
|||
|
|||
if (this._inner.inclusions.length && !isValid) { |
|||
if (options.stripUnknown) { |
|||
internals.fastSplice(items, v); |
|||
--v; |
|||
--vl; |
|||
continue; |
|||
} |
|||
|
|||
errors.push(Errors.create(wasArray ? 'array.includes' : 'array.includesSingle', { pos: v, value: item }, { key: state.key, path: localState.path }, options)); |
|||
|
|||
if (options.abortEarly) { |
|||
return errors; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (requireds.length) { |
|||
internals.fillMissedErrors(errors, requireds, state, options); |
|||
} |
|||
|
|||
if (ordereds.length) { |
|||
internals.fillOrderedErrors(errors, ordereds, state, options); |
|||
} |
|||
|
|||
return errors.length ? errors : null; |
|||
}; |
|||
|
|||
internals.fillMissedErrors = function (errors, requireds, state, options) { |
|||
|
|||
var knownMisses = []; |
|||
var unknownMisses = 0; |
|||
for (var i = 0, il = requireds.length; i < il; ++i) { |
|||
var label = Hoek.reach(requireds[i], '_settings.language.label'); |
|||
if (label) { |
|||
knownMisses.push(label); |
|||
} |
|||
else { |
|||
++unknownMisses; |
|||
} |
|||
} |
|||
|
|||
if (knownMisses.length) { |
|||
if (unknownMisses) { |
|||
errors.push(Errors.create('array.includesRequiredBoth', { knownMisses: knownMisses, unknownMisses: unknownMisses }, { key: state.key, path: state.patk }, options)); |
|||
} |
|||
else { |
|||
errors.push(Errors.create('array.includesRequiredKnowns', { knownMisses: knownMisses }, { key: state.key, path: state.path }, options)); |
|||
} |
|||
} |
|||
else { |
|||
errors.push(Errors.create('array.includesRequiredUnknowns', { unknownMisses: unknownMisses }, { key: state.key, path: state.path }, options)); |
|||
} |
|||
}; |
|||
|
|||
internals.fillOrderedErrors = function (errors, ordereds, state, options) { |
|||
|
|||
var requiredOrdereds = []; |
|||
|
|||
for (var i = 0, il = ordereds.length; i < il; ++i) { |
|||
var presence = Hoek.reach(ordereds[i], '_flags.presence'); |
|||
if (presence === 'required') { |
|||
requiredOrdereds.push(ordereds[i]); |
|||
} |
|||
} |
|||
|
|||
if (requiredOrdereds.length) { |
|||
internals.fillMissedErrors(errors, requiredOrdereds, state, options); |
|||
} |
|||
}; |
|||
|
|||
internals.Array.prototype.describe = function () { |
|||
|
|||
var description = Any.prototype.describe.call(this); |
|||
|
|||
if (this._inner.ordereds.length) { |
|||
description.orderedItems = []; |
|||
|
|||
for (var o = 0, ol = this._inner.ordereds.length; o < ol; ++o) { |
|||
description.orderedItems.push(this._inner.ordereds[o].describe()); |
|||
} |
|||
} |
|||
|
|||
if (this._inner.items.length) { |
|||
description.items = []; |
|||
|
|||
for (var i = 0, il = this._inner.items.length; i < il; ++i) { |
|||
description.items.push(this._inner.items[i].describe()); |
|||
} |
|||
} |
|||
|
|||
return description; |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.items = function () { |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
Hoek.flatten(Array.prototype.slice.call(arguments)).forEach(function (type, index) { |
|||
|
|||
try { |
|||
type = Cast.schema(type); |
|||
} |
|||
catch (castErr) { |
|||
if (castErr.hasOwnProperty('path')) { |
|||
castErr.path = index + '.' + castErr.path; |
|||
} |
|||
else { |
|||
castErr.path = index; |
|||
} |
|||
castErr.message += '(' + castErr.path + ')'; |
|||
throw castErr; |
|||
} |
|||
|
|||
obj._inner.items.push(type); |
|||
|
|||
if (type._flags.presence === 'required') { |
|||
obj._inner.requireds.push(type); |
|||
} |
|||
else if (type._flags.presence === 'forbidden') { |
|||
obj._inner.exclusions.push(type.optional()); |
|||
} |
|||
else { |
|||
obj._inner.inclusions.push(type); |
|||
} |
|||
}); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.ordered = function () { |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
Hoek.flatten(Array.prototype.slice.call(arguments)).forEach(function (type, index) { |
|||
|
|||
try { |
|||
type = Cast.schema(type); |
|||
} |
|||
catch (castErr) { |
|||
if (castErr.hasOwnProperty('path')) { |
|||
castErr.path = index + '.' + castErr.path; |
|||
} |
|||
else { |
|||
castErr.path = index; |
|||
} |
|||
castErr.message += '(' + castErr.path + ')'; |
|||
throw castErr; |
|||
} |
|||
obj._inner.ordereds.push(type); |
|||
}); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.min = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('min', limit, function (value, state, options) { |
|||
|
|||
if (value.length >= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('array.min', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.max = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('max', limit, function (value, state, options) { |
|||
|
|||
if (value.length <= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('array.max', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.length = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('length', limit, function (value, state, options) { |
|||
|
|||
if (value.length === limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('array.length', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.unique = function () { |
|||
|
|||
return this._test('unique', undefined, function (value, state, options) { |
|||
|
|||
var found = { |
|||
string: {}, |
|||
number: {}, |
|||
undefined: {}, |
|||
boolean: {}, |
|||
object: [], |
|||
function: [] |
|||
}; |
|||
|
|||
for (var i = 0, il = value.length; i < il; ++i) { |
|||
var item = value[i]; |
|||
var type = typeof item; |
|||
var records = found[type]; |
|||
|
|||
// All available types are supported, so it's not possible to reach 100% coverage without ignoring this line.
|
|||
// I still want to keep the test for future js versions with new types (eg. Symbol).
|
|||
if (/* $lab:coverage:off$ */ records /* $lab:coverage:on$ */) { |
|||
if (Array.isArray(records)) { |
|||
for (var r = 0, rl = records.length; r < rl; ++r) { |
|||
if (Hoek.deepEqual(records[r], item)) { |
|||
return Errors.create('array.unique', { pos: i, value: item }, state, options); |
|||
} |
|||
} |
|||
|
|||
records.push(item); |
|||
} |
|||
else { |
|||
if (records[item]) { |
|||
return Errors.create('array.unique', { pos: i, value: item }, state, options); |
|||
} |
|||
|
|||
records[item] = true; |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.sparse = function (enabled) { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.sparse = enabled === undefined ? true : !!enabled; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Array.prototype.single = function (enabled) { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.single = enabled === undefined ? true : !!enabled; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Array(); |
@ -0,0 +1,98 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Errors = require('./errors'); |
|||
var Hoek = require('hoek'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.Binary = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'binary'; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Binary, Any); |
|||
|
|||
|
|||
internals.Binary.prototype._base = function (value, state, options) { |
|||
|
|||
var result = { |
|||
value: value |
|||
}; |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
try { |
|||
var converted = new Buffer(value, this._flags.encoding); |
|||
result.value = converted; |
|||
} |
|||
catch (e) { } |
|||
} |
|||
|
|||
result.errors = Buffer.isBuffer(result.value) ? null : Errors.create('binary.base', null, state, options); |
|||
return result; |
|||
}; |
|||
|
|||
|
|||
internals.Binary.prototype.encoding = function (encoding) { |
|||
|
|||
Hoek.assert(Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.encoding = encoding; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Binary.prototype.min = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('min', limit, function (value, state, options) { |
|||
|
|||
if (value.length >= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('binary.min', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Binary.prototype.max = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('max', limit, function (value, state, options) { |
|||
|
|||
if (value.length <= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('binary.max', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Binary.prototype.length = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('length', limit, function (value, state, options) { |
|||
|
|||
if (value.length === limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('binary.length', { limit: limit, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Binary(); |
@ -0,0 +1,41 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Errors = require('./errors'); |
|||
var Hoek = require('hoek'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.Boolean = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'boolean'; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Boolean, Any); |
|||
|
|||
|
|||
internals.Boolean.prototype._base = function (value, state, options) { |
|||
|
|||
var result = { |
|||
value: value |
|||
}; |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
var lower = value.toLowerCase(); |
|||
result.value = (lower === 'true' || lower === 'yes' || lower === 'on' ? true |
|||
: (lower === 'false' || lower === 'no' || lower === 'off' ? false : value)); |
|||
} |
|||
|
|||
result.errors = (typeof result.value === 'boolean') ? null : Errors.create('boolean.base', null, state, options); |
|||
return result; |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Boolean(); |
@ -0,0 +1,75 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
var Ref = require('./ref'); |
|||
|
|||
// Type modules are delay-loaded to prevent circular dependencies
|
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = { |
|||
any: null, |
|||
date: require('./date'), |
|||
string: require('./string'), |
|||
number: require('./number'), |
|||
boolean: require('./boolean'), |
|||
alt: null, |
|||
object: null |
|||
}; |
|||
|
|||
|
|||
exports.schema = function (config) { |
|||
|
|||
internals.any = internals.any || new (require('./any'))(); |
|||
internals.alt = internals.alt || require('./alternatives'); |
|||
internals.object = internals.object || require('./object'); |
|||
|
|||
if (config && |
|||
typeof config === 'object') { |
|||
|
|||
if (config.isJoi) { |
|||
return config; |
|||
} |
|||
|
|||
if (Array.isArray(config)) { |
|||
return internals.alt.try(config); |
|||
} |
|||
|
|||
if (config instanceof RegExp) { |
|||
return internals.string.regex(config); |
|||
} |
|||
|
|||
if (config instanceof Date) { |
|||
return internals.date.valid(config); |
|||
} |
|||
|
|||
return internals.object.keys(config); |
|||
} |
|||
|
|||
if (typeof config === 'string') { |
|||
return internals.string.valid(config); |
|||
} |
|||
|
|||
if (typeof config === 'number') { |
|||
return internals.number.valid(config); |
|||
} |
|||
|
|||
if (typeof config === 'boolean') { |
|||
return internals.boolean.valid(config); |
|||
} |
|||
|
|||
if (Ref.isRef(config)) { |
|||
return internals.any.valid(config); |
|||
} |
|||
|
|||
Hoek.assert(config === null, 'Invalid schema content:', config); |
|||
|
|||
return internals.any.valid(null); |
|||
}; |
|||
|
|||
|
|||
exports.ref = function (id) { |
|||
|
|||
return Ref.isRef(id) ? id : Ref.create(id); |
|||
}; |
@ -0,0 +1,168 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Errors = require('./errors'); |
|||
var Ref = require('./ref'); |
|||
var Hoek = require('hoek'); |
|||
var Moment = require('moment'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
internals.isoDate = /^(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24\:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/; |
|||
internals.invalidDate = new Date(''); |
|||
internals.isIsoDate = (function () { |
|||
|
|||
var isoString = internals.isoDate.toString(); |
|||
|
|||
return function (date) { |
|||
|
|||
return date && (date.toString() === isoString); |
|||
}; |
|||
})(); |
|||
|
|||
internals.Date = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'date'; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Date, Any); |
|||
|
|||
|
|||
internals.Date.prototype._base = function (value, state, options) { |
|||
|
|||
var result = { |
|||
value: (options.convert && internals.toDate(value, this._flags.format)) || value |
|||
}; |
|||
|
|||
if (result.value instanceof Date && !isNaN(result.value.getTime())) { |
|||
result.errors = null; |
|||
} |
|||
else { |
|||
result.errors = Errors.create(internals.isIsoDate(this._flags.format) ? 'date.isoDate' : 'date.base', null, state, options); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
internals.toDate = function (value, format) { |
|||
|
|||
if (value instanceof Date) { |
|||
return value; |
|||
} |
|||
|
|||
if (typeof value === 'string' || |
|||
Hoek.isInteger(value)) { |
|||
|
|||
if (typeof value === 'string' && |
|||
/^[+-]?\d+$/.test(value)) { |
|||
|
|||
value = parseInt(value, 10); |
|||
} |
|||
|
|||
var date; |
|||
if (format) { |
|||
if (internals.isIsoDate(format)) { |
|||
date = format.test(value) ? new Date(value) : internals.invalidDate; |
|||
} |
|||
else { |
|||
date = Moment(value, format, true); |
|||
date = date.isValid() ? date.toDate() : internals.invalidDate; |
|||
} |
|||
} |
|||
else { |
|||
date = new Date(value); |
|||
} |
|||
|
|||
if (!isNaN(date.getTime())) { |
|||
return date; |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
}; |
|||
|
|||
|
|||
internals.compare = function (type, compare) { |
|||
|
|||
return function (date) { |
|||
|
|||
var isNow = date === 'now'; |
|||
var isRef = Ref.isRef(date); |
|||
|
|||
if (!isNow && !isRef) { |
|||
date = internals.toDate(date); |
|||
} |
|||
|
|||
Hoek.assert(date, 'Invalid date format'); |
|||
|
|||
return this._test(type, date, function (value, state, options) { |
|||
|
|||
var compareTo; |
|||
if (isNow) { |
|||
compareTo = Date.now(); |
|||
} |
|||
else if (isRef) { |
|||
compareTo = internals.toDate(date(state.parent, options)); |
|||
|
|||
if (!compareTo) { |
|||
return Errors.create('date.ref', { ref: date.key }, state, options); |
|||
} |
|||
|
|||
compareTo = compareTo.getTime(); |
|||
} |
|||
else { |
|||
compareTo = date.getTime(); |
|||
} |
|||
|
|||
if (compare(value.getTime(), compareTo)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('date.' + type, { limit: new Date(compareTo) }, state, options); |
|||
}); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
internals.Date.prototype.min = internals.compare('min', function (value, date) { |
|||
|
|||
return value >= date; |
|||
}); |
|||
|
|||
|
|||
internals.Date.prototype.max = internals.compare('max', function (value, date) { |
|||
|
|||
return value <= date; |
|||
}); |
|||
|
|||
|
|||
internals.Date.prototype.format = function (format) { |
|||
|
|||
Hoek.assert(typeof format === 'string' || (Array.isArray(format) && format.every(function (f) { |
|||
|
|||
return typeof f === 'string'; |
|||
})), 'Invalid format.'); |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.format = format; |
|||
return obj; |
|||
}; |
|||
|
|||
internals.Date.prototype.iso = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.format = internals.isoDate; |
|||
return obj; |
|||
}; |
|||
|
|||
internals.Date.prototype._isIsoDate = function (value) { |
|||
|
|||
return internals.isoDate.test(value); |
|||
}; |
|||
|
|||
module.exports = new internals.Date(); |
@ -0,0 +1,297 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
var Language = require('./language'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
internals.stringify = function (value, wrapArrays) { |
|||
|
|||
var type = typeof value; |
|||
|
|||
if (value === null) { |
|||
return 'null'; |
|||
} |
|||
|
|||
if (type === 'string') { |
|||
return value; |
|||
} |
|||
|
|||
if (value instanceof internals.Err || type === 'function') { |
|||
return value.toString(); |
|||
} |
|||
|
|||
if (type === 'object') { |
|||
if (Array.isArray(value)) { |
|||
var partial = ''; |
|||
|
|||
for (var i = 0, il = value.length; i < il; ++i) { |
|||
partial += (partial.length ? ', ' : '') + internals.stringify(value[i], wrapArrays); |
|||
} |
|||
|
|||
return wrapArrays ? '[' + partial + ']' : partial; |
|||
} |
|||
|
|||
return value.toString(); |
|||
} |
|||
|
|||
return JSON.stringify(value); |
|||
}; |
|||
|
|||
internals.Err = function (type, context, state, options) { |
|||
|
|||
this.type = type; |
|||
this.context = context || {}; |
|||
this.context.key = state.key; |
|||
this.path = state.path; |
|||
this.options = options; |
|||
}; |
|||
|
|||
|
|||
internals.Err.prototype.toString = function () { |
|||
|
|||
var self = this; |
|||
|
|||
var localized = this.options.language; |
|||
|
|||
if (localized.label) { |
|||
this.context.key = localized.label; |
|||
} |
|||
else if (this.context.key === '' || this.context.key === null) { |
|||
this.context.key = localized.root || Language.errors.root; |
|||
} |
|||
|
|||
var format = Hoek.reach(localized, this.type) || Hoek.reach(Language.errors, this.type); |
|||
var hasKey = /\{\{\!?key\}\}/.test(format); |
|||
var skipKey = format.length > 2 && format[0] === '!' && format[1] === '!'; |
|||
|
|||
if (skipKey) { |
|||
format = format.slice(2); |
|||
} |
|||
|
|||
if (!hasKey && !skipKey) { |
|||
format = (Hoek.reach(localized, 'key') || Hoek.reach(Language.errors, 'key')) + format; |
|||
} |
|||
|
|||
var wrapArrays = Hoek.reach(localized, 'messages.wrapArrays'); |
|||
if (typeof wrapArrays !== 'boolean') { |
|||
wrapArrays = Language.errors.messages.wrapArrays; |
|||
} |
|||
|
|||
var message = format.replace(/\{\{(\!?)([^}]+)\}\}/g, function ($0, isSecure, name) { |
|||
|
|||
var value = Hoek.reach(self.context, name); |
|||
var normalized = internals.stringify(value, wrapArrays); |
|||
return (isSecure ? Hoek.escapeHtml(normalized) : normalized); |
|||
}); |
|||
|
|||
return message; |
|||
}; |
|||
|
|||
|
|||
exports.create = function (type, context, state, options) { |
|||
|
|||
return new internals.Err(type, context, state, options); |
|||
}; |
|||
|
|||
|
|||
exports.process = function (errors, object) { |
|||
|
|||
if (!errors || !errors.length) { |
|||
return null; |
|||
} |
|||
|
|||
// Construct error
|
|||
|
|||
var message = ''; |
|||
var details = []; |
|||
|
|||
var processErrors = function (localErrors, parent) { |
|||
|
|||
for (var i = 0, il = localErrors.length; i < il; ++i) { |
|||
var item = localErrors[i]; |
|||
|
|||
var detail = { |
|||
message: item.toString(), |
|||
path: internals.getPath(item), |
|||
type: item.type, |
|||
context: item.context |
|||
}; |
|||
|
|||
if (!parent) { |
|||
message += (message ? '. ' : '') + detail.message; |
|||
} |
|||
|
|||
// Do not push intermediate errors, we're only interested in leafs
|
|||
if (item.context.reason && item.context.reason.length) { |
|||
processErrors(item.context.reason, item.path); |
|||
} |
|||
else { |
|||
details.push(detail); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
processErrors(errors); |
|||
|
|||
var error = new Error(message); |
|||
error.name = 'ValidationError'; |
|||
error.details = details; |
|||
error._object = object; |
|||
error.annotate = internals.annotate; |
|||
return error; |
|||
}; |
|||
|
|||
|
|||
internals.getPath = function (item) { |
|||
|
|||
var recursePath = function (it) { |
|||
|
|||
var reachedItem = Hoek.reach(it, 'context.reason.0'); |
|||
if (reachedItem && reachedItem.context) { |
|||
return recursePath(reachedItem); |
|||
} |
|||
|
|||
return it.path; |
|||
}; |
|||
|
|||
return recursePath(item) || item.context.key; |
|||
}; |
|||
|
|||
|
|||
// Inspired by json-stringify-safe
|
|||
internals.safeStringify = function (obj, spaces) { |
|||
|
|||
return JSON.stringify(obj, internals.serializer(), spaces); |
|||
}; |
|||
|
|||
internals.serializer = function () { |
|||
|
|||
var cycleReplacer = function (key, value) { |
|||
|
|||
if (stack[0] === value) { |
|||
return '[Circular ~]'; |
|||
} |
|||
|
|||
return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; |
|||
}; |
|||
|
|||
var keys = [], stack = []; |
|||
|
|||
return function (key, value) { |
|||
|
|||
if (stack.length > 0) { |
|||
var thisPos = stack.indexOf(this); |
|||
if (~thisPos) { |
|||
stack.length = thisPos + 1; |
|||
keys.length = thisPos + 1; |
|||
keys[thisPos] = key; |
|||
} |
|||
else { |
|||
stack.push(this); |
|||
keys.push(key); |
|||
} |
|||
|
|||
if (~stack.indexOf(value)) { |
|||
value = cycleReplacer.call(this, key, value); |
|||
} |
|||
} |
|||
else { |
|||
stack.push(value); |
|||
} |
|||
|
|||
if (Array.isArray(value) && value.placeholders) { |
|||
var placeholders = value.placeholders; |
|||
var arrWithPlaceholders = []; |
|||
for (var i = 0, il = value.length; i < il; ++i) { |
|||
if (placeholders[i]) { |
|||
arrWithPlaceholders.push(placeholders[i]); |
|||
} |
|||
arrWithPlaceholders.push(value[i]); |
|||
} |
|||
|
|||
value = arrWithPlaceholders; |
|||
} |
|||
|
|||
return value; |
|||
}; |
|||
}; |
|||
|
|||
|
|||
internals.annotate = function () { |
|||
|
|||
var obj = Hoek.clone(this._object || {}); |
|||
|
|||
var lookup = {}; |
|||
var el = this.details.length; |
|||
for (var e = el - 1; e >= 0; --e) { // Reverse order to process deepest child first
|
|||
var pos = el - e; |
|||
var error = this.details[e]; |
|||
var path = error.path.split('.'); |
|||
var ref = obj; |
|||
for (var i = 0, il = path.length; i < il && ref; ++i) { |
|||
var seg = path[i]; |
|||
if (i + 1 < il) { |
|||
ref = ref[seg]; |
|||
} |
|||
else { |
|||
var value = ref[seg]; |
|||
if (Array.isArray(ref)) { |
|||
var arrayLabel = '_$idx$_' + (e + 1) + '_$end$_'; |
|||
if (!ref.placeholders) { |
|||
ref.placeholders = {}; |
|||
} |
|||
|
|||
if (ref.placeholders[seg]) { |
|||
ref.placeholders[seg] = ref.placeholders[seg].replace('_$end$_', ', ' + (e + 1) + '_$end$_'); |
|||
} |
|||
else { |
|||
ref.placeholders[seg] = arrayLabel; |
|||
} |
|||
} else { |
|||
if (value !== undefined) { |
|||
delete ref[seg]; |
|||
var objectLabel = seg + '_$key$_' + pos + '_$end$_'; |
|||
ref[objectLabel] = value; |
|||
lookup[error.path] = objectLabel; |
|||
} |
|||
else if (lookup[error.path]) { |
|||
var replacement = lookup[error.path]; |
|||
var appended = replacement.replace('_$end$_', ', ' + pos + '_$end$_'); |
|||
ref[appended] = ref[replacement]; |
|||
lookup[error.path] = appended; |
|||
delete ref[replacement]; |
|||
} |
|||
else { |
|||
ref['_$miss$_' + seg + '|' + pos + '_$end$_'] = '__missing__'; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
var message = internals.safeStringify(obj, 2) |
|||
.replace(/_\$key\$_([, \d]+)_\$end\$_\"/g, function ($0, $1) { |
|||
|
|||
return '" \u001b[31m[' + $1 + ']\u001b[0m'; |
|||
}).replace(/\"_\$miss\$_([^\|]+)\|(\d+)_\$end\$_\"\: \"__missing__\"/g, function ($0, $1, $2) { |
|||
|
|||
return '\u001b[41m"' + $1 + '"\u001b[0m\u001b[31m [' + $2 + ']: -- missing --\u001b[0m'; |
|||
}).replace(/\s*\"_\$idx\$_([, \d]+)_\$end\$_\",?\n(.*)/g, function ($0, $1, $2) { |
|||
|
|||
return '\n' + $2 + ' \u001b[31m[' + $1 + ']\u001b[0m'; |
|||
}); |
|||
|
|||
message += '\n\u001b[31m'; |
|||
|
|||
for (e = 0; e < el; ++e) { |
|||
message += '\n[' + (e + 1) + '] ' + this.details[e].message; |
|||
} |
|||
|
|||
message += '\u001b[0m'; |
|||
|
|||
return message; |
|||
}; |
@ -0,0 +1,152 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Cast = require('./cast'); |
|||
var Ref = require('./ref'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = { |
|||
alternatives: require('./alternatives'), |
|||
array: require('./array'), |
|||
boolean: require('./boolean'), |
|||
binary: require('./binary'), |
|||
date: require('./date'), |
|||
number: require('./number'), |
|||
object: require('./object'), |
|||
string: require('./string') |
|||
}; |
|||
|
|||
|
|||
internals.root = function () { |
|||
|
|||
var any = new Any(); |
|||
|
|||
var root = any.clone(); |
|||
root.any = function () { |
|||
|
|||
return any; |
|||
}; |
|||
|
|||
root.alternatives = root.alt = function () { |
|||
|
|||
return arguments.length ? internals.alternatives.try.apply(internals.alternatives, arguments) : internals.alternatives; |
|||
}; |
|||
|
|||
root.array = function () { |
|||
|
|||
return internals.array; |
|||
}; |
|||
|
|||
root.boolean = root.bool = function () { |
|||
|
|||
return internals.boolean; |
|||
}; |
|||
|
|||
root.binary = function () { |
|||
|
|||
return internals.binary; |
|||
}; |
|||
|
|||
root.date = function () { |
|||
|
|||
return internals.date; |
|||
}; |
|||
|
|||
root.func = function () { |
|||
|
|||
return internals.object._func(); |
|||
}; |
|||
|
|||
root.number = function () { |
|||
|
|||
return internals.number; |
|||
}; |
|||
|
|||
root.object = function () { |
|||
|
|||
return arguments.length ? internals.object.keys.apply(internals.object, arguments) : internals.object; |
|||
}; |
|||
|
|||
root.string = function () { |
|||
|
|||
return internals.string; |
|||
}; |
|||
|
|||
root.ref = function () { |
|||
|
|||
return Ref.create.apply(null, arguments); |
|||
}; |
|||
|
|||
root.isRef = function (ref) { |
|||
|
|||
return Ref.isRef(ref); |
|||
}; |
|||
|
|||
root.validate = function (value /*, [schema], [options], callback */) { |
|||
|
|||
var last = arguments[arguments.length - 1]; |
|||
var callback = typeof last === 'function' ? last : null; |
|||
|
|||
var count = arguments.length - (callback ? 1 : 0); |
|||
if (count === 1) { |
|||
return any.validate(value, callback); |
|||
} |
|||
|
|||
var options = count === 3 ? arguments[2] : {}; |
|||
var schema = root.compile(arguments[1]); |
|||
|
|||
return schema._validateWithOptions(value, options, callback); |
|||
}; |
|||
|
|||
root.describe = function () { |
|||
|
|||
var schema = arguments.length ? root.compile(arguments[0]) : any; |
|||
return schema.describe(); |
|||
}; |
|||
|
|||
root.compile = function (schema) { |
|||
|
|||
try { |
|||
return Cast.schema(schema); |
|||
} |
|||
catch (err) { |
|||
if (err.hasOwnProperty('path')) { |
|||
err.message += '(' + err.path + ')'; |
|||
} |
|||
throw err; |
|||
} |
|||
}; |
|||
|
|||
root.assert = function (value, schema, message) { |
|||
|
|||
root.attempt(value, schema, message); |
|||
}; |
|||
|
|||
root.attempt = function (value, schema, message) { |
|||
|
|||
var result = root.validate(value, schema); |
|||
var error = result.error; |
|||
if (error) { |
|||
if (!message) { |
|||
error.message = error.annotate(); |
|||
throw error; |
|||
} |
|||
|
|||
if (!(message instanceof Error)) { |
|||
error.message = message + ' ' + error.annotate(); |
|||
throw error; |
|||
} |
|||
|
|||
throw message; |
|||
} |
|||
|
|||
return result.value; |
|||
}; |
|||
|
|||
return root; |
|||
}; |
|||
|
|||
|
|||
module.exports = internals.root(); |
@ -0,0 +1,125 @@ |
|||
// Load modules
|
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
exports.errors = { |
|||
root: 'value', |
|||
key: '"{{!key}}" ', |
|||
messages: { |
|||
wrapArrays: true |
|||
}, |
|||
any: { |
|||
unknown: 'is not allowed', |
|||
invalid: 'contains an invalid value', |
|||
empty: 'is not allowed to be empty', |
|||
required: 'is required', |
|||
allowOnly: 'must be one of {{valids}}', |
|||
default: 'threw an error when running default method' |
|||
}, |
|||
alternatives: { |
|||
base: 'not matching any of the allowed alternatives' |
|||
}, |
|||
array: { |
|||
base: 'must be an array', |
|||
includes: 'at position {{pos}} does not match any of the allowed types', |
|||
includesSingle: 'single value of "{{!key}}" does not match any of the allowed types', |
|||
includesOne: 'at position {{pos}} fails because {{reason}}', |
|||
includesOneSingle: 'single value of "{{!key}}" fails because {{reason}}', |
|||
includesRequiredUnknowns: 'does not contain {{unknownMisses}} required value(s)', |
|||
includesRequiredKnowns: 'does not contain {{knownMisses}}', |
|||
includesRequiredBoth: 'does not contain {{knownMisses}} and {{unknownMisses}} other required value(s)', |
|||
excludes: 'at position {{pos}} contains an excluded value', |
|||
excludesSingle: 'single value of "{{!key}}" contains an excluded value', |
|||
min: 'must contain at least {{limit}} items', |
|||
max: 'must contain less than or equal to {{limit}} items', |
|||
length: 'must contain {{limit}} items', |
|||
ordered: 'at position {{pos}} fails because {{reason}}', |
|||
orderedLength: 'at position {{pos}} fails because array must contain at most {{limit}} items', |
|||
sparse: 'must not be a sparse array', |
|||
unique: 'position {{pos}} contains a duplicate value' |
|||
}, |
|||
boolean: { |
|||
base: 'must be a boolean' |
|||
}, |
|||
binary: { |
|||
base: 'must be a buffer or a string', |
|||
min: 'must be at least {{limit}} bytes', |
|||
max: 'must be less than or equal to {{limit}} bytes', |
|||
length: 'must be {{limit}} bytes' |
|||
}, |
|||
date: { |
|||
base: 'must be a number of milliseconds or valid date string', |
|||
min: 'must be larger than or equal to "{{limit}}"', |
|||
max: 'must be less than or equal to "{{limit}}"', |
|||
isoDate: 'must be a valid ISO 8601 date', |
|||
ref: 'references "{{ref}}" which is not a date' |
|||
}, |
|||
function: { |
|||
base: 'must be a Function' |
|||
}, |
|||
object: { |
|||
base: 'must be an object', |
|||
child: 'child "{{!key}}" fails because {{reason}}', |
|||
min: 'must have at least {{limit}} children', |
|||
max: 'must have less than or equal to {{limit}} children', |
|||
length: 'must have {{limit}} children', |
|||
allowUnknown: 'is not allowed', |
|||
with: 'missing required peer "{{peer}}"', |
|||
without: 'conflict with forbidden peer "{{peer}}"', |
|||
missing: 'must contain at least one of {{peers}}', |
|||
xor: 'contains a conflict between exclusive peers {{peers}}', |
|||
or: 'must contain at least one of {{peers}}', |
|||
and: 'contains {{present}} without its required peers {{missing}}', |
|||
nand: '!!"{{main}}" must not exist simultaneously with {{peers}}', |
|||
assert: '!!"{{ref}}" validation failed because "{{ref}}" failed to {{message}}', |
|||
rename: { |
|||
multiple: 'cannot rename child "{{from}}" because multiple renames are disabled and another key was already renamed to "{{to}}"', |
|||
override: 'cannot rename child "{{from}}" because override is disabled and target "{{to}}" exists' |
|||
}, |
|||
type: 'must be an instance of "{{type}}"' |
|||
}, |
|||
number: { |
|||
base: 'must be a number', |
|||
min: 'must be larger than or equal to {{limit}}', |
|||
max: 'must be less than or equal to {{limit}}', |
|||
less: 'must be less than {{limit}}', |
|||
greater: 'must be greater than {{limit}}', |
|||
float: 'must be a float or double', |
|||
integer: 'must be an integer', |
|||
negative: 'must be a negative number', |
|||
positive: 'must be a positive number', |
|||
precision: 'must have no more than {{limit}} decimal places', |
|||
ref: 'references "{{ref}}" which is not a number', |
|||
multiple: 'must be a multiple of {{multiple}}' |
|||
}, |
|||
string: { |
|||
base: 'must be a string', |
|||
min: 'length must be at least {{limit}} characters long', |
|||
max: 'length must be less than or equal to {{limit}} characters long', |
|||
length: 'length must be {{limit}} characters long', |
|||
alphanum: 'must only contain alpha-numeric characters', |
|||
token: 'must only contain alpha-numeric and underscore characters', |
|||
regex: { |
|||
base: 'with value "{{!value}}" fails to match the required pattern: {{pattern}}', |
|||
name: 'with value "{{!value}}" fails to match the {{name}} pattern' |
|||
}, |
|||
email: 'must be a valid email', |
|||
uri: 'must be a valid uri', |
|||
uriCustomScheme: 'must be a valid uri with a scheme matching the {{scheme}} pattern', |
|||
isoDate: 'must be a valid ISO 8601 date', |
|||
guid: 'must be a valid GUID', |
|||
hex: 'must only contain hexadecimal characters', |
|||
hostname: 'must be a valid hostname', |
|||
lowercase: 'must only contain lowercase characters', |
|||
uppercase: 'must only contain uppercase characters', |
|||
trim: 'must not have leading or trailing whitespace', |
|||
creditCard: 'must be a credit card', |
|||
ref: 'references "{{ref}}" which is not a number', |
|||
ip: 'must be a valid ip address with a {{cidr}} CIDR', |
|||
ipVersion: 'must be a valid ip address of one of the following versions {{version}} with a {{cidr}} CIDR' |
|||
} |
|||
}; |
@ -0,0 +1,184 @@ |
|||
// Load modules
|
|||
|
|||
var Any = require('./any'); |
|||
var Ref = require('./ref'); |
|||
var Errors = require('./errors'); |
|||
var Hoek = require('hoek'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.Number = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'number'; |
|||
this._invalids.add(Infinity); |
|||
this._invalids.add(-Infinity); |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Number, Any); |
|||
|
|||
internals.compare = function (type, compare) { |
|||
|
|||
return function (limit) { |
|||
|
|||
var isRef = Ref.isRef(limit); |
|||
var isNumber = typeof limit === 'number' && !isNaN(limit); |
|||
|
|||
Hoek.assert(isNumber || isRef, 'limit must be a number or reference'); |
|||
|
|||
return this._test(type, limit, function (value, state, options) { |
|||
|
|||
var compareTo; |
|||
if (isRef) { |
|||
compareTo = limit(state.parent, options); |
|||
|
|||
if (!(typeof compareTo === 'number' && !isNaN(compareTo))) { |
|||
return Errors.create('number.ref', { ref: limit.key }, state, options); |
|||
} |
|||
} |
|||
else { |
|||
compareTo = limit; |
|||
} |
|||
|
|||
if (compare(value, compareTo)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('number.' + type, { limit: compareTo, value: value }, state, options); |
|||
}); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
internals.Number.prototype._base = function (value, state, options) { |
|||
|
|||
var result = { |
|||
errors: null, |
|||
value: value |
|||
}; |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
var number = parseFloat(value); |
|||
result.value = (isNaN(number) || !isFinite(value)) ? NaN : number; |
|||
} |
|||
|
|||
var isNumber = typeof result.value === 'number' && !isNaN(result.value); |
|||
|
|||
if (options.convert && 'precision' in this._flags && isNumber) { |
|||
|
|||
// This is conceptually equivalent to using toFixed but it should be much faster
|
|||
var precision = Math.pow(10, this._flags.precision); |
|||
result.value = Math.round(result.value * precision) / precision; |
|||
} |
|||
|
|||
result.errors = isNumber ? null : Errors.create('number.base', null, state, options); |
|||
return result; |
|||
}; |
|||
|
|||
|
|||
internals.Number.prototype.min = internals.compare('min', function (value, limit) { |
|||
|
|||
return value >= limit; |
|||
}); |
|||
|
|||
|
|||
internals.Number.prototype.max = internals.compare('max', function (value, limit) { |
|||
|
|||
return value <= limit; |
|||
}); |
|||
|
|||
|
|||
internals.Number.prototype.greater = internals.compare('greater', function (value, limit) { |
|||
|
|||
return value > limit; |
|||
}); |
|||
|
|||
|
|||
internals.Number.prototype.less = internals.compare('less', function (value, limit) { |
|||
|
|||
return value < limit; |
|||
}); |
|||
|
|||
|
|||
internals.Number.prototype.multiple = function (base) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(base), 'multiple must be an integer'); |
|||
Hoek.assert(base > 0, 'multiple must be greater than 0'); |
|||
|
|||
return this._test('multiple', base, function (value, state, options) { |
|||
|
|||
if (value % base === 0) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('number.multiple', { multiple: base, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Number.prototype.integer = function () { |
|||
|
|||
return this._test('integer', undefined, function (value, state, options) { |
|||
|
|||
return Hoek.isInteger(value) ? null : Errors.create('number.integer', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Number.prototype.negative = function () { |
|||
|
|||
return this._test('negative', undefined, function (value, state, options) { |
|||
|
|||
if (value < 0) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('number.negative', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Number.prototype.positive = function () { |
|||
|
|||
return this._test('positive', undefined, function (value, state, options) { |
|||
|
|||
if (value > 0) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('number.positive', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.precisionRx = /(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/; |
|||
|
|||
|
|||
internals.Number.prototype.precision = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit), 'limit must be an integer'); |
|||
Hoek.assert(!('precision' in this._flags), 'precision already set'); |
|||
|
|||
var obj = this._test('precision', limit, function (value, state, options){ |
|||
|
|||
var places = value.toString().match(internals.precisionRx); |
|||
var decimals = Math.max((places[1] ? places[1].length : 0) - (places[2] ? parseInt(places[2], 10) : 0), 0); |
|||
if (decimals <= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('number.precision', { limit: limit, value: value }, state, options); |
|||
}); |
|||
|
|||
obj._flags.precision = limit; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Number(); |
@ -0,0 +1,754 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
var Topo = require('topo'); |
|||
var Any = require('./any'); |
|||
var Cast = require('./cast'); |
|||
var Errors = require('./errors'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
internals.Object = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'object'; |
|||
this._inner.children = null; |
|||
this._inner.renames = []; |
|||
this._inner.dependencies = []; |
|||
this._inner.patterns = []; |
|||
}; |
|||
|
|||
Hoek.inherits(internals.Object, Any); |
|||
|
|||
|
|||
internals.Object.prototype._base = function (value, state, options) { |
|||
|
|||
var item, key, localState, result; |
|||
var target = value; |
|||
var errors = []; |
|||
var finish = function () { |
|||
|
|||
return { |
|||
value: target, |
|||
errors: errors.length ? errors : null |
|||
}; |
|||
}; |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
try { |
|||
value = JSON.parse(value); |
|||
} |
|||
catch (parseErr) { } |
|||
} |
|||
|
|||
var type = this._flags.func ? 'function' : 'object'; |
|||
if (!value || |
|||
typeof value !== type || |
|||
Array.isArray(value)) { |
|||
|
|||
errors.push(Errors.create(type + '.base', null, state, options)); |
|||
return finish(); |
|||
} |
|||
|
|||
// Skip if there are no other rules to test
|
|||
|
|||
if (!this._inner.renames.length && |
|||
!this._inner.dependencies.length && |
|||
!this._inner.children && // null allows any keys
|
|||
!this._inner.patterns.length) { |
|||
|
|||
target = value; |
|||
return finish(); |
|||
} |
|||
|
|||
// Ensure target is a local copy (parsed) or shallow copy
|
|||
|
|||
if (target === value) { |
|||
if (type === 'object') { |
|||
target = Object.create(Object.getPrototypeOf(value)); |
|||
} |
|||
else { |
|||
target = function () { |
|||
|
|||
return value.apply(this, arguments); |
|||
}; |
|||
|
|||
target.prototype = Hoek.clone(value.prototype); |
|||
} |
|||
|
|||
var valueKeys = Object.keys(value); |
|||
for (var t = 0, tl = valueKeys.length; t < tl; ++t) { |
|||
target[valueKeys[t]] = value[valueKeys[t]]; |
|||
} |
|||
} |
|||
else { |
|||
target = value; |
|||
} |
|||
|
|||
// Rename keys
|
|||
|
|||
var renamed = {}; |
|||
for (var r = 0, rl = this._inner.renames.length; r < rl; ++r) { |
|||
item = this._inner.renames[r]; |
|||
|
|||
if (item.options.ignoreUndefined && target[item.from] === undefined) { |
|||
continue; |
|||
} |
|||
|
|||
if (!item.options.multiple && |
|||
renamed[item.to]) { |
|||
|
|||
errors.push(Errors.create('object.rename.multiple', { from: item.from, to: item.to }, state, options)); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
if (Object.prototype.hasOwnProperty.call(target, item.to) && |
|||
!item.options.override && |
|||
!renamed[item.to]) { |
|||
|
|||
errors.push(Errors.create('object.rename.override', { from: item.from, to: item.to }, state, options)); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
if (target[item.from] === undefined) { |
|||
delete target[item.to]; |
|||
} |
|||
else { |
|||
target[item.to] = target[item.from]; |
|||
} |
|||
|
|||
renamed[item.to] = true; |
|||
|
|||
if (!item.options.alias) { |
|||
delete target[item.from]; |
|||
} |
|||
} |
|||
|
|||
// Validate schema
|
|||
|
|||
if (!this._inner.children && // null allows any keys
|
|||
!this._inner.patterns.length && |
|||
!this._inner.dependencies.length) { |
|||
|
|||
return finish(); |
|||
} |
|||
|
|||
var unprocessed = Hoek.mapToObject(Object.keys(target)); |
|||
|
|||
if (this._inner.children) { |
|||
for (var i = 0, il = this._inner.children.length; i < il; ++i) { |
|||
var child = this._inner.children[i]; |
|||
key = child.key; |
|||
item = target[key]; |
|||
|
|||
delete unprocessed[key]; |
|||
|
|||
localState = { key: key, path: (state.path || '') + (state.path && key ? '.' : '') + key, parent: target, reference: state.reference }; |
|||
result = child.schema._validate(item, localState, options); |
|||
if (result.errors) { |
|||
errors.push(Errors.create('object.child', { key: key, reason: result.errors }, localState, options)); |
|||
|
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
if (child.schema._flags.strip || (result.value === undefined && result.value !== item)) { |
|||
delete target[key]; |
|||
} |
|||
else if (result.value !== undefined) { |
|||
target[key] = result.value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Unknown keys
|
|||
|
|||
var unprocessedKeys = Object.keys(unprocessed); |
|||
if (unprocessedKeys.length && |
|||
this._inner.patterns.length) { |
|||
|
|||
for (i = 0, il = unprocessedKeys.length; i < il; ++i) { |
|||
key = unprocessedKeys[i]; |
|||
|
|||
for (var p = 0, pl = this._inner.patterns.length; p < pl; ++p) { |
|||
var pattern = this._inner.patterns[p]; |
|||
|
|||
if (pattern.regex.test(key)) { |
|||
delete unprocessed[key]; |
|||
|
|||
item = target[key]; |
|||
localState = { key: key, path: (state.path ? state.path + '.' : '') + key, parent: target, reference: state.reference }; |
|||
result = pattern.rule._validate(item, localState, options); |
|||
if (result.errors) { |
|||
errors.push(Errors.create('object.child', { key: key, reason: result.errors }, localState, options)); |
|||
|
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
|
|||
if (result.value !== undefined) { |
|||
target[key] = result.value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
unprocessedKeys = Object.keys(unprocessed); |
|||
} |
|||
|
|||
if ((this._inner.children || this._inner.patterns.length) && unprocessedKeys.length) { |
|||
if (options.stripUnknown || |
|||
options.skipFunctions) { |
|||
|
|||
for (var k = 0, kl = unprocessedKeys.length; k < kl; ++k) { |
|||
key = unprocessedKeys[k]; |
|||
|
|||
if (options.stripUnknown) { |
|||
delete target[key]; |
|||
delete unprocessed[key]; |
|||
} |
|||
else if (typeof target[key] === 'function') { |
|||
delete unprocessed[key]; |
|||
} |
|||
} |
|||
|
|||
unprocessedKeys = Object.keys(unprocessed); |
|||
} |
|||
|
|||
if (unprocessedKeys.length && |
|||
(this._flags.allowUnknown !== undefined ? !this._flags.allowUnknown : !options.allowUnknown)) { |
|||
|
|||
for (var e = 0, el = unprocessedKeys.length; e < el; ++e) { |
|||
errors.push(Errors.create('object.allowUnknown', null, { key: unprocessedKeys[e], path: state.path + (state.path ? '.' : '') + unprocessedKeys[e] }, options)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Validate dependencies
|
|||
|
|||
for (var d = 0, dl = this._inner.dependencies.length; d < dl; ++d) { |
|||
var dep = this._inner.dependencies[d]; |
|||
var err = internals[dep.type](dep.key !== null && value[dep.key], dep.peers, target, { key: dep.key, path: (state.path || '') + (dep.key ? '.' + dep.key : '') }, options); |
|||
if (err) { |
|||
errors.push(err); |
|||
if (options.abortEarly) { |
|||
return finish(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return finish(); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype._func = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.func = true; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.keys = function (schema) { |
|||
|
|||
Hoek.assert(schema === null || schema === undefined || typeof schema === 'object', 'Object schema must be a valid object'); |
|||
Hoek.assert(!schema || !schema.isJoi, 'Object schema cannot be a joi schema'); |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
if (!schema) { |
|||
obj._inner.children = null; |
|||
return obj; |
|||
} |
|||
|
|||
var children = Object.keys(schema); |
|||
|
|||
if (!children.length) { |
|||
obj._inner.children = []; |
|||
return obj; |
|||
} |
|||
|
|||
var topo = new Topo(); |
|||
var child; |
|||
if (obj._inner.children) { |
|||
for (var i = 0, il = obj._inner.children.length; i < il; ++i) { |
|||
child = obj._inner.children[i]; |
|||
|
|||
// Only add the key if we are not going to replace it later
|
|||
if (children.indexOf(child.key) === -1) { |
|||
topo.add(child, { after: child._refs, group: child.key }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
for (var c = 0, cl = children.length; c < cl; ++c) { |
|||
var key = children[c]; |
|||
child = schema[key]; |
|||
try { |
|||
var cast = Cast.schema(child); |
|||
topo.add({ key: key, schema: cast }, { after: cast._refs, group: key }); |
|||
} |
|||
catch (castErr) { |
|||
if (castErr.hasOwnProperty('path')) { |
|||
castErr.path = key + '.' + castErr.path; |
|||
} |
|||
else { |
|||
castErr.path = key; |
|||
} |
|||
throw castErr; |
|||
} |
|||
} |
|||
|
|||
obj._inner.children = topo.nodes; |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.unknown = function (allow) { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.allowUnknown = (allow !== false); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.length = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('length', limit, function (value, state, options) { |
|||
|
|||
if (Object.keys(value).length === limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('object.length', { limit: limit }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.min = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('min', limit, function (value, state, options) { |
|||
|
|||
if (Object.keys(value).length >= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('object.min', { limit: limit }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.max = function (limit) { |
|||
|
|||
Hoek.assert(Hoek.isInteger(limit) && limit >= 0, 'limit must be a positive integer'); |
|||
|
|||
return this._test('max', limit, function (value, state, options) { |
|||
|
|||
if (Object.keys(value).length <= limit) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('object.max', { limit: limit }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.pattern = function (pattern, schema) { |
|||
|
|||
Hoek.assert(pattern instanceof RegExp, 'Invalid regular expression'); |
|||
Hoek.assert(schema !== undefined, 'Invalid rule'); |
|||
|
|||
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
|
|||
|
|||
try { |
|||
schema = Cast.schema(schema); |
|||
} |
|||
catch (castErr) { |
|||
if (castErr.hasOwnProperty('path')) { |
|||
castErr.message += '(' + castErr.path + ')'; |
|||
} |
|||
|
|||
throw castErr; |
|||
} |
|||
|
|||
|
|||
var obj = this.clone(); |
|||
obj._inner.patterns.push({ regex: pattern, rule: schema }); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.with = function (key, peers) { |
|||
|
|||
return this._dependency('with', key, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.without = function (key, peers) { |
|||
|
|||
return this._dependency('without', key, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.xor = function () { |
|||
|
|||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this._dependency('xor', null, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.or = function () { |
|||
|
|||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this._dependency('or', null, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.and = function () { |
|||
|
|||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this._dependency('and', null, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.nand = function () { |
|||
|
|||
var peers = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this._dependency('nand', null, peers); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.requiredKeys = function (children) { |
|||
|
|||
children = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this.applyFunctionToChildren(children, 'required'); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.optionalKeys = function (children) { |
|||
|
|||
children = Hoek.flatten(Array.prototype.slice.call(arguments)); |
|||
return this.applyFunctionToChildren(children, 'optional'); |
|||
}; |
|||
|
|||
|
|||
internals.renameDefaults = { |
|||
alias: false, // Keep old value in place
|
|||
multiple: false, // Allow renaming multiple keys into the same target
|
|||
override: false // Overrides an existing key
|
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.rename = function (from, to, options) { |
|||
|
|||
Hoek.assert(typeof from === 'string', 'Rename missing the from argument'); |
|||
Hoek.assert(typeof to === 'string', 'Rename missing the to argument'); |
|||
Hoek.assert(to !== from, 'Cannot rename key to same name:', from); |
|||
|
|||
for (var i = 0, il = this._inner.renames.length; i < il; ++i) { |
|||
Hoek.assert(this._inner.renames[i].from !== from, 'Cannot rename the same key multiple times'); |
|||
} |
|||
|
|||
var obj = this.clone(); |
|||
|
|||
obj._inner.renames.push({ |
|||
from: from, |
|||
to: to, |
|||
options: Hoek.applyToDefaults(internals.renameDefaults, options || {}) |
|||
}); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.groupChildren = function (children) { |
|||
|
|||
children.sort(); |
|||
|
|||
var grouped = {}; |
|||
|
|||
for (var c = 0, lc = children.length; c < lc; c++) { |
|||
var child = children[c]; |
|||
Hoek.assert(typeof child === 'string', 'children must be strings'); |
|||
var group = child.split('.')[0]; |
|||
var childGroup = grouped[group] = (grouped[group] || []); |
|||
childGroup.push(child.substring(group.length + 1)); |
|||
} |
|||
|
|||
return grouped; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.applyFunctionToChildren = function (children, fn, args, root) { |
|||
|
|||
children = [].concat(children); |
|||
Hoek.assert(children.length > 0, 'expected at least one children'); |
|||
|
|||
var groupedChildren = internals.groupChildren(children); |
|||
var obj; |
|||
|
|||
if ('' in groupedChildren) { |
|||
obj = this[fn].apply(this, args); |
|||
delete groupedChildren['']; |
|||
} |
|||
else { |
|||
obj = this.clone(); |
|||
} |
|||
|
|||
if (obj._inner.children) { |
|||
root = root ? (root + '.') : ''; |
|||
|
|||
for (var i = 0, il = obj._inner.children.length; i < il; ++i) { |
|||
var child = obj._inner.children[i]; |
|||
var group = groupedChildren[child.key]; |
|||
|
|||
if (group) { |
|||
obj._inner.children[i] = { |
|||
key: child.key, |
|||
_refs: child._refs, |
|||
schema: child.schema.applyFunctionToChildren(group, fn, args, root + child.key) |
|||
}; |
|||
|
|||
delete groupedChildren[child.key]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
var remaining = Object.keys(groupedChildren); |
|||
Hoek.assert(remaining.length === 0, 'unknown key(s)', remaining.join(', ')); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype._dependency = function (type, key, peers) { |
|||
|
|||
peers = [].concat(peers); |
|||
for (var i = 0, li = peers.length; i < li; i++) { |
|||
Hoek.assert(typeof peers[i] === 'string', type, 'peers must be a string or array of strings'); |
|||
} |
|||
|
|||
var obj = this.clone(); |
|||
obj._inner.dependencies.push({ type: type, key: key, peers: peers }); |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.with = function (value, peers, parent, state, options) { |
|||
|
|||
if (value === undefined) { |
|||
return null; |
|||
} |
|||
|
|||
for (var i = 0, il = peers.length; i < il; ++i) { |
|||
var peer = peers[i]; |
|||
if (!Object.prototype.hasOwnProperty.call(parent, peer) || |
|||
parent[peer] === undefined) { |
|||
|
|||
return Errors.create('object.with', { peer: peer }, state, options); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
}; |
|||
|
|||
|
|||
internals.without = function (value, peers, parent, state, options) { |
|||
|
|||
if (value === undefined) { |
|||
return null; |
|||
} |
|||
|
|||
for (var i = 0, il = peers.length; i < il; ++i) { |
|||
var peer = peers[i]; |
|||
if (Object.prototype.hasOwnProperty.call(parent, peer) && |
|||
parent[peer] !== undefined) { |
|||
|
|||
return Errors.create('object.without', { peer: peer }, state, options); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
}; |
|||
|
|||
|
|||
internals.xor = function (value, peers, parent, state, options) { |
|||
|
|||
var present = []; |
|||
for (var i = 0, il = peers.length; i < il; ++i) { |
|||
var peer = peers[i]; |
|||
if (Object.prototype.hasOwnProperty.call(parent, peer) && |
|||
parent[peer] !== undefined) { |
|||
|
|||
present.push(peer); |
|||
} |
|||
} |
|||
|
|||
if (present.length === 1) { |
|||
return null; |
|||
} |
|||
|
|||
if (present.length === 0) { |
|||
return Errors.create('object.missing', { peers: peers }, state, options); |
|||
} |
|||
|
|||
return Errors.create('object.xor', { peers: peers }, state, options); |
|||
}; |
|||
|
|||
|
|||
internals.or = function (value, peers, parent, state, options) { |
|||
|
|||
for (var i = 0, il = peers.length; i < il; ++i) { |
|||
var peer = peers[i]; |
|||
if (Object.prototype.hasOwnProperty.call(parent, peer) && |
|||
parent[peer] !== undefined) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
return Errors.create('object.missing', { peers: peers }, state, options); |
|||
}; |
|||
|
|||
|
|||
internals.and = function (value, peers, parent, state, options) { |
|||
|
|||
var missing = []; |
|||
var present = []; |
|||
var count = peers.length; |
|||
for (var i = 0; i < count; ++i) { |
|||
var peer = peers[i]; |
|||
if (!Object.prototype.hasOwnProperty.call(parent, peer) || |
|||
parent[peer] === undefined) { |
|||
|
|||
missing.push(peer); |
|||
} |
|||
else { |
|||
present.push(peer); |
|||
} |
|||
} |
|||
|
|||
var aon = (missing.length === count || present.length === count); |
|||
return !aon ? Errors.create('object.and', { present: present, missing: missing }, state, options) : null; |
|||
}; |
|||
|
|||
|
|||
internals.nand = function (value, peers, parent, state, options) { |
|||
|
|||
var present = []; |
|||
for (var i = 0, il = peers.length; i < il; ++i) { |
|||
var peer = peers[i]; |
|||
if (Object.prototype.hasOwnProperty.call(parent, peer) && |
|||
parent[peer] !== undefined) { |
|||
|
|||
present.push(peer); |
|||
} |
|||
} |
|||
|
|||
var values = Hoek.clone(peers); |
|||
var main = values.splice(0, 1)[0]; |
|||
var allPresent = (present.length === peers.length); |
|||
return allPresent ? Errors.create('object.nand', { main: main, peers: values }, state, options) : null; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.describe = function (shallow) { |
|||
|
|||
var description = Any.prototype.describe.call(this); |
|||
|
|||
if (this._inner.children && |
|||
!shallow) { |
|||
|
|||
description.children = {}; |
|||
for (var i = 0, il = this._inner.children.length; i < il; ++i) { |
|||
var child = this._inner.children[i]; |
|||
description.children[child.key] = child.schema.describe(); |
|||
} |
|||
} |
|||
|
|||
if (this._inner.dependencies.length) { |
|||
description.dependencies = Hoek.clone(this._inner.dependencies); |
|||
} |
|||
|
|||
if (this._inner.patterns.length) { |
|||
description.patterns = []; |
|||
|
|||
for (var p = 0, pl = this._inner.patterns.length; p < pl; ++p) { |
|||
var pattern = this._inner.patterns[p]; |
|||
description.patterns.push({ regex: pattern.regex.toString(), rule: pattern.rule.describe() }); |
|||
} |
|||
} |
|||
|
|||
return description; |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.assert = function (ref, schema, message) { |
|||
|
|||
ref = Cast.ref(ref); |
|||
Hoek.assert(ref.isContext || ref.depth > 1, 'Cannot use assertions for root level references - use direct key rules instead'); |
|||
message = message || 'pass the assertion test'; |
|||
|
|||
var cast; |
|||
try { |
|||
cast = Cast.schema(schema); |
|||
} |
|||
catch (castErr) { |
|||
if (castErr.hasOwnProperty('path')) { |
|||
castErr.message += '(' + castErr.path + ')'; |
|||
} |
|||
|
|||
throw castErr; |
|||
} |
|||
|
|||
var key = ref.path[ref.path.length - 1]; |
|||
var path = ref.path.join('.'); |
|||
|
|||
return this._test('assert', { cast: cast, ref: ref }, function (value, state, options) { |
|||
|
|||
var result = cast._validate(ref(value), null, options, value); |
|||
if (!result.errors) { |
|||
return null; |
|||
} |
|||
|
|||
var localState = Hoek.merge({}, state); |
|||
localState.key = key; |
|||
localState.path = path; |
|||
return Errors.create('object.assert', { ref: localState.path, message: message }, localState, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.Object.prototype.type = function (constructor, name) { |
|||
|
|||
Hoek.assert(typeof constructor === 'function', 'type must be a constructor function'); |
|||
name = name || constructor.name; |
|||
|
|||
return this._test('type', name, function (value, state, options) { |
|||
|
|||
if (value instanceof constructor) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('object.type', { type: name }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
module.exports = new internals.Object(); |
@ -0,0 +1,51 @@ |
|||
// Load modules
|
|||
|
|||
var Hoek = require('hoek'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
exports.create = function (key, options) { |
|||
|
|||
Hoek.assert(typeof key === 'string', 'Invalid reference key:', key); |
|||
|
|||
var settings = Hoek.clone(options); // options can be reused and modified
|
|||
|
|||
var ref = function (value, validationOptions) { |
|||
|
|||
return Hoek.reach(ref.isContext ? validationOptions.context : value, ref.key, settings); |
|||
}; |
|||
|
|||
ref.isContext = (key[0] === ((settings && settings.contextPrefix) || '$')); |
|||
ref.key = (ref.isContext ? key.slice(1) : key); |
|||
ref.path = ref.key.split((settings && settings.separator) || '.'); |
|||
ref.depth = ref.path.length; |
|||
ref.root = ref.path[0]; |
|||
ref.isJoi = true; |
|||
|
|||
ref.toString = function () { |
|||
|
|||
return (ref.isContext ? 'context:' : 'ref:') + ref.key; |
|||
}; |
|||
|
|||
return ref; |
|||
}; |
|||
|
|||
|
|||
exports.isRef = function (ref) { |
|||
|
|||
return typeof ref === 'function' && ref.isJoi; |
|||
}; |
|||
|
|||
|
|||
exports.push = function (array, ref) { |
|||
|
|||
if (exports.isRef(ref) && |
|||
!ref.isContext) { |
|||
|
|||
array.push(ref.root); |
|||
} |
|||
}; |
@ -0,0 +1,468 @@ |
|||
// Load modules
|
|||
|
|||
var Net = require('net'); |
|||
var Hoek = require('hoek'); |
|||
var Isemail = require('isemail'); |
|||
var Any = require('./any'); |
|||
var Ref = require('./ref'); |
|||
var JoiDate = require('./date'); |
|||
var Errors = require('./errors'); |
|||
var Uri = require('./string/uri'); |
|||
var Ip = require('./string/ip'); |
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = { |
|||
uriRegex: Uri.createUriRegex(), |
|||
ipRegex: Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], 'optional') |
|||
}; |
|||
|
|||
internals.String = function () { |
|||
|
|||
Any.call(this); |
|||
this._type = 'string'; |
|||
this._invalids.add(''); |
|||
}; |
|||
|
|||
Hoek.inherits(internals.String, Any); |
|||
|
|||
internals.compare = function (type, compare) { |
|||
|
|||
return function (limit, encoding) { |
|||
|
|||
var isRef = Ref.isRef(limit); |
|||
|
|||
Hoek.assert((Hoek.isInteger(limit) && limit >= 0) || isRef, 'limit must be a positive integer or reference'); |
|||
Hoek.assert(!encoding || Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); |
|||
|
|||
return this._test(type, limit, function (value, state, options) { |
|||
|
|||
var compareTo; |
|||
if (isRef) { |
|||
compareTo = limit(state.parent, options); |
|||
|
|||
if (!Hoek.isInteger(compareTo)) { |
|||
return Errors.create('string.ref', { ref: limit.key }, state, options); |
|||
} |
|||
} |
|||
else { |
|||
compareTo = limit; |
|||
} |
|||
|
|||
if (compare(value, compareTo, encoding)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.' + type, { limit: compareTo, value: value, encoding: encoding }, state, options); |
|||
}); |
|||
}; |
|||
}; |
|||
|
|||
internals.String.prototype._base = function (value, state, options) { |
|||
|
|||
if (typeof value === 'string' && |
|||
options.convert) { |
|||
|
|||
if (this._flags.case) { |
|||
value = (this._flags.case === 'upper' ? value.toLocaleUpperCase() : value.toLocaleLowerCase()); |
|||
} |
|||
|
|||
if (this._flags.trim) { |
|||
value = value.trim(); |
|||
} |
|||
|
|||
if (this._inner.replacements) { |
|||
|
|||
for (var r = 0, rl = this._inner.replacements.length; r < rl; ++r) { |
|||
var replacement = this._inner.replacements[r]; |
|||
value = value.replace(replacement.pattern, replacement.replacement); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return { |
|||
value: value, |
|||
errors: (typeof value === 'string') ? null : Errors.create('string.base', { value: value }, state, options) |
|||
}; |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.insensitive = function () { |
|||
|
|||
var obj = this.clone(); |
|||
obj._flags.insensitive = true; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.min = internals.compare('min', function (value, limit, encoding) { |
|||
|
|||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; |
|||
return length >= limit; |
|||
}); |
|||
|
|||
|
|||
internals.String.prototype.max = internals.compare('max', function (value, limit, encoding) { |
|||
|
|||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; |
|||
return length <= limit; |
|||
}); |
|||
|
|||
|
|||
internals.String.prototype.creditCard = function () { |
|||
|
|||
return this._test('creditCard', undefined, function (value, state, options) { |
|||
|
|||
var i = value.length; |
|||
var sum = 0; |
|||
var mul = 1; |
|||
var char; |
|||
|
|||
while (i--) { |
|||
char = value.charAt(i) * mul; |
|||
sum += char - (char > 9) * 9; |
|||
mul ^= 3; |
|||
} |
|||
|
|||
var check = (sum % 10 === 0) && (sum > 0); |
|||
return check ? null : Errors.create('string.creditCard', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
internals.String.prototype.length = internals.compare('length', function (value, limit, encoding) { |
|||
|
|||
var length = encoding ? Buffer.byteLength(value, encoding) : value.length; |
|||
return length === limit; |
|||
}); |
|||
|
|||
|
|||
internals.String.prototype.regex = function (pattern, name) { |
|||
|
|||
Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); |
|||
|
|||
pattern = new RegExp(pattern.source, pattern.ignoreCase ? 'i' : undefined); // Future version should break this and forbid unsupported regex flags
|
|||
|
|||
return this._test('regex', pattern, function (value, state, options) { |
|||
|
|||
if (pattern.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create((name ? 'string.regex.name' : 'string.regex.base'), { name: name, pattern: pattern, value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.alphanum = function () { |
|||
|
|||
return this._test('alphanum', undefined, function (value, state, options) { |
|||
|
|||
if (/^[a-zA-Z0-9]+$/.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.alphanum', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.token = function () { |
|||
|
|||
return this._test('token', undefined, function (value, state, options) { |
|||
|
|||
if (/^\w+$/.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.token', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.email = function (isEmailOptions) { |
|||
|
|||
if (isEmailOptions) { |
|||
Hoek.assert(typeof isEmailOptions === 'object', 'email options must be an object'); |
|||
Hoek.assert(typeof isEmailOptions.checkDNS === 'undefined', 'checkDNS option is not supported'); |
|||
Hoek.assert(typeof isEmailOptions.tldWhitelist === 'undefined' || |
|||
typeof isEmailOptions.tldWhitelist === 'object', 'tldWhitelist must be an array or object'); |
|||
Hoek.assert(typeof isEmailOptions.minDomainAtoms === 'undefined' || |
|||
Hoek.isInteger(isEmailOptions.minDomainAtoms) && isEmailOptions.minDomainAtoms > 0, |
|||
'minDomainAtoms must be a positive integer'); |
|||
Hoek.assert(typeof isEmailOptions.errorLevel === 'undefined' || typeof isEmailOptions.errorLevel === 'boolean' || |
|||
(Hoek.isInteger(isEmailOptions.errorLevel) && isEmailOptions.errorLevel >= 0), |
|||
'errorLevel must be a non-negative integer or boolean'); |
|||
} |
|||
|
|||
return this._test('email', isEmailOptions, function (value, state, options) { |
|||
|
|||
try { |
|||
var result = Isemail(value, isEmailOptions); |
|||
if (result === true || result === 0) { |
|||
return null; |
|||
} |
|||
} |
|||
catch (e) {} |
|||
|
|||
return Errors.create('string.email', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.ip = function (ipOptions) { |
|||
|
|||
var regex = internals.ipRegex; |
|||
ipOptions = ipOptions || {}; |
|||
Hoek.assert(typeof ipOptions === 'object', 'options must be an object'); |
|||
|
|||
if (ipOptions.cidr) { |
|||
Hoek.assert(typeof ipOptions.cidr === 'string', 'cidr must be a string'); |
|||
ipOptions.cidr = ipOptions.cidr.toLowerCase(); |
|||
|
|||
Hoek.assert(ipOptions.cidr in Ip.cidrs, 'cidr must be one of ' + Object.keys(Ip.cidrs).join(', ')); |
|||
|
|||
// If we only received a `cidr` setting, create a regex for it. But we don't need to create one if `cidr` is "optional" since that is the default
|
|||
if (!ipOptions.version && ipOptions.cidr !== 'optional') { |
|||
regex = Ip.createIpRegex(['ipv4', 'ipv6', 'ipvfuture'], ipOptions.cidr); |
|||
} |
|||
} |
|||
else { |
|||
|
|||
// Set our default cidr strategy
|
|||
ipOptions.cidr = 'optional'; |
|||
} |
|||
|
|||
if (ipOptions.version) { |
|||
if (!Array.isArray(ipOptions.version)) { |
|||
ipOptions.version = [ipOptions.version]; |
|||
} |
|||
|
|||
Hoek.assert(ipOptions.version.length >= 1, 'version must have at least 1 version specified'); |
|||
|
|||
var versions = []; |
|||
for (var i = 0, il = ipOptions.version.length; i < il; ++i) { |
|||
var version = ipOptions.version[i]; |
|||
Hoek.assert(typeof version === 'string', 'version at position ' + i + ' must be a string'); |
|||
version = version.toLowerCase(); |
|||
Hoek.assert(Ip.versions[version], 'version at position ' + i + ' must be one of ' + Object.keys(Ip.versions).join(', ')); |
|||
versions.push(version); |
|||
} |
|||
|
|||
// Make sure we have a set of versions
|
|||
versions = Hoek.unique(versions); |
|||
|
|||
regex = Ip.createIpRegex(versions, ipOptions.cidr); |
|||
} |
|||
|
|||
return this._test('ip', ipOptions, function (value, state, options) { |
|||
|
|||
if (regex.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
if (versions) { |
|||
return Errors.create('string.ipVersion', { value: value, cidr: ipOptions.cidr, version: versions }, state, options); |
|||
} |
|||
|
|||
return Errors.create('string.ip', { value: value, cidr: ipOptions.cidr }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.uri = function (uriOptions) { |
|||
|
|||
var customScheme = '', |
|||
regex = internals.uriRegex; |
|||
|
|||
if (uriOptions) { |
|||
Hoek.assert(typeof uriOptions === 'object', 'options must be an object'); |
|||
|
|||
if (uriOptions.scheme) { |
|||
Hoek.assert(uriOptions.scheme instanceof RegExp || typeof uriOptions.scheme === 'string' || Array.isArray(uriOptions.scheme), 'scheme must be a RegExp, String, or Array'); |
|||
|
|||
if (!Array.isArray(uriOptions.scheme)) { |
|||
uriOptions.scheme = [uriOptions.scheme]; |
|||
} |
|||
|
|||
Hoek.assert(uriOptions.scheme.length >= 1, 'scheme must have at least 1 scheme specified'); |
|||
|
|||
// Flatten the array into a string to be used to match the schemes.
|
|||
for (var i = 0, il = uriOptions.scheme.length; i < il; ++i) { |
|||
var scheme = uriOptions.scheme[i]; |
|||
Hoek.assert(scheme instanceof RegExp || typeof scheme === 'string', 'scheme at position ' + i + ' must be a RegExp or String'); |
|||
|
|||
// Add OR separators if a value already exists
|
|||
customScheme += customScheme ? '|' : ''; |
|||
|
|||
// If someone wants to match HTTP or HTTPS for example then we need to support both RegExp and String so we don't escape their pattern unknowingly.
|
|||
if (scheme instanceof RegExp) { |
|||
customScheme += scheme.source; |
|||
} |
|||
else { |
|||
Hoek.assert(/[a-zA-Z][a-zA-Z0-9+-\.]*/.test(scheme), 'scheme at position ' + i + ' must be a valid scheme'); |
|||
customScheme += Hoek.escapeRegex(scheme); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (customScheme) { |
|||
regex = Uri.createUriRegex(customScheme); |
|||
} |
|||
|
|||
return this._test('uri', uriOptions, function (value, state, options) { |
|||
|
|||
if (regex.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
if (customScheme) { |
|||
return Errors.create('string.uriCustomScheme', { scheme: customScheme, value: value }, state, options); |
|||
} |
|||
|
|||
return Errors.create('string.uri', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.isoDate = function () { |
|||
|
|||
return this._test('isoDate', undefined, function (value, state, options) { |
|||
|
|||
if (JoiDate._isIsoDate(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.isoDate', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.guid = function () { |
|||
|
|||
var regex = /^[A-F0-9]{8}(?:-?[A-F0-9]{4}){3}-?[A-F0-9]{12}$/i; |
|||
var regex2 = /^\{[A-F0-9]{8}(?:-?[A-F0-9]{4}){3}-?[A-F0-9]{12}\}$/i; |
|||
|
|||
return this._test('guid', undefined, function (value, state, options) { |
|||
|
|||
if (regex.test(value) || regex2.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.guid', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.hex = function () { |
|||
|
|||
var regex = /^[a-f0-9]+$/i; |
|||
|
|||
return this._test('hex', regex, function (value, state, options) { |
|||
|
|||
if (regex.test(value)) { |
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.hex', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.hostname = function () { |
|||
|
|||
var regex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; |
|||
|
|||
return this._test('hostname', undefined, function (value, state, options) { |
|||
|
|||
if ((value.length <= 255 && regex.test(value)) || |
|||
Net.isIPv6(value)) { |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.hostname', { value: value }, state, options); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.lowercase = function () { |
|||
|
|||
var obj = this._test('lowercase', undefined, function (value, state, options) { |
|||
|
|||
if (options.convert || |
|||
value === value.toLocaleLowerCase()) { |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.lowercase', { value: value }, state, options); |
|||
}); |
|||
|
|||
obj._flags.case = 'lower'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.uppercase = function () { |
|||
|
|||
var obj = this._test('uppercase', undefined, function (value, state, options) { |
|||
|
|||
if (options.convert || |
|||
value === value.toLocaleUpperCase()) { |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.uppercase', { value: value }, state, options); |
|||
}); |
|||
|
|||
obj._flags.case = 'upper'; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.trim = function () { |
|||
|
|||
var obj = this._test('trim', undefined, function (value, state, options) { |
|||
|
|||
if (options.convert || |
|||
value === value.trim()) { |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return Errors.create('string.trim', { value: value }, state, options); |
|||
}); |
|||
|
|||
obj._flags.trim = true; |
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
internals.String.prototype.replace = function (pattern, replacement) { |
|||
|
|||
if (typeof pattern === 'string') { |
|||
pattern = new RegExp(Hoek.escapeRegex(pattern), 'g'); |
|||
} |
|||
|
|||
Hoek.assert(pattern instanceof RegExp, 'pattern must be a RegExp'); |
|||
Hoek.assert(typeof replacement === 'string', 'replacement must be a String'); |
|||
|
|||
// This can not be considere a test like trim, we can't "reject"
|
|||
// anything from this rule, so just clone the current object
|
|||
var obj = this.clone(); |
|||
|
|||
if (!obj._inner.replacements) { |
|||
obj._inner.replacements = []; |
|||
} |
|||
|
|||
obj._inner.replacements.push({ |
|||
pattern: pattern, |
|||
replacement: replacement |
|||
}); |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
module.exports = new internals.String(); |
@ -0,0 +1,32 @@ |
|||
var RFC3986 = require('./rfc3986'); |
|||
|
|||
var internals = { |
|||
Ip: { |
|||
cidrs: { |
|||
required: '\\/(?:' + RFC3986.cidr + ')', |
|||
optional: '(?:\\/(?:' + RFC3986.cidr + '))?', |
|||
forbidden: '' |
|||
}, |
|||
versions: { |
|||
ipv4: RFC3986.IPv4address, |
|||
ipv6: RFC3986.IPv6address, |
|||
ipvfuture: RFC3986.IPvFuture |
|||
} |
|||
} |
|||
}; |
|||
|
|||
internals.Ip.createIpRegex = function (versions, cidr) { |
|||
|
|||
var regex; |
|||
for (var i = 0, il = versions.length; i < il; ++i) { |
|||
var version = versions[i]; |
|||
if (!regex) { |
|||
regex = '^(?:' + internals.Ip.versions[version]; |
|||
} |
|||
regex += '|' + internals.Ip.versions[version]; |
|||
} |
|||
|
|||
return new RegExp(regex + ')' + internals.Ip.cidrs[cidr] + '$'); |
|||
}; |
|||
|
|||
module.exports = internals.Ip; |
@ -0,0 +1,174 @@ |
|||
var internals = { |
|||
rfc3986: {} |
|||
}; |
|||
|
|||
/** |
|||
* elements separated by forward slash ("/") are alternatives. |
|||
*/ |
|||
var or = '|'; |
|||
|
|||
/** |
|||
* DIGIT = %x30-39 ; 0-9 |
|||
*/ |
|||
var digit = '0-9'; |
|||
var digitOnly = '[' + digit + ']'; |
|||
|
|||
/** |
|||
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z |
|||
*/ |
|||
var alpha = 'a-zA-Z'; |
|||
var alphaOnly = '[' + alpha + ']'; |
|||
|
|||
/** |
|||
* cidr = DIGIT ; 0-9 |
|||
* / %x31-32 DIGIT ; 10-29 |
|||
* / "3" %x30-32 ; 30-32 |
|||
*/ |
|||
internals.rfc3986.cidr = digitOnly + or + '[1-2]' + digitOnly + or + '3' + '[0-2]'; |
|||
|
|||
/** |
|||
* HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" |
|||
*/ |
|||
var hexDigit = digit + 'A-Fa-f', |
|||
hexDigitOnly = '[' + hexDigit + ']'; |
|||
|
|||
/** |
|||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" |
|||
*/ |
|||
var unreserved = alpha + digit + '-\\._~'; |
|||
|
|||
/** |
|||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" |
|||
*/ |
|||
var subDelims = '!\\$&\'\\(\\)\\*\\+,;='; |
|||
|
|||
/** |
|||
* pct-encoded = "%" HEXDIG HEXDIG |
|||
*/ |
|||
var pctEncoded = '%' + hexDigit; |
|||
|
|||
/** |
|||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
|||
*/ |
|||
var pchar = unreserved + pctEncoded + subDelims + ':@'; |
|||
var pcharOnly = '[' + pchar + ']'; |
|||
|
|||
/** |
|||
* Rule to support zero-padded addresses. |
|||
*/ |
|||
var zeroPad = '0?'; |
|||
|
|||
/** |
|||
* dec-octet = DIGIT ; 0-9 |
|||
* / %x31-39 DIGIT ; 10-99 |
|||
* / "1" 2DIGIT ; 100-199 |
|||
* / "2" %x30-34 DIGIT ; 200-249 |
|||
* / "25" %x30-35 ; 250-255 |
|||
*/ |
|||
var decOctect = '(?:' + zeroPad + zeroPad + digitOnly + or + zeroPad + '[1-9]' + digitOnly + or + '1' + digitOnly + digitOnly + or + '2' + '[0-4]' + digitOnly + or + '25' + '[0-5])'; |
|||
|
|||
/** |
|||
* IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet |
|||
*/ |
|||
internals.rfc3986.IPv4address = '(?:' + decOctect + '\\.){3}' + decOctect; |
|||
|
|||
/** |
|||
* h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal |
|||
* ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address |
|||
* IPv6address = 6( h16 ":" ) ls32 |
|||
* / "::" 5( h16 ":" ) ls32 |
|||
* / [ h16 ] "::" 4( h16 ":" ) ls32 |
|||
* / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 |
|||
* / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 |
|||
* / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 |
|||
* / [ *4( h16 ":" ) h16 ] "::" ls32 |
|||
* / [ *5( h16 ":" ) h16 ] "::" h16 |
|||
* / [ *6( h16 ":" ) h16 ] "::" |
|||
*/ |
|||
var h16 = hexDigitOnly + '{1,4}'; |
|||
var ls32 = '(?:' + h16 + ':' + h16 + '|' + internals.rfc3986.IPv4address + ')'; |
|||
var IPv6SixHex = '(?:' + h16 + ':){6}' + ls32; |
|||
var IPv6FiveHex = '::(?:' + h16 + ':){5}' + ls32; |
|||
var IPv6FourHex = h16 + '::(?:' + h16 + ':){4}' + ls32; |
|||
var IPv6ThreeHex = '(?:' + h16 + ':){0,1}' + h16 + '::(?:' + h16 + ':){3}' + ls32; |
|||
var IPv6TwoHex = '(?:' + h16 + ':){0,2}' + h16 + '::(?:' + h16 + ':){2}' + ls32; |
|||
var IPv6OneHex = '(?:' + h16 + ':){0,3}' + h16 + '::' + h16 + ':' + ls32; |
|||
var IPv6NoneHex = '(?:' + h16 + ':){0,4}' + h16 + '::' + ls32; |
|||
var IPv6NoneHex2 = '(?:' + h16 + ':){0,5}' + h16 + '::' + h16; |
|||
var IPv6NoneHex3 = '(?:' + h16 + ':){0,6}' + h16 + '::'; |
|||
internals.rfc3986.IPv6address = '(?:' + IPv6SixHex + or + IPv6FiveHex + or + IPv6FourHex + or + IPv6ThreeHex + or + IPv6TwoHex + or + IPv6OneHex + or + IPv6NoneHex + or + IPv6NoneHex2 + or + IPv6NoneHex3 + ')'; |
|||
|
|||
/** |
|||
* IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) |
|||
*/ |
|||
internals.rfc3986.IPvFuture = 'v' + hexDigitOnly + '+\\.[' + unreserved + subDelims + ':]+'; |
|||
|
|||
/** |
|||
* scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
|||
*/ |
|||
internals.rfc3986.scheme = alphaOnly + '[' + alpha + digit + '+-\\.]*'; |
|||
|
|||
/** |
|||
* userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) |
|||
*/ |
|||
var userinfo = '[' + unreserved + pctEncoded + subDelims + ':]*'; |
|||
|
|||
/** |
|||
* IP-literal = "[" ( IPv6address / IPvFuture ) "]" |
|||
*/ |
|||
var IPLiteral = '\\[(?:' + internals.rfc3986.IPv6address + or + internals.rfc3986.IPvFuture + ')\\]'; |
|||
|
|||
/** |
|||
* reg-name = *( unreserved / pct-encoded / sub-delims ) |
|||
*/ |
|||
var regName = '[' + unreserved + pctEncoded + subDelims + ']{0,255}'; |
|||
|
|||
/** |
|||
* host = IP-literal / IPv4address / reg-name |
|||
*/ |
|||
var host = '(?:' + IPLiteral + or + internals.rfc3986.IPv4address + or + regName + ')'; |
|||
|
|||
/** |
|||
* port = *DIGIT |
|||
*/ |
|||
var port = digitOnly + '*'; |
|||
|
|||
/** |
|||
* authority = [ userinfo "@" ] host [ ":" port ] |
|||
*/ |
|||
var authority = '(?:' + userinfo + '@)?' + host + '(?::' + port + ')?'; |
|||
|
|||
/** |
|||
* segment = *pchar |
|||
* segment-nz = 1*pchar |
|||
* path = path-abempty ; begins with "/" or is empty |
|||
* / path-absolute ; begins with "/" but not "//"
|
|||
* / path-noscheme ; begins with a non-colon segment |
|||
* / path-rootless ; begins with a segment |
|||
* / path-empty ; zero characters |
|||
* path-abempty = *( "/" segment ) |
|||
* path-absolute = "/" [ segment-nz *( "/" segment ) ] |
|||
* path-rootless = segment-nz *( "/" segment ) |
|||
*/ |
|||
var segment = pcharOnly + '*'; |
|||
var segmentNz = pcharOnly + '+'; |
|||
var pathAbEmpty = '(?:\\/' + segment + ')*'; |
|||
var pathAbsolute = '\\/(?:' + segmentNz + pathAbEmpty + ')?'; |
|||
var pathRootless = segmentNz + pathAbEmpty; |
|||
|
|||
/** |
|||
* hier-part = "//" authority path |
|||
*/ |
|||
internals.rfc3986.hierPart = '(?:\\/\\/' + authority + pathAbEmpty + or + pathAbsolute + or + pathRootless + ')'; |
|||
|
|||
/** |
|||
* query = *( pchar / "/" / "?" ) |
|||
*/ |
|||
internals.rfc3986.query = '[' + pchar + '\\/\\?]*(?=#|$)'; //Finish matching either at the fragment part or end of the line.
|
|||
|
|||
/** |
|||
* fragment = *( pchar / "/" / "?" ) |
|||
*/ |
|||
internals.rfc3986.fragment = '[' + pchar + '\\/\\?]*'; |
|||
|
|||
module.exports = internals.rfc3986; |
@ -0,0 +1,24 @@ |
|||
var RFC3986 = require('./rfc3986'); |
|||
|
|||
var internals = { |
|||
Uri: { |
|||
createUriRegex: function (optionalScheme) { |
|||
|
|||
var scheme = RFC3986.scheme; |
|||
|
|||
// If we were passed a scheme, use it instead of the generic one
|
|||
if (optionalScheme) { |
|||
|
|||
// Have to put this in a non-capturing group to handle the OR statements
|
|||
scheme = '(?:' + optionalScheme + ')'; |
|||
} |
|||
|
|||
/** |
|||
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] |
|||
*/ |
|||
return new RegExp('^' + scheme + ':' + RFC3986.hierPart + '(?:\\?' + RFC3986.query + ')?' + '(?:#' + RFC3986.fragment + ')?$'); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
module.exports = internals.Uri; |
@ -0,0 +1,18 @@ |
|||
.idea |
|||
*.iml |
|||
npm-debug.log |
|||
dump.rdb |
|||
node_modules |
|||
results.tap |
|||
results.xml |
|||
npm-shrinkwrap.json |
|||
config.json |
|||
.DS_Store |
|||
*/.DS_Store |
|||
*/*/.DS_Store |
|||
._* |
|||
*/._* |
|||
*/*/._* |
|||
coverage.* |
|||
lib-cov |
|||
complexity.md |
@ -0,0 +1,7 @@ |
|||
language: node_js |
|||
|
|||
node_js: |
|||
- 0.10 |
|||
- 4.0 |
|||
|
|||
sudo: false |
@ -0,0 +1 @@ |
|||
Please view our [hapijs contributing guide](https://github.com/hapijs/hapi/blob/master/CONTRIBUTING.md). |
@ -0,0 +1,31 @@ |
|||
Copyright (c) 2011-2014, Walmart and other contributors. |
|||
Copyright (c) 2011, Yahoo Inc. |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
* Redistributions of source code must retain the above copyright |
|||
notice, this list of conditions and the following disclaimer. |
|||
* Redistributions in binary form must reproduce the above copyright |
|||
notice, this list of conditions and the following disclaimer in the |
|||
documentation and/or other materials provided with the distribution. |
|||
* The names of any contributors may not be used to endorse or promote |
|||
products derived from this software without specific prior written |
|||
permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY |
|||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
|
|||
* * * |
|||
|
|||
The complete list of contributors can be found at: https://github.com/hapijs/hapi/graphs/contributors |
|||
Portions of this project were initially based on the Yahoo! Inc. Postmile project, |
|||
published at https://github.com/yahoo/postmile. |
@ -0,0 +1,584 @@ |
|||
![hoek Logo](https://raw.github.com/hapijs/hoek/master/images/hoek.png) |
|||
|
|||
Utility methods for the hapi ecosystem. This module is not intended to solve every problem for everyone, but rather as a central place to store hapi-specific methods. If you're looking for a general purpose utility module, check out [lodash](https://github.com/lodash/lodash) or [underscore](https://github.com/jashkenas/underscore). |
|||
|
|||
[![Build Status](https://secure.travis-ci.org/hapijs/hoek.svg)](http://travis-ci.org/hapijs/hoek) |
|||
|
|||
Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf) |
|||
|
|||
# Table of Contents |
|||
|
|||
* [Introduction](#introduction "Introduction") |
|||
* [Object](#object "Object") |
|||
* [clone](#cloneobj "clone") |
|||
* [cloneWithShallow](#clonewithshallowobj-keys "cloneWithShallow") |
|||
* [merge](#mergetarget-source-isnulloverride-ismergearrays "merge") |
|||
* [applyToDefaults](#applytodefaultsdefaults-options-isnulloverride "applyToDefaults") |
|||
* [applyToDefaultsWithShallow](#applytodefaultswithshallowdefaults-options-keys "applyToDefaultsWithShallow") |
|||
* [deepEqual](#deepequala-b "deepEqual") |
|||
* [unique](#uniquearray-key "unique") |
|||
* [mapToObject](#maptoobjectarray-key "mapToObject") |
|||
* [intersect](#intersectarray1-array2 "intersect") |
|||
* [contain](#containref-values-options "contain") |
|||
* [flatten](#flattenarray-target "flatten") |
|||
* [reach](#reachobj-chain-options "reach") |
|||
* [reachTemplate](#reachtemplateobj-template-options "reachTemplate") |
|||
* [transform](#transformobj-transform-options "transform") |
|||
* [shallow](#shallowobj "shallow") |
|||
* [stringify](#stringifyobj "stringify") |
|||
* [Timer](#timer "Timer") |
|||
* [Bench](#bench "Bench") |
|||
* [Binary Encoding/Decoding](#binary-encodingdecoding "Binary Encoding/Decoding") |
|||
* [base64urlEncode](#base64urlencodevalue "binary64urlEncode") |
|||
* [base64urlDecode](#base64urldecodevalue "binary64urlDecode") |
|||
* [Escaping Characters](#escaping-characters "Escaping Characters") |
|||
* [escapeHtml](#escapehtmlstring "escapeHtml") |
|||
* [escapeHeaderAttribute](#escapeheaderattributeattribute "escapeHeaderAttribute") |
|||
* [escapeRegex](#escaperegexstring "escapeRegex") |
|||
* [Errors](#errors "Errors") |
|||
* [assert](#assertcondition-message "assert") |
|||
* [abort](#abortmessage "abort") |
|||
* [displayStack](#displaystackslice "displayStack") |
|||
* [callStack](#callstackslice "callStack") |
|||
* [Function](#function "Function") |
|||
* [nextTick](#nexttickfn "nextTick") |
|||
* [once](#oncefn "once") |
|||
* [ignore](#ignore "ignore") |
|||
* [Miscellaneous](#miscellaneous "Miscellaneous") |
|||
* [uniqueFilename](#uniquefilenamepath-extension "uniqueFilename") |
|||
* [isAbsolutePath](#isabsolutepathpath-platform "isAbsolutePath") |
|||
* [isInteger](#isintegervalue "isInteger") |
|||
|
|||
|
|||
|
|||
# Introduction |
|||
|
|||
The *Hoek* library contains some common functions used within the hapi ecosystem. It comes with useful methods for Arrays (clone, merge, applyToDefaults), Objects (removeKeys, copy), Asserting and more. |
|||
|
|||
For example, to use Hoek to set configuration with default options: |
|||
```javascript |
|||
var Hoek = require('hoek'); |
|||
|
|||
var default = {url : "www.github.com", port : "8000", debug : true}; |
|||
|
|||
var config = Hoek.applyToDefaults(default, {port : "3000", admin : true}); |
|||
|
|||
// In this case, config would be { url: 'www.github.com', port: '3000', debug: true, admin: true } |
|||
``` |
|||
|
|||
Under each of the sections (such as Array), there are subsections which correspond to Hoek methods. Each subsection will explain how to use the corresponding method. In each js excerpt below, the `var Hoek = require('hoek');` is omitted for brevity. |
|||
|
|||
## Object |
|||
|
|||
Hoek provides several helpful methods for objects and arrays. |
|||
|
|||
### clone(obj) |
|||
|
|||
This method is used to clone an object or an array. A *deep copy* is made (duplicates everything, including values that are objects, as well as non-enumerable properties). |
|||
|
|||
```javascript |
|||
|
|||
var nestedObj = { |
|||
w: /^something$/ig, |
|||
x: { |
|||
a: [1, 2, 3], |
|||
b: 123456, |
|||
c: new Date() |
|||
}, |
|||
y: 'y', |
|||
z: new Date() |
|||
}; |
|||
|
|||
var copy = Hoek.clone(nestedObj); |
|||
|
|||
copy.x.b = 100; |
|||
|
|||
console.log(copy.y); // results in 'y' |
|||
console.log(nestedObj.x.b); // results in 123456 |
|||
console.log(copy.x.b); // results in 100 |
|||
``` |
|||
|
|||
### cloneWithShallow(obj, keys) |
|||
keys is an array of key names to shallow copy |
|||
|
|||
This method is also used to clone an object or array, however any keys listed in the `keys` array are shallow copied while those not listed are deep copied. |
|||
|
|||
```javascript |
|||
|
|||
var nestedObj = { |
|||
w: /^something$/ig, |
|||
x: { |
|||
a: [1, 2, 3], |
|||
b: 123456, |
|||
c: new Date() |
|||
}, |
|||
y: 'y', |
|||
z: new Date() |
|||
}; |
|||
|
|||
var copy = Hoek.cloneWithShallow(nestedObj, ['x']); |
|||
|
|||
copy.x.b = 100; |
|||
|
|||
console.log(copy.y); // results in 'y' |
|||
console.log(nestedObj.x.b); // results in 100 |
|||
console.log(copy.x.b); // results in 100 |
|||
``` |
|||
|
|||
### merge(target, source, isNullOverride, isMergeArrays) |
|||
isNullOverride, isMergeArrays default to true |
|||
|
|||
Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied. |
|||
Merge is destructive where the target is modified. For non destructive merge, use `applyToDefaults`. |
|||
|
|||
|
|||
```javascript |
|||
|
|||
var target = {a: 1, b : 2}; |
|||
var source = {a: 0, c: 5}; |
|||
var source2 = {a: null, c: 5}; |
|||
|
|||
Hoek.merge(target, source); // results in {a: 0, b: 2, c: 5} |
|||
Hoek.merge(target, source2); // results in {a: null, b: 2, c: 5} |
|||
Hoek.merge(target, source2, false); // results in {a: 1, b: 2, c: 5} |
|||
|
|||
var targetArray = [1, 2, 3]; |
|||
var sourceArray = [4, 5]; |
|||
|
|||
Hoek.merge(targetArray, sourceArray); // results in [1, 2, 3, 4, 5] |
|||
Hoek.merge(targetArray, sourceArray, true, false); // results in [4, 5] |
|||
``` |
|||
|
|||
### applyToDefaults(defaults, options, isNullOverride) |
|||
isNullOverride defaults to false |
|||
|
|||
Apply options to a copy of the defaults |
|||
|
|||
```javascript |
|||
|
|||
var defaults = { host: "localhost", port: 8000 }; |
|||
var options = { port: 8080 }; |
|||
|
|||
var config = Hoek.applyToDefaults(defaults, options); // results in { host: "localhost", port: 8080 } |
|||
``` |
|||
|
|||
Apply options with a null value to a copy of the defaults |
|||
|
|||
```javascript |
|||
|
|||
var defaults = { host: "localhost", port: 8000 }; |
|||
var options = { host: null, port: 8080 }; |
|||
|
|||
var config = Hoek.applyToDefaults(defaults, options, true); // results in { host: null, port: 8080 } |
|||
``` |
|||
|
|||
### applyToDefaultsWithShallow(defaults, options, keys) |
|||
keys is an array of key names to shallow copy |
|||
|
|||
Apply options to a copy of the defaults. Keys specified in the last parameter are shallow copied from options instead of merged. |
|||
|
|||
```javascript |
|||
|
|||
var defaults = { |
|||
server: { |
|||
host: "localhost", |
|||
port: 8000 |
|||
}, |
|||
name: 'example' |
|||
}; |
|||
|
|||
var options = { server: { port: 8080 } }; |
|||
|
|||
var config = Hoek.applyToDefaultsWithShallow(defaults, options, ['server']); // results in { server: { port: 8080 }, name: 'example' } |
|||
``` |
|||
|
|||
### deepEqual(b, a, [options]) |
|||
|
|||
Performs a deep comparison of the two values including support for circular dependencies, prototype, and properties. To skip prototype comparisons, use `options.prototype = false` |
|||
|
|||
```javascript |
|||
Hoek.deepEqual({ a: [1, 2], b: 'string', c: { d: true } }, { a: [1, 2], b: 'string', c: { d: true } }); //results in true |
|||
Hoek.deepEqual(Object.create(null), {}, { prototype: false }); //results in true |
|||
Hoek.deepEqual(Object.create(null), {}); //results in false |
|||
``` |
|||
|
|||
### unique(array, key) |
|||
|
|||
Remove duplicate items from Array |
|||
|
|||
```javascript |
|||
|
|||
var array = [1, 2, 2, 3, 3, 4, 5, 6]; |
|||
|
|||
var newArray = Hoek.unique(array); // results in [1,2,3,4,5,6] |
|||
|
|||
array = [{id: 1}, {id: 1}, {id: 2}]; |
|||
|
|||
newArray = Hoek.unique(array, "id"); // results in [{id: 1}, {id: 2}] |
|||
``` |
|||
|
|||
### mapToObject(array, key) |
|||
|
|||
Convert an Array into an Object |
|||
|
|||
```javascript |
|||
|
|||
var array = [1,2,3]; |
|||
var newObject = Hoek.mapToObject(array); // results in [{"1": true}, {"2": true}, {"3": true}] |
|||
|
|||
array = [{id: 1}, {id: 2}]; |
|||
newObject = Hoek.mapToObject(array, "id"); // results in [{"id": 1}, {"id": 2}] |
|||
``` |
|||
|
|||
### intersect(array1, array2) |
|||
|
|||
Find the common unique items in two arrays |
|||
|
|||
```javascript |
|||
|
|||
var array1 = [1, 2, 3]; |
|||
var array2 = [1, 4, 5]; |
|||
|
|||
var newArray = Hoek.intersect(array1, array2); // results in [1] |
|||
``` |
|||
|
|||
### contain(ref, values, [options]) |
|||
|
|||
Tests if the reference value contains the provided values where: |
|||
- `ref` - the reference string, array, or object. |
|||
- `values` - a single or array of values to find within the `ref` value. If `ref` is an object, `values` can be a key name, |
|||
an array of key names, or an object with key-value pairs to compare. |
|||
- `options` - an optional object with the following optional settings: |
|||
- `deep` - if `true`, performed a deep comparison of the values. |
|||
- `once` - if `true`, allows only one occurrence of each value. |
|||
- `only` - if `true`, does not allow values not explicitly listed. |
|||
- `part` - if `true`, allows partial match of the values (at least one must always match). |
|||
|
|||
Note: comparing a string to overlapping values will result in failed comparison (e.g. `contain('abc', ['ab', 'bc'])`). |
|||
Also, if an object key's value does not match the provided value, `false` is returned even when `part` is specified. |
|||
|
|||
```javascript |
|||
Hoek.contain('aaa', 'a', { only: true }); // true |
|||
Hoek.contain([{ a: 1 }], [{ a: 1 }], { deep: true }); // true |
|||
Hoek.contain([1, 2, 2], [1, 2], { once: true }); // false |
|||
Hoek.contain({ a: 1, b: 2, c: 3 }, { a: 1, d: 4 }, { part: true }); // true |
|||
``` |
|||
|
|||
### flatten(array, [target]) |
|||
|
|||
Flatten an array |
|||
|
|||
```javascript |
|||
|
|||
var array = [1, [2, 3]]; |
|||
|
|||
var flattenedArray = Hoek.flatten(array); // results in [1, 2, 3] |
|||
|
|||
array = [1, [2, 3]]; |
|||
target = [4, [5]]; |
|||
|
|||
flattenedArray = Hoek.flatten(array, target); // results in [4, [5], 1, 2, 3] |
|||
``` |
|||
|
|||
### reach(obj, chain, [options]) |
|||
|
|||
Converts an object key chain string to reference |
|||
|
|||
- `options` - optional settings |
|||
- `separator` - string to split chain path on, defaults to '.' |
|||
- `default` - value to return if the path or value is not present, default is `undefined` |
|||
- `strict` - if `true`, will throw an error on missing member, default is `false` |
|||
- `functions` - if `true` allow traversing functions for properties. `false` will throw an error if a function is part of the chain. |
|||
|
|||
A chain including negative numbers will work like negative indices on an |
|||
array. |
|||
|
|||
If chain is `null`, `undefined` or `false`, the object itself will be returned. |
|||
|
|||
```javascript |
|||
|
|||
var chain = 'a.b.c'; |
|||
var obj = {a : {b : { c : 1}}}; |
|||
|
|||
Hoek.reach(obj, chain); // returns 1 |
|||
|
|||
var chain = 'a.b.-1'; |
|||
var obj = {a : {b : [2,3,6]}}; |
|||
|
|||
Hoek.reach(obj, chain); // returns 6 |
|||
``` |
|||
|
|||
### reachTemplate(obj, template, [options]) |
|||
|
|||
Replaces string parameters (`{name}`) with their corresponding object key values by applying the |
|||
(`reach()`)[#reachobj-chain-options] method where: |
|||
|
|||
- `obj` - the context object used for key lookup. |
|||
- `template` - a string containing `{}` parameters. |
|||
- `options` - optional (`reach()`)[#reachobj-chain-options] options. |
|||
|
|||
```javascript |
|||
|
|||
var chain = 'a.b.c'; |
|||
var obj = {a : {b : { c : 1}}}; |
|||
|
|||
Hoek.reachTemplate(obj, '1+{a.b.c}=2'); // returns '1+1=2' |
|||
``` |
|||
|
|||
### transform(obj, transform, [options]) |
|||
|
|||
Transforms an existing object into a new one based on the supplied `obj` and `transform` map. `options` are the same as the `reach` options. The first argument can also be an array of objects. In that case the method will return an array of transformed objects. |
|||
|
|||
```javascript |
|||
var source = { |
|||
address: { |
|||
one: '123 main street', |
|||
two: 'PO Box 1234' |
|||
}, |
|||
title: 'Warehouse', |
|||
state: 'CA' |
|||
}; |
|||
|
|||
var result = Hoek.transform(source, { |
|||
'person.address.lineOne': 'address.one', |
|||
'person.address.lineTwo': 'address.two', |
|||
'title': 'title', |
|||
'person.address.region': 'state' |
|||
}); |
|||
// Results in |
|||
// { |
|||
// person: { |
|||
// address: { |
|||
// lineOne: '123 main street', |
|||
// lineTwo: 'PO Box 1234', |
|||
// region: 'CA' |
|||
// } |
|||
// }, |
|||
// title: 'Warehouse' |
|||
// } |
|||
``` |
|||
|
|||
### shallow(obj) |
|||
|
|||
Performs a shallow copy by copying the references of all the top level children where: |
|||
- `obj` - the object to be copied. |
|||
|
|||
```javascript |
|||
var shallow = Hoek.shallow({ a: { b: 1 } }); |
|||
``` |
|||
|
|||
### stringify(obj) |
|||
|
|||
Converts an object to string using the built-in `JSON.stringify()` method with the difference that any errors are caught |
|||
and reported back in the form of the returned string. Used as a shortcut for displaying information to the console (e.g. in |
|||
error message) without the need to worry about invalid conversion. |
|||
|
|||
```javascript |
|||
var a = {}; |
|||
a.b = a; |
|||
Hoek.stringify(a); // Returns '[Cannot display object: Converting circular structure to JSON]' |
|||
``` |
|||
|
|||
# Timer |
|||
|
|||
A Timer object. Initializing a new timer object sets the ts to the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. |
|||
|
|||
```javascript |
|||
|
|||
var timerObj = new Hoek.Timer(); |
|||
console.log("Time is now: " + timerObj.ts); |
|||
console.log("Elapsed time from initialization: " + timerObj.elapsed() + 'milliseconds'); |
|||
``` |
|||
|
|||
|
|||
# Bench |
|||
|
|||
Same as Timer with the exception that `ts` stores the internal node clock which is not related to `Date.now()` and cannot be used to display |
|||
human-readable timestamps. More accurate for benchmarking or internal timers. |
|||
|
|||
# Binary Encoding/Decoding |
|||
|
|||
### base64urlEncode(value) |
|||
|
|||
Encodes value in Base64 or URL encoding |
|||
|
|||
### base64urlDecode(value) |
|||
|
|||
Decodes data in Base64 or URL encoding. |
|||
# Escaping Characters |
|||
|
|||
Hoek provides convenient methods for escaping html characters. The escaped characters are as followed: |
|||
|
|||
```javascript |
|||
|
|||
internals.htmlEscaped = { |
|||
'&': '&', |
|||
'<': '<', |
|||
'>': '>', |
|||
'"': '"', |
|||
"'": ''', |
|||
'`': '`' |
|||
}; |
|||
``` |
|||
|
|||
### escapeHtml(string) |
|||
|
|||
```javascript |
|||
|
|||
var string = '<html> hey </html>'; |
|||
var escapedString = Hoek.escapeHtml(string); // returns <html> hey </html> |
|||
``` |
|||
|
|||
### escapeHeaderAttribute(attribute) |
|||
|
|||
Escape attribute value for use in HTTP header |
|||
|
|||
```javascript |
|||
|
|||
var a = Hoek.escapeHeaderAttribute('I said "go w\\o me"'); //returns I said \"go w\\o me\" |
|||
``` |
|||
|
|||
|
|||
### escapeRegex(string) |
|||
|
|||
Escape string for Regex construction |
|||
|
|||
```javascript |
|||
|
|||
var a = Hoek.escapeRegex('4^f$s.4*5+-_?%=#!:@|~\\/`"(>)[<]d{}s,'); // returns 4\^f\$s\.4\*5\+\-_\?%\=#\!\:@\|~\\\/`"\(>\)\[<\]d\{\}s\, |
|||
``` |
|||
|
|||
# Errors |
|||
|
|||
### assert(condition, message) |
|||
|
|||
```javascript |
|||
|
|||
var a = 1, b = 2; |
|||
|
|||
Hoek.assert(a === b, 'a should equal b'); // Throws 'a should equal b' |
|||
``` |
|||
|
|||
Note that you may also pass an already created Error object as the second parameter, and `assert` will throw that object. |
|||
|
|||
```javascript |
|||
|
|||
var a = 1, b = 2; |
|||
|
|||
Hoek.assert(a === b, new Error('a should equal b')); // Throws the given error object |
|||
``` |
|||
|
|||
### abort(message) |
|||
|
|||
First checks if `process.env.NODE_ENV === 'test'`, and if so, throws error message. Otherwise, |
|||
displays most recent stack and then exits process. |
|||
|
|||
|
|||
|
|||
### displayStack(slice) |
|||
|
|||
Displays the trace stack |
|||
|
|||
```javascript |
|||
|
|||
var stack = Hoek.displayStack(); |
|||
console.log(stack); // returns something like: |
|||
|
|||
[ 'null (/Users/user/Desktop/hoek/test.js:4:18)', |
|||
'Module._compile (module.js:449:26)', |
|||
'Module._extensions..js (module.js:467:10)', |
|||
'Module.load (module.js:356:32)', |
|||
'Module._load (module.js:312:12)', |
|||
'Module.runMain (module.js:492:10)', |
|||
'startup.processNextTick.process._tickCallback (node.js:244:9)' ] |
|||
``` |
|||
|
|||
### callStack(slice) |
|||
|
|||
Returns a trace stack array. |
|||
|
|||
```javascript |
|||
|
|||
var stack = Hoek.callStack(); |
|||
console.log(stack); // returns something like: |
|||
|
|||
[ [ '/Users/user/Desktop/hoek/test.js', 4, 18, null, false ], |
|||
[ 'module.js', 449, 26, 'Module._compile', false ], |
|||
[ 'module.js', 467, 10, 'Module._extensions..js', false ], |
|||
[ 'module.js', 356, 32, 'Module.load', false ], |
|||
[ 'module.js', 312, 12, 'Module._load', false ], |
|||
[ 'module.js', 492, 10, 'Module.runMain', false ], |
|||
[ 'node.js', |
|||
244, |
|||
9, |
|||
'startup.processNextTick.process._tickCallback', |
|||
false ] ] |
|||
``` |
|||
|
|||
## Function |
|||
|
|||
### nextTick(fn) |
|||
|
|||
Returns a new function that wraps `fn` in `process.nextTick`. |
|||
|
|||
```javascript |
|||
|
|||
var myFn = function () { |
|||
console.log('Do this later'); |
|||
}; |
|||
|
|||
var nextFn = Hoek.nextTick(myFn); |
|||
|
|||
nextFn(); |
|||
console.log('Do this first'); |
|||
|
|||
// Results in: |
|||
// |
|||
// Do this first |
|||
// Do this later |
|||
``` |
|||
|
|||
### once(fn) |
|||
|
|||
Returns a new function that can be run multiple times, but makes sure `fn` is only run once. |
|||
|
|||
```javascript |
|||
|
|||
var myFn = function () { |
|||
console.log('Ran myFn'); |
|||
}; |
|||
|
|||
var onceFn = Hoek.once(myFn); |
|||
onceFn(); // results in "Ran myFn" |
|||
onceFn(); // results in undefined |
|||
``` |
|||
|
|||
### ignore |
|||
|
|||
A simple no-op function. It does nothing at all. |
|||
|
|||
## Miscellaneous |
|||
|
|||
### uniqueFilename(path, extension) |
|||
`path` to prepend with the randomly generated file name. `extension` is the optional file extension, defaults to `''`. |
|||
|
|||
Returns a randomly generated file name at the specified `path`. The result is a fully resolved path to a file. |
|||
|
|||
```javascript |
|||
var result = Hoek.uniqueFilename('./test/modules', 'txt'); // results in "full/path/test/modules/{random}.txt" |
|||
``` |
|||
|
|||
### isAbsolutePath(path, [platform]) |
|||
|
|||
Determines whether `path` is an absolute path. Returns `true` or `false`. |
|||
|
|||
- `path` - A file path to test for whether it is absolute or not. |
|||
- `platform` - An optional parameter used for specifying the platform. Defaults to `process.platform`. |
|||
|
|||
### isInteger(value) |
|||
|
|||
Check `value` to see if it is an integer. Returns true/false. |
|||
|
|||
```javascript |
|||
var result = Hoek.isInteger('23') |
|||
``` |
@ -0,0 +1,132 @@ |
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
exports.escapeJavaScript = function (input) { |
|||
|
|||
if (!input) { |
|||
return ''; |
|||
} |
|||
|
|||
var escaped = ''; |
|||
|
|||
for (var i = 0, il = input.length; i < il; ++i) { |
|||
|
|||
var charCode = input.charCodeAt(i); |
|||
|
|||
if (internals.isSafe(charCode)) { |
|||
escaped += input[i]; |
|||
} |
|||
else { |
|||
escaped += internals.escapeJavaScriptChar(charCode); |
|||
} |
|||
} |
|||
|
|||
return escaped; |
|||
}; |
|||
|
|||
|
|||
exports.escapeHtml = function (input) { |
|||
|
|||
if (!input) { |
|||
return ''; |
|||
} |
|||
|
|||
var escaped = ''; |
|||
|
|||
for (var i = 0, il = input.length; i < il; ++i) { |
|||
|
|||
var charCode = input.charCodeAt(i); |
|||
|
|||
if (internals.isSafe(charCode)) { |
|||
escaped += input[i]; |
|||
} |
|||
else { |
|||
escaped += internals.escapeHtmlChar(charCode); |
|||
} |
|||
} |
|||
|
|||
return escaped; |
|||
}; |
|||
|
|||
|
|||
internals.escapeJavaScriptChar = function (charCode) { |
|||
|
|||
if (charCode >= 256) { |
|||
return '\\u' + internals.padLeft('' + charCode, 4); |
|||
} |
|||
|
|||
var hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); |
|||
return '\\x' + internals.padLeft(hexValue, 2); |
|||
}; |
|||
|
|||
|
|||
internals.escapeHtmlChar = function (charCode) { |
|||
|
|||
var namedEscape = internals.namedHtml[charCode]; |
|||
if (typeof namedEscape !== 'undefined') { |
|||
return namedEscape; |
|||
} |
|||
|
|||
if (charCode >= 256) { |
|||
return '&#' + charCode + ';'; |
|||
} |
|||
|
|||
var hexValue = new Buffer(String.fromCharCode(charCode), 'ascii').toString('hex'); |
|||
return '&#x' + internals.padLeft(hexValue, 2) + ';'; |
|||
}; |
|||
|
|||
|
|||
internals.padLeft = function (str, len) { |
|||
|
|||
while (str.length < len) { |
|||
str = '0' + str; |
|||
} |
|||
|
|||
return str; |
|||
}; |
|||
|
|||
|
|||
internals.isSafe = function (charCode) { |
|||
|
|||
return (typeof internals.safeCharCodes[charCode] !== 'undefined'); |
|||
}; |
|||
|
|||
|
|||
internals.namedHtml = { |
|||
'38': '&', |
|||
'60': '<', |
|||
'62': '>', |
|||
'34': '"', |
|||
'160': ' ', |
|||
'162': '¢', |
|||
'163': '£', |
|||
'164': '¤', |
|||
'169': '©', |
|||
'174': '®' |
|||
}; |
|||
|
|||
|
|||
internals.safeCharCodes = (function () { |
|||
|
|||
var safe = {}; |
|||
|
|||
for (var i = 32; i < 123; ++i) { |
|||
|
|||
if ((i >= 97) || // a-z
|
|||
(i >= 65 && i <= 90) || // A-Z
|
|||
(i >= 48 && i <= 57) || // 0-9
|
|||
i === 32 || // space
|
|||
i === 46 || // .
|
|||
i === 44 || // ,
|
|||
i === 45 || // -
|
|||
i === 58 || // :
|
|||
i === 95) { // _
|
|||
|
|||
safe[i] = null; |
|||
} |
|||
} |
|||
|
|||
return safe; |
|||
}()); |
@ -0,0 +1,993 @@ |
|||
// Load modules
|
|||
|
|||
var Crypto = require('crypto'); |
|||
var Path = require('path'); |
|||
var Util = require('util'); |
|||
var Escape = require('./escape'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
// Clone object or array
|
|||
|
|||
exports.clone = function (obj, seen) { |
|||
|
|||
if (typeof obj !== 'object' || |
|||
obj === null) { |
|||
|
|||
return obj; |
|||
} |
|||
|
|||
seen = seen || { orig: [], copy: [] }; |
|||
|
|||
var lookup = seen.orig.indexOf(obj); |
|||
if (lookup !== -1) { |
|||
return seen.copy[lookup]; |
|||
} |
|||
|
|||
var newObj; |
|||
var cloneDeep = false; |
|||
|
|||
if (!Array.isArray(obj)) { |
|||
if (Buffer.isBuffer(obj)) { |
|||
newObj = new Buffer(obj); |
|||
} |
|||
else if (obj instanceof Date) { |
|||
newObj = new Date(obj.getTime()); |
|||
} |
|||
else if (obj instanceof RegExp) { |
|||
newObj = new RegExp(obj); |
|||
} |
|||
else { |
|||
var proto = Object.getPrototypeOf(obj); |
|||
if (proto && |
|||
proto.isImmutable) { |
|||
|
|||
newObj = obj; |
|||
} |
|||
else { |
|||
newObj = Object.create(proto); |
|||
cloneDeep = true; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
newObj = []; |
|||
cloneDeep = true; |
|||
} |
|||
|
|||
seen.orig.push(obj); |
|||
seen.copy.push(newObj); |
|||
|
|||
if (cloneDeep) { |
|||
var keys = Object.getOwnPropertyNames(obj); |
|||
for (var i = 0, il = keys.length; i < il; ++i) { |
|||
var key = keys[i]; |
|||
var descriptor = Object.getOwnPropertyDescriptor(obj, key); |
|||
if (descriptor && |
|||
(descriptor.get || |
|||
descriptor.set)) { |
|||
|
|||
Object.defineProperty(newObj, key, descriptor); |
|||
} |
|||
else { |
|||
newObj[key] = exports.clone(obj[key], seen); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return newObj; |
|||
}; |
|||
|
|||
|
|||
// Merge all the properties of source into target, source wins in conflict, and by default null and undefined from source are applied
|
|||
/*eslint-disable */ |
|||
exports.merge = function (target, source, isNullOverride /* = true */, isMergeArrays /* = true */) { |
|||
/*eslint-enable */ |
|||
exports.assert(target && typeof target === 'object', 'Invalid target value: must be an object'); |
|||
exports.assert(source === null || source === undefined || typeof source === 'object', 'Invalid source value: must be null, undefined, or an object'); |
|||
|
|||
if (!source) { |
|||
return target; |
|||
} |
|||
|
|||
if (Array.isArray(source)) { |
|||
exports.assert(Array.isArray(target), 'Cannot merge array onto an object'); |
|||
if (isMergeArrays === false) { // isMergeArrays defaults to true
|
|||
target.length = 0; // Must not change target assignment
|
|||
} |
|||
|
|||
for (var i = 0, il = source.length; i < il; ++i) { |
|||
target.push(exports.clone(source[i])); |
|||
} |
|||
|
|||
return target; |
|||
} |
|||
|
|||
var keys = Object.keys(source); |
|||
for (var k = 0, kl = keys.length; k < kl; ++k) { |
|||
var key = keys[k]; |
|||
var value = source[key]; |
|||
if (value && |
|||
typeof value === 'object') { |
|||
|
|||
if (!target[key] || |
|||
typeof target[key] !== 'object' || |
|||
(Array.isArray(target[key]) ^ Array.isArray(value)) || |
|||
value instanceof Date || |
|||
Buffer.isBuffer(value) || |
|||
value instanceof RegExp) { |
|||
|
|||
target[key] = exports.clone(value); |
|||
} |
|||
else { |
|||
exports.merge(target[key], value, isNullOverride, isMergeArrays); |
|||
} |
|||
} |
|||
else { |
|||
if (value !== null && |
|||
value !== undefined) { // Explicit to preserve empty strings
|
|||
|
|||
target[key] = value; |
|||
} |
|||
else if (isNullOverride !== false) { // Defaults to true
|
|||
target[key] = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
return target; |
|||
}; |
|||
|
|||
|
|||
// Apply options to a copy of the defaults
|
|||
|
|||
exports.applyToDefaults = function (defaults, options, isNullOverride) { |
|||
|
|||
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); |
|||
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); |
|||
|
|||
if (!options) { // If no options, return null
|
|||
return null; |
|||
} |
|||
|
|||
var copy = exports.clone(defaults); |
|||
|
|||
if (options === true) { // If options is set to true, use defaults
|
|||
return copy; |
|||
} |
|||
|
|||
return exports.merge(copy, options, isNullOverride === true, false); |
|||
}; |
|||
|
|||
|
|||
// Clone an object except for the listed keys which are shallow copied
|
|||
|
|||
exports.cloneWithShallow = function (source, keys) { |
|||
|
|||
if (!source || |
|||
typeof source !== 'object') { |
|||
|
|||
return source; |
|||
} |
|||
|
|||
var storage = internals.store(source, keys); // Move shallow copy items to storage
|
|||
var copy = exports.clone(source); // Deep copy the rest
|
|||
internals.restore(copy, source, storage); // Shallow copy the stored items and restore
|
|||
return copy; |
|||
}; |
|||
|
|||
|
|||
internals.store = function (source, keys) { |
|||
|
|||
var storage = {}; |
|||
for (var i = 0, il = keys.length; i < il; ++i) { |
|||
var key = keys[i]; |
|||
var value = exports.reach(source, key); |
|||
if (value !== undefined) { |
|||
storage[key] = value; |
|||
internals.reachSet(source, key, undefined); |
|||
} |
|||
} |
|||
|
|||
return storage; |
|||
}; |
|||
|
|||
|
|||
internals.restore = function (copy, source, storage) { |
|||
|
|||
var keys = Object.keys(storage); |
|||
for (var i = 0, il = keys.length; i < il; ++i) { |
|||
var key = keys[i]; |
|||
internals.reachSet(copy, key, storage[key]); |
|||
internals.reachSet(source, key, storage[key]); |
|||
} |
|||
}; |
|||
|
|||
|
|||
internals.reachSet = function (obj, key, value) { |
|||
|
|||
var path = key.split('.'); |
|||
var ref = obj; |
|||
for (var i = 0, il = path.length; i < il; ++i) { |
|||
var segment = path[i]; |
|||
if (i + 1 === il) { |
|||
ref[segment] = value; |
|||
} |
|||
|
|||
ref = ref[segment]; |
|||
} |
|||
}; |
|||
|
|||
|
|||
// Apply options to defaults except for the listed keys which are shallow copied from option without merging
|
|||
|
|||
exports.applyToDefaultsWithShallow = function (defaults, options, keys) { |
|||
|
|||
exports.assert(defaults && typeof defaults === 'object', 'Invalid defaults value: must be an object'); |
|||
exports.assert(!options || options === true || typeof options === 'object', 'Invalid options value: must be true, falsy or an object'); |
|||
exports.assert(keys && Array.isArray(keys), 'Invalid keys'); |
|||
|
|||
if (!options) { // If no options, return null
|
|||
return null; |
|||
} |
|||
|
|||
var copy = exports.cloneWithShallow(defaults, keys); |
|||
|
|||
if (options === true) { // If options is set to true, use defaults
|
|||
return copy; |
|||
} |
|||
|
|||
var storage = internals.store(options, keys); // Move shallow copy items to storage
|
|||
exports.merge(copy, options, false, false); // Deep copy the rest
|
|||
internals.restore(copy, options, storage); // Shallow copy the stored items and restore
|
|||
return copy; |
|||
}; |
|||
|
|||
|
|||
// Deep object or array comparison
|
|||
|
|||
exports.deepEqual = function (obj, ref, options, seen) { |
|||
|
|||
options = options || { prototype: true }; |
|||
|
|||
var type = typeof obj; |
|||
|
|||
if (type !== typeof ref) { |
|||
return false; |
|||
} |
|||
|
|||
if (type !== 'object' || |
|||
obj === null || |
|||
ref === null) { |
|||
|
|||
if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
|
|||
return obj !== 0 || 1 / obj === 1 / ref; // -0 / +0
|
|||
} |
|||
|
|||
return obj !== obj && ref !== ref; // NaN
|
|||
} |
|||
|
|||
seen = seen || []; |
|||
if (seen.indexOf(obj) !== -1) { |
|||
return true; // If previous comparison failed, it would have stopped execution
|
|||
} |
|||
|
|||
seen.push(obj); |
|||
|
|||
if (Array.isArray(obj)) { |
|||
if (!Array.isArray(ref)) { |
|||
return false; |
|||
} |
|||
|
|||
if (!options.part && obj.length !== ref.length) { |
|||
return false; |
|||
} |
|||
|
|||
for (var i = 0, il = obj.length; i < il; ++i) { |
|||
if (options.part) { |
|||
var found = false; |
|||
for (var r = 0, rl = ref.length; r < rl; ++r) { |
|||
if (exports.deepEqual(obj[i], ref[r], options, seen)) { |
|||
found = true; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return found; |
|||
} |
|||
|
|||
if (!exports.deepEqual(obj[i], ref[i], options, seen)) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
if (Buffer.isBuffer(obj)) { |
|||
if (!Buffer.isBuffer(ref)) { |
|||
return false; |
|||
} |
|||
|
|||
if (obj.length !== ref.length) { |
|||
return false; |
|||
} |
|||
|
|||
for (var j = 0, jl = obj.length; j < jl; ++j) { |
|||
if (obj[j] !== ref[j]) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
if (obj instanceof Date) { |
|||
return (ref instanceof Date && obj.getTime() === ref.getTime()); |
|||
} |
|||
|
|||
if (obj instanceof RegExp) { |
|||
return (ref instanceof RegExp && obj.toString() === ref.toString()); |
|||
} |
|||
|
|||
if (options.prototype) { |
|||
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
var keys = Object.getOwnPropertyNames(obj); |
|||
|
|||
if (!options.part && keys.length !== Object.getOwnPropertyNames(ref).length) { |
|||
return false; |
|||
} |
|||
|
|||
for (var k = 0, kl = keys.length; k < kl; ++k) { |
|||
var key = keys[k]; |
|||
var descriptor = Object.getOwnPropertyDescriptor(obj, key); |
|||
if (descriptor.get) { |
|||
if (!exports.deepEqual(descriptor, Object.getOwnPropertyDescriptor(ref, key), options, seen)) { |
|||
return false; |
|||
} |
|||
} |
|||
else if (!exports.deepEqual(obj[key], ref[key], options, seen)) { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
|
|||
// Remove duplicate items from array
|
|||
|
|||
exports.unique = function (array, key) { |
|||
|
|||
var index = {}; |
|||
var result = []; |
|||
|
|||
for (var i = 0, il = array.length; i < il; ++i) { |
|||
var id = (key ? array[i][key] : array[i]); |
|||
if (index[id] !== true) { |
|||
|
|||
result.push(array[i]); |
|||
index[id] = true; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
// Convert array into object
|
|||
|
|||
exports.mapToObject = function (array, key) { |
|||
|
|||
if (!array) { |
|||
return null; |
|||
} |
|||
|
|||
var obj = {}; |
|||
for (var i = 0, il = array.length; i < il; ++i) { |
|||
if (key) { |
|||
if (array[i][key]) { |
|||
obj[array[i][key]] = true; |
|||
} |
|||
} |
|||
else { |
|||
obj[array[i]] = true; |
|||
} |
|||
} |
|||
|
|||
return obj; |
|||
}; |
|||
|
|||
|
|||
// Find the common unique items in two arrays
|
|||
|
|||
exports.intersect = function (array1, array2, justFirst) { |
|||
|
|||
if (!array1 || !array2) { |
|||
return []; |
|||
} |
|||
|
|||
var common = []; |
|||
var hash = (Array.isArray(array1) ? exports.mapToObject(array1) : array1); |
|||
var found = {}; |
|||
for (var i = 0, il = array2.length; i < il; ++i) { |
|||
if (hash[array2[i]] && !found[array2[i]]) { |
|||
if (justFirst) { |
|||
return array2[i]; |
|||
} |
|||
|
|||
common.push(array2[i]); |
|||
found[array2[i]] = true; |
|||
} |
|||
} |
|||
|
|||
return (justFirst ? null : common); |
|||
}; |
|||
|
|||
|
|||
// Test if the reference contains the values
|
|||
|
|||
exports.contain = function (ref, values, options) { |
|||
|
|||
/* |
|||
string -> string(s) |
|||
array -> item(s) |
|||
object -> key(s) |
|||
object -> object (key:value) |
|||
*/ |
|||
|
|||
var valuePairs = null; |
|||
if (typeof ref === 'object' && |
|||
typeof values === 'object' && |
|||
!Array.isArray(ref) && |
|||
!Array.isArray(values)) { |
|||
|
|||
valuePairs = values; |
|||
values = Object.keys(values); |
|||
} |
|||
else { |
|||
values = [].concat(values); |
|||
} |
|||
|
|||
options = options || {}; // deep, once, only, part
|
|||
|
|||
exports.assert(arguments.length >= 2, 'Insufficient arguments'); |
|||
exports.assert(typeof ref === 'string' || typeof ref === 'object', 'Reference must be string or an object'); |
|||
exports.assert(values.length, 'Values array cannot be empty'); |
|||
|
|||
var compare, compareFlags; |
|||
if (options.deep) { |
|||
compare = exports.deepEqual; |
|||
|
|||
var hasOnly = options.hasOwnProperty('only'), hasPart = options.hasOwnProperty('part'); |
|||
|
|||
compareFlags = { |
|||
prototype: hasOnly ? options.only : hasPart ? !options.part : false, |
|||
part: hasOnly ? !options.only : hasPart ? options.part : true |
|||
}; |
|||
} |
|||
else { |
|||
compare = function (a, b) { |
|||
|
|||
return a === b; |
|||
}; |
|||
} |
|||
|
|||
var misses = false; |
|||
var matches = new Array(values.length); |
|||
for (var i = 0, il = matches.length; i < il; ++i) { |
|||
matches[i] = 0; |
|||
} |
|||
|
|||
if (typeof ref === 'string') { |
|||
var pattern = '('; |
|||
for (i = 0, il = values.length; i < il; ++i) { |
|||
var value = values[i]; |
|||
exports.assert(typeof value === 'string', 'Cannot compare string reference to non-string value'); |
|||
pattern += (i ? '|' : '') + exports.escapeRegex(value); |
|||
} |
|||
|
|||
var regex = new RegExp(pattern + ')', 'g'); |
|||
var leftovers = ref.replace(regex, function ($0, $1) { |
|||
|
|||
var index = values.indexOf($1); |
|||
++matches[index]; |
|||
return ''; // Remove from string
|
|||
}); |
|||
|
|||
misses = !!leftovers; |
|||
} |
|||
else if (Array.isArray(ref)) { |
|||
for (i = 0, il = ref.length; i < il; ++i) { |
|||
for (var j = 0, jl = values.length, matched = false; j < jl && matched === false; ++j) { |
|||
matched = compare(values[j], ref[i], compareFlags) && j; |
|||
} |
|||
|
|||
if (matched !== false) { |
|||
++matches[matched]; |
|||
} |
|||
else { |
|||
misses = true; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
var keys = Object.keys(ref); |
|||
for (i = 0, il = keys.length; i < il; ++i) { |
|||
var key = keys[i]; |
|||
var pos = values.indexOf(key); |
|||
if (pos !== -1) { |
|||
if (valuePairs && |
|||
!compare(valuePairs[key], ref[key], compareFlags)) { |
|||
|
|||
return false; |
|||
} |
|||
|
|||
++matches[pos]; |
|||
} |
|||
else { |
|||
misses = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
var result = false; |
|||
for (i = 0, il = matches.length; i < il; ++i) { |
|||
result = result || !!matches[i]; |
|||
if ((options.once && matches[i] > 1) || |
|||
(!options.part && !matches[i])) { |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
|
|||
if (options.only && |
|||
misses) { |
|||
|
|||
return false; |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
// Flatten array
|
|||
|
|||
exports.flatten = function (array, target) { |
|||
|
|||
var result = target || []; |
|||
|
|||
for (var i = 0, il = array.length; i < il; ++i) { |
|||
if (Array.isArray(array[i])) { |
|||
exports.flatten(array[i], result); |
|||
} |
|||
else { |
|||
result.push(array[i]); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
// Convert an object key chain string ('a.b.c') to reference (object[a][b][c])
|
|||
|
|||
exports.reach = function (obj, chain, options) { |
|||
|
|||
if (chain === false || |
|||
chain === null || |
|||
typeof chain === 'undefined') { |
|||
|
|||
return obj; |
|||
} |
|||
|
|||
options = options || {}; |
|||
if (typeof options === 'string') { |
|||
options = { separator: options }; |
|||
} |
|||
|
|||
var path = chain.split(options.separator || '.'); |
|||
var ref = obj; |
|||
for (var i = 0, il = path.length; i < il; ++i) { |
|||
var key = path[i]; |
|||
if (key[0] === '-' && Array.isArray(ref)) { |
|||
key = key.slice(1, key.length); |
|||
key = ref.length - key; |
|||
} |
|||
|
|||
if (!ref || |
|||
!ref.hasOwnProperty(key) || |
|||
(typeof ref !== 'object' && options.functions === false)) { // Only object and function can have properties
|
|||
|
|||
exports.assert(!options.strict || i + 1 === il, 'Missing segment', key, 'in reach path ', chain); |
|||
exports.assert(typeof ref === 'object' || options.functions === true || typeof ref !== 'function', 'Invalid segment', key, 'in reach path ', chain); |
|||
ref = options.default; |
|||
break; |
|||
} |
|||
|
|||
ref = ref[key]; |
|||
} |
|||
|
|||
return ref; |
|||
}; |
|||
|
|||
|
|||
exports.reachTemplate = function (obj, template, options) { |
|||
|
|||
return template.replace(/{([^}]+)}/g, function ($0, chain) { |
|||
|
|||
var value = exports.reach(obj, chain, options); |
|||
return (value === undefined || value === null ? '' : value); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
exports.formatStack = function (stack) { |
|||
|
|||
var trace = []; |
|||
for (var i = 0, il = stack.length; i < il; ++i) { |
|||
var item = stack[i]; |
|||
trace.push([item.getFileName(), item.getLineNumber(), item.getColumnNumber(), item.getFunctionName(), item.isConstructor()]); |
|||
} |
|||
|
|||
return trace; |
|||
}; |
|||
|
|||
|
|||
exports.formatTrace = function (trace) { |
|||
|
|||
var display = []; |
|||
|
|||
for (var i = 0, il = trace.length; i < il; ++i) { |
|||
var row = trace[i]; |
|||
display.push((row[4] ? 'new ' : '') + row[3] + ' (' + row[0] + ':' + row[1] + ':' + row[2] + ')'); |
|||
} |
|||
|
|||
return display; |
|||
}; |
|||
|
|||
|
|||
exports.callStack = function (slice) { |
|||
|
|||
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
|
|||
|
|||
var v8 = Error.prepareStackTrace; |
|||
Error.prepareStackTrace = function (err, stack) { |
|||
|
|||
return stack; |
|||
}; |
|||
|
|||
var capture = {}; |
|||
Error.captureStackTrace(capture, arguments.callee); /*eslint no-caller:0 */ |
|||
var stack = capture.stack; |
|||
|
|||
Error.prepareStackTrace = v8; |
|||
|
|||
var trace = exports.formatStack(stack); |
|||
|
|||
if (slice) { |
|||
return trace.slice(slice); |
|||
} |
|||
|
|||
return trace; |
|||
}; |
|||
|
|||
|
|||
exports.displayStack = function (slice) { |
|||
|
|||
var trace = exports.callStack(slice === undefined ? 1 : slice + 1); |
|||
|
|||
return exports.formatTrace(trace); |
|||
}; |
|||
|
|||
|
|||
exports.abortThrow = false; |
|||
|
|||
|
|||
exports.abort = function (message, hideStack) { |
|||
|
|||
if (process.env.NODE_ENV === 'test' || exports.abortThrow === true) { |
|||
throw new Error(message || 'Unknown error'); |
|||
} |
|||
|
|||
var stack = ''; |
|||
if (!hideStack) { |
|||
stack = exports.displayStack(1).join('\n\t'); |
|||
} |
|||
console.log('ABORT: ' + message + '\n\t' + stack); |
|||
process.exit(1); |
|||
}; |
|||
|
|||
|
|||
exports.assert = function (condition /*, msg1, msg2, msg3 */) { |
|||
|
|||
if (condition) { |
|||
return; |
|||
} |
|||
|
|||
if (arguments.length === 2 && arguments[1] instanceof Error) { |
|||
throw arguments[1]; |
|||
} |
|||
|
|||
var msgs = []; |
|||
for (var i = 1, il = arguments.length; i < il; ++i) { |
|||
if (arguments[i] !== '') { |
|||
msgs.push(arguments[i]); // Avoids Array.slice arguments leak, allowing for V8 optimizations
|
|||
} |
|||
} |
|||
|
|||
msgs = msgs.map(function (msg) { |
|||
|
|||
return typeof msg === 'string' ? msg : msg instanceof Error ? msg.message : exports.stringify(msg); |
|||
}); |
|||
throw new Error(msgs.join(' ') || 'Unknown error'); |
|||
}; |
|||
|
|||
|
|||
exports.Timer = function () { |
|||
|
|||
this.ts = 0; |
|||
this.reset(); |
|||
}; |
|||
|
|||
|
|||
exports.Timer.prototype.reset = function () { |
|||
|
|||
this.ts = Date.now(); |
|||
}; |
|||
|
|||
|
|||
exports.Timer.prototype.elapsed = function () { |
|||
|
|||
return Date.now() - this.ts; |
|||
}; |
|||
|
|||
|
|||
exports.Bench = function () { |
|||
|
|||
this.ts = 0; |
|||
this.reset(); |
|||
}; |
|||
|
|||
|
|||
exports.Bench.prototype.reset = function () { |
|||
|
|||
this.ts = exports.Bench.now(); |
|||
}; |
|||
|
|||
|
|||
exports.Bench.prototype.elapsed = function () { |
|||
|
|||
return exports.Bench.now() - this.ts; |
|||
}; |
|||
|
|||
|
|||
exports.Bench.now = function () { |
|||
|
|||
var ts = process.hrtime(); |
|||
return (ts[0] * 1e3) + (ts[1] / 1e6); |
|||
}; |
|||
|
|||
|
|||
// Escape string for Regex construction
|
|||
|
|||
exports.escapeRegex = function (string) { |
|||
|
|||
// Escape ^$.*+-?=!:|\/()[]{},
|
|||
return string.replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&'); |
|||
}; |
|||
|
|||
|
|||
// Base64url (RFC 4648) encode
|
|||
|
|||
exports.base64urlEncode = function (value, encoding) { |
|||
|
|||
var buf = (Buffer.isBuffer(value) ? value : new Buffer(value, encoding || 'binary')); |
|||
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); |
|||
}; |
|||
|
|||
|
|||
// Base64url (RFC 4648) decode
|
|||
|
|||
exports.base64urlDecode = function (value, encoding) { |
|||
|
|||
if (value && |
|||
!/^[\w\-]*$/.test(value)) { |
|||
|
|||
return new Error('Invalid character'); |
|||
} |
|||
|
|||
try { |
|||
var buf = new Buffer(value, 'base64'); |
|||
return (encoding === 'buffer' ? buf : buf.toString(encoding || 'binary')); |
|||
} |
|||
catch (err) { |
|||
return err; |
|||
} |
|||
}; |
|||
|
|||
|
|||
// Escape attribute value for use in HTTP header
|
|||
|
|||
exports.escapeHeaderAttribute = function (attribute) { |
|||
|
|||
// Allowed value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9, \, "
|
|||
|
|||
exports.assert(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~\"\\]*$/.test(attribute), 'Bad attribute value (' + attribute + ')'); |
|||
|
|||
return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); // Escape quotes and slash
|
|||
}; |
|||
|
|||
|
|||
exports.escapeHtml = function (string) { |
|||
|
|||
return Escape.escapeHtml(string); |
|||
}; |
|||
|
|||
|
|||
exports.escapeJavaScript = function (string) { |
|||
|
|||
return Escape.escapeJavaScript(string); |
|||
}; |
|||
|
|||
|
|||
exports.nextTick = function (callback) { |
|||
|
|||
return function () { |
|||
|
|||
var args = arguments; |
|||
process.nextTick(function () { |
|||
|
|||
callback.apply(null, args); |
|||
}); |
|||
}; |
|||
}; |
|||
|
|||
|
|||
exports.once = function (method) { |
|||
|
|||
if (method._hoekOnce) { |
|||
return method; |
|||
} |
|||
|
|||
var once = false; |
|||
var wrapped = function () { |
|||
|
|||
if (!once) { |
|||
once = true; |
|||
method.apply(null, arguments); |
|||
} |
|||
}; |
|||
|
|||
wrapped._hoekOnce = true; |
|||
|
|||
return wrapped; |
|||
}; |
|||
|
|||
|
|||
exports.isAbsolutePath = function (path, platform) { |
|||
|
|||
if (!path) { |
|||
return false; |
|||
} |
|||
|
|||
if (Path.isAbsolute) { // node >= 0.11
|
|||
return Path.isAbsolute(path); |
|||
} |
|||
|
|||
platform = platform || process.platform; |
|||
|
|||
// Unix
|
|||
|
|||
if (platform !== 'win32') { |
|||
return path[0] === '/'; |
|||
} |
|||
|
|||
// Windows
|
|||
|
|||
return !!/^(?:[a-zA-Z]:[\\\/])|(?:[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/])/.test(path); // C:\ or \\something\something
|
|||
}; |
|||
|
|||
|
|||
exports.isInteger = function (value) { |
|||
|
|||
return (typeof value === 'number' && |
|||
parseFloat(value) === parseInt(value, 10) && |
|||
!isNaN(value)); |
|||
}; |
|||
|
|||
|
|||
exports.ignore = function () { }; |
|||
|
|||
|
|||
exports.inherits = Util.inherits; |
|||
|
|||
|
|||
exports.format = Util.format; |
|||
|
|||
|
|||
exports.transform = function (source, transform, options) { |
|||
|
|||
exports.assert(source === null || source === undefined || typeof source === 'object' || Array.isArray(source), 'Invalid source object: must be null, undefined, an object, or an array'); |
|||
|
|||
if (Array.isArray(source)) { |
|||
var results = []; |
|||
for (var i = 0, il = source.length; i < il; ++i) { |
|||
results.push(exports.transform(source[i], transform, options)); |
|||
} |
|||
return results; |
|||
} |
|||
|
|||
var result = {}; |
|||
var keys = Object.keys(transform); |
|||
|
|||
for (var k = 0, kl = keys.length; k < kl; ++k) { |
|||
var key = keys[k]; |
|||
var path = key.split('.'); |
|||
var sourcePath = transform[key]; |
|||
|
|||
exports.assert(typeof sourcePath === 'string', 'All mappings must be "." delineated strings'); |
|||
|
|||
var segment; |
|||
var res = result; |
|||
|
|||
while (path.length > 1) { |
|||
segment = path.shift(); |
|||
if (!res[segment]) { |
|||
res[segment] = {}; |
|||
} |
|||
res = res[segment]; |
|||
} |
|||
segment = path.shift(); |
|||
res[segment] = exports.reach(source, sourcePath, options); |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
|
|||
exports.uniqueFilename = function (path, extension) { |
|||
|
|||
if (extension) { |
|||
extension = extension[0] !== '.' ? '.' + extension : extension; |
|||
} |
|||
else { |
|||
extension = ''; |
|||
} |
|||
|
|||
path = Path.resolve(path); |
|||
var name = [Date.now(), process.pid, Crypto.randomBytes(8).toString('hex')].join('-') + extension; |
|||
return Path.join(path, name); |
|||
}; |
|||
|
|||
|
|||
exports.stringify = function () { |
|||
|
|||
try { |
|||
return JSON.stringify.apply(null, arguments); |
|||
} |
|||
catch (err) { |
|||
return '[Cannot display object: ' + err.message + ']'; |
|||
} |
|||
}; |
|||
|
|||
|
|||
exports.shallow = function (source) { |
|||
|
|||
var target = {}; |
|||
var keys = Object.keys(source); |
|||
for (var i = 0, il = keys.length; i < il; ++i) { |
|||
var key = keys[i]; |
|||
target[key] = source[key]; |
|||
} |
|||
|
|||
return target; |
|||
}; |
@ -0,0 +1,61 @@ |
|||
{ |
|||
"name": "hoek", |
|||
"description": "General purpose node utilities", |
|||
"version": "2.16.3", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git://github.com/hapijs/hoek.git" |
|||
}, |
|||
"main": "lib/index.js", |
|||
"keywords": [ |
|||
"utilities" |
|||
], |
|||
"engines": { |
|||
"node": ">=0.10.40" |
|||
}, |
|||
"dependencies": {}, |
|||
"devDependencies": { |
|||
"code": "1.x.x", |
|||
"lab": "5.x.x" |
|||
}, |
|||
"scripts": { |
|||
"test": "lab -a code -t 100 -L", |
|||
"test-cov-html": "lab -a code -t 100 -L -r html -o coverage.html" |
|||
}, |
|||
"license": "BSD-3-Clause", |
|||
"gitHead": "20f36e85616264d4b73a64a374803175213a9121", |
|||
"bugs": { |
|||
"url": "https://github.com/hapijs/hoek/issues" |
|||
}, |
|||
"homepage": "https://github.com/hapijs/hoek#readme", |
|||
"_id": "hoek@2.16.3", |
|||
"_shasum": "20bb7403d3cea398e91dc4710a8ff1b8274a25ed", |
|||
"_from": "hoek@>=2.0.0 <3.0.0", |
|||
"_npmVersion": "3.3.3", |
|||
"_nodeVersion": "4.1.0", |
|||
"_npmUser": { |
|||
"name": "nlf", |
|||
"email": "quitlahok@gmail.com" |
|||
}, |
|||
"dist": { |
|||
"shasum": "20bb7403d3cea398e91dc4710a8ff1b8274a25ed", |
|||
"tarball": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" |
|||
}, |
|||
"maintainers": [ |
|||
{ |
|||
"name": "hueniverse", |
|||
"email": "eran@hueniverse.com" |
|||
}, |
|||
{ |
|||
"name": "wyatt", |
|||
"email": "wpreul@gmail.com" |
|||
}, |
|||
{ |
|||
"name": "nlf", |
|||
"email": "quitlahok@gmail.com" |
|||
} |
|||
], |
|||
"directories": {}, |
|||
"_resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", |
|||
"readme": "ERROR: No README data found!" |
|||
} |
@ -0,0 +1,88 @@ |
|||
// Load modules
|
|||
|
|||
var Code = require('code'); |
|||
var Hoek = require('../lib'); |
|||
var Lab = require('lab'); |
|||
|
|||
|
|||
// Declare internals
|
|||
|
|||
var internals = {}; |
|||
|
|||
|
|||
// Test shortcuts
|
|||
|
|||
var lab = exports.lab = Lab.script(); |
|||
var describe = lab.experiment; |
|||
var it = lab.test; |
|||
var expect = Code.expect; |
|||
|
|||
|
|||
describe('escapeJavaScript()', function () { |
|||
|
|||
it('encodes / characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeJavaScript('<script>alert(1)</script>'); |
|||
expect(encoded).to.equal('\\x3cscript\\x3ealert\\x281\\x29\\x3c\\x2fscript\\x3e'); |
|||
done(); |
|||
}); |
|||
|
|||
it('encodes \' characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeJavaScript('something(\'param\')'); |
|||
expect(encoded).to.equal('something\\x28\\x27param\\x27\\x29'); |
|||
done(); |
|||
}); |
|||
|
|||
it('encodes large unicode characters with the correct padding', function (done) { |
|||
|
|||
var encoded = Hoek.escapeJavaScript(String.fromCharCode(500) + String.fromCharCode(1000)); |
|||
expect(encoded).to.equal('\\u0500\\u1000'); |
|||
done(); |
|||
}); |
|||
|
|||
it('doesn\'t throw an exception when passed null', function (done) { |
|||
|
|||
var encoded = Hoek.escapeJavaScript(null); |
|||
expect(encoded).to.equal(''); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
describe('escapeHtml()', function () { |
|||
|
|||
it('encodes / characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeHtml('<script>alert(1)</script>'); |
|||
expect(encoded).to.equal('<script>alert(1)</script>'); |
|||
done(); |
|||
}); |
|||
|
|||
it('encodes < and > as named characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeHtml('<script><>'); |
|||
expect(encoded).to.equal('<script><>'); |
|||
done(); |
|||
}); |
|||
|
|||
it('encodes large unicode characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeHtml(String.fromCharCode(500) + String.fromCharCode(1000)); |
|||
expect(encoded).to.equal('ǴϨ'); |
|||
done(); |
|||
}); |
|||
|
|||
it('doesn\'t throw an exception when passed null', function (done) { |
|||
|
|||
var encoded = Hoek.escapeHtml(null); |
|||
expect(encoded).to.equal(''); |
|||
done(); |
|||
}); |
|||
|
|||
it('encodes {} characters', function (done) { |
|||
|
|||
var encoded = Hoek.escapeHtml('{}'); |
|||
expect(encoded).to.equal('{}'); |
|||
done(); |
|||
}); |
|||
}); |
@ -0,0 +1 @@ |
|||
exports.x = 1; |
@ -0,0 +1 @@ |
|||
exports.y = 2; |
@ -0,0 +1 @@ |
|||
exports.z = 3; |
@ -0,0 +1,3 @@ |
|||
/coverage.html |
|||
/coverage.lcov |
|||
/node_modules |
@ -0,0 +1,5 @@ |
|||
language: node_js |
|||
node_js: |
|||
- "0.10" |
|||
- "4.0" |
|||
sudo: false |
@ -0,0 +1,13 @@ |
|||
Copyright © 2008-2011, Dominic Sayers |
|||
Copyright © 2013-2014, GlobeSherpa |
|||
Copyright © 2014-2015, Eli Skeggs |
|||
|
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
|||
|
|||
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
|||
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. |
|||
- Neither the name of Dominic Sayers nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,4 @@ |
|||
test: |
|||
npm test |
|||
|
|||
.PHONY: test |
@ -0,0 +1,95 @@ |
|||
isemail |
|||
======= |
|||
|
|||
Node email address validation library |
|||
|
|||
[![Build Status](https://travis-ci.org/hapijs/isemail.png)](https://travis-ci.org/hapijs/isemail) |
|||
[![Coverage Status](https://coveralls.io/repos/hapijs/isemail/badge.png?branch=master)](https://coveralls.io/r/hapijs/isemail?branch=master) |
|||
|
|||
Lead Maintainer: [Eli Skeggs](https://github.com/skeggse) |
|||
|
|||
This first version of `isemail` is a port of the PHP `is_email` function by Dominic Sayers. |
|||
|
|||
Install |
|||
------- |
|||
|
|||
```sh |
|||
$ npm install isemail |
|||
``` |
|||
|
|||
Test |
|||
---- |
|||
|
|||
The tests were pulled from is_email's extensive [test suite][tests] on October 15, 2013. Many thanks to the contributors! Additional tests have been added to increase code coverage and verify edge-cases. |
|||
|
|||
Run any of the following. |
|||
|
|||
```sh |
|||
$ lab |
|||
$ npm test |
|||
$ make test |
|||
``` |
|||
|
|||
_remember to_ `npm install`! |
|||
|
|||
API |
|||
--- |
|||
|
|||
### isEmail(email, [options], [callback]) |
|||
|
|||
Determines whether the `email` is valid or not, for various definitions thereof. Optionally accepts an `options` object and a `callback` function. Options may include `errorLevel` and `checkDNS`. The `callback` function will always be called if specified, and the result of the operation supplied as the only parameter to the callback function. If `isEmail` is not asked to check for the existence of the domain (`checkDNS`), it will also synchronously return the result of the operation. |
|||
|
|||
Use `errorLevel` to specify the type of result for `isEmail`. Passing a `false` literal will result in a true or false boolean indicating whether the email address is sufficiently defined for use in sending an email. Passing a `true` literal will result in a more granular numeric status, with zero being a perfectly valid email address. Passing a number will return `0` if the numeric status is below the `errorLevel` and the numeric status otherwise. |
|||
|
|||
The `tldWhitelist` option can be either an object lookup table or an array of valid top-level domains. If the email address has a top-level domain that is not in the whitelist, the email will be marked as invalid. |
|||
|
|||
The `minDomainAtoms` option is an optional positive integer that specifies the minimum number of domain atoms that must be included for the email address to be considered valid. Be careful with the option, as some top-level domains, like `io`, directly support email addresses. To better handle fringe cases like the `io` TLD, use the `checkDNS` parameter, which will only allow email addresses for domains which have an MX record. |
|||
|
|||
#### Examples |
|||
|
|||
```js |
|||
$ node |
|||
> var isEmail = require('isemail'); |
|||
undefined |
|||
> var log = console.log.bind(console, 'result'); |
|||
undefined |
|||
> isEmail('test@iana.org'); |
|||
true |
|||
> isEmail('test@iana.org', log); |
|||
result true |
|||
true |
|||
> isEmail('test@iana.org', {checkDNS: true}); |
|||
undefined |
|||
> isEmail('test@iana.org', {checkDNS: true}, log); |
|||
undefined |
|||
result true |
|||
> isEmail('test@iana.org', {errorLevel: true}); |
|||
0 |
|||
> isEmail('test@iana.org', {errorLevel: true}, log); |
|||
result 0 |
|||
0 |
|||
> isEmail('test@e.com'); |
|||
true |
|||
> isEmail('test@e.com', {checkDNS: true, errorLevel: true}, log); |
|||
undefined |
|||
result 6 |
|||
> isEmail('test@e.com', {checkDNS: true, errorLevel: 7}, log); |
|||
undefined |
|||
result 0 |
|||
> isEmail('test@e.com', {checkDNS: true, errorLevel: 6}, log); |
|||
undefined |
|||
result 6 |
|||
``` |
|||
|
|||
TODO |
|||
==== |
|||
|
|||
Add tests for library usage, not just functionality comparisons. |
|||
Future versions will improve upon the current version, optimizing it for efficient usage and DRYing the code. |
|||
|
|||
License |
|||
======= |
|||
|
|||
[BSD License](http://www.opensource.org/licenses/bsd-license.php) |
|||
|
|||
[tests]: http://isemail.info/_system/is_email/test/?all "is_email test suite" |
@ -0,0 +1 @@ |
|||
module.exports = require('./lib/isemail'); |
@ -0,0 +1,62 @@ |
|||
{ |
|||
"name": "isemail", |
|||
"version": "1.2.0", |
|||
"author": { |
|||
"name": "Eli Skeggs", |
|||
"email": "skeggse@gmail.com" |
|||
}, |
|||
"description": "validate an email address according to RFCs 5321, 5322, and others", |
|||
"main": "./index", |
|||
"scripts": { |
|||
"test": "lab -r console -o stdout -r lcov -o coverage.lcov -a code -L", |
|||
"test-cov-html": "lab -r html -o coverage.html -a code" |
|||
}, |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/hapijs/isemail.git" |
|||
}, |
|||
"homepage": "https://github.com/hapijs/isemail", |
|||
"bugs": { |
|||
"url": "https://github.com/hapijs/isemail/issues" |
|||
}, |
|||
"keywords": [ |
|||
"isemail", |
|||
"validation", |
|||
"check", |
|||
"checking", |
|||
"verification", |
|||
"email", |
|||
"address", |
|||
"email address" |
|||
], |
|||
"devDependencies": { |
|||
"code": "^1.5.0", |
|||
"lab": "^5.16.1" |
|||
}, |
|||
"license": "BSD-2-Clause", |
|||
"engines": { |
|||
"node": ">=0.10" |
|||
}, |
|||
"gitHead": "9441961a6c9979dc0e339971b111e25dc1a5b2fd", |
|||
"_id": "isemail@1.2.0", |
|||
"_shasum": "be03df8cc3e29de4d2c5df6501263f1fa4595e9a", |
|||
"_from": "isemail@>=1.0.0 <2.0.0", |
|||
"_npmVersion": "2.14.2", |
|||
"_nodeVersion": "4.0.0", |
|||
"_npmUser": { |
|||
"name": "skeggse", |
|||
"email": "skeggse@gmail.com" |
|||
}, |
|||
"dist": { |
|||
"shasum": "be03df8cc3e29de4d2c5df6501263f1fa4595e9a", |
|||
"tarball": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz" |
|||
}, |
|||
"maintainers": [ |
|||
{ |
|||
"name": "skeggse", |
|||
"email": "skeggse@gmail.com" |
|||
} |
|||
], |
|||
"directories": {}, |
|||
"_resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz" |
|||
} |
@ -0,0 +1,175 @@ |
|||
var Lab = require('lab'); |
|||
var Code = require('code'); |
|||
var IsEmail = require('..'); |
|||
var Tests = require('./tests.json'); |
|||
|
|||
var internals = { |
|||
defaultThreshold: 16 |
|||
}; |
|||
|
|||
// Test shortcuts
|
|||
var isEmail = IsEmail; |
|||
var lab = exports.lab = Lab.script(); |
|||
var before = lab.before; |
|||
var after = lab.after; |
|||
var describe = lab.describe; |
|||
var it = lab.it; |
|||
var expect = Code.expect; |
|||
|
|||
// Diagnoses
|
|||
var diag = isEmail.diagnoses; |
|||
|
|||
// Expectations
|
|||
var expectations = Tests.map(function mapper (value) { |
|||
|
|||
value[1] = diag[value[1]]; |
|||
return value; |
|||
}); |
|||
|
|||
// Null characters aren't supported in JSON
|
|||
expectations.push(['test@[\0', diag.errExpectingDTEXT]); |
|||
expectations.push(['(\0)test@example.com', diag.errExpectingCTEXT]); |
|||
|
|||
var tldExpectations = [ |
|||
['shouldbe@invalid', diag.errUnknownTLD], |
|||
['shouldbe@example.com', diag.valid] |
|||
]; |
|||
|
|||
describe('isEmail', function () { |
|||
|
|||
it('should check options.tldWhitelist', function (done) { |
|||
|
|||
expect(isEmail('person@top', { |
|||
tldWhitelist: 'top', |
|||
checkDNS: false |
|||
})).to.equal(true); |
|||
|
|||
expect(isEmail('person@top', { |
|||
tldWhitelist: ['com'], |
|||
checkDNS: false |
|||
})).to.equal(false); |
|||
|
|||
expect(function () { |
|||
|
|||
isEmail('', { |
|||
tldWhitelist: 77 |
|||
}); |
|||
}).to.throw(/tldWhitelist/); |
|||
done(); |
|||
}); |
|||
|
|||
it('should check options.minDomainAtoms', function (done) { |
|||
|
|||
expect(function () { |
|||
|
|||
isEmail('person@top', { |
|||
minDomainAtoms: -1 |
|||
}); |
|||
}).to.throw(/minDomainAtoms/); |
|||
|
|||
expect(function () { |
|||
|
|||
isEmail('person@top', { |
|||
minDomainAtoms: 1.5 |
|||
}); |
|||
}).to.throw(/minDomainAtoms/); |
|||
done(); |
|||
}); |
|||
|
|||
it('should use options.errorLevel', function (done) { |
|||
|
|||
expect(isEmail('person@123', { |
|||
errorLevel: diag.rfc5321TLDNumeric + 1 |
|||
})).to.equal(0); |
|||
|
|||
expect(isEmail('person@123', { |
|||
errorLevel: diag.rfc5321TLDNumeric |
|||
})).to.equal(diag.rfc5321TLDNumeric); |
|||
done(); |
|||
}); |
|||
|
|||
it('should ensure callback provided with checkDNS', function (done) { |
|||
|
|||
expect(function () { |
|||
|
|||
isEmail('person@top', { |
|||
checkDNS: true |
|||
}); |
|||
}).to.throw(/(?=.*\bcheckDNS\b)(?=.*\bcallback\b)/); |
|||
done(); |
|||
}); |
|||
|
|||
it('should handle omitted options', function (done) { |
|||
|
|||
expect(isEmail(expectations[0][0])).to.equal(expectations[0][1] < internals.defaultThreshold); |
|||
done(); |
|||
}); |
|||
|
|||
it('should handle omitted options with callback', function (done) { |
|||
|
|||
isEmail(expectations[0][0], function (res) { |
|||
|
|||
expect(res).to.equal(expectations[0][1] < internals.defaultThreshold); |
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
expectations.forEach(function (obj, i) { |
|||
|
|||
var email = obj[0], result = obj[1]; |
|||
it('should handle test ' + (i + 1), function (done) { |
|||
|
|||
isEmail(email, { |
|||
errorLevel: 0, |
|||
checkDNS: true |
|||
}, function (res) { |
|||
|
|||
expect(res).to.equal(result); |
|||
done(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
tldExpectations.forEach(function (obj, i) { |
|||
|
|||
var email = obj[0]; |
|||
var result = obj[1]; |
|||
|
|||
it('should handle tld test ' + (i + 1), function (done) { |
|||
|
|||
expect(isEmail(email, { |
|||
errorLevel: 0, |
|||
tldWhitelist: { |
|||
com: true |
|||
} |
|||
})).to.equal(result); |
|||
|
|||
expect(isEmail(email, { |
|||
errorLevel: 0, |
|||
tldWhitelist: ['com'] |
|||
})).to.equal(result); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
it('should handle domain atom test 1', function (done) { |
|||
|
|||
expect(isEmail('shouldbe@invalid', { |
|||
errorLevel: 0, |
|||
minDomainAtoms: 2 |
|||
})).to.equal(diag.errDomainTooShort); |
|||
|
|||
done(); |
|||
}); |
|||
|
|||
it('should handle domain atom test 2', function (done) { |
|||
|
|||
expect(isEmail('valid@example.com', { |
|||
errorLevel: 0, |
|||
minDomainAtoms: 2 |
|||
})).to.equal(diag.valid); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
@ -0,0 +1,2 @@ |
|||
--reporter dot |
|||
--check-leaks |
@ -0,0 +1,187 @@ |
|||
[ |
|||
["", "errNoDomain"], |
|||
["\r", "errCRNoLF"], |
|||
["test", "errNoDomain"], |
|||
["@", "errNoLocalPart"], |
|||
["test@", "errNoDomain"], |
|||
["test@io", "valid"], |
|||
["@io", "errNoLocalPart"], |
|||
["@iana.org", "errNoLocalPart"], |
|||
["test@iana.org", "valid"], |
|||
["test@nominet.org.uk", "valid"], |
|||
["test@about.museum", "valid"], |
|||
["a@iana.org", "valid"], |
|||
["test@e.com", "dnsWarnNoRecord"], |
|||
["test@iana.a", "dnsWarnNoRecord"], |
|||
["test.test@iana.org", "valid"], |
|||
[".test@iana.org", "errDotStart"], |
|||
["test.@iana.org", "errDotEnd"], |
|||
["test..iana.org", "errConsecutiveDots"], |
|||
["test_exa-mple.com", "errNoDomain"], |
|||
["!#$%&`*+/=?^`{|}~@iana.org", "valid"], |
|||
["test\\@test@iana.org", "errExpectingATEXT"], |
|||
["123@iana.org", "valid"], |
|||
["test@123.com", "valid"], |
|||
["test@iana.123", "rfc5321TLDNumeric"], |
|||
["test@255.255.255.255", "rfc5321TLDNumeric"], |
|||
["abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org", "valid"], |
|||
["abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org", "rfc5322LocalTooLong"], |
|||
["test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm", "rfc5322LabelTooLong"], |
|||
["test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com", "dnsWarnNoRecord"], |
|||
["test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm.com", "rfc5322LabelTooLong"], |
|||
["test@mason-dixon.com", "valid"], |
|||
["test@-iana.org", "errDomainHyphenStart"], |
|||
["test@iana-.com", "errDomainHyphenEnd"], |
|||
["test@iana.co-uk", "dnsWarnNoRecord"], |
|||
["test@.iana.org", "errDotStart"], |
|||
["test@iana.org.", "errDotEnd"], |
|||
["test@iana..com", "errConsecutiveDots"], |
|||
["a@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v", "dnsWarnNoRecord"], |
|||
["abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi", "dnsWarnNoRecord"], |
|||
["abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij", "rfc5322TooLong"], |
|||
["a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij", "rfc5322TooLong"], |
|||
["a@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk", "rfc5322DomainTooLong"], |
|||
["\"\r", "errCRNoLF"], |
|||
["\"test\"@iana.org", "rfc5321QuotedString"], |
|||
["\"\"@iana.org", "rfc5321QuotedString"], |
|||
["\"\"\"@iana.org", "errExpectingATEXT"], |
|||
["\"\\a\"@iana.org", "rfc5321QuotedString"], |
|||
["\"\\\"\"@iana.org", "rfc5321QuotedString"], |
|||
["\"\\\"@iana.org", "errUnclosedQuotedString"], |
|||
["\"\\\\\"@iana.org", "rfc5321QuotedString"], |
|||
["test\"@iana.org", "errExpectingATEXT"], |
|||
["\"test@iana.org", "errUnclosedQuotedString"], |
|||
["\"test\"test@iana.org", "errATEXTAfterQS"], |
|||
["test\"text\"@iana.org", "errExpectingATEXT"], |
|||
["\"test\"\"test\"@iana.org", "errExpectingATEXT"], |
|||
["\"test\".\"test\"@iana.org", "deprecatedLocalPart"], |
|||
["\"test\\ test\"@iana.org", "rfc5321QuotedString"], |
|||
["\"test\".test@iana.org", "deprecatedLocalPart"], |
|||
["\"test\u0000\"@iana.org", "errExpectingQTEXT"], |
|||
["\"test\\\u0000\"@iana.org", "deprecatedQP"], |
|||
["\"test\r\n test\"@iana.org", "cfwsFWS"], |
|||
["\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj\"@iana.org", "rfc5322LocalTooLong"], |
|||
["\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\\h\"@iana.org", "rfc5322LocalTooLong"], |
|||
["test@[255.255.255.255]", "rfc5321AddressLiteral"], |
|||
["test@a[255.255.255.255]", "errExpectingATEXT"], |
|||
["test@[255.255.255]", "rfc5322DomainLiteral"], |
|||
["test@[255.255.255.255.255]", "rfc5322DomainLiteral"], |
|||
["test@[255.255.255.256]", "rfc5322DomainLiteral"], |
|||
["test@[1111:2222:3333:4444:5555:6666:7777:8888]", "rfc5322DomainLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:7777]", "rfc5322IPv6GroupCount"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]", "rfc5322IPv6GroupCount"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]", "rfc5322IPv6BadCharacter"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666::8888]", "deprecatedIPv6"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555::8888]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]", "rfc5322IPv6MaxGroups"], |
|||
["test@[IPv6::3333:4444:5555:6666:7777:8888]", "rfc5322IPv6ColonStart"], |
|||
["test@[IPv6:::3333:4444:5555:6666:7777:8888]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111::4444:5555::8888]", "rfc5322IPv62x2xColon"], |
|||
["test@[IPv6:::]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]", "rfc5322IPv6GroupCount"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]", "rfc5322IPv6GroupCount"], |
|||
["test@[IPv6:1111:2222:3333:4444::255.255.255.255]", "rfc5321AddressLiteral"], |
|||
["test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]", "rfc5322IPv6MaxGroups"], |
|||
["test@[IPv6:1111:2222:3333:4444:::255.255.255.255]", "rfc5322IPv62x2xColon"], |
|||
["test@[IPv6::255.255.255.255]", "rfc5322IPv6ColonStart"], |
|||
[" test @iana.org", "deprecatedCFWSNearAt"], |
|||
["test@ iana .com", "deprecatedCFWSNearAt"], |
|||
["test . test@iana.org", "deprecatedFWS"], |
|||
["\r\n test@iana.org", "cfwsFWS"], |
|||
["\r\n \r\n test@iana.org", "deprecatedFWS"], |
|||
["(\r", "errCRNoLF"], |
|||
["(comment)test@iana.org", "cfwsComment"], |
|||
["((comment)test@iana.org", "errUnclosedComment"], |
|||
["(comment(comment))test@iana.org", "cfwsComment"], |
|||
["test@(comment)iana.org", "deprecatedCFWSNearAt"], |
|||
["test(comment)@iana.org", "deprecatedCFWSNearAt"], |
|||
["test(comment)test@iana.org", "errATEXTAfterCFWS"], |
|||
["test@(comment)[255.255.255.255]", "deprecatedCFWSNearAt"], |
|||
["(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org", "cfwsComment"], |
|||
["test@(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl.com", "deprecatedCFWSNearAt"], |
|||
["(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu", "cfwsComment"], |
|||
["test@iana.org\n", "errExpectingATEXT"], |
|||
["test@xn--hxajbheg2az3al.xn--jxalpdlp", "dnsWarnNoRecord"], |
|||
["xn--test@iana.org", "valid"], |
|||
["test@iana.org-", "errDomainHyphenEnd"], |
|||
["\"test@iana.org", "errUnclosedQuotedString"], |
|||
["(test@iana.org", "errUnclosedComment"], |
|||
["test@(iana.org", "errUnclosedComment"], |
|||
["test@[1.2.3.4", "errUnclosedDomainLiteral"], |
|||
["\"test\\\"@iana.org", "errUnclosedQuotedString"], |
|||
["(comment\\)test@iana.org", "errUnclosedComment"], |
|||
["test@iana.org(comment\\)", "errUnclosedComment"], |
|||
["test@iana.org(comment\\", "errBackslashEnd"], |
|||
["test@[RFC-5322-domain-literal]", "rfc5322DomainLiteral"], |
|||
["test@[RFC-5322]-domain-literal]", "errATEXTAfterDomainLiteral"], |
|||
["test@[RFC-5322-[domain-literal]", "errExpectingDTEXT"], |
|||
["test@[", "errExpectingDTEXT"], |
|||
["test@[\u0007]", "rfc5322DomainLiteralOBSDText"], |
|||
["test@[RFC-5322-\\\u0007-domain-literal]", "rfc5322DomainLiteralOBSDText"], |
|||
["test@[RFC-5322-\\\t-domain-literal]", "rfc5322DomainLiteralOBSDText"], |
|||
["test@[RFC-5322-\\]-domain-literal]", "rfc5322DomainLiteralOBSDText"], |
|||
["test@[RFC-5322--domain-literal]", "rfc5322DomainLiteralOBSDText"], |
|||
["test@[RFC-5322-domain-literal\\]", "errUnclosedDomainLiteral"], |
|||
["test@[RFC-5322-domain-literal\\", "errBackslashEnd"], |
|||
["test@[RFC 5322 domain literal]", "rfc5322DomainLiteral"], |
|||
["test@[RFC-5322-domain-literal] (comment)", "rfc5322DomainLiteral"], |
|||
["@iana.org", "errExpectingATEXT"], |
|||
["test@.org", "errExpectingATEXT"], |
|||
["\"\"@iana.org", "deprecatedQTEXT"], |
|||
["\"\"@iana.org", "errExpectingQTEXT"], |
|||
["\"\\\"@iana.org", "deprecatedQP"], |
|||
["()test@iana.org", "deprecatedCTEXT"], |
|||
["()test@iana.org", "errExpectingCTEXT"], |
|||
["test@iana.org\r", "errCRNoLF"], |
|||
["\rtest@iana.org", "errCRNoLF"], |
|||
["\"\rtest\"@iana.org", "errCRNoLF"], |
|||
["(\r)test@iana.org", "errCRNoLF"], |
|||
["test@iana.org(\r)", "errCRNoLF"], |
|||
["\ntest@iana.org", "errExpectingATEXT"], |
|||
["\"\n\"@iana.org", "errExpectingQTEXT"], |
|||
["\"\\\n\"@iana.org", "deprecatedQP"], |
|||
["(\n)test@iana.org", "errExpectingCTEXT"], |
|||
["\u0007@iana.org", "errExpectingATEXT"], |
|||
["test@\u0007.org", "errExpectingATEXT"], |
|||
["\"\u0007\"@iana.org", "deprecatedQTEXT"], |
|||
["\"\\\u0007\"@iana.org", "deprecatedQP"], |
|||
["(\u0007)test@iana.org", "deprecatedCTEXT"], |
|||
["\r\ntest@iana.org", "errFWSCRLFEnd"], |
|||
["\r\n \r\ntest@iana.org", "errFWSCRLFEnd"], |
|||
[" \r\ntest@iana.org", "errFWSCRLFEnd"], |
|||
[" \r\n test@iana.org", "cfwsFWS"], |
|||
[" \r\n \r\ntest@iana.org", "errFWSCRLFEnd"], |
|||
[" \r\n\r\ntest@iana.org", "errFWSCRLFx2"], |
|||
[" \r\n\r\n test@iana.org", "errFWSCRLFx2"], |
|||
["test@iana.org\r\n ", "cfwsFWS"], |
|||
["test@iana.org\r\n \r\n ", "deprecatedFWS"], |
|||
["test@iana.org\r\n", "errFWSCRLFEnd"], |
|||
["test@iana.org \r", "errCRNoLF"], |
|||
["test@iana.org\r\n \r\n", "errFWSCRLFEnd"], |
|||
["test@iana.org \r\n", "errFWSCRLFEnd"], |
|||
["test@iana.org \r\n ", "cfwsFWS"], |
|||
["test@iana.org \r\n \r\n", "errFWSCRLFEnd"], |
|||
["test@iana.org \r\n\r\n", "errFWSCRLFx2"], |
|||
["test@iana.org \r\n\r\n ", "errFWSCRLFx2"], |
|||
["test@iana. org", "deprecatedFWS"], |
|||
["test@[\r", "errCRNoLF"], |
|||
["test@[\r\n", "errFWSCRLFEnd"], |
|||
[" test@iana.org", "cfwsFWS"], |
|||
["test@iana.org ", "cfwsFWS"], |
|||
["test@[IPv6:1::2:]", "rfc5322IPv6ColonEnd"], |
|||
["\"test\\©\"@iana.org", "errExpectingQPair"], |
|||
["test@iana/icann.org", "rfc5322Domain"], |
|||
["test@iana!icann.org", "rfc5322Domain"], |
|||
["test@iana?icann.org", "rfc5322Domain"], |
|||
["test@iana^icann.org", "rfc5322Domain"], |
|||
["test@iana{icann}.org", "rfc5322Domain"], |
|||
["test.(comment)test@iana.org", "deprecatedComment"], |
|||
["test@iana.(comment)org", "deprecatedComment"], |
|||
["test@iana(comment)iana.org", "errATEXTAfterCFWS"], |
|||
["(comment\r\n comment)test@iana.org", "cfwsFWS"], |
|||
["test@org", "rfc5321TLD"], |
|||
["test@example.com", "dnsWarnNoMXRecord"], |
|||
["test@nic.no", "dnsWarnNoRecord"] |
|||
] |
@ -0,0 +1,2 @@ |
|||
test |
|||
min/tests.js |
@ -0,0 +1,563 @@ |
|||
Changelog |
|||
========= |
|||
|
|||
### 2.14.1 |
|||
* [#3280](https://github.com/moment/moment/pull/3280) Fix typescript definitions |
|||
|
|||
|
|||
### 2.14.0 [See full changelog](https://gist.github.com/ichernev/812e79ac36a7829a22598fe964bfc18a) |
|||
|
|||
## New Features |
|||
* [#3233](http://github.com/moment/moment/pull/3233) Introduce month.isFormat for format/standalone discovery |
|||
* [#2848](http://github.com/moment/moment/pull/2848) Allow user to get/set the rounding method used when calculating relative time |
|||
* [#3112](http://github.com/moment/moment/pull/3112) optimize configFromStringAndFormat |
|||
* [#3147](http://github.com/moment/moment/pull/3147) Call calendar format function with moment context |
|||
* [#3160](http://github.com/moment/moment/pull/3160) deprecate isDSTShifted |
|||
* [#3175](http://github.com/moment/moment/pull/3175) make moment calendar extensible with ad-hoc options |
|||
* [#3191](http://github.com/moment/moment/pull/3191) toDate returns a copy of the internal date object |
|||
* [#3192](http://github.com/moment/moment/pull/3192) Adding support for rollup import. |
|||
* [#3238](http://github.com/moment/moment/pull/3238) Handle empty object and empty array for creation as now |
|||
* [#3082](http://github.com/moment/moment/pull/3082) Use relative AMD moment dependency |
|||
|
|||
## Bugfixes |
|||
* [#3241](http://github.com/moment/moment/pull/3241) Escape all 24 mixed pieces, not only first 12 in computeMonthsParse |
|||
* [#3008](http://github.com/moment/moment/pull/3008) Object setter orders sets based on size of unit |
|||
* [#3177](http://github.com/moment/moment/pull/3177) Bug Fix [#2704](http://github.com/moment/moment/pull/2704) - isoWeekday(String) inconsistent with isoWeekday(Number) |
|||
* [#3230](http://github.com/moment/moment/pull/3230) fix passing date with format string to ignore format string |
|||
* [#3232](http://github.com/moment/moment/pull/3232) Fix negative 0 in certain diff cases |
|||
* [#3235](http://github.com/moment/moment/pull/3235) Use proper locale inheritance for the base locale, fixes [#3137](http://github.com/moment/moment/pull/3137) |
|||
|
|||
Plus es-do locale and locale bugfixes |
|||
|
|||
### 2.13.0 [See full changelog](https://gist.github.com/ichernev/0132fcf5b61f7fc140b0bb0090480d49) |
|||
|
|||
## Enhancements: |
|||
* [#2982](https://github.com/moment/moment/pull/2982) Add 'date' as alias to 'day' for startOf() and endOf(). |
|||
* [#2955](https://github.com/moment/moment/pull/2955) Add parsing negative components in durations when ISO 8601 |
|||
* [#2991](https://github.com/moment/moment/pull/2991) isBetween support for both open and closed intervals |
|||
* [#3105](https://github.com/moment/moment/pull/3105) Add localeSorted argument to weekday listers |
|||
* [#3102](https://github.com/moment/moment/pull/3102) Add k and kk formatting tokens |
|||
|
|||
## Bugfixes |
|||
* [#3109](https://github.com/moment/moment/pull/3109) Fix [#1756](https://github.com/moment/moment/issues/1756) Resolved thread-safe issue on server side. |
|||
* [#3078](https://github.com/moment/moment/pull/3078) Fix parsing for months/weekdays with weird characters |
|||
* [#3098](https://github.com/moment/moment/pull/3098) Use Z suffix when in UTC mode ([#3020](https://github.com/moment/moment/issues/3020)) |
|||
* [#2995](https://github.com/moment/moment/pull/2995) Fix floating point rounding errors in durations |
|||
* [#3059](https://github.com/moment/moment/pull/3059) fix bug where diff returns -0 in month-related diffs |
|||
* [#3045](https://github.com/moment/moment/pull/3045) Fix mistaking any input for 'a' token |
|||
* [#2877](https://github.com/moment/moment/pull/2877) Use explicit .valueOf() calls instead of coercion |
|||
* [#3036](https://github.com/moment/moment/pull/3036) Year setter should keep time when DST changes |
|||
|
|||
Plus 3 new locales and locale fixes. |
|||
|
|||
### 2.12.0 [See full changelog](https://gist.github.com/ichernev/6e5bfdf8d6522fc4ac73) |
|||
|
|||
## Enhancements: |
|||
* [#2932](https://github.com/moment/moment/pull/2932) List loaded locales |
|||
* [#2818](https://github.com/moment/moment/pull/2818) Parse ISO-8061 duration containing both day and week values |
|||
* [#2774](https://github.com/moment/moment/pull/2774) Implement locale inheritance and locale updating |
|||
|
|||
## Bugfixes: |
|||
* [#2970](https://github.com/moment/moment/pull/2970) change add subtract to handle decimal values by rounding |
|||
* [#2887](https://github.com/moment/moment/pull/2887) Fix toJSON casting of invalid moment |
|||
* [#2897](https://github.com/moment/moment/pull/2897) parse string arguments for month() correctly, closes #2884 |
|||
* [#2946](https://github.com/moment/moment/pull/2946) Fix usage suggestions for min and max |
|||
|
|||
## New locales: |
|||
* [#2917](https://github.com/moment/moment/pull/2917) Locale Punjabi(Gurmukhi) India format conversion |
|||
|
|||
And more |
|||
|
|||
### 2.11.2 (Fix ReDoS attack vector) |
|||
|
|||
* [#2939](https://github.com/moment/moment/pull/2939) use full-string match to speed up aspnet regex match |
|||
|
|||
### 2.11.1 [See full changelog](https://gist.github.com/ichernev/8ec3ee25b749b4cff3c2) |
|||
|
|||
## Bugfixes: |
|||
* [#2881](https://github.com/moment/moment/pull/2881) Revert "Merge pull request #2746 from mbad0la:develop" Sep->Sept |
|||
* [#2868](https://github.com/moment/moment/pull/2868) Add format and parse token Y, so it actually works |
|||
* [#2865](https://github.com/moment/moment/pull/2865) Use typeof checks for undefined for global variables |
|||
* [#2858](https://github.com/moment/moment/pull/2858) Fix Date mocking regression introduced in 2.11.0 |
|||
* [#2864](https://github.com/moment/moment/pull/2864) Include changelog in npm release |
|||
* [#2830](https://github.com/moment/moment/pull/2830) dep: add grunt-cli |
|||
* [#2869](https://github.com/moment/moment/pull/2869) Fix months parsing for some locales |
|||
|
|||
### 2.11.0 [See full changelog](https://gist.github.com/ichernev/6594bc29719dde6b2f66) |
|||
|
|||
* [#2624](https://github.com/moment/moment/pull/2624) Proper handling of invalid moments |
|||
* [#2634](https://github.com/moment/moment/pull/2634) Fix strict month parsing issue in cs,ru,sk |
|||
* [#2735](https://github.com/moment/moment/pull/2735) Reset the locale back to 'en' after defining all locales in min/locales.js |
|||
* [#2702](https://github.com/moment/moment/pull/2702) Week rework |
|||
* [#2746](https://github.com/moment/moment/pull/2746) Changed September Abbreviation to "Sept" in locale-specific english |
|||
files and default locale file |
|||
* [#2646](https://github.com/moment/moment/pull/2646) Fix [#2645](https://github.com/moment/moment/pull/2645) - invalid dates pre-1970 |
|||
|
|||
* [#2641](https://github.com/moment/moment/pull/2641) Implement basic format and comma as ms separator in ISO 8601 |
|||
* [#2665](https://github.com/moment/moment/pull/2665) Implement stricter weekday parsing |
|||
* [#2700](https://github.com/moment/moment/pull/2700) Add [Hh]mm and [Hh]mmss formatting tokens, so you can parse 123 with |
|||
hmm for example |
|||
* [#2565](https://github.com/moment/moment/pull/2565) [#2835](https://github.com/moment/moment/pull/2835) Expose arguments used for moment creation with creationData |
|||
(fix [#2443](https://github.com/moment/moment/pull/2443)) |
|||
* [#2648](https://github.com/moment/moment/pull/2648) fix issue [#2640](https://github.com/moment/moment/pull/2640): support instanceof operator |
|||
* [#2709](https://github.com/moment/moment/pull/2709) Add isSameOrAfter and isSameOrBefore comparison methods |
|||
* [#2721](https://github.com/moment/moment/pull/2721) Fix moment creation from object with strings values |
|||
* [#2740](https://github.com/moment/moment/pull/2740) Enable 'd hh:mm:ss.sss' format for durations |
|||
* [#2766](https://github.com/moment/moment/pull/2766) [#2833](https://github.com/moment/moment/pull/2833) Alternate Clock Source Support |
|||
|
|||
### 2.10.6 |
|||
|
|||
[#2515](https://github.com/moment/moment/pull/2515) Fix regression introduced |
|||
in `2.10.5` related to `moment.ISO_8601` parsing. |
|||
|
|||
### 2.10.5 [See full changelog](https://gist.github.com/ichernev/6ec13ac7efc396da44b2) |
|||
|
|||
Important changes: |
|||
* [#2357](https://github.com/moment/moment/pull/2357) Improve unit bubbling for ISO dates |
|||
this fixes day to year conversions to work around end-of-year (~365 days). As |
|||
a side effect 365 days is 11 months and 30 days, and 366 days is one year. |
|||
* [#2438](https://github.com/moment/moment/pull/2438) Fix inconsistent moment.min and moment.max results |
|||
Return invalid result if any of the inputs is invalid |
|||
* [#2494](https://github.com/moment/moment/pull/2494) Fix two digit year parsing with YYYY format |
|||
This brings the benefits of YY to YYYY |
|||
* [#2368](https://github.com/moment/moment/pull/2368) perf: use faster form of copying dates, across the board improvement |
|||
|
|||
|
|||
### 2.10.3 [See full changelog](https://gist.github.com/ichernev/f264b9bed5b00f8b1b7f) |
|||
|
|||
* add `moment.fn.to` and `moment.fn.toNow` (similar to `from` and `fromNow`) |
|||
* new locales (Sinhalese (si), Montenegrin (me), Javanese (ja)) |
|||
* performance improvements |
|||
|
|||
### 2.10.2 |
|||
|
|||
* fixed moment-with-locales in browser env caused by esperanto change |
|||
|
|||
### 2.10.1 |
|||
|
|||
* regression: Add moment.duration.fn back |
|||
|
|||
### 2.10.0 |
|||
|
|||
Ported code to es6 modules. |
|||
|
|||
### 2.9.0 [See full changelog](https://gist.github.com/ichernev/0c9a9b49951111a27ce7) |
|||
|
|||
languages: |
|||
* [2104](https://github.com/moment/moment/issues/2104) Frisian (fy) language file with unit test |
|||
* [2097](https://github.com/moment/moment/issues/2097) add ar-tn locale |
|||
|
|||
deprecations: |
|||
* [2074](https://github.com/moment/moment/issues/2074) Implement `moment.fn.utcOffset`, deprecate `moment.fn.zone` |
|||
|
|||
features: |
|||
* [2088](https://github.com/moment/moment/issues/2088) add moment.fn.isBetween |
|||
* [2054](https://github.com/moment/moment/issues/2054) Call updateOffset when creating moment (needed for default timezone in |
|||
moment-timezone) |
|||
* [1893](https://github.com/moment/moment/issues/1893) Add moment.isDate method |
|||
* [1825](https://github.com/moment/moment/issues/1825) Implement toJSON function on Duration |
|||
* [1809](https://github.com/moment/moment/issues/1809) Allowing moment.set() to accept a hash of units |
|||
* [2128](https://github.com/moment/moment/issues/2128) Add firstDayOfWeek, firstDayOfYear locale getters |
|||
* [2131](https://github.com/moment/moment/issues/2131) Add quarter diff support |
|||
|
|||
Some bugfixes and language improvements -- [full changelog](https://gist.github.com/ichernev/0c9a9b49951111a27ce7) |
|||
|
|||
### 2.8.4 [See full changelog](https://gist.github.com/ichernev/a4fcb0a46d74e4b9b996) |
|||
|
|||
Features: |
|||
|
|||
* [#2000](https://github.com/moment/moment/issues/2000) Add LTS localised format that includes seconds |
|||
* [#1960](https://github.com/moment/moment/issues/1960) added formatToken 'x' for unix offset in milliseconds #1938 |
|||
* [#1965](https://github.com/moment/moment/issues/1965) Support 24:00:00.000 to mean next day, at midnight. |
|||
* [#2002](https://github.com/moment/moment/issues/2002) Accept 'date' key when creating moment with object |
|||
* [#2009](https://github.com/moment/moment/issues/2009) Use native toISOString when we can |
|||
|
|||
Some bugfixes and language improvements -- [full changelog](https://gist.github.com/ichernev/a4fcb0a46d74e4b9b996) |
|||
|
|||
### 2.8.3 |
|||
|
|||
Bugfixes: |
|||
|
|||
* [#1801](https://github.com/moment/moment/issues/1801) proper pluralization for Arabic |
|||
* [#1833](https://github.com/moment/moment/issues/1833) improve spm integration |
|||
* [#1871](https://github.com/moment/moment/issues/1871) fix zone bug caused by Firefox 24 |
|||
* [#1882](https://github.com/moment/moment/issues/1882) Use hh:mm in Czech |
|||
* [#1883](https://github.com/moment/moment/issues/1883) Fix 2.8.0 regression in duration as conversions |
|||
* [#1890](https://github.com/moment/moment/issues/1890) Faster travis builds |
|||
* [#1892](https://github.com/moment/moment/issues/1892) Faster isBefore/After/Same |
|||
* [#1848](https://github.com/moment/moment/issues/1848) Fix flaky month diffs |
|||
* [#1895](https://github.com/moment/moment/issues/1895) Fix 2.8.0 regression in moment.utc with format array |
|||
* [#1896](https://github.com/moment/moment/issues/1896) Support setting invalid instance locale (noop) |
|||
* [#1897](https://github.com/moment/moment/issues/1897) Support moment([str]) in addition to moment([int]) |
|||
|
|||
### 2.8.2 |
|||
|
|||
Minor bugfixes: |
|||
|
|||
* [#1874](https://github.com/moment/moment/issues/1874) use `Object.prototype.hasOwnProperty` |
|||
instead of `obj.hasOwnProperty` (ie8 bug) |
|||
* [#1873](https://github.com/moment/moment/issues/1873) add `duration#toString()` |
|||
* [#1859](https://github.com/moment/moment/issues/1859) better month/weekday names in norwegian |
|||
* [#1812](https://github.com/moment/moment/issues/1812) meridiem parsing for greek |
|||
* [#1804](https://github.com/moment/moment/issues/1804) spanish del -> de |
|||
* [#1800](https://github.com/moment/moment/issues/1800) korean LT improvement |
|||
|
|||
### 2.8.1 |
|||
|
|||
* bugfix [#1813](https://github.com/moment/moment/issues/1813): fix moment().lang([key]) incompatibility |
|||
|
|||
### 2.8.0 [See changelog](https://gist.github.com/ichernev/ac3899324a5fa6c8c9b4) |
|||
|
|||
* incompatible changes |
|||
* [#1761](https://github.com/moment/moment/issues/1761): moments created without a language are no longer following the global language, in case it changes. Only newly created moments take the global language by default. In case you're affected by this, wait, comment on [#1797](https://github.com/moment/moment/issues/1797) and wait for a proper reimplementation |
|||
* [#1642](https://github.com/moment/moment/issues/1642): 45 days is no longer "a month" according to humanize, cutoffs for month, and year have changed. Hopefully your code does not depend on a particular answer from humanize (which it shouldn't anyway) |
|||
* [#1784](https://github.com/moment/moment/issues/1784): if you use the human readable English datetime format in a weird way (like storing them in a database) that would break when the format changes you're at risk. |
|||
|
|||
* deprecations (old behavior will be dropped in 3.0) |
|||
* [#1761](https://github.com/moment/moment/issues/1761) `lang` is renamed to `locale`, `langData` -> `localeData`. Also there is now `defineLocale` that should be used when creating new locales |
|||
* [#1763](https://github.com/moment/moment/issues/1763) `add(unit, value)` and `subtract(unit, value)` are now deprecated. Use `add(value, unit)` and `subtract(value, unit)` instead. |
|||
* [#1759](https://github.com/moment/moment/issues/1759) rename `duration.toIsoString` to `duration.toISOString`. The js standard library and moment's `toISOString` follow that convention. |
|||
|
|||
* new locales |
|||
* [#1789](https://github.com/moment/moment/issues/1789) Tibetan (bo) |
|||
* [#1786](https://github.com/moment/moment/issues/1786) Africaans (af) |
|||
* [#1778](https://github.com/moment/moment/issues/1778) Burmese (my) |
|||
* [#1727](https://github.com/moment/moment/issues/1727) Belarusian (be) |
|||
|
|||
* bugfixes, locale bugfixes, performance improvements, features |
|||
|
|||
### 2.7.0 [See changelog](https://gist.github.com/ichernev/b0a3d456d5a84c9901d7) |
|||
|
|||
* new languages |
|||
|
|||
* [#1678](https://github.com/moment/moment/issues/1678) Bengali (bn) |
|||
* [#1628](https://github.com/moment/moment/issues/1628) Azerbaijani (az) |
|||
* [#1633](https://github.com/moment/moment/issues/1633) Arabic, Saudi Arabia (ar-sa) |
|||
* [#1648](https://github.com/moment/moment/issues/1648) Austrian German (de-at) |
|||
|
|||
* features |
|||
|
|||
* [#1663](https://github.com/moment/moment/issues/1663) configurable relative time thresholds |
|||
* [#1554](https://github.com/moment/moment/issues/1554) support anchor time in moment.calendar |
|||
* [#1693](https://github.com/moment/moment/issues/1693) support moment.ISO_8601 as parsing format |
|||
* [#1637](https://github.com/moment/moment/issues/1637) add moment.min and moment.max and deprecate min/max instance methods |
|||
* [#1704](https://github.com/moment/moment/issues/1704) support string value in add/subtract |
|||
* [#1647](https://github.com/moment/moment/issues/1647) add spm support (package manager) |
|||
|
|||
* bugfixes |
|||
|
|||
### 2.6.0 [See changelog](https://gist.github.com/ichernev/10544682) |
|||
|
|||
* languages |
|||
* [#1529](https://github.com/moment/moment/issues/1529) Serbian-Cyrillic (sr-cyr) |
|||
* [#1544](https://github.com/moment/moment/issues/1544), [#1546](https://github.com/moment/moment/issues/1546) Khmer Cambodia (km) |
|||
|
|||
* features |
|||
* [#1419](https://github.com/moment/moment/issues/1419), [#1468](https://github.com/moment/moment/issues/1468), [#1467](https://github.com/moment/moment/issues/1467), [#1546](https://github.com/moment/moment/issues/1546) better handling of timezone-d moments around DST |
|||
* [#1462](https://github.com/moment/moment/issues/1462) add weeksInYear and isoWeeksInYear |
|||
* [#1475](https://github.com/moment/moment/issues/1475) support ordinal parsing |
|||
* [#1499](https://github.com/moment/moment/issues/1499) composer support |
|||
* [#1577](https://github.com/moment/moment/issues/1577), [#1604](https://github.com/moment/moment/issues/1604) put Date parsing in moment.createFromInputFallback so it can be properly deprecated and controlled in the future |
|||
* [#1545](https://github.com/moment/moment/issues/1545) extract two-digit year parsing in moment.parseTwoDigitYear, so it can be overwritten |
|||
* [#1590](https://github.com/moment/moment/issues/1590) (see [#1574](https://github.com/moment/moment/issues/1574)) set AMD global before module definition to better support non AMD module dependencies used in AMD environment |
|||
* [#1589](https://github.com/moment/moment/issues/1589) remove global in Node.JS environment (was not working before, nobody complained, was scheduled for removal anyway) |
|||
* [#1586](https://github.com/moment/moment/issues/1586) support quarter setting and parsing |
|||
|
|||
* 18 bugs fixed |
|||
|
|||
### 2.5.1 |
|||
|
|||
* languages |
|||
* [#1392](https://github.com/moment/moment/issues/1392) Armenian (hy-am) |
|||
|
|||
* bugfixes |
|||
* [#1429](https://github.com/moment/moment/issues/1429) fixes [#1423](https://github.com/moment/moment/issues/1423) weird chrome-32 bug with js object creation |
|||
* [#1421](https://github.com/moment/moment/issues/1421) remove html entities from Welsh |
|||
* [#1418](https://github.com/moment/moment/issues/1418) fixes [#1401](https://github.com/moment/moment/issues/1401) improved non-padded tokens in strict matching |
|||
* [#1417](https://github.com/moment/moment/issues/1417) fixes [#1404](https://github.com/moment/moment/issues/1404) handle buggy moment object created by property cloning |
|||
* [#1398](https://github.com/moment/moment/issues/1398) fixes [#1397](https://github.com/moment/moment/issues/1397) fix Arabic-like week number parsing |
|||
* [#1396](https://github.com/moment/moment/issues/1396) add leftZeroFill(4) to GGGG and gggg formats |
|||
* [#1373](https://github.com/moment/moment/issues/1373) use lowercase for months and days in Catalan |
|||
|
|||
* testing |
|||
* [#1374](https://github.com/moment/moment/issues/1374) run tests on multiple browser/os combos via SauceLabs and Travis |
|||
|
|||
### 2.5.0 [See changelog](https://gist.github.com/ichernev/8104451) |
|||
|
|||
* New languages |
|||
* Luxemburish (lb) [1247](https://github.com/moment/moment/issues/1247) |
|||
* Serbian (rs) [1319](https://github.com/moment/moment/issues/1319) |
|||
* Tamil (ta) [1324](https://github.com/moment/moment/issues/1324) |
|||
* Macedonian (mk) [1337](https://github.com/moment/moment/issues/1337) |
|||
|
|||
* Features |
|||
* [1311](https://github.com/moment/moment/issues/1311) Add quarter getter and format token `Q` |
|||
* [1303](https://github.com/moment/moment/issues/1303) strict parsing now respects number of digits per token (fix [1196](https://github.com/moment/moment/issues/1196)) |
|||
* 0d30bb7 add jspm support |
|||
* [1347](https://github.com/moment/moment/issues/1347) improve zone parsing |
|||
* [1362](https://github.com/moment/moment/issues/1362) support merideam parsing in Korean |
|||
|
|||
* 22 bugfixes |
|||
|
|||
### 2.4.0 |
|||
|
|||
* **Deprecate** globally exported moment, will be removed in next major |
|||
* New languages |
|||
* Farose (fo) [#1206](https://github.com/moment/moment/issues/1206) |
|||
* Tagalog/Filipino (tl-ph) [#1197](https://github.com/moment/moment/issues/1197) |
|||
* Welsh (cy) [#1215](https://github.com/moment/moment/issues/1215) |
|||
* Bugfixes |
|||
* properly handle Z at the end of iso RegExp [#1187](https://github.com/moment/moment/issues/1187) |
|||
* chinese meridian time improvements [#1076](https://github.com/moment/moment/issues/1076) |
|||
* fix language tests [#1177](https://github.com/moment/moment/issues/1177) |
|||
* remove some failing tests (that should have never existed :)) |
|||
[#1185](https://github.com/moment/moment/issues/1185) |
|||
[#1183](https://github.com/moment/moment/issues/1183) |
|||
* handle russian noun cases in weird cases [#1195](https://github.com/moment/moment/issues/1195) |
|||
|
|||
### 2.3.1 |
|||
|
|||
Removed a trailing comma [1169] and fixed a bug with `months`, `weekdays` getters [#1171](https://github.com/moment/moment/issues/1171). |
|||
|
|||
### 2.3.0 [See changelog](https://gist.github.com/ichernev/6864354) |
|||
|
|||
Changed isValid, added strict parsing. |
|||
Week tokens parsing. |
|||
|
|||
### 2.2.1 |
|||
|
|||
Fixed bug in string prototype test. |
|||
Updated authors and contributors. |
|||
|
|||
### 2.2.0 [See changelog](https://gist.github.com/ichernev/00f837a9baf46a3565e4) |
|||
|
|||
Added bower support. |
|||
|
|||
Language files now use UMD. |
|||
|
|||
Creating moment defaults to current date/month/year. |
|||
|
|||
Added a bundle of moment and all language files. |
|||
|
|||
### 2.1.0 [See changelog](https://gist.github.com/timrwood/b8c2d90d528eddb53ab5) |
|||
|
|||
Added better week support. |
|||
|
|||
Added ability to set offset with `moment#zone`. |
|||
|
|||
Added ability to set month or weekday from a string. |
|||
|
|||
Added `moment#min` and `moment#max` |
|||
|
|||
### 2.0.0 [See changelog](https://gist.github.com/timrwood/e72f2eef320ed9e37c51) |
|||
|
|||
Added short form localized tokens. |
|||
|
|||
Added ability to define language a string should be parsed in. |
|||
|
|||
Added support for reversed add/subtract arguments. |
|||
|
|||
Added support for `endOf('week')` and `startOf('week')`. |
|||
|
|||
Fixed the logic for `moment#diff(Moment, 'months')` and `moment#diff(Moment, 'years')` |
|||
|
|||
`moment#diff` now floors instead of rounds. |
|||
|
|||
Normalized `moment#toString`. |
|||
|
|||
Added `isSame`, `isAfter`, and `isBefore` methods. |
|||
|
|||
Added better week support. |
|||
|
|||
Added `moment#toJSON` |
|||
|
|||
Bugfix: Fixed parsing of first century dates |
|||
|
|||
Bugfix: Parsing 10Sep2001 should work as expected |
|||
|
|||
Bugfix: Fixed weirdness with `moment.utc()` parsing. |
|||
|
|||
Changed language ordinal method to return the number + ordinal instead of just the ordinal. |
|||
|
|||
Changed two digit year parsing cutoff to match strptime. |
|||
|
|||
Removed `moment#sod` and `moment#eod` in favor of `moment#startOf` and `moment#endOf`. |
|||
|
|||
Removed `moment.humanizeDuration()` in favor of `moment.duration().humanize()`. |
|||
|
|||
Removed the lang data objects from the top level namespace. |
|||
|
|||
Duplicate `Date` passed to `moment()` instead of referencing it. |
|||
|
|||
### 1.7.2 [See discussion](https://github.com/timrwood/moment/issues/456) |
|||
|
|||
Bugfixes |
|||
|
|||
### 1.7.1 [See discussion](https://github.com/timrwood/moment/issues/384) |
|||
|
|||
Bugfixes |
|||
|
|||
### 1.7.0 [See discussion](https://github.com/timrwood/moment/issues/288) |
|||
|
|||
Added `moment.fn.endOf()` and `moment.fn.startOf()`. |
|||
|
|||
Added validation via `moment.fn.isValid()`. |
|||
|
|||
Made formatting method 3x faster. http://jsperf.com/momentjs-cached-format-functions |
|||
|
|||
Add support for month/weekday callbacks in `moment.fn.format()` |
|||
|
|||
Added instance specific languages. |
|||
|
|||
Added two letter weekday abbreviations with the formatting token `dd`. |
|||
|
|||
Various language updates. |
|||
|
|||
Various bugfixes. |
|||
|
|||
### 1.6.0 [See discussion](https://github.com/timrwood/moment/pull/268) |
|||
|
|||
Added Durations. |
|||
|
|||
Revamped parser to support parsing non-separated strings (YYYYMMDD vs YYYY-MM-DD). |
|||
|
|||
Added support for millisecond parsing and formatting tokens (S SS SSS) |
|||
|
|||
Added a getter for `moment.lang()` |
|||
|
|||
Various bugfixes. |
|||
|
|||
There are a few things deprecated in the 1.6.0 release. |
|||
|
|||
1. The format tokens `z` and `zz` (timezone abbreviations like EST CST MST etc) will no longer be supported. Due to inconsistent browser support, we are unable to consistently produce this value. See [this issue](https://github.com/timrwood/moment/issues/162) for more background. |
|||
|
|||
2. The method `moment.fn.native` is deprecated in favor of `moment.fn.toDate`. There continue to be issues with Google Closure Compiler throwing errors when using `native`, even in valid instances. |
|||
|
|||
3. The way to customize am/pm strings is being changed. This would only affect you if you created a custom language file. For more information, see [this issue](https://github.com/timrwood/moment/pull/222). |
|||
|
|||
### 1.5.0 [See milestone](https://github.com/timrwood/moment/issues?milestone=10&page=1&state=closed) |
|||
|
|||
Added UTC mode. |
|||
|
|||
Added automatic ISO8601 parsing. |
|||
|
|||
Various bugfixes. |
|||
|
|||
### 1.4.0 [See milestone](https://github.com/timrwood/moment/issues?milestone=8&state=closed) |
|||
|
|||
Added `moment.fn.toDate` as a replacement for `moment.fn.native`. |
|||
|
|||
Added `moment.fn.sod` and `moment.fn.eod` to get the start and end of day. |
|||
|
|||
Various bugfixes. |
|||
|
|||
### 1.3.0 [See milestone](https://github.com/timrwood/moment/issues?milestone=7&state=closed) |
|||
|
|||
Added support for parsing month names in the current language. |
|||
|
|||
Added escape blocks for parsing tokens. |
|||
|
|||
Added `moment.fn.calendar` to format strings like 'Today 2:30 PM', 'Tomorrow 1:25 AM', and 'Last Sunday 4:30 AM'. |
|||
|
|||
Added `moment.fn.day` as a setter. |
|||
|
|||
Various bugfixes |
|||
|
|||
### 1.2.0 [See milestone](https://github.com/timrwood/moment/issues?milestone=4&state=closed) |
|||
|
|||
Added timezones to parser and formatter. |
|||
|
|||
Added `moment.fn.isDST`. |
|||
|
|||
Added `moment.fn.zone` to get the timezone offset in minutes. |
|||
|
|||
### 1.1.2 [See milestone](https://github.com/timrwood/moment/issues?milestone=6&state=closed) |
|||
|
|||
Various bugfixes |
|||
|
|||
### 1.1.1 [See milestone](https://github.com/timrwood/moment/issues?milestone=5&state=closed) |
|||
|
|||
Added time specific diffs (months, days, hours, etc) |
|||
|
|||
### 1.1.0 |
|||
|
|||
Added `moment.fn.format` localized masks. 'L LL LLL LLLL' [issue 29](https://github.com/timrwood/moment/pull/29) |
|||
|
|||
Fixed [issue 31](https://github.com/timrwood/moment/pull/31). |
|||
|
|||
### 1.0.1 |
|||
|
|||
Added `moment.version` to get the current version. |
|||
|
|||
Removed `window !== undefined` when checking if module exists to support browserify. [issue 25](https://github.com/timrwood/moment/pull/25) |
|||
|
|||
### 1.0.0 |
|||
|
|||
Added convenience methods for getting and setting date parts. |
|||
|
|||
Added better support for `moment.add()`. |
|||
|
|||
Added better lang support in NodeJS. |
|||
|
|||
Renamed library from underscore.date to Moment.js |
|||
|
|||
### 0.6.1 |
|||
|
|||
Added Portuguese, Italian, and French language support |
|||
|
|||
### 0.6.0 |
|||
|
|||
Added _date.lang() support. |
|||
Added support for passing multiple formats to try to parse a date. _date("07-10-1986", ["MM-DD-YYYY", "YYYY-MM-DD"]); |
|||
Made parse from string and single format 25% faster. |
|||
|
|||
### 0.5.2 |
|||
|
|||
Bugfix for [issue 8](https://github.com/timrwood/underscore.date/pull/8) and [issue 9](https://github.com/timrwood/underscore.date/pull/9). |
|||
|
|||
### 0.5.1 |
|||
|
|||
Bugfix for [issue 5](https://github.com/timrwood/underscore.date/pull/5). |
|||
|
|||
### 0.5.0 |
|||
|
|||
Dropped the redundant `_date.date()` in favor of `_date()`. |
|||
Removed `_date.now()`, as it is a duplicate of `_date()` with no parameters. |
|||
Removed `_date.isLeapYear(yearNumber)`. Use `_date([yearNumber]).isLeapYear()` instead. |
|||
Exposed customization options through the `_date.relativeTime`, `_date.weekdays`, `_date.weekdaysShort`, `_date.months`, `_date.monthsShort`, and `_date.ordinal` variables instead of the `_date.customize()` function. |
|||
|
|||
### 0.4.1 |
|||
|
|||
Added date input formats for input strings. |
|||
|
|||
### 0.4.0 |
|||
|
|||
Added underscore.date to npm. Removed dependencies on underscore. |
|||
|
|||
### 0.3.2 |
|||
|
|||
Added `'z'` and `'zz'` to `_.date().format()`. Cleaned up some redundant code to trim off some bytes. |
|||
|
|||
### 0.3.1 |
|||
|
|||
Cleaned up the namespace. Moved all date manipulation and display functions to the _.date() object. |
|||
|
|||
### 0.3.0 |
|||
|
|||
Switched to the Underscore methodology of not mucking with the native objects' prototypes. |
|||
Made chaining possible. |
|||
|
|||
### 0.2.1 |
|||
|
|||
Changed date names to be a more pseudo standardized 'dddd, MMMM Do YYYY, h:mm:ss a'. |
|||
Added `Date.prototype` functions `add`, `subtract`, `isdst`, and `isleapyear`. |
|||
|
|||
### 0.2.0 |
|||
|
|||
Changed function names to be more concise. |
|||
Changed date format from php date format to custom format. |
|||
|
|||
### 0.1.0 |
|||
|
|||
Initial release |
|||
|
@ -0,0 +1,22 @@ |
|||
Copyright (c) 2011-2016 Tim Wood, Iskren Chernev, Moment.js contributors |
|||
|
|||
Permission is hereby granted, free of charge, to any person |
|||
obtaining a copy of this software and associated documentation |
|||
files (the "Software"), to deal in the Software without |
|||
restriction, including without limitation the rights to use, |
|||
copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the |
|||
Software is furnished to do so, subject to the following |
|||
conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be |
|||
included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
|||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
|||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
|||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|||
OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,58 @@ |
|||
[![Join the chat at https://gitter.im/moment/moment](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/moment/moment?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) |
|||
|
|||
[![NPM version][npm-version-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![MIT License][license-image]][license-url] [![Build Status][travis-image]][travis-url] |
|||
[![Coverage Status](https://coveralls.io/repos/moment/moment/badge.svg?branch=develop)](https://coveralls.io/r/moment/moment?branch=develop) |
|||
|
|||
A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates. |
|||
|
|||
**[Documentation](http://momentjs.com/docs/)** |
|||
|
|||
## Port to ECMAScript 6 (version 2.10.0) |
|||
|
|||
Moment 2.10.0 does not bring any new features, but the code is now written in |
|||
ECMAScript 6 modules and placed inside `src/`. Previously `moment.js`, `locale/*.js` and |
|||
`test/moment/*.js`, `test/locale/*.js` contained the source of the project. Now |
|||
the source is in `src/`, temporary build (ECMAScript 5) files are placed under |
|||
`build/umd/` (for running tests during development), and the `moment.js` and |
|||
`locale/*.js` files are updated only on release. |
|||
|
|||
If you want to use a particular revision of the code, make sure to run |
|||
`grunt transpile update-index`, so `moment.js` and `locales/*.js` are synced |
|||
with `src/*`. We might place that in a commit hook in the future. |
|||
|
|||
## Upgrading to 2.0.0 |
|||
|
|||
There are a number of small backwards incompatible changes with version 2.0.0. [See the full descriptions here](https://gist.github.com/timrwood/e72f2eef320ed9e37c51#backwards-incompatible-changes) |
|||
|
|||
* Changed language ordinal method to return the number + ordinal instead of just the ordinal. |
|||
|
|||
* Changed two digit year parsing cutoff to match strptime. |
|||
|
|||
* Removed `moment#sod` and `moment#eod` in favor of `moment#startOf` and `moment#endOf`. |
|||
|
|||
* Removed `moment.humanizeDuration()` in favor of `moment.duration().humanize()`. |
|||
|
|||
* Removed the lang data objects from the top level namespace. |
|||
|
|||
* Duplicate `Date` passed to `moment()` instead of referencing it. |
|||
|
|||
## [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) |
|||
|
|||
## [Contributing](https://github.com/moment/moment/blob/develop/CONTRIBUTING.md) |
|||
|
|||
We're looking for co-maintainers! If you want to become a master of time please |
|||
write to [ichernev](https://github.com/ichernev). |
|||
|
|||
## License |
|||
|
|||
Moment.js is freely distributable under the terms of the [MIT license](https://github.com/moment/moment/blob/develop/LICENSE). |
|||
|
|||
[license-image]: http://img.shields.io/badge/license-MIT-blue.svg?style=flat |
|||
[license-url]: LICENSE |
|||
|
|||
[npm-url]: https://npmjs.org/package/moment |
|||
[npm-version-image]: http://img.shields.io/npm/v/moment.svg?style=flat |
|||
[npm-downloads-image]: http://img.shields.io/npm/dm/moment.svg?style=flat |
|||
|
|||
[travis-url]: http://travis-ci.org/moment/moment |
|||
[travis-image]: http://img.shields.io/travis/moment/moment/develop.svg?style=flat |
@ -0,0 +1 @@ |
|||
$.ender({ moment: require('moment') }) |
@ -0,0 +1,73 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Afrikaans [af]
|
|||
//! author : Werner Mollentze : https://github.com/wernerm
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var af = moment.defineLocale('af', { |
|||
months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'), |
|||
monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'), |
|||
weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'), |
|||
weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'), |
|||
weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'), |
|||
meridiemParse: /vm|nm/i, |
|||
isPM : function (input) { |
|||
return /^nm$/i.test(input); |
|||
}, |
|||
meridiem : function (hours, minutes, isLower) { |
|||
if (hours < 12) { |
|||
return isLower ? 'vm' : 'VM'; |
|||
} else { |
|||
return isLower ? 'nm' : 'NM'; |
|||
} |
|||
}, |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY HH:mm', |
|||
LLLL : 'dddd, D MMMM YYYY HH:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[Vandag om] LT', |
|||
nextDay : '[Môre om] LT', |
|||
nextWeek : 'dddd [om] LT', |
|||
lastDay : '[Gister om] LT', |
|||
lastWeek : '[Laas] dddd [om] LT', |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'oor %s', |
|||
past : '%s gelede', |
|||
s : '\'n paar sekondes', |
|||
m : '\'n minuut', |
|||
mm : '%d minute', |
|||
h : '\'n uur', |
|||
hh : '%d ure', |
|||
d : '\'n dag', |
|||
dd : '%d dae', |
|||
M : '\'n maand', |
|||
MM : '%d maande', |
|||
y : '\'n jaar', |
|||
yy : '%d jaar' |
|||
}, |
|||
ordinalParse: /\d{1,2}(ste|de)/, |
|||
ordinal : function (number) { |
|||
return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter
|
|||
}, |
|||
week : { |
|||
dow : 1, // Maandag is die eerste dag van die week.
|
|||
doy : 4 // Die week wat die 4de Januarie bevat is die eerste week van die jaar.
|
|||
} |
|||
}); |
|||
|
|||
return af; |
|||
|
|||
})); |
@ -0,0 +1,60 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Arabic (Morocco) [ar-ma]
|
|||
//! author : ElFadili Yassine : https://github.com/ElFadiliY
|
|||
//! author : Abdel Said : https://github.com/abdelsaid
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var ar_ma = moment.defineLocale('ar-ma', { |
|||
months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), |
|||
monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), |
|||
weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), |
|||
weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), |
|||
weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY HH:mm', |
|||
LLLL : 'dddd D MMMM YYYY HH:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay: '[اليوم على الساعة] LT', |
|||
nextDay: '[غدا على الساعة] LT', |
|||
nextWeek: 'dddd [على الساعة] LT', |
|||
lastDay: '[أمس على الساعة] LT', |
|||
lastWeek: 'dddd [على الساعة] LT', |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'في %s', |
|||
past : 'منذ %s', |
|||
s : 'ثوان', |
|||
m : 'دقيقة', |
|||
mm : '%d دقائق', |
|||
h : 'ساعة', |
|||
hh : '%d ساعات', |
|||
d : 'يوم', |
|||
dd : '%d أيام', |
|||
M : 'شهر', |
|||
MM : '%d أشهر', |
|||
y : 'سنة', |
|||
yy : '%d سنوات' |
|||
}, |
|||
week : { |
|||
dow : 6, // Saturday is the first day of the week.
|
|||
doy : 12 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return ar_ma; |
|||
|
|||
})); |
@ -0,0 +1,104 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Arabic (Saudi Arabia) [ar-sa]
|
|||
//! author : Suhail Alkowaileet : https://github.com/xsoh
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var symbolMap = { |
|||
'1': '١', |
|||
'2': '٢', |
|||
'3': '٣', |
|||
'4': '٤', |
|||
'5': '٥', |
|||
'6': '٦', |
|||
'7': '٧', |
|||
'8': '٨', |
|||
'9': '٩', |
|||
'0': '٠' |
|||
}, numberMap = { |
|||
'١': '1', |
|||
'٢': '2', |
|||
'٣': '3', |
|||
'٤': '4', |
|||
'٥': '5', |
|||
'٦': '6', |
|||
'٧': '7', |
|||
'٨': '8', |
|||
'٩': '9', |
|||
'٠': '0' |
|||
}; |
|||
|
|||
var ar_sa = moment.defineLocale('ar-sa', { |
|||
months : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), |
|||
monthsShort : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), |
|||
weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), |
|||
weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), |
|||
weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY HH:mm', |
|||
LLLL : 'dddd D MMMM YYYY HH:mm' |
|||
}, |
|||
meridiemParse: /ص|م/, |
|||
isPM : function (input) { |
|||
return 'م' === input; |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 12) { |
|||
return 'ص'; |
|||
} else { |
|||
return 'م'; |
|||
} |
|||
}, |
|||
calendar : { |
|||
sameDay: '[اليوم على الساعة] LT', |
|||
nextDay: '[غدا على الساعة] LT', |
|||
nextWeek: 'dddd [على الساعة] LT', |
|||
lastDay: '[أمس على الساعة] LT', |
|||
lastWeek: 'dddd [على الساعة] LT', |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'في %s', |
|||
past : 'منذ %s', |
|||
s : 'ثوان', |
|||
m : 'دقيقة', |
|||
mm : '%d دقائق', |
|||
h : 'ساعة', |
|||
hh : '%d ساعات', |
|||
d : 'يوم', |
|||
dd : '%d أيام', |
|||
M : 'شهر', |
|||
MM : '%d أشهر', |
|||
y : 'سنة', |
|||
yy : '%d سنوات' |
|||
}, |
|||
preparse: function (string) { |
|||
return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { |
|||
return numberMap[match]; |
|||
}).replace(/،/g, ','); |
|||
}, |
|||
postformat: function (string) { |
|||
return string.replace(/\d/g, function (match) { |
|||
return symbolMap[match]; |
|||
}).replace(/,/g, '،'); |
|||
}, |
|||
week : { |
|||
dow : 6, // Saturday is the first day of the week.
|
|||
doy : 12 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return ar_sa; |
|||
|
|||
})); |
@ -0,0 +1,58 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Arabic (Tunisia) [ar-tn]
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var ar_tn = moment.defineLocale('ar-tn', { |
|||
months: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), |
|||
monthsShort: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), |
|||
weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), |
|||
weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), |
|||
weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat: { |
|||
LT: 'HH:mm', |
|||
LTS: 'HH:mm:ss', |
|||
L: 'DD/MM/YYYY', |
|||
LL: 'D MMMM YYYY', |
|||
LLL: 'D MMMM YYYY HH:mm', |
|||
LLLL: 'dddd D MMMM YYYY HH:mm' |
|||
}, |
|||
calendar: { |
|||
sameDay: '[اليوم على الساعة] LT', |
|||
nextDay: '[غدا على الساعة] LT', |
|||
nextWeek: 'dddd [على الساعة] LT', |
|||
lastDay: '[أمس على الساعة] LT', |
|||
lastWeek: 'dddd [على الساعة] LT', |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime: { |
|||
future: 'في %s', |
|||
past: 'منذ %s', |
|||
s: 'ثوان', |
|||
m: 'دقيقة', |
|||
mm: '%d دقائق', |
|||
h: 'ساعة', |
|||
hh: '%d ساعات', |
|||
d: 'يوم', |
|||
dd: '%d أيام', |
|||
M: 'شهر', |
|||
MM: '%d أشهر', |
|||
y: 'سنة', |
|||
yy: '%d سنوات' |
|||
}, |
|||
week: { |
|||
dow: 1, // Monday is the first day of the week.
|
|||
doy: 4 // The week that contains Jan 4th is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return ar_tn; |
|||
|
|||
})); |
@ -0,0 +1,137 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Arabic [ar]
|
|||
//! author : Abdel Said: https://github.com/abdelsaid
|
|||
//! changes in months, weekdays: Ahmed Elkhatib
|
|||
//! Native plural forms: forabi https://github.com/forabi
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var symbolMap = { |
|||
'1': '١', |
|||
'2': '٢', |
|||
'3': '٣', |
|||
'4': '٤', |
|||
'5': '٥', |
|||
'6': '٦', |
|||
'7': '٧', |
|||
'8': '٨', |
|||
'9': '٩', |
|||
'0': '٠' |
|||
}, numberMap = { |
|||
'١': '1', |
|||
'٢': '2', |
|||
'٣': '3', |
|||
'٤': '4', |
|||
'٥': '5', |
|||
'٦': '6', |
|||
'٧': '7', |
|||
'٨': '8', |
|||
'٩': '9', |
|||
'٠': '0' |
|||
}, pluralForm = function (n) { |
|||
return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; |
|||
}, plurals = { |
|||
s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], |
|||
m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], |
|||
h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], |
|||
d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], |
|||
M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], |
|||
y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] |
|||
}, pluralize = function (u) { |
|||
return function (number, withoutSuffix, string, isFuture) { |
|||
var f = pluralForm(number), |
|||
str = plurals[u][pluralForm(number)]; |
|||
if (f === 2) { |
|||
str = str[withoutSuffix ? 0 : 1]; |
|||
} |
|||
return str.replace(/%d/i, number); |
|||
}; |
|||
}, months = [ |
|||
'كانون الثاني يناير', |
|||
'شباط فبراير', |
|||
'آذار مارس', |
|||
'نيسان أبريل', |
|||
'أيار مايو', |
|||
'حزيران يونيو', |
|||
'تموز يوليو', |
|||
'آب أغسطس', |
|||
'أيلول سبتمبر', |
|||
'تشرين الأول أكتوبر', |
|||
'تشرين الثاني نوفمبر', |
|||
'كانون الأول ديسمبر' |
|||
]; |
|||
|
|||
var ar = moment.defineLocale('ar', { |
|||
months : months, |
|||
monthsShort : months, |
|||
weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), |
|||
weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), |
|||
weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'D/\u200FM/\u200FYYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY HH:mm', |
|||
LLLL : 'dddd D MMMM YYYY HH:mm' |
|||
}, |
|||
meridiemParse: /ص|م/, |
|||
isPM : function (input) { |
|||
return 'م' === input; |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 12) { |
|||
return 'ص'; |
|||
} else { |
|||
return 'م'; |
|||
} |
|||
}, |
|||
calendar : { |
|||
sameDay: '[اليوم عند الساعة] LT', |
|||
nextDay: '[غدًا عند الساعة] LT', |
|||
nextWeek: 'dddd [عند الساعة] LT', |
|||
lastDay: '[أمس عند الساعة] LT', |
|||
lastWeek: 'dddd [عند الساعة] LT', |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'بعد %s', |
|||
past : 'منذ %s', |
|||
s : pluralize('s'), |
|||
m : pluralize('m'), |
|||
mm : pluralize('m'), |
|||
h : pluralize('h'), |
|||
hh : pluralize('h'), |
|||
d : pluralize('d'), |
|||
dd : pluralize('d'), |
|||
M : pluralize('M'), |
|||
MM : pluralize('M'), |
|||
y : pluralize('y'), |
|||
yy : pluralize('y') |
|||
}, |
|||
preparse: function (string) { |
|||
return string.replace(/\u200f/g, '').replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { |
|||
return numberMap[match]; |
|||
}).replace(/،/g, ','); |
|||
}, |
|||
postformat: function (string) { |
|||
return string.replace(/\d/g, function (match) { |
|||
return symbolMap[match]; |
|||
}).replace(/,/g, '،'); |
|||
}, |
|||
week : { |
|||
dow : 6, // Saturday is the first day of the week.
|
|||
doy : 12 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return ar; |
|||
|
|||
})); |
@ -0,0 +1,105 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Azerbaijani [az]
|
|||
//! author : topchiyev : https://github.com/topchiyev
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var suffixes = { |
|||
1: '-inci', |
|||
5: '-inci', |
|||
8: '-inci', |
|||
70: '-inci', |
|||
80: '-inci', |
|||
2: '-nci', |
|||
7: '-nci', |
|||
20: '-nci', |
|||
50: '-nci', |
|||
3: '-üncü', |
|||
4: '-üncü', |
|||
100: '-üncü', |
|||
6: '-ncı', |
|||
9: '-uncu', |
|||
10: '-uncu', |
|||
30: '-uncu', |
|||
60: '-ıncı', |
|||
90: '-ıncı' |
|||
}; |
|||
|
|||
var az = moment.defineLocale('az', { |
|||
months : 'yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr'.split('_'), |
|||
monthsShort : 'yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek'.split('_'), |
|||
weekdays : 'Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə'.split('_'), |
|||
weekdaysShort : 'Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən'.split('_'), |
|||
weekdaysMin : 'Bz_BE_ÇA_Çə_CA_Cü_Şə'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'DD.MM.YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY HH:mm', |
|||
LLLL : 'dddd, D MMMM YYYY HH:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[bugün saat] LT', |
|||
nextDay : '[sabah saat] LT', |
|||
nextWeek : '[gələn həftə] dddd [saat] LT', |
|||
lastDay : '[dünən] LT', |
|||
lastWeek : '[keçən həftə] dddd [saat] LT', |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : '%s sonra', |
|||
past : '%s əvvəl', |
|||
s : 'birneçə saniyyə', |
|||
m : 'bir dəqiqə', |
|||
mm : '%d dəqiqə', |
|||
h : 'bir saat', |
|||
hh : '%d saat', |
|||
d : 'bir gün', |
|||
dd : '%d gün', |
|||
M : 'bir ay', |
|||
MM : '%d ay', |
|||
y : 'bir il', |
|||
yy : '%d il' |
|||
}, |
|||
meridiemParse: /gecə|səhər|gündüz|axşam/, |
|||
isPM : function (input) { |
|||
return /^(gündüz|axşam)$/.test(input); |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 4) { |
|||
return 'gecə'; |
|||
} else if (hour < 12) { |
|||
return 'səhər'; |
|||
} else if (hour < 17) { |
|||
return 'gündüz'; |
|||
} else { |
|||
return 'axşam'; |
|||
} |
|||
}, |
|||
ordinalParse: /\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/, |
|||
ordinal : function (number) { |
|||
if (number === 0) { // special case for zero
|
|||
return number + '-ıncı'; |
|||
} |
|||
var a = number % 10, |
|||
b = number % 100 - a, |
|||
c = number >= 100 ? 100 : null; |
|||
return number + (suffixes[a] || suffixes[b] || suffixes[c]); |
|||
}, |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return az; |
|||
|
|||
})); |
@ -0,0 +1,134 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Belarusian [be]
|
|||
//! author : Dmitry Demidov : https://github.com/demidov91
|
|||
//! author: Praleska: http://praleska.pro/
|
|||
//! Author : Menelion Elensúle : https://github.com/Oire
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
function plural(word, num) { |
|||
var forms = word.split('_'); |
|||
return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); |
|||
} |
|||
function relativeTimeWithPlural(number, withoutSuffix, key) { |
|||
var format = { |
|||
'mm': withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін', |
|||
'hh': withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін', |
|||
'dd': 'дзень_дні_дзён', |
|||
'MM': 'месяц_месяцы_месяцаў', |
|||
'yy': 'год_гады_гадоў' |
|||
}; |
|||
if (key === 'm') { |
|||
return withoutSuffix ? 'хвіліна' : 'хвіліну'; |
|||
} |
|||
else if (key === 'h') { |
|||
return withoutSuffix ? 'гадзіна' : 'гадзіну'; |
|||
} |
|||
else { |
|||
return number + ' ' + plural(format[key], +number); |
|||
} |
|||
} |
|||
|
|||
var be = moment.defineLocale('be', { |
|||
months : { |
|||
format: 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_'), |
|||
standalone: 'студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_') |
|||
}, |
|||
monthsShort : 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'), |
|||
weekdays : { |
|||
format: 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_'), |
|||
standalone: 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'), |
|||
isFormat: /\[ ?[Вв] ?(?:мінулую|наступную)? ?\] ?dddd/ |
|||
}, |
|||
weekdaysShort : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), |
|||
weekdaysMin : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), |
|||
longDateFormat : { |
|||
LT : 'HH:mm', |
|||
LTS : 'HH:mm:ss', |
|||
L : 'DD.MM.YYYY', |
|||
LL : 'D MMMM YYYY г.', |
|||
LLL : 'D MMMM YYYY г., HH:mm', |
|||
LLLL : 'dddd, D MMMM YYYY г., HH:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay: '[Сёння ў] LT', |
|||
nextDay: '[Заўтра ў] LT', |
|||
lastDay: '[Учора ў] LT', |
|||
nextWeek: function () { |
|||
return '[У] dddd [ў] LT'; |
|||
}, |
|||
lastWeek: function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
case 3: |
|||
case 5: |
|||
case 6: |
|||
return '[У мінулую] dddd [ў] LT'; |
|||
case 1: |
|||
case 2: |
|||
case 4: |
|||
return '[У мінулы] dddd [ў] LT'; |
|||
} |
|||
}, |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'праз %s', |
|||
past : '%s таму', |
|||
s : 'некалькі секунд', |
|||
m : relativeTimeWithPlural, |
|||
mm : relativeTimeWithPlural, |
|||
h : relativeTimeWithPlural, |
|||
hh : relativeTimeWithPlural, |
|||
d : 'дзень', |
|||
dd : relativeTimeWithPlural, |
|||
M : 'месяц', |
|||
MM : relativeTimeWithPlural, |
|||
y : 'год', |
|||
yy : relativeTimeWithPlural |
|||
}, |
|||
meridiemParse: /ночы|раніцы|дня|вечара/, |
|||
isPM : function (input) { |
|||
return /^(дня|вечара)$/.test(input); |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 4) { |
|||
return 'ночы'; |
|||
} else if (hour < 12) { |
|||
return 'раніцы'; |
|||
} else if (hour < 17) { |
|||
return 'дня'; |
|||
} else { |
|||
return 'вечара'; |
|||
} |
|||
}, |
|||
ordinalParse: /\d{1,2}-(і|ы|га)/, |
|||
ordinal: function (number, period) { |
|||
switch (period) { |
|||
case 'M': |
|||
case 'd': |
|||
case 'DDD': |
|||
case 'w': |
|||
case 'W': |
|||
return (number % 10 === 2 || number % 10 === 3) && (number % 100 !== 12 && number % 100 !== 13) ? number + '-і' : number + '-ы'; |
|||
case 'D': |
|||
return number + '-га'; |
|||
default: |
|||
return number; |
|||
} |
|||
}, |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return be; |
|||
|
|||
})); |
@ -0,0 +1,90 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Bulgarian [bg]
|
|||
//! author : Krasen Borisov : https://github.com/kraz
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var bg = moment.defineLocale('bg', { |
|||
months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'), |
|||
monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'), |
|||
weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'), |
|||
weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'), |
|||
weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), |
|||
longDateFormat : { |
|||
LT : 'H:mm', |
|||
LTS : 'H:mm:ss', |
|||
L : 'D.MM.YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY H:mm', |
|||
LLLL : 'dddd, D MMMM YYYY H:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[Днес в] LT', |
|||
nextDay : '[Утре в] LT', |
|||
nextWeek : 'dddd [в] LT', |
|||
lastDay : '[Вчера в] LT', |
|||
lastWeek : function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
case 3: |
|||
case 6: |
|||
return '[В изминалата] dddd [в] LT'; |
|||
case 1: |
|||
case 2: |
|||
case 4: |
|||
case 5: |
|||
return '[В изминалия] dddd [в] LT'; |
|||
} |
|||
}, |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'след %s', |
|||
past : 'преди %s', |
|||
s : 'няколко секунди', |
|||
m : 'минута', |
|||
mm : '%d минути', |
|||
h : 'час', |
|||
hh : '%d часа', |
|||
d : 'ден', |
|||
dd : '%d дни', |
|||
M : 'месец', |
|||
MM : '%d месеца', |
|||
y : 'година', |
|||
yy : '%d години' |
|||
}, |
|||
ordinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/, |
|||
ordinal : function (number) { |
|||
var lastDigit = number % 10, |
|||
last2Digits = number % 100; |
|||
if (number === 0) { |
|||
return number + '-ев'; |
|||
} else if (last2Digits === 0) { |
|||
return number + '-ен'; |
|||
} else if (last2Digits > 10 && last2Digits < 20) { |
|||
return number + '-ти'; |
|||
} else if (lastDigit === 1) { |
|||
return number + '-ви'; |
|||
} else if (lastDigit === 2) { |
|||
return number + '-ри'; |
|||
} else if (lastDigit === 7 || lastDigit === 8) { |
|||
return number + '-ми'; |
|||
} else { |
|||
return number + '-ти'; |
|||
} |
|||
}, |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return bg; |
|||
|
|||
})); |
@ -0,0 +1,119 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Bengali [bn]
|
|||
//! author : Kaushik Gandhi : https://github.com/kaushikgandhi
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var symbolMap = { |
|||
'1': '১', |
|||
'2': '২', |
|||
'3': '৩', |
|||
'4': '৪', |
|||
'5': '৫', |
|||
'6': '৬', |
|||
'7': '৭', |
|||
'8': '৮', |
|||
'9': '৯', |
|||
'0': '০' |
|||
}, |
|||
numberMap = { |
|||
'১': '1', |
|||
'২': '2', |
|||
'৩': '3', |
|||
'৪': '4', |
|||
'৫': '5', |
|||
'৬': '6', |
|||
'৭': '7', |
|||
'৮': '8', |
|||
'৯': '9', |
|||
'০': '0' |
|||
}; |
|||
|
|||
var bn = moment.defineLocale('bn', { |
|||
months : 'জানুয়ারী_ফেবুয়ারী_মার্চ_এপ্রিল_মে_জুন_জুলাই_অগাস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'), |
|||
monthsShort : 'জানু_ফেব_মার্চ_এপর_মে_জুন_জুল_অগ_সেপ্ট_অক্টো_নভ_ডিসেম্'.split('_'), |
|||
weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পত্তিবার_শুক্রবার_শনিবার'.split('_'), |
|||
weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পত্তি_শুক্র_শনি'.split('_'), |
|||
weekdaysMin : 'রব_সম_মঙ্গ_বু_ব্রিহ_শু_শনি'.split('_'), |
|||
longDateFormat : { |
|||
LT : 'A h:mm সময়', |
|||
LTS : 'A h:mm:ss সময়', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY, A h:mm সময়', |
|||
LLLL : 'dddd, D MMMM YYYY, A h:mm সময়' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[আজ] LT', |
|||
nextDay : '[আগামীকাল] LT', |
|||
nextWeek : 'dddd, LT', |
|||
lastDay : '[গতকাল] LT', |
|||
lastWeek : '[গত] dddd, LT', |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : '%s পরে', |
|||
past : '%s আগে', |
|||
s : 'কয়েক সেকেন্ড', |
|||
m : 'এক মিনিট', |
|||
mm : '%d মিনিট', |
|||
h : 'এক ঘন্টা', |
|||
hh : '%d ঘন্টা', |
|||
d : 'এক দিন', |
|||
dd : '%d দিন', |
|||
M : 'এক মাস', |
|||
MM : '%d মাস', |
|||
y : 'এক বছর', |
|||
yy : '%d বছর' |
|||
}, |
|||
preparse: function (string) { |
|||
return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) { |
|||
return numberMap[match]; |
|||
}); |
|||
}, |
|||
postformat: function (string) { |
|||
return string.replace(/\d/g, function (match) { |
|||
return symbolMap[match]; |
|||
}); |
|||
}, |
|||
meridiemParse: /রাত|সকাল|দুপুর|বিকাল|রাত/, |
|||
meridiemHour : function (hour, meridiem) { |
|||
if (hour === 12) { |
|||
hour = 0; |
|||
} |
|||
if ((meridiem === 'রাত' && hour >= 4) || |
|||
(meridiem === 'দুপুর' && hour < 5) || |
|||
meridiem === 'বিকাল') { |
|||
return hour + 12; |
|||
} else { |
|||
return hour; |
|||
} |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 4) { |
|||
return 'রাত'; |
|||
} else if (hour < 10) { |
|||
return 'সকাল'; |
|||
} else if (hour < 17) { |
|||
return 'দুপুর'; |
|||
} else if (hour < 20) { |
|||
return 'বিকাল'; |
|||
} else { |
|||
return 'রাত'; |
|||
} |
|||
}, |
|||
week : { |
|||
dow : 0, // Sunday is the first day of the week.
|
|||
doy : 6 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return bn; |
|||
|
|||
})); |
@ -0,0 +1,119 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Tibetan [bo]
|
|||
//! author : Thupten N. Chakrishar : https://github.com/vajradog
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var symbolMap = { |
|||
'1': '༡', |
|||
'2': '༢', |
|||
'3': '༣', |
|||
'4': '༤', |
|||
'5': '༥', |
|||
'6': '༦', |
|||
'7': '༧', |
|||
'8': '༨', |
|||
'9': '༩', |
|||
'0': '༠' |
|||
}, |
|||
numberMap = { |
|||
'༡': '1', |
|||
'༢': '2', |
|||
'༣': '3', |
|||
'༤': '4', |
|||
'༥': '5', |
|||
'༦': '6', |
|||
'༧': '7', |
|||
'༨': '8', |
|||
'༩': '9', |
|||
'༠': '0' |
|||
}; |
|||
|
|||
var bo = moment.defineLocale('bo', { |
|||
months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), |
|||
monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), |
|||
weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'), |
|||
weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), |
|||
weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), |
|||
longDateFormat : { |
|||
LT : 'A h:mm', |
|||
LTS : 'A h:mm:ss', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY, A h:mm', |
|||
LLLL : 'dddd, D MMMM YYYY, A h:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[དི་རིང] LT', |
|||
nextDay : '[སང་ཉིན] LT', |
|||
nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT', |
|||
lastDay : '[ཁ་སང] LT', |
|||
lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : '%s ལ་', |
|||
past : '%s སྔན་ལ', |
|||
s : 'ལམ་སང', |
|||
m : 'སྐར་མ་གཅིག', |
|||
mm : '%d སྐར་མ', |
|||
h : 'ཆུ་ཚོད་གཅིག', |
|||
hh : '%d ཆུ་ཚོད', |
|||
d : 'ཉིན་གཅིག', |
|||
dd : '%d ཉིན་', |
|||
M : 'ཟླ་བ་གཅིག', |
|||
MM : '%d ཟླ་བ', |
|||
y : 'ལོ་གཅིག', |
|||
yy : '%d ལོ' |
|||
}, |
|||
preparse: function (string) { |
|||
return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) { |
|||
return numberMap[match]; |
|||
}); |
|||
}, |
|||
postformat: function (string) { |
|||
return string.replace(/\d/g, function (match) { |
|||
return symbolMap[match]; |
|||
}); |
|||
}, |
|||
meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/, |
|||
meridiemHour : function (hour, meridiem) { |
|||
if (hour === 12) { |
|||
hour = 0; |
|||
} |
|||
if ((meridiem === 'མཚན་མོ' && hour >= 4) || |
|||
(meridiem === 'ཉིན་གུང' && hour < 5) || |
|||
meridiem === 'དགོང་དག') { |
|||
return hour + 12; |
|||
} else { |
|||
return hour; |
|||
} |
|||
}, |
|||
meridiem : function (hour, minute, isLower) { |
|||
if (hour < 4) { |
|||
return 'མཚན་མོ'; |
|||
} else if (hour < 10) { |
|||
return 'ཞོགས་ཀས'; |
|||
} else if (hour < 17) { |
|||
return 'ཉིན་གུང'; |
|||
} else if (hour < 20) { |
|||
return 'དགོང་དག'; |
|||
} else { |
|||
return 'མཚན་མོ'; |
|||
} |
|||
}, |
|||
week : { |
|||
dow : 0, // Sunday is the first day of the week.
|
|||
doy : 6 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return bo; |
|||
|
|||
})); |
@ -0,0 +1,108 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Breton [br]
|
|||
//! author : Jean-Baptiste Le Duigou : https://github.com/jbleduigou
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
function relativeTimeWithMutation(number, withoutSuffix, key) { |
|||
var format = { |
|||
'mm': 'munutenn', |
|||
'MM': 'miz', |
|||
'dd': 'devezh' |
|||
}; |
|||
return number + ' ' + mutation(format[key], number); |
|||
} |
|||
function specialMutationForYears(number) { |
|||
switch (lastNumber(number)) { |
|||
case 1: |
|||
case 3: |
|||
case 4: |
|||
case 5: |
|||
case 9: |
|||
return number + ' bloaz'; |
|||
default: |
|||
return number + ' vloaz'; |
|||
} |
|||
} |
|||
function lastNumber(number) { |
|||
if (number > 9) { |
|||
return lastNumber(number % 10); |
|||
} |
|||
return number; |
|||
} |
|||
function mutation(text, number) { |
|||
if (number === 2) { |
|||
return softMutation(text); |
|||
} |
|||
return text; |
|||
} |
|||
function softMutation(text) { |
|||
var mutationTable = { |
|||
'm': 'v', |
|||
'b': 'v', |
|||
'd': 'z' |
|||
}; |
|||
if (mutationTable[text.charAt(0)] === undefined) { |
|||
return text; |
|||
} |
|||
return mutationTable[text.charAt(0)] + text.substring(1); |
|||
} |
|||
|
|||
var br = moment.defineLocale('br', { |
|||
months : 'Genver_C\'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'), |
|||
monthsShort : 'Gen_C\'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'), |
|||
weekdays : 'Sul_Lun_Meurzh_Merc\'her_Yaou_Gwener_Sadorn'.split('_'), |
|||
weekdaysShort : 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'), |
|||
weekdaysMin : 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'h[e]mm A', |
|||
LTS : 'h[e]mm:ss A', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D [a viz] MMMM YYYY', |
|||
LLL : 'D [a viz] MMMM YYYY h[e]mm A', |
|||
LLLL : 'dddd, D [a viz] MMMM YYYY h[e]mm A' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[Hiziv da] LT', |
|||
nextDay : '[Warc\'hoazh da] LT', |
|||
nextWeek : 'dddd [da] LT', |
|||
lastDay : '[Dec\'h da] LT', |
|||
lastWeek : 'dddd [paset da] LT', |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'a-benn %s', |
|||
past : '%s \'zo', |
|||
s : 'un nebeud segondennoù', |
|||
m : 'ur vunutenn', |
|||
mm : relativeTimeWithMutation, |
|||
h : 'un eur', |
|||
hh : '%d eur', |
|||
d : 'un devezh', |
|||
dd : relativeTimeWithMutation, |
|||
M : 'ur miz', |
|||
MM : relativeTimeWithMutation, |
|||
y : 'ur bloaz', |
|||
yy : specialMutationForYears |
|||
}, |
|||
ordinalParse: /\d{1,2}(añ|vet)/, |
|||
ordinal : function (number) { |
|||
var output = (number === 1) ? 'añ' : 'vet'; |
|||
return number + output; |
|||
}, |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return br; |
|||
|
|||
})); |
@ -0,0 +1,143 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Bosnian [bs]
|
|||
//! author : Nedim Cholich : https://github.com/frontyard
|
|||
//! based on (hr) translation by Bojan Marković
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
function translate(number, withoutSuffix, key) { |
|||
var result = number + ' '; |
|||
switch (key) { |
|||
case 'm': |
|||
return withoutSuffix ? 'jedna minuta' : 'jedne minute'; |
|||
case 'mm': |
|||
if (number === 1) { |
|||
result += 'minuta'; |
|||
} else if (number === 2 || number === 3 || number === 4) { |
|||
result += 'minute'; |
|||
} else { |
|||
result += 'minuta'; |
|||
} |
|||
return result; |
|||
case 'h': |
|||
return withoutSuffix ? 'jedan sat' : 'jednog sata'; |
|||
case 'hh': |
|||
if (number === 1) { |
|||
result += 'sat'; |
|||
} else if (number === 2 || number === 3 || number === 4) { |
|||
result += 'sata'; |
|||
} else { |
|||
result += 'sati'; |
|||
} |
|||
return result; |
|||
case 'dd': |
|||
if (number === 1) { |
|||
result += 'dan'; |
|||
} else { |
|||
result += 'dana'; |
|||
} |
|||
return result; |
|||
case 'MM': |
|||
if (number === 1) { |
|||
result += 'mjesec'; |
|||
} else if (number === 2 || number === 3 || number === 4) { |
|||
result += 'mjeseca'; |
|||
} else { |
|||
result += 'mjeseci'; |
|||
} |
|||
return result; |
|||
case 'yy': |
|||
if (number === 1) { |
|||
result += 'godina'; |
|||
} else if (number === 2 || number === 3 || number === 4) { |
|||
result += 'godine'; |
|||
} else { |
|||
result += 'godina'; |
|||
} |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
var bs = moment.defineLocale('bs', { |
|||
months : 'januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar'.split('_'), |
|||
monthsShort : 'jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.'.split('_'), |
|||
monthsParseExact: true, |
|||
weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), |
|||
weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), |
|||
weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'H:mm', |
|||
LTS : 'H:mm:ss', |
|||
L : 'DD. MM. YYYY', |
|||
LL : 'D. MMMM YYYY', |
|||
LLL : 'D. MMMM YYYY H:mm', |
|||
LLLL : 'dddd, D. MMMM YYYY H:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : '[danas u] LT', |
|||
nextDay : '[sutra u] LT', |
|||
nextWeek : function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
return '[u] [nedjelju] [u] LT'; |
|||
case 3: |
|||
return '[u] [srijedu] [u] LT'; |
|||
case 6: |
|||
return '[u] [subotu] [u] LT'; |
|||
case 1: |
|||
case 2: |
|||
case 4: |
|||
case 5: |
|||
return '[u] dddd [u] LT'; |
|||
} |
|||
}, |
|||
lastDay : '[jučer u] LT', |
|||
lastWeek : function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
case 3: |
|||
return '[prošlu] dddd [u] LT'; |
|||
case 6: |
|||
return '[prošle] [subote] [u] LT'; |
|||
case 1: |
|||
case 2: |
|||
case 4: |
|||
case 5: |
|||
return '[prošli] dddd [u] LT'; |
|||
} |
|||
}, |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'za %s', |
|||
past : 'prije %s', |
|||
s : 'par sekundi', |
|||
m : translate, |
|||
mm : translate, |
|||
h : translate, |
|||
hh : translate, |
|||
d : 'dan', |
|||
dd : translate, |
|||
M : 'mjesec', |
|||
MM : translate, |
|||
y : 'godinu', |
|||
yy : translate |
|||
}, |
|||
ordinalParse: /\d{1,2}\./, |
|||
ordinal : '%d.', |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 7 // The week that contains Jan 1st is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return bs; |
|||
|
|||
})); |
@ -0,0 +1,81 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Catalan [ca]
|
|||
//! author : Juan G. Hurtado : https://github.com/juanghurtado
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var ca = moment.defineLocale('ca', { |
|||
months : 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'), |
|||
monthsShort : 'gen._febr._mar._abr._mai._jun._jul._ag._set._oct._nov._des.'.split('_'), |
|||
monthsParseExact : true, |
|||
weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'), |
|||
weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'), |
|||
weekdaysMin : 'Dg_Dl_Dt_Dc_Dj_Dv_Ds'.split('_'), |
|||
weekdaysParseExact : true, |
|||
longDateFormat : { |
|||
LT : 'H:mm', |
|||
LTS : 'H:mm:ss', |
|||
L : 'DD/MM/YYYY', |
|||
LL : 'D MMMM YYYY', |
|||
LLL : 'D MMMM YYYY H:mm', |
|||
LLLL : 'dddd D MMMM YYYY H:mm' |
|||
}, |
|||
calendar : { |
|||
sameDay : function () { |
|||
return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; |
|||
}, |
|||
nextDay : function () { |
|||
return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; |
|||
}, |
|||
nextWeek : function () { |
|||
return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; |
|||
}, |
|||
lastDay : function () { |
|||
return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; |
|||
}, |
|||
lastWeek : function () { |
|||
return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; |
|||
}, |
|||
sameElse : 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'en %s', |
|||
past : 'fa %s', |
|||
s : 'uns segons', |
|||
m : 'un minut', |
|||
mm : '%d minuts', |
|||
h : 'una hora', |
|||
hh : '%d hores', |
|||
d : 'un dia', |
|||
dd : '%d dies', |
|||
M : 'un mes', |
|||
MM : '%d mesos', |
|||
y : 'un any', |
|||
yy : '%d anys' |
|||
}, |
|||
ordinalParse: /\d{1,2}(r|n|t|è|a)/, |
|||
ordinal : function (number, period) { |
|||
var output = (number === 1) ? 'r' : |
|||
(number === 2) ? 'n' : |
|||
(number === 3) ? 'r' : |
|||
(number === 4) ? 't' : 'è'; |
|||
if (period === 'w' || period === 'W') { |
|||
output = 'a'; |
|||
} |
|||
return number + output; |
|||
}, |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return ca; |
|||
|
|||
})); |
@ -0,0 +1,172 @@ |
|||
//! moment.js locale configuration
|
|||
//! locale : Czech [cs]
|
|||
//! author : petrbela : https://github.com/petrbela
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, function (moment) { 'use strict'; |
|||
|
|||
|
|||
var months = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'), |
|||
monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_'); |
|||
function plural(n) { |
|||
return (n > 1) && (n < 5) && (~~(n / 10) !== 1); |
|||
} |
|||
function translate(number, withoutSuffix, key, isFuture) { |
|||
var result = number + ' '; |
|||
switch (key) { |
|||
case 's': // a few seconds / in a few seconds / a few seconds ago
|
|||
return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami'; |
|||
case 'm': // a minute / in a minute / a minute ago
|
|||
return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou'); |
|||
case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago
|
|||
if (withoutSuffix || isFuture) { |
|||
return result + (plural(number) ? 'minuty' : 'minut'); |
|||
} else { |
|||
return result + 'minutami'; |
|||
} |
|||
break; |
|||
case 'h': // an hour / in an hour / an hour ago
|
|||
return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); |
|||
case 'hh': // 9 hours / in 9 hours / 9 hours ago
|
|||
if (withoutSuffix || isFuture) { |
|||
return result + (plural(number) ? 'hodiny' : 'hodin'); |
|||
} else { |
|||
return result + 'hodinami'; |
|||
} |
|||
break; |
|||
case 'd': // a day / in a day / a day ago
|
|||
return (withoutSuffix || isFuture) ? 'den' : 'dnem'; |
|||
case 'dd': // 9 days / in 9 days / 9 days ago
|
|||
if (withoutSuffix || isFuture) { |
|||
return result + (plural(number) ? 'dny' : 'dní'); |
|||
} else { |
|||
return result + 'dny'; |
|||
} |
|||
break; |
|||
case 'M': // a month / in a month / a month ago
|
|||
return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem'; |
|||
case 'MM': // 9 months / in 9 months / 9 months ago
|
|||
if (withoutSuffix || isFuture) { |
|||
return result + (plural(number) ? 'měsíce' : 'měsíců'); |
|||
} else { |
|||
return result + 'měsíci'; |
|||
} |
|||
break; |
|||
case 'y': // a year / in a year / a year ago
|
|||
return (withoutSuffix || isFuture) ? 'rok' : 'rokem'; |
|||
case 'yy': // 9 years / in 9 years / 9 years ago
|
|||
if (withoutSuffix || isFuture) { |
|||
return result + (plural(number) ? 'roky' : 'let'); |
|||
} else { |
|||
return result + 'lety'; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var cs = moment.defineLocale('cs', { |
|||
months : months, |
|||
monthsShort : monthsShort, |
|||
monthsParse : (function (months, monthsShort) { |
|||
var i, _monthsParse = []; |
|||
for (i = 0; i < 12; i++) { |
|||
// use custom parser to solve problem with July (červenec)
|
|||
_monthsParse[i] = new RegExp('^' + months[i] + '$|^' + monthsShort[i] + '$', 'i'); |
|||
} |
|||
return _monthsParse; |
|||
}(months, monthsShort)), |
|||
shortMonthsParse : (function (monthsShort) { |
|||
var i, _shortMonthsParse = []; |
|||
for (i = 0; i < 12; i++) { |
|||
_shortMonthsParse[i] = new RegExp('^' + monthsShort[i] + '$', 'i'); |
|||
} |
|||
return _shortMonthsParse; |
|||
}(monthsShort)), |
|||
longMonthsParse : (function (months) { |
|||
var i, _longMonthsParse = []; |
|||
for (i = 0; i < 12; i++) { |
|||
_longMonthsParse[i] = new RegExp('^' + months[i] + '$', 'i'); |
|||
} |
|||
return _longMonthsParse; |
|||
}(months)), |
|||
weekdays : 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'), |
|||
weekdaysShort : 'ne_po_út_st_čt_pá_so'.split('_'), |
|||
weekdaysMin : 'ne_po_út_st_čt_pá_so'.split('_'), |
|||
longDateFormat : { |
|||
LT: 'H:mm', |
|||
LTS : 'H:mm:ss', |
|||
L : 'DD.MM.YYYY', |
|||
LL : 'D. MMMM YYYY', |
|||
LLL : 'D. MMMM YYYY H:mm', |
|||
LLLL : 'dddd D. MMMM YYYY H:mm', |
|||
l : 'D. M. YYYY' |
|||
}, |
|||
calendar : { |
|||
sameDay: '[dnes v] LT', |
|||
nextDay: '[zítra v] LT', |
|||
nextWeek: function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
return '[v neděli v] LT'; |
|||
case 1: |
|||
case 2: |
|||
return '[v] dddd [v] LT'; |
|||
case 3: |
|||
return '[ve středu v] LT'; |
|||
case 4: |
|||
return '[ve čtvrtek v] LT'; |
|||
case 5: |
|||
return '[v pátek v] LT'; |
|||
case 6: |
|||
return '[v sobotu v] LT'; |
|||
} |
|||
}, |
|||
lastDay: '[včera v] LT', |
|||
lastWeek: function () { |
|||
switch (this.day()) { |
|||
case 0: |
|||
return '[minulou neděli v] LT'; |
|||
case 1: |
|||
case 2: |
|||
return '[minulé] dddd [v] LT'; |
|||
case 3: |
|||
return '[minulou středu v] LT'; |
|||
case 4: |
|||
case 5: |
|||
return '[minulý] dddd [v] LT'; |
|||
case 6: |
|||
return '[minulou sobotu v] LT'; |
|||
} |
|||
}, |
|||
sameElse: 'L' |
|||
}, |
|||
relativeTime : { |
|||
future : 'za %s', |
|||
past : 'před %s', |
|||
s : translate, |
|||
m : translate, |
|||
mm : translate, |
|||
h : translate, |
|||
hh : translate, |
|||
d : translate, |
|||
dd : translate, |
|||
M : translate, |
|||
MM : translate, |
|||
y : translate, |
|||
yy : translate |
|||
}, |
|||
ordinalParse : /\d{1,2}\./, |
|||
ordinal : '%d.', |
|||
week : { |
|||
dow : 1, // Monday is the first day of the week.
|
|||
doy : 4 // The week that contains Jan 4th is the first week of the year.
|
|||
} |
|||
}); |
|||
|
|||
return cs; |
|||
|
|||
})); |