diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..90d05f0 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +# Configuration for the GitHub feature to automatically request reviews from the code owners when a pull request changes any owned files. +# +# [References] +# https://github.com/blog/2392-introducing-code-owners +# https://help.github.com/articles/about-codeowners + +# +--------------------+ +# + Default Code Owner + +# +--------------------+ +* @arcticicestudio diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..64a9896 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,81 @@ + + +> **Please read the [contribution guidelines](https://github.com/arcticicestudio/icecore-hashids/blob/develop/CONTRIBUTING.md) before filling out this issue template**. + +## Prerequisites + +This section and the instructions in the sections below are only part of this issue template. Please ensure to **delete this whole section, all pre-filled instructions of the sections below and sections you have not filled out before submitting** to ensure a clear structure and overview. + +Please do your best to provide as much information as possible and use a clear and descriptive title for your enhancement suggestion or bug report to help maintainers and the community understand and reproduce the behavior, find related reports and to resolve the ticket faster. + +* **Ensure the issue has not already been reported by using the [GitHub Issues search](https://github.com/arcticicestudio/icecore-hashids/issues)** — if it has **and the issue is still open**, add a comment to the existing issue instead of opening this new one. If you find a closed issue that seems to be similar to this one, include a link to the original issue in the [metadata head](#metadata-head) section of this issue. +* **Ensure the issue is reproducible** — try to use the [latest version](https://github.com/arcticicestudio/icecore-hashids/releases/latest) and [`develop`](https://github.com/arcticicestudio/icecore-hashids/tree/develop) branch. + +## Metadata Head + +The metadata head should be added to the top of the issue as [Markdown text quote](https://help.github.com/articles/basic-writing-and-formatting-syntax) containing the [issue type](#issue-type) and if necessary the ID of other related issues. + +> Issue type: +Related issues: + +### Issue Type + +Set the *type* of this issue. It determines which information will be required in the following sections when it is an [bug report](https://github.com/arcticicestudio/icecore-hashids/blob/develop/CONTRIBUTING.md#bug-reports) or an [enhancement suggestion](https://github.com/arcticicestudio/icecore-hashids/blob/develop/CONTRIBUTING.md#enhancement-suggestions). + +* *feature* +* *improvement* +* *bug* +* *test* +* *task* +* *subtask* + +## Description + +Describe the enhancement or bug as in many relevant details as possible. If this is a enhancement suggestion add specific use-cases and explain why this feature or improvement would be useful. If this is a bug provide ensure to fill in the [steps to reproduce](#steps-to-reproduce) it. + +### Steps to Reproduce + +1. Step One +2. Step Two +3. ... + +### Expected Behavior + +What you expect to happen? + +### Actual Behavior + +What actually happens? + +## Example + +Provide a [MCVE - The Minimal, Complete, and Verifiable Example](https://github.com/arcticicestudio/icecore-hashids/blob/develop/CONTRIBUTING.md#mcve) + +**This is a optional section, but it can drastically increase the speed at which this issue can be processed since it takes away the time-consuming reconstruction to reproduce the enhancement or bug.** + +The recommened way is to upload it as [Gist](https://gist.github.com) or new repository to GitHub, but of course you can [attach it to this issue](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests), use any free file hosting service or paste the code in [Markdown code blocks](https://help.github.com/articles/basic-writing-and-formatting-syntax) into this issue. + +## Environment and Versions + +* What is the version of IceCore Hashids you are using? +* What is the name and the version of the OS you're using? + * Have you tried to reproduce it on different OS environments and if yes is the behavior the same for all? +* Which Java JDK/JRE distribution and version are you using? (*Oracle*, *OpenJDK*, ...) +* Are you running the project with your IDE or through Maven? + * Are you using any additional CLI arguments for Java or Maven? + +## Stack Trace and Error Messages + +``` +Paste the full stack trace, error messages or the logfile here ... +``` + +... or [attach them as files](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests) to this issue. + +## References + +Add any other references and links which are relevant for this issue. + +## Potential Solution + +If this is a bug report this might include the lines of code that you have identified as causing the bug or in case of an enhancement suggestion references to other projects where this enhancement already exists. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..8f387d1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,27 @@ + + +> **Please read the [contribution guidelines](https://github.com/arcticicestudio/icecore-hashids/blob/develop/CONTRIBUTING.md) before filling out this pull request template**. + +## Prerequisites + +This section and the instructions in the sections below are only part of this pull request template. Please ensure to **delete this whole section, all pre-filled instructions of the sections below and sections you have not filled out before submitting** to ensure a clear structure and overview. + +Please do your best to provide as much information as possible and use a clear and descriptive title for your enhancement suggestion or bug fix to help maintainers and the community understand and reproduce the behavior, find related pull requests and to merge it faster. + +* **Ensure the pull request has not already been reported by using the [GitHub Pull Request search](https://github.com/arcticicestudio/icecore-hashids/pulls)** — if it has **and the pull request is still open**, add a comment to the existing pull request instead of opening this new one. If you find a closed pull request that seems to be similar to this one, include a link to it in the [metadata head](#metadata-head) section of this pull request. +* **Ensure to adhere to the [pull request contribution guidelines](https://github.com/arcticicestudio/icecore-hashids/blob/feature/gh-12-github-open-source-community-standards/CONTRIBUTING.md#pull-requests)**, especially the one for tests and documentations. + +## Metadata Head + +The metadata head should be added to the top of the pull request as [Markdown text quote](https://help.github.com/articles/basic-writing-and-formatting-syntax) containing the [GitHub issue keywords][gh-help-issue-keywords] to link to the related enhancements suggestions (`Closes`) or bug reports (`Fixes`). You can add additional details like dependencies to other pull requests and the order it needs to be merged. + +> Closes ISSUE_ID +Must be merged **after**/**before** ISSUE_ID + +## Description + +Describe the changes as in many relevant details as possible. If this is a enhancement suggestion add specific use-cases and explain why this feature or improvement would be useful. If this is a bug fix ensure to provide a *before/after* comparison by describing the current behavior and the new behavior. + +## References + +Add any other references and links which are relevant for this issue. diff --git a/.gitignore b/.gitignore index 9cb88b8..512e5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ out/ !.idea/runConfigurations !.idea/compiler.xml !.idea/encodings.xml +!.idea/icon_dark.png +!.idea/icon.png !.idea/misc.xml !.idea/modules.xml !.idea/vcs.xml @@ -25,3 +27,9 @@ out/ # JVM crash logs # http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# +-----+ +# + NPM + +# +-----+ +node_modules/ +npm-debug.log diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..94d4617 Binary files /dev/null and b/.idea/icon.png differ diff --git a/.idea/icon_dark.png b/.idea/icon_dark.png new file mode 100644 index 0000000..b53f6cc Binary files /dev/null and b/.idea/icon_dark.png differ diff --git a/.idea/runConfigurations/Interop_Tests.xml b/.idea/runConfigurations/Interop_Tests.xml new file mode 100644 index 0000000..6064f80 --- /dev/null +++ b/.idea/runConfigurations/Interop_Tests.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Unit_Tests.xml b/.idea/runConfigurations/Unit_Tests.xml new file mode 100644 index 0000000..c1da780 --- /dev/null +++ b/.idea/runConfigurations/Unit_Tests.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 5f03032..1160cd0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,15 +8,25 @@ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # # [References] -# Travis CI Documentation -# (https://docs.travis-ci.com) +# Travis CI +# https://docs.travis-ci.com language: java +dist: trusty +sudo: false jdk: + - openjdk8 - oraclejdk8 -sudo: false cache: directories: - $HOME/.m2 +notifications: + email: + on_success: never + on_failure: change +before_script: mvn --version +script: + - mvn -B clean verify -P node,code-coverage + - npm install + - npm run docs:build after_success: - bash <(curl -s https://codecov.io/bash) -script: mvn -B clean verify -P code-coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index f436e67..dba7df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,27 +1,98 @@

-

+

--- +# 0.4.0 +![Release Date: 2017-08-13](https://img.shields.io/badge/Release_Date-2017--08--13-88C0D0.svg?style=flat-square) [![Project Board](https://img.shields.io/badge/Project_Board-0.4.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/projects/5) [![Milestone](https://img.shields.io/badge/Milestone-0.4.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/milestone/4) [![docs](https://img.shields.io/badge/docs-0.4.0-81A1C1.svg?style=flat-square)][docs] [![JavaDoc](https://img.shields.io/badge/JavaDoc-0.4.0-81A1C1.svg?style=flat-square)][javadoc] + +This version represents a large milestone which is a revise of the whole project to improve the clarity, performance and stability. + +> Detailed information about each new API feature- and change can be found in the [project documentation][docs]. + +## Features +### API + +❯ The public- and internal APIs have been [redesigned](https://github.com/arcticicestudio/icecore-hashids/issues/9) and rewritten from scratch regarding optimizations for Java 8, reduction of complexity and the new code style guide conventions. + +❯ The new `com.arcticicestudio.icecore.hashids.HashidsFeature` enum contains constants for various [features][docs-features] which can be enabled per instance through the `Hashids.Builder` method `features(HashidFeature) : Builder`. + +❯ The public API now also provides the `decodeOne(String) : Optional` method to simplify the use-case where the amount of resulting numbers is known before to handle the return value as single value instead of an array. This improves the prevention of an `ArrayIndexOutOfBounceException` when users try to access an non-existent array index due to an failed encoding- or decoding. + +#### Public API Breaking Changes + +❯ The obsolete class `com.arcticicestudio.icecore.hashids.Hashid` has been removed which was previously added to simplify the binding handling of the numbers and their hash value, but users should implement the simple POJO on their own instead of forcing them to use this approach. + +The public API methods now returning the primitive values which have been hold by the `Hashid` class. (#9 closed by PR #22, 6d5b21f4) + +❯ Due to the removement of the `Hashid` class some public API methods have become unnecessary and have also been removed. (#9 closed by PR #22, 8e21e96a) + +* `decodeLongNumbers(String) : long[]` - The removed `Hashid` class has been used as a kind of container to store both the numbers and their associated hash values. This method allowed to directly receive the `Long` numbers instead of the `Hashid` POJO which is now be provided by the `decode(String) : long[]` method. +* `decodeIntegerNumbers(String) : int[]` - The algorithm is designed for `Long` numbers. This method has been implemented to provide an easy conversion interface to use the library with integer IDs which should now be handled by the user. +* `encodeToString(long...) : String` and `encodeToString(int...) : String` - These methods are the counterparts for the decoding logic methods described above. Both have been removed for the same reason and the functionality is now available through the `encode(String) : long[]` method. + +❯ Removed all overloaded constructors due to the already provided interfaces through the `Hashids.Builder` class. It provides methods for a accurate instance configuration while an instance with the default interoperable configurations is still available through the default constructor or by using default constructed builder instance. It allows a finer grained instance building since it doesn't depends on parameter ordering. The default constructor is still available for brevity and the functionality of all other removed constructors can now be achieved with the builder. (#9 closed by PR #22, 40231ad1) + +❯ The `Hashids.Builder` method `minHashLength(int) : Builder` has been renamed to `minLength(int) : Builder` to adapt to the the builder methods naming style. (#9 closed by PR #22, 8617f4c2) + +❯ Separators are not customizable anymore since the curse word protection of the reference algorithm implementation does +not allow to modify the separators. The API provided methods to change those separators causing invalid and inconsistent hash- and numbers values. (#9 closed by PR #22, 1a082681) + +#### Internal API Changes + +❯ The private main logic methods `doEncode(long...)` and `doDecode(String, String)` have been removed and rewritten from scratch into the associated public API methods. (#9 closed by PR #22) + +❯ The private method `consistentShuffle(String, String) : String` has been rewritten from scratch and renamed to `shuffle(char[], char[]) : char[]`. (#9 closed by PR #22, 8617f4c2) + +❯ The private methods `hash(long, String)` and `unhash(String, String)` have been rewritten from scratch and renamed to `transform(long, char[], StringBuilder, int)` and `transform(char[], char"])`. (#9 closed by PR #22, 8617f4c2) + +❯ The primary algorithm logic, which was mainly implemented in the private methods `doEncode` and `doDecode`, has been rewritten from scratch and modularized into the new methods `deriveNewAlphabet(char[], char[], char) : char[]` and `filterSeparators(char[], char[]) : char[]`. (#9 closed by PR #22, 8617f4c2) + +❯ The private support methods `toArray(List) : long[]` and `isEmpty(String) : boolean` have been removed. (#9 closed by PR #22, 8617f4c2) + +### Documentation + +❯ The new [project documentation][docs] includes detailed information on how to [get started][docs-get-started], the new [API overview][docs-api] and [API guide][docs-api-guide] and how to [build][docs-dev-build] the project and [run the tests][docs-dev-test]. (#11 closed by PR #23) + +❯ Next to the project documentation the new [GitHub Open Source community standards](https://github.com/arcticicestudio/icecore-hashids/issues/12) have been added to complete the project's [community profile][community-profile] and adapt to the [Open Source Guides][open-source-guides] consisting of the [Contributing Guidelines][docs-contrib-guide] and the [Code of Conduct][docs-code-of-conduct]. (#12 closed by PR #20) + +❯ The new [GitHub templates](https://github.com/arcticicestudio/icecore-hashids/issues/13) providing support for contributors to [create issues][issue-template] and [submitting pull requests][pr-template]. (#13 closed by PR #19) + +❯ To improve the code review process the new [GitHub code owner](https://github.com/arcticicestudio/icecore-hashids/issues/14) feature has been adapted. (#14 closed by PR #15) + +### Tests + +All unit tests have been rewritten from scratch to match the new API and include as much use-cases as possible to increase the code coverage. The new interoperability tests run against the matching version of the reference implementation [hashids.js][hashids-js]. The script is loaded by using the [maven-frontend-plugin][maven-frontend-plugin] which installs [NodeJS][nodejs] locally and runs [NPM][npm] to install all dependencies defined in the `package.json`. The parameterized `InteropHashidsTest` class compares the results of both +algorithms to ensure interoperability. (#9 closed by PR #22, 8617f4c2) + +> Detailed information about the functionality and how to run the unit- and interoperability tests can be found in the [project documentation][docs-dev-test]. + +## Improvements + +❯ The POM has been refactored to + +* update all plugin- and dependency versions +* remove the unnecessary comment documentation header +* fix the copyright year for generated JavaDoc +* add the `maven-enforcer-plugin` +* remove unused profiles and configured core plugins +* remove unused test dependencies + +(#17 closed by PR #21) + # 0.3.0 -
- Version Details -

- Release Date: 2017-07-17
- Milestone
- Project Board -

-
+![Release Date: 2017-07-17](https://img.shields.io/badge/Release_Date-2017--07--17-88C0D0.svg?style=flat-square) [![Project Board](https://img.shields.io/badge/Project_Board-0.3.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/projects/4) [![Milestone](https://img.shields.io/badge/Milestone-0.3.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/milestone/3) [![docs](https://img.shields.io/badge/docs-0.3.0-81A1C1.svg?style=flat-square)][docs] [![JavaDoc](https://img.shields.io/badge/JavaDoc-0.3.0-81A1C1.svg?style=flat-square)][javadoc] ## Features ### API + ❯ Implemented a non-parameter constructor as an equivalent to the default `Hashids.Builder` instance. The builder supports the creation of an Hashids instance without parameters for custom configurations, but there was no equivalent constructor for this case. (#1) ## Improvements ### API -#### Performance -##### Internal +#### Performance (Internal) + ❯ The private method `consistentShuffle(String,String)` now uses `char` arrays instead of `String` operations. This removes five additional `String` operations/methods and six String concatenations to three simple array assignments. (#7) ❯ Replaced `toCharArray(int)` methods with `charAt(int)` since `Strings` are implemented as array internally so there is no need to convert to a char array. @@ -31,116 +102,99 @@ ❯ Adapted to SonarQube minor rule [squid:S3400](https://sonarcloud.io/coding_rules#q=squid%3AS1643) to improve the performance. (#6) ### Documentation + ❯ Adapted to a new project setup and documentation style. (#2) ### Tests + ❯ Slighly increased the code coverage by implementation unit tests for the `Hashid` *equals* symmetric. (#8) ## Bug Fixes ### API + ❯ Fixed a `ArrayIndexOutOfBoundsException` when using a invalid decode salt. ### JavaDoc + ❯ Fixed JavaDoc lint compilation errors for self-closed tags. (#3) -# 0.2.0 (2016-06-11) +# 0.2.0 +![Release Date: 2016-06-11](https://img.shields.io/badge/Release_Date-2016--06--11-88C0D0.svg?style=flat-square) [![Project Board](https://img.shields.io/badge/Project_Board-0.2.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/projects/3) [![Milestone](https://img.shields.io/badge/Milestone-0.2.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/milestone/2) + ## Features ### API - - Implemented public API Builder methods: -| Class | Constructor | Description | -| ----- | ----------- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ getVersion(String):Builder` | Returns the [ArcVer][arcver-github]- and [SemVer][semver] compatible version. | +❯ Implemented the public API method `getVersion(String)` to get the version. ## Improvements ### API - - Now using `StringBuilder` class for non-constant `String` attributes to avoid unnecessary `String` primitive-wrapping - - No more boxing of primitive for `String` conversion only - Make use of the static JDK method `+ String.valueOf(char):String` to avoid waste of memory and cycles by not using a primitive-wrapper with concatenating empty string `""` to a primitive. - This change fulfills the [Eclipse SonarQube][sonarqube-eclipse] rule [`squid:S2131`][sonarqube-eclipse-rule-squid-s2131]: - > Primitives should not be boxed just for `String` conversion - - Changed private methods without access to instance data to be `static` - Clarifies that these methods will not modify the state of the object. - This change fulfills the [Eclipse SonarQube][sonarqube-eclipse] rule `squid:S2325`: - > Private methods that don't access instance data should be static +❯ Now using `StringBuilder` for non-constant `String` attributes to avoid unnecessary `String` primitive-wrapping + +❯ No more boxing of primitive for `String` conversion only. Make use of the static JDK method `+ String.valueOf(char):String` to avoid waste of memory and cycles by not using a primitive-wrapper with concatenating empty string `""` to a primitive. This change fulfills the [Eclipse SonarQube][sonarqube-eclipse] rule [`squid:S2131`][sonarqube-eclipse-rule-squid-s2131]: + +> Primitives should not be boxed just for `String` conversion + +❯ Changed private methods without access to instance data to be `static`. Clarifies that these methods will not modify the state of the object. This change fulfills the [Eclipse SonarQube][sonarqube-eclipse] rule `squid:S2325`: + +> Private methods that don't access instance data should be static + +❯ Pre-compile regex `Pattern` pattern. Improves the performance (less memory and CPU cycles) by using pre-compiled regex pattern instead of creating instances for each `Pattern` call. - - Pre-compile regex `Pattern` pattern - Improves the performance (less memory and CPU cycles) by using pre-compiled regex pattern instead of creating instances for each `Pattern` call. +# 0.1.0 +![Release Date: 2016-06-10](https://img.shields.io/badge/Release_Date-2016--06--10-88C0D0.svg?style=flat-square) [![Project Board](https://img.shields.io/badge/Project_Board-0.1.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/projects/2) [![Milestone](https://img.shields.io/badge/Milestone-0.1.0-88C0D0.svg?style=flat-square)](https://github.com/arcticicestudio/icecore-hashids/milestone/1) -# 0.1.0 (2016-06-10) - Public API ## Features ### API - - Implemented public API classes: - -| Class | Description | -| ----- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids` | Generates short, unique, non-sequential and decodable hashids from positive unsigned (long) integer numbers. Serves as the entry point to the "IceCore - JSON" public API. | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | An immutable and reusable Hashids builder. Each method returns a new builder instance. | -| `com.arcticicestudio.icecore.hashids.Hashid` | Represents a hashid which holds all numbers and the encoded string. | - - - Implemented public API constructors: - -| Class | Constructor | Description | -| ----- | ----------- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ Hashids(String)` | - | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ Hashids(String,int)` | - | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ Hashids(String,int,String)` | - | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ Hashids(String,int,String,String)` | - | - - - Implemented public API Builder constructors: - -| Class | Constructor | Description | -| ----- | ----------- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | `+ Builder()` | - | - - - Implemented public API Builder methods: - -| Class | Constructor | Description | -| ----- | ----------- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | `+ salt(String):Builder` | - | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | `+ alphabet(String):Builder` | - | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | `+ minHashLength(int):Builder` | - | -| `com.arcticicestudio.icecore.hashids.Hashids.Builder` | `+ build(int):Hashids` | - | - - - Implemented public API methods: - -| Class | Method | Description | -| ----- | ------ | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ encode(long...):Hashid` | Encode number(s). | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ encodeToString(long...):String` | Encode number(s) to string. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ encodeToString(int...):String` | Encode number(s) to string. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ encodeHex(String):String` | Encode an hexadecimal string to string. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ decode(String):Hashid` | Decode an encoded string. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ decodeLongNumbers(String):long[]` | Decode an encoded string to long numbers. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ decodeIntegerNumbers(String):int[]` | Decode an encoded string to integer numbers. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ decodeHex(String):String` | Decode an string to hexadecimal numbers. | - - - Implemented public API static constants: - -| Class | Constructor | Value | Description | -| ----- | ----------- | ----- | ----------- | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ DEFAULT_ALPHABET:String` | `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` | Holds the default alphabet. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ DEFAULT_SEPARATORS:String` | `´cfhistuCFHISTU` | Holds the default separators. Used to prevent the generation of strings that contain bad, offensive or rude words. | -| `com.arcticicestudio.icecore.hashids.Hashids` | `+ MAX_NUMBER_VALUE:long` | `9_007_199_254_740_992L - 1` | Holds the maximum number value. This limit is mandatory in order to ensure interoperability. | -### Documentation - - Added a usage guide for the API. - This includes all currently implemented public API methods and general notes to the library algorithm. +❯ Implemented the public API. The `Hashids.Builder` class configures `Hashids` default instances. To create instances with custom configurations either the parameterized constructors can be used or by using the instance builder methods -## Tests - - Implemented public API test classes: +* `salt(String)` +* `alphabet(String)` +* `minHashLength(int)` +* `build(int)` -| Class | Description | -| ----- | ----------- | -| `com.arcticicestudio.icecore.hashids.HashidsTest` | Tests the "IceCore - Hashids" public API class `Hashids`. | +To encode numbers and decode hashes the following public API methods have been implemented: -## 0.0.0 (2016-06-04) - Project Initialization +* `encode(long...) : Hashid` - Encode number(s) +* `encodeToString(long...):String` - Encode number(s) to string. | +* `encodeToString(int...):String` | Encode number(s) to string. | +* `encodeHex(String):String` | Encode an hexadecimal string to string. | +* `decode(String):Hashid` | Decode an encoded string. | +* `decodeLongNumbers(String):long[]` | Decode an encoded string to long numbers. | +* `decodeIntegerNumbers(String):int[]` | Decode an encoded string to integer numbers. | +* `decodeHex(String):String` | Decode an string to hexadecimal numbers. | + +### Documentation + +❯ Added a usage guide for the API. This includes all currently implemented public API methods and general notes to the library algorithm. + +## Tests -[hashids-text-banner]: https://camo.githubusercontent.com/1c71719d94fca1132cd4570f7e54f9aedfb27ba0/687474703a2f2f686173686964732e6f72672f7075626c69632f696d672f686173686964732d6c6f676f2d6e6f726d616c2e706e67 -[hashids-logo]: https://avatars1.githubusercontent.com/u/8481000 +❯ Implemented unit tests for the public- and internal API. + +## Project Initialization + +Date: *2016-06-04* + +[community-profile]: https://github.com/arcticicestudio/icecore-hashids/community +[docs]: https://arcticicestudio.github.io/icecore-hashids +[docs-api]: https://arcticicestudio.github.io/icecore-hashids/api +[docs-api-guide]: https://arcticicestudio.github.io/icecore-hashids/api/guide +[docs-code-of-conduct]: https://arcticicestudio.github.io/icecore-hashids/development/code-of-conduct.html +[docs-contrib-guide]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html +[docs-dev-build]: https://arcticicestudio.github.io/icecore-hashids/development/building.html +[docs-dev-test]: https://arcticicestudio.github.io/icecore-hashids/development/testing.html +[docs-features]: https://arcticicestudio.github.io/icecore-hashids/#features +[docs-get-started]: https://arcticicestudio.github.io/icecore-hashids/getting-started/requirements.html +[hashids-js]: https://github.com/ivanakimov/hashids.js +[issue-template]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/.github/ISSUE_TEMPLATE.md +[javadoc]: https://arcticicestudio.github.io/icecore-hashids/javadoc +[maven-frontend-plugin]: https://github.com/eirslett/frontend-maven-plugin +[nodejs]: https://nodejs.org +[npm]: https://npmjs.com +[open-source-guides]: https://opensource.guide +[pr-template]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/.github/PULL_REQUEST_TEMPLATE.md [sonarqube-eclipse]: https://dev.eclipse.org/sonar [sonarqube-eclipse-rule-squid-s2131]: https://dev.eclipse.org/sonar/rules/show/squid:S2131 [sonarqube-eclipse-rule-squid-s2325]: https://dev.eclipse.org/sonar/rules/show/squid:S232 -[arcver-github]: https://github.com/arcticicestudio/arcver -[semver]: http://semver.org diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..67cce9c --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,59 @@ +# Code of Conduct + +All members, committers and volunteers in this project are required to act according to the following Code of Conduct. We ask you to follow these guidelines which help steer our interactions and strive to keep IceCore Hashids a positive and growing project and help us provide and ensure a safe environment for everyone. + +If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact us via [email][email]. Your reports will be taken seriously and not dismissed or argued with. + +## What we believe in and how we act + +* In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. We welcome people regardless of these or other attributes. +* Our community is based on mutual respect, tolerance, and encouragement. +* We believe that a diverse community where people treat each other with respect is stronger, more vibrant and has more potential contributors and more sources for ideas. We aim for more diversity. +* We are kind, welcoming and courteous to everyone. +* We are respectful of others, their positions, their skills, their commitments and their efforts. +* We are attentive in our communications, whether in person or online, and we are tactful and respectful when approaching differing views and experiences. +* We are aware that language shapes reality. Thus, we use inclusive, gender-neutral language in the documents we provide and when we talk to people. When referring to a group of people, we aim to use gender-neutral terms like "team", "folks" or "everyone". [1][ref-gender-neutral-docs] +* We respect that people have differences of opinion and criticize constructively. + +If you are being harassed, notice that someone else is being harassed, or have any other concerns, please contact us via [email][email]. Your reports will be taken seriously and not dismissed or argued with. + +## Unacceptable Behavior + +* Do not be mean or rude. +* Do not discriminate against anyone. +* Sexism and racism of any kind (including sexist and racist "jokes"), demeaning or insulting behaviour and harassment are seen as direct violations to this Code of Conduct. Harassment includes offensive verbal comments related to age, body size, culture, ethnicity, gender expression, gender identity, level of experience, nationality, personal ability or disability, physical appearance, physical or mental difference, race, religion, set of skills, sexual orientation, socio-economic status, and subculture. Harassment also includes sexual images in public spaces, deliberate intimidation, stalking, following, harassing photography or recording, inappropriate physical contact, and unwelcome sexual attention. +* Respect that some individuals and cultures consider the casual use of profanity offensive and off-putting. +* Derailing, tone arguments and otherwise playing on people's desires to be nice are not welcome, especially in discussions about violations to this Code of Conduct. +* Please avoid unstructured critique. +* Likewise any spamming, trolling, flaming, baiting or other attention-stealing behaviour is not welcome. +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project core team members or owner via [email][email]. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Consequences for Violations + +If a participant engages in any behaviour violating this Code of Conduct, the core team members and project owner of this project may take any action they deem appropriate, including warning the offender or expulsion from the project, exclusion from any interaction and loss of all rights in this project. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +Decisions about consequences of violations of this Code of Conduct are made by this projects core team members and project owner as named above and will not be discussed with the person responsible for the violation. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +--- + +Thanks for the inspirations and attributions to GitHub's [Open Source Guides](https://opensource.guide), [Hoodie's Code of Conduct](http://hood.ie/code-of-conduct) and the [Contributor Covenant Version 1.4](http://contributor-covenant.org/version/1/4/). + +[email]: mailto:development@arcticicestudio.com +[ref-gender-neutral-docs]: https://modelviewculture.com/pieces/gendered-language-feature-or-bug-in-software-documentation diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dcd64c9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,208 @@ +# Contributing to IceCore Hashids +Thanks for contributing to IceCore Hashids! + +This is a set of guidelines for contributing to IceCore Hashids. Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved. + +Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. + +As for everything else in the project, the contributions to IceCore Hashids are governed by our [Code of Conduct][code-of-conduct]. By participating, you are expected to uphold this code. Please report unacceptable behavior via [email][email]. + +## Getting Started + +IceCore Hashids is an open source project and we love to receive contributions from the community! There are many ways to contribute, from [writing- and improving documentation and tutorials](), [reporting bugs](#bug-reports), [submitting enhancement suggestions](#enhancement-suggestions) which can be incorporated into IceCore Hashids itself by [submitting a pull request](#pull-requests). + +The project development workflow and process uses [GitHub Issues][gh-issues]- and [Pull Requests][gh-pr] management to track issues and pull requests. + +Before you continue with this contribution guideslines we highly recommend to read the awesome GitHub [Open Source Guide](https://opensource.guide) on how to [making open source contributions][gh-osguide-contribute]. + +### Bug Reports + +A bug is a *demonstrable problem* that is caused by the code in the repository. This section guides you through submitting a bug report for IceCore Hashids. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior and find related reports. + +**Do NOT report security vulnerabilities in public issues!** Please contact the core team members and the project owner in a responsible manner by [email][email] only. We will assess the issue as soon as possible on a best-effort basis and will give you an estimate for when we have a fix and release available for an eventual public disclosure. + +* **Use the [GitHub Issues search][gh-issues]** — check if the issue has already been reported. If it has **and the issue is still open**, add a comment to the existing issue instead of opening a new one. If you find a closed issue that seems like it is the same thing that you are experiencing, open a new issue and include a link to the original issue in the body of your new one. +* **Check if the issue has been fixed** — try to reproduce it using the [latest version][version-latest] and [`develop`][branch-develop] branch in the repository. +* **Isolate the problem** — ideally create a [MCVE](#mcve). + +When you are creating a bug report, please provide as much detail and context as possible. Fill out [the required template][template-issue], the information it asks for helps maintainers to reproduce the problem and resolve issues faster. + +* **Use a clear and descriptive title** for the issue to identify the problem. +* **Describe the exact steps which reproduce the problem** in as many details as possible. +* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. +* **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets. If you are providing snippets in the issue, use [Markdown code blocks][gh-help-markdown-code-blocks] or [attach files to the issue](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests). + +If possible please provide more context by answering these questions: + +* **Did the problem start happening recently** (e.g. after updating to a new version of IceCore Hashids) or was this always a problem? If the problem started happening recently, **can you reproduce the problem in an older version of IceCore Hashids**? What is the most recent version in which the problem does not happen? +* **Can you reliably reproduce the issue?** If not, provide details about how often the problem happens and under which conditions it normally happens. + +Please include details about your configuration and environment: + +* What is the version of IceCore Hashids you are using? +* What is the name and the version of the OS you're using? + * Have you tried to reproduce it on different OS environments and if yes is the behavior the same for all? +* Which Java JDK/JRE distribution and version are you using? (*Oracle*, *OpenJDK*, ...) +* Are you running the project with your IDE or Maven? + * Are you using any additional CLI arguments for Java or Maven? + * What is the name and the version of the IDE you're using? + +### Enhancement Suggestions + +This section guides you through submitting an enhancement suggestion, including completely new features and minor improvements to existing functionality. Following these guidelines helps maintainers and the community understand your suggestion and find related suggestions. + +* **Use the [GitHub Issues search][gh-issues]** — check if this enhancement has already been suggested. If it has **and the issue is still open**, add your additions as comment to the existing issue instead of opening a new one. +* **Check if the enhancement has already been implemented** — use the [latest version][version-latest] and [`develop`][branch-develop] branch to ensure that the feature or improvement has not already been added. +* **Provide a reduced show case** — ideally create a [MCVE](#mcve). + +Before creating enhancement suggestions, please check if your idea fits with the scope and provide as much detail and context as possible using a structured layout like the [the issue template][template-issue]. + +* **Use a clear and descriptive title** for the issue to identify the suggestion. +* **Provide a step-by-step description of the suggested enhancement** in as many details as possible and provide use-cases. +* **Provide examples to demonstrate the need of an enhancement**. Include copy/pasteable snippets which you use in those examples, use [Markdown code blocks][gh-help-markdown-code-blocks] or [attach files to the issue][gh-help-attach-files]. +* **Describe the current behavior** and **explain which behavior you expected to see instead** and why. +* **Explain why this enhancement would be useful** to most IceCore Hashids users. +* **List some other libraries where this enhancement exists.** + +### Pull Requests + +This section guides you through submitting an pull request. Following these guidelines helps maintainers and the community to better understand your code. + +**Please [suggest an enhancement](#enhancement-suggestions) or [report a bug](#bug-reports) first before embarking on any significant pull request** (e.g. implementing features, refactoring code, fixing a bug), otherwise you risk spending a lot of time working on something that the core team members and project owner might not want to merge into the project. + +When you are submitting an pull request, please provide as much detail and context as possible. Fill out [the required template][template-pr] to help maintainers to understand your submitted code. + +* **Use a clear and descriptive title for the pull request** +* **Do not include issue numbers in the pull request title** but fill in the metadata section at the top of the [required pull request template][template-pr] making use of the [GitHub issue keywords][gh-help-issue-keywords] to link to specific [enhancement suggestions](#enhancement-suggestions) or [bug reports](#bug-reports). +* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the change. +* **Make sure to follow the [Java](#java-code-style) and [Git commit message](#git-commit-messages) style guides**. +* **Remain focused in scope and avoid to include unrelated commits**. +* **Features and improvements should always be accompanied with tests and documentation**. If the pull request improves the performance consider to include a benchmark test, optimally including a chart. +* **Lint and test before submitting the pull request**. +* **Make sure to create the pull request from a [topic branch][git-docs-branching-workflows]**. + +**All pull requests must be send against the `develop` branch** - Please read the [branch organization](#branch-organization) section below for details about the branching model. + +### Documentations + +IceCore Hashids has two main sets of documentation: the docs and guides, which helps users to learn about the project, and the API, which serves as a reference. + +You can help improve the docs and guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, bringing them up to date when there are differences to the latest version. This can be done by submitting a [enhancement suggestion](#enhancement-suggestions) and then opening a [pull request](#pull-requests) for it. + +## Branch Organization + +The IceCore Hashids uses the [gitflow][gitflow] branching model. The repository consists of two core branches with an infinite development lifecycle: + +* `master` - The source code of `HEAD` always reflects a tagged release version. +* `develop` - The default branch where the source code of `HEAD` always reflects a state with the latest development state. + +**All [pull requests](#pull-requests) for the limited development lifecycle *story*/*topic* branches must be send against the `develop` branch**. + +## How else can I help? + +### Improve Issues + +Some issues are created with missing information, not reproducible, or plain invalid. You can help to make it easier for maintainer to understand and resolve them faster. since handling issues takes a lot of time that could rather spend on writing code. + +### Give Feedback On Issues and Pull Requests + +We're always looking for more opinions on discussions in issues and pull request reviews which is a good opportunity to influence the future direction of IceCore Hashids. + +The [question][gh-issues-label-question] issue label is a good place to find ongoing discussions and questions. + +## Styleguides + +Every major open source project has its own style guide, a set of standards and conventions for the writing and design of code, documentations and Git commit messages. It is much easier to understand a large codebase when all the code in it is in a consistent style. + +A style guide establishes and enforces style to improve the intelligibility and communication within the project community. It ensures consistency and enforces best practice in usage and language composition. + +### Java Code Style + +IceCore Hasids adheres to the [Arctic Ice Studio Java Style Guide][styleguide-java-github]. + +[![][styleguide-java-badge]][styleguide-java-github] + +### Git Commit Messages + +A well-crafted Git commit message is the best way to communicate *context* about a change to the maintainers. The code will tell what changed, but only the commit message can properly tell why. Re-establishing the context of a piece of code is wasteful. We can't avoid it completely, so our efforts should go to reducing it as much as possible. + +IceCore Hasids adheres to the [Arctic Ice Studio Git Style Guide][styleguide-git-github]. + +[![][styleguide-git-badge]][styleguide-git-github] + +The style guide assumes that you are familiar with the [gitflow][gitflow] branching model. + +## MCVE + +A Minimal, Complete, and Verifiable Example. + +When [reporting a bug](#bug-reports), somtimes even when [suggestig a enhancement](#enhancement-suggestions), the issue can be processed faster if you provide code for reproduction. That code should be… + +* …Minimal – Use as little code as possible that still produces the same behavior +* …Complete – Provide all parts needed to reproduce the behavior +* …Verifiable – Test the code you're about to provide to make sure it reproduces the behavior + +A MCVE is a common practice like on [Stack Overflow][stackoverflow-mcve] and sometimes it is also called [SSCCE][sscce], a *Short, Self Contained, Correct (Compilable), Example*. + +The recommened way for GitHub based projects is to create it as [Gist](https://gist.github.com) or new repository, but of course you can [attach it to issues and pull requests as files](https://help.github.com/articles/file-attachments-on-issues-and-pull-requests), use any free code paste- or file hosting service or paste the code in [Markdown code blocks][gh-help-markdown-code-blocks] into the issue. + +### Minimal + +The more code there is to go through, the less likely developers can understand your enhancement or find the bug. Streamline your example in one of two ways: + +* **Restart from scratch**. Create new code, adding in only what is needed to demonstrate the behavior and is also useful if you can't post the original code publicly for legal or ethical reasons. +* **Divide and conquer**. When you have a small amount of code, but the source of the bug is entirely unclear, start removing code a bit at a time until the problem disappears – then add the last part back and document this behavior to help developers to trace- and debug faster. + +#### Minimal and readable + +Minimal does not mean terse – don't sacrifice communication to brevity. Use consistent naming and indentation following the [styleguide](#styleguides), and include comments if needed to explain portions of the code. + +### Complete + +Make sure all resources and code necessary to reproduce the behavior is included. The problem might not be in the part you suspect it is, but another part entirely. + +### Verifiable + +To entirely understand your enhancement or bug report, developers will need to verify that it *exists*: + +* **Follow the contribution guidelines regarding the description and details**. Without information developers won't be able to understand and reproduce the behavior. +* **Eliminate any issues that aren't relevant**. Ensure that there are no compile-time errors. +* **Make sure that the example actually reproduces the problem**. Sometimes the bug gets fixed inadvertently or unconsciously while composing the example or does not occur when running on fresh machine environment. + +## Versioning + +IceCore Hashids follows the [Arctic Versioning Specification][arcver] (ArcVer) which is a lightweight and fully compatible derivative of [Semantic Versioning][semver] (SemVer). We release patch versions for bugfixes, minor versions for enhancements like new features and improvements, and major versions for any backwards incompatible changes. Deprecation warnings are introduced for breaking changes in a minor version so that users learn about the upcoming changes and migrate their code in advance. + +Every significant change is documented in the [changelog][changelog]. + +## Credits + +Thanks for the inspirations and attributions to GitHub's [Open Source Guides](https://opensource.guide) and various contribution guides of large open source projects like [Atom][ref-atom-contributing], [React][ref-react-contributing] and [Ruby on Rails][ref-rubyonrails-contributing]. + +[arcver]: https://github.com/arcticicestudio/arcver +[branch-develop]: https://github.com/arcticicestudio/icecore-hashids/tree/develop +[changelog]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/CHANGELOG.md +[code-of-conduct]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/CODE_OF_CONDUCT.md +[email]: mailto:development@arcticicestudio.com +[gh-help-attach-files]: https://help.github.com/articles/file-attachments-on-issues-and-pull-requests +[gh-help-issue-keywords]: https://help.github.com/articles/closing-issues-using-keywords +[gh-help-markdown-code-blocks]: https://help.github.com/articles/basic-writing-and-formatting-syntax +[gh-issues]: https://github.com/arcticicestudio/icecore-hashids/issues +[gh-issues-label-question]: https://github.com/arcticicestudio/icecore-hashids/labels/question +[gh-pr]: https://github.com/arcticicestudio/icecore-hashids/pulls +[gh-osguide-contribute]: https://opensource.guide/how-to-contribute +[git-docs-branching-workflows]: https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows +[gitflow]: http://nvie.com/posts/a-successful-git-branching-model +[ref-atom-contributing]: https://github.com/atom/atom/blob/master/CONTRIBUTING.md +[ref-react-contributing]: https://facebook.github.io/react/contributing/how-to-contribute.html +[ref-rubyonrails-contributing]: http://guides.rubyonrails.org/contributing_to_ruby_on_rails.html +[semver]: http://semver.org +[stackoverflow-mcve]: https://stackoverflow.com/help/mcve +[styleguide-git-badge]: https://rawgit.com/arcticicestudio/styleguide-git/develop/src/assets/styleguide-git-banner-typography-badge.svg +[styleguide-git-github]: https://github.com/arcticicestudio/styleguide-git +[styleguide-java-badge]: https://rawgit.com/arcticicestudio/styleguide-java/develop/src/assets/styleguide-java-banner-typography-badge.svg +[styleguide-java-github]: https://github.com/arcticicestudio/styleguide-java +[sscce]: http://sscce.org +[template-issue]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/.github/ISSUE_TEMPLATE.md +[template-pr]: https://github.com/arcticicestudio/icecore-hashids/blob/develop/.github/PULL_REQUEST_TEMPLATE.md +[version-latest]: https://github.com/arcticicestudio/icecore-hashids/releases/latest diff --git a/LICENSE.md b/LICENSE.md index 59506ed..75823b6 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -168,27 +168,3 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. *END OF TERMS AND CONDITIONS* - -### APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets `[]` replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index 7b51bc6..8c62951 100644 --- a/README.md +++ b/README.md @@ -1,243 +1,91 @@

-

+

-

A lightweight generator for short, unique, non-sequential and decodable Hashids from positive unsigned (long) integer numbers.
-Implementation of the Hashids project.

+

---- +

+ +

A lightweight generator for short, unique, non-sequential and decodable Hashids from non-negative numbers.

-Designed for websites to use in URL shortening, tracking stuff, or making pages private or at least unguessable. -Can be used to create e.g obfuscated database ids, invitation codes, store shard numbers and much more. - -It converts numbers like `347` into strings like `yr8`, or array of numbers like `[27, 986]` into `3kTMd`. -You can also decode those ids back. -This is useful in bundling several parameters into one or simply using them as short UIDs. - -This algorithm tries to satisfy the following requirements: - 1. Hashids must be unique and decodable - 2. Hashids should be able to contain more than one integer to use them in complex or clustered systems - 3. Ability to specify minimum hash length - 4. Hashids should not contain basic English curse words since they are meant to appear in public places like a URL - -**NOTE**: All (long) integers must be greater than or equal to zero. - -## Getting started -### Setup -To use icecore-hashids it must be available on your classpath. -You can use it as a dependency for your favorite build tool or [download the latest version](https://github.com/arcticicestudio/icecore-hashids/releases/latest). - - Apache Maven -```xml - - com.arcticicestudio - icecore-hashids - 0.3.0 - -``` - - Gradle -```java -compile(group: 'com.arcticicestudio', name: 'icecore-hashids', version: '0.3.0') -``` - - Apache Ivy -```xml - -``` - -Development snapshots are available via [OSS Sonatype](https://oss.sonatype.org/content/repositories/snapshots/com/arcticicestudio/icecore-hashids). - -### Build -Build and install icecore-hashids into your local repository without GPG signing: -``` -mvn clean install -``` - -Signed artifacts may be build by using the `sign-gpg` profile with a provided `gpg.keyname` property: -``` -mvn clean install -Dgpg.keyname=YourGPGKeyId -``` - -Continuous integration builds are running at [Travis CI](https://travis-ci.org/arcticicestudio/icecore-hashids) and [Circle CI](https://circleci.com/bb/arcticicestudio/icecore-hashids). - -## Usage Guide -This is a basic guide to show the common usage of the icecore-hashids API. -The API documentation can be found in the JavaDoc. - -The class `Hashids` is the entrypoint to the icecore-hashids API. - - 1. [Encoding a number](#encoding-a-number) - 2. [Decoding](#decoding) - 3. [Encoding several numbers](#encoding-several-numbers) - 4. [Decoding a hash of several numbers](#decoding-a-hash-of-several-numbers) - 5. [Encoding with a specified minimum hash length](#encoding-with-a-specified-minimum-hash-length) - 6. [Specify a custom hash alphabet](#specify-a-custom-hash-alphabet) - 7. [Specify custom separators](#specify-custom-separators) - 8. [Randomness](#randomness) - - [Repeating numbers](#repeating-numbers) - 9. [Bad or offensive hashes](#bad-or-offensive-hashes) - -### Encoding a number -You can pass a unique salt value to create a individual hash: -```java -Hashids hashids = new Hashids("salt"); -String hash = hashids.encodeToString(12345L); -``` -`hash` will be `X4j1`. - -Via the `Builder`: -```java -Hashids hashids = new Hashids.Builder() - .salt("salt") - .build(); -String hash = hashids.encodeToString(12345L); -``` - -Supported number types are `long` and `int`. - -### Decoding -**NOTE**: Make sure to use the same salt during decoding!** -```java -Hashids hashids = new Hashids("salt"); -long[] numbers = hashids.decodeLongNumbers("X4j1"); -``` -`numbers` will be `[ 12345 ]`. - -Via the `Builder`: -```java -Hashids hashids = new Hashids.Builder() - .salt("salt") - .build(); -long[] numbers = hashids.decodeLongNumbers("X4j1"); -``` - -Decoding will not work if salt is changed: -```java -Hashids hashids = new Hashids("salt and pepper"); -long[] numbers = hashids.decodeLongNumbers("X4j1"); -``` -**This will throw a `IllegalArgumentException.class` exception!** - -### Encoding several numbers -```java -Hashids hashids = new Hashids("salt"); -String hash = hashids.encodeToString(683L, 94108L, 123L, 5L); -``` -`hash` will be `1eMToyKzsRAfO`. - -### Decoding a hash of several numbers -```java -Hashids hashids = new Hashids("salt"); -long[] numbers = hashids.decodeLongNumbers("1eMToyKzsRAfO"); -``` -`numbers` will be `[ 683, 94108, 123, 5 ]`. - -### Encoding with a specified minimum hash length -You can set the minimum hash length by passing it to the constructor or the `Builder` method `minHashLength(int)`. -The default value is `0` to generate hashes with the shortest possible length. -```java -Hashids hashids = new Hashids("salt", 8); -String hash = hashids.encodeToString(1L); -``` -`hash` will be `zxkXG8ZW`. - -Via the `Builder`: -```java -Hashids hashids = new Hashids.Builder() - .salt("salt") - .minHashLength(8) - .build(); -String hash = hashids.encodeToString(1L); -``` - -### Specify a custom hash alphabet -You can also set a custom alphabet by passing it to the constructor or the `Builder` method `alphabet(String)`. -```java -Hashids hashids = new Hashids("salt", "0123456789abcdef"); -String hash = hashids.encodeToString(1234567L); -``` -`hash` will be `e884ade`. - -Via the `Builder`: -```java -Hashids hashids = new Hashids.Builder() - .salt("salt") - .alphabet("0123456789abcdef") - .build(); -String hash = hashids.encodeToString(1234567L); -``` - -### Specify custom separators -You can set custom separators by passing it to the constructor or the `Builder` method `separators(String)`. -```java -Hashids hashids = new Hashids("salt", 0, Hashids.DEFAULT_ALPHABET, "aeiosuyzAEIOSUYZ0179"); -String hash = hashids.encodeToString(12345L); -``` -`hash` will be `VFKk`. - -Via the `Builder`: -```java -Hashids hashids = new Hashids.Builder() - .salt("salt") - .separators("aeiosuyzAEIOSUYZ0179") - .build(); -String hash = hashids.encodeToString(12345L); -``` - -Separators try to prevent accidentally generated bad or offensive hashes. -Read the section [9. Bad or offensive hashes](#bad-or-offensive-hashes) for more information. - -### Randomness -The primary purpose of hashids is to obfuscate ids. -This algorithm does try to make these hashes unguessable and unpredictable. -**Note that it is not meant to be used for security purposes or compression!** - -#### Repeating numbers -```java -Hashids hashids = new Hashids("salt"); -String hash = hashids.encodeToString(5L, 5L, 5L, 5L); -``` -`hash` is going to be `YBF7FKFz`. - -You don't see any repeating patterns that might show there's 4 identical numbers in the hash. -Same with incremented numbers: -```java -Hashids hashids = new Hashids("salt"); -String hash = hashids.encodeToString(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L); -``` -`hash` will be `rjiJulUECaFAS1TBhzcX`. - -You can compare this result by encoding every number on its own: -```java -Hashids hashids = new Hashids("this is my salt"); -String hash1 = hashids.encodeToString(1L); /* XG */ -String hash2 = hashids.encodeToString(2L); /* dv */ -String hash3 = hashids.encodeToString(3L); /* w2 */ -String hash4 = hashids.encodeToString(4L); /* nO */ -String hash5 = hashids.encodeToString(5L); /* q2 */ -//... -``` - -### Bad or offensive hashes -This module was written with the intent of placing these hashes in visible places like the URL. -It is possible that the created hash ends up accidentally being a bad or offensice word. - -Therefore, this algorithm tries to avoid generating most common english curse words with the default alphabet. -This is done by never placing the following letters next to each other: -`c, C, s, S, f, F, h, H, u, U, i, I, t, T` +

Implementation of the Hashids algorithm.

--- -## Development -[![](https://img.shields.io/badge/Changelog-0.3.0-blue.svg)](https://github.com/arcticicestudio/icecore-hashids/blob/v0.3.0/CHANGELOG.md) [![](https://img.shields.io/badge/Workflow-gitflow_Branching_Model-blue.svg)](http://nvie.com/posts/a-successful-git-branching-model) [![](https://img.shields.io/badge/Versioning-ArcVer_0.8.0-blue.svg)](https://github.com/arcticicestudio/arcver) +[Hashids][hashids] are obfuscated unique hashes of non-negative (long) integer numbers, but in contrast to cryptographic one-way hash algorithms they are can be decoded again. The algorithm can be used to either generate a hash from a single number or bundling several numbers into one to be stored as simple short UIDs. This design allows to use them for many use-cases like + +* URL shortening +* database ID protection +* shard numbers storage +* invitation-, authorization- and gift codes +* complex- or clustered system parameters + +Numbers like `347` are converted into strings like `yr8`, or an array of numbers like `[27, 986]` into `3kTMd`. + +## Features + +The **algorithm** provides the following features: + +* Generation of short, unique, case-sensitive and non-sequential [decodable][docs-api-guide-decoding] hashes of natural numbers +* Additional entropy through [salt][docs-api-guide-config-salt] usage +* Configurable [minimum hash length][docs-api-guide-config-min-hash-length] and [alphabet][docs-api-guide-config-alphabet] +* Combining of [several numbers to one hash][docs-api-guide-encoding] +* Deterministic hash computation given the same input and parametrization/[instance configuration][docs-api-instances] +* [Prevention of curse words][docs-api-curse-word-prevention] through separator characters + +In addition, the **library** provides features to + +* pass `0x` or `0X` [prefixed hexadecimal numbers][docs-api-guide-config-feature-hex-prefix] to the public API methods +* [handle exceptions][docs-api-guide-config-feature-exception-handling] instead of returning empty values when invalid parameters are passed to any public API method +* [disable the maximum number size limit][docs-api-guide-config-feature-no-max-number-size] which ensures the interoperability with the [algorithm reference implementation][hashids-js] and allows the usage of the Java `Long` [maximum value][long-max-value] + +> Please note that most features will break the interoperability with the [algorithm reference implementation][hashids-js]! + + +## Getting Started + +The [project documentation][docs] contains chapters to learn about the [installation][docs-getting-started-installation] and [requirements][docs-getting-started-requirements], get an [overview][docs-api] of the API and [learn how to use it][docs-api-guide], and [build][docs-dev-building] the project and [running the tests][docs-dev-testing]. + +## Contributing + +Read the [contributing guide][docs-dev-contributing] to learn about the development process and how to propose [enhancement suggestions][docs-dev-contributing-enhancements] and [report bugs][docs-dev-contributing-bug-reports], how to [submit pull requests][docs-dev-contributing-pr] and the project's [styleguides][docs-dev-contributing-styleguides], [branch organization][docs-dev-contributing-branch-org] and [versioning][docs-dev-contributing-versioning] model. -### Contribution -Please report issues/bugs, feature requests and suggestions for improvements to the [issue tracker](https://github.com/arcticicestudio/icecore-hashids/issues). +The guide also includes information about [minimal, complete, and verifiable examples][docs-dev-contributing-mcve] and other ways to contribute to the project like [improving existing issues][docs-dev-contributing-other-improve-issues] and [giving feedback on issues and pull requests][docs-dev-contributing-other-feedback]. ---

Copyright © 2017 Arctic Ice Studio

-

+

+ +[docs]: https://arcticicestudio.github.io/icecore-hashids +[docs-api]: https://arcticicestudio.github.io/icecore-hashids/api +[docs-api-curse-word-prevention]: https://arcticicestudio.github.io/icecore-hashids/api/curse-word-prevention.html +[docs-api-guide]: https://arcticicestudio.github.io/icecore-hashids/api/guide +[docs-api-guide-config-alphabet]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/#determine-a-custom-alphabet +[docs-api-guide-config-feature-exception-handling]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/features.html#exception-handling +[docs-api-guide-config-feature-hex-prefix]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/features.html#allow-hexadecimal-number-prefixes +[docs-api-guide-config-feature-no-max-number-size]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/features.html#no-number-size-limit +[docs-api-guide-config-min-hash-length]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/#defining-a-minimum-hash-length +[docs-api-guide-config-salt]: https://arcticicestudio.github.io/icecore-hashids/api/guide/configuration/#using-a-salt +[docs-api-guide-decoding]: https://arcticicestudio.github.io/icecore-hashids/api/guide/decoding.html +[docs-api-guide-encoding]: https://arcticicestudio.github.io/icecore-hashids/api/guide/encoding.html +[docs-api-instances]: https://arcticicestudio.github.io/icecore-hashids/api/instances.html +[docs-dev-building]: https://arcticicestudio.github.io/icecore-hashids/development/building.html +[docs-dev-contributing]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html +[docs-dev-contributing-branch-org]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#branch-organization +[docs-dev-contributing-bug-reports]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#bug-reports +[docs-dev-contributing-enhancements]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#enhancement-suggestions +[docs-dev-contributing-mcve]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#mcve +[docs-dev-contributing-other-feedback]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#give-feedback-on-issues-and-pull-requests +[docs-dev-contributing-other-improve-issues]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#improve-issues +[docs-dev-contributing-pr]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#pull-requests +[docs-dev-contributing-styleguides]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#styleguides +[docs-dev-contributing-versioning]: https://arcticicestudio.github.io/icecore-hashids/development/contributing.html#versioning +[docs-dev-testing]: https://arcticicestudio.github.io/icecore-hashids/development/testing.html +[docs-getting-started-installation]: https://arcticicestudio.github.io/icecore-hashids/getting-started/installation.html +[docs-getting-started-requirements]: https://arcticicestudio.github.io/icecore-hashids/getting-started/requirements.html +[hashids]: http://hashids.org +[hashids-js]: https://github.com/ivanakimov/hashids.js +[long-max-value]: https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html#MAX_VALUE diff --git a/circle.yml b/circle.yml index dd652e4..537aafd 100644 --- a/circle.yml +++ b/circle.yml @@ -9,7 +9,7 @@ # # [References] # Circle CI -# (https://circleci.com/docs) +# https://circleci.com/docs machine: java: version: oraclejdk8 @@ -17,11 +17,18 @@ machine: general: artifacts: - ./target/*.jar + - ./target/docs + +dependencies: + pre: + - mvn --version test: post: - mkdir -p $CIRCLE_TEST_REPORTS/junit/ - find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; override: - - mvn clean verify assembly:single -P code-coverage,assemble - - find . -type f -regextype posix-egrep -regex ".*/target/(.*asc|.*jar|.*md5|.*pom|.*sha1|.*tar.gz|.*zip)" -exec cp {} $CIRCLE_ARTIFACTS \; + - mvn -B clean verify -P node,code-coverage + - find . -type f -regextype posix-egrep -regex ".*/target/(.*asc|.*jar|.*md5|.*pom|.*sha1)" -exec cp {} $CIRCLE_ARTIFACTS \; + - npm install + - npm run docs:build diff --git a/icecore-hashids.iml b/icecore-hashids.iml index 3360b15..671b4c7 100644 --- a/icecore-hashids.iml +++ b/icecore-hashids.iml @@ -5,15 +5,15 @@ - - + - + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..804d2af --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5507 @@ +{ + "name": "icecore-hashids", + "version": "0.3.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", + "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "async-some": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/async-some/-/async-some-1.0.2.tgz", + "integrity": "sha1-TYqBYg1ZWHkbW5j4AtMgd3bpVQk=", + "dev": true, + "requires": { + "dezalgo": "1.0.3" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bash-color": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/bash-color/-/bash-color-0.0.4.tgz", + "integrity": "sha1-6b6M4zVAytpIgXaMWb1jhlc26RM=", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "builtins": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz", + "integrity": "sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=", + "dev": true + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "2.0.6", + "wrappy": "1.0.2" + } + }, + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "3.0.1", + "universalify": "0.1.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "gitbook-cli": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/gitbook-cli/-/gitbook-cli-2.3.2.tgz", + "integrity": "sha512-eyGtkY7jKHhmgpfuvgAP5fZcUob/FBz4Ld0aLRdEmiTrS1RklimN9epzPp75dd4MWpGhYvSbiwxnpyLiv1wh6A==", + "dev": true, + "requires": { + "bash-color": "0.0.4", + "commander": "2.11.0", + "fs-extra": "3.0.1", + "lodash": "4.17.4", + "npm": "5.1.0", + "npmi": "1.0.1", + "optimist": "0.6.1", + "q": "1.5.0", + "semver": "5.3.0", + "tmp": "0.0.31", + "user-home": "2.0.0" + } + }, + "github-url-from-username-repo": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/github-url-from-username-repo/-/github-url-from-username-repo-1.0.2.tgz", + "integrity": "sha1-fdeTMNKr5pwQws73lxTJchV5Hfo=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "hashids": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.1.tgz", + "integrity": "sha1-PDb8xbO6Gpao+memMut4d8QcbT4=", + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "jju": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", + "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=", + "dev": true + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "requires": { + "jju": "1.3.0" + } + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.1.0" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1" + } + }, + "npm": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-5.1.0.tgz", + "integrity": "sha512-pt5ClxEmY/dLpb60SmGQQBKi3nB6Ljx1FXmpoCUdAULlGqGVn2uCyXxPCWFbcuHGthT7qGiaGa1wOfs/UjGYMw==", + "dev": true, + "requires": { + "JSONStream": "1.3.1", + "abbrev": "1.1.0", + "ansi-regex": "3.0.0", + "ansicolors": "0.3.2", + "ansistyles": "0.1.3", + "aproba": "1.1.2", + "archy": "1.0.0", + "bluebird": "3.5.0", + "cacache": "9.2.9", + "call-limit": "1.1.0", + "chownr": "1.0.1", + "cmd-shim": "2.0.2", + "columnify": "1.5.4", + "config-chain": "1.1.11", + "debuglog": "1.0.1", + "detect-indent": "5.0.0", + "dezalgo": "1.0.3", + "editor": "1.0.0", + "fs-vacuum": "1.2.10", + "fs-write-stream-atomic": "1.0.10", + "fstream": "1.0.11", + "fstream-npm": "1.2.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "has-unicode": "2.0.1", + "hosted-git-info": "2.5.0", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "inflight": "1.0.6", + "inherits": "2.0.3", + "ini": "1.3.4", + "init-package-json": "1.10.1", + "lazy-property": "1.0.0", + "lockfile": "1.0.3", + "lodash._baseindexof": "3.1.0", + "lodash._baseuniq": "4.6.0", + "lodash._bindcallback": "3.0.1", + "lodash._cacheindexof": "3.0.2", + "lodash._createcache": "3.1.2", + "lodash._getnative": "3.9.1", + "lodash.clonedeep": "4.5.0", + "lodash.restparam": "3.6.1", + "lodash.union": "4.6.0", + "lodash.uniq": "4.5.0", + "lodash.without": "4.4.0", + "lru-cache": "4.1.1", + "mississippi": "1.3.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "node-gyp": "3.6.2", + "nopt": "4.0.1", + "normalize-package-data": "2.4.0", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "3.0.0", + "npm-package-arg": "5.1.2", + "npm-registry-client": "8.4.0", + "npm-user-validate": "1.0.0", + "npmlog": "4.1.2", + "once": "1.4.0", + "opener": "1.4.3", + "osenv": "0.1.4", + "pacote": "2.7.38", + "path-is-inside": "1.0.2", + "promise-inflight": "1.0.1", + "read": "1.0.7", + "read-cmd-shim": "1.0.1", + "read-installed": "4.0.3", + "read-package-json": "2.0.9", + "read-package-tree": "5.1.6", + "readable-stream": "2.3.2", + "readdir-scoped-modules": "1.0.2", + "request": "2.81.0", + "retry": "0.10.1", + "rimraf": "2.6.1", + "safe-buffer": "5.1.1", + "semver": "5.3.0", + "sha": "2.0.1", + "slide": "1.1.6", + "sorted-object": "2.0.1", + "sorted-union-stream": "2.1.3", + "ssri": "4.1.6", + "strip-ansi": "4.0.0", + "tar": "2.2.1", + "text-table": "0.2.0", + "uid-number": "0.0.6", + "umask": "1.1.0", + "unique-filename": "1.1.0", + "unpipe": "1.0.0", + "update-notifier": "2.2.0", + "uuid": "3.1.0", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-name": "3.0.0", + "which": "1.2.14", + "worker-farm": "1.3.1", + "wrappy": "1.0.2", + "write-file-atomic": "2.1.0" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true, + "requires": { + "jsonparse": "1.3.1", + "through": "2.3.8" + }, + "dependencies": { + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + } + } + }, + "abbrev": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", + "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/ansistyles/-/ansistyles-0.1.3.tgz", + "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", + "dev": true + }, + "aproba": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", + "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==", + "dev": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "bluebird": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", + "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=", + "dev": true + }, + "cacache": { + "version": "9.2.9", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-9.2.9.tgz", + "integrity": "sha512-ghg1j5OyTJ6qsrqU++dN23QiTDxb5AZCFGsF3oB+v9v/gY+F4X8L/0gdQMEjd+8Ot3D29M2etX5PKozHRn2JQw==", + "dev": true, + "requires": { + "bluebird": "3.5.0", + "chownr": "1.0.1", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "lru-cache": "4.1.1", + "mississippi": "1.3.0", + "mkdirp": "0.5.1", + "move-concurrently": "1.0.1", + "promise-inflight": "1.0.1", + "rimraf": "2.6.1", + "ssri": "4.1.6", + "unique-filename": "1.1.0", + "y18n": "3.2.1" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + } + } + }, + "call-limit": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/call-limit/-/call-limit-1.1.0.tgz", + "integrity": "sha1-b9YbA/PaQqLNDsK2DwK9DnGZH+o=", + "dev": true + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true + }, + "cmd-shim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", + "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "mkdirp": "0.5.1" + } + }, + "columnify": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "dev": true, + "requires": { + "strip-ansi": "3.0.1", + "wcwidth": "1.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "1.0.3" + }, + "dependencies": { + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", + "dev": true + } + } + } + } + } + } + }, + "config-chain": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", + "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "dev": true, + "requires": { + "ini": "1.3.4", + "proto-list": "1.2.4" + }, + "dependencies": { + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "2.0.5", + "wrappy": "1.0.2" + }, + "dependencies": { + "asap": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", + "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=", + "dev": true + } + } + }, + "editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", + "dev": true + }, + "fs-vacuum": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.10.tgz", + "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "path-is-inside": "1.0.2", + "rimraf": "2.6.1" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.3.2" + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-npm": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fstream-npm/-/fstream-npm-1.2.1.tgz", + "integrity": "sha512-iBHpm/LmD1qw0TlHMAqVd9rwdU6M+EHRUnPkXpRi5G/Hf0FIFH+oZFryodAU2MFNfGRh/CzhUFlMKV3pdeOTDw==", + "dev": true, + "requires": { + "fstream-ignore": "1.0.5", + "inherits": "2.0.3" + }, + "dependencies": { + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + } + } + } + } + } + } + } + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + } + } + } + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "init-package-json": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.1.tgz", + "integrity": "sha1-zYc6FneWvvuZYSsodioLY5P9j2o=", + "dev": true, + "requires": { + "glob": "7.1.2", + "npm-package-arg": "5.1.2", + "promzard": "0.3.0", + "read": "1.0.7", + "read-package-json": "2.0.9", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-name": "3.0.0" + }, + "dependencies": { + "promzard": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "dev": true, + "requires": { + "read": "1.0.7" + } + } + } + }, + "lazy-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazy-property/-/lazy-property-1.0.0.tgz", + "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", + "dev": true + }, + "lockfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.3.tgz", + "integrity": "sha1-Jjj8OaAzHpysGgS3F5mTHJxQ33k=", + "dev": true + }, + "lodash._baseindexof": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "dev": true, + "requires": { + "lodash._createset": "4.0.3", + "lodash._root": "3.0.1" + }, + "dependencies": { + "lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + } + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz", + "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "mississippi": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-1.3.0.tgz", + "integrity": "sha1-0gFYPrEjJ+PFwWQqQEqcrPlONPU=", + "dev": true, + "requires": { + "concat-stream": "1.6.0", + "duplexify": "3.5.0", + "end-of-stream": "1.4.0", + "flush-write-stream": "1.0.2", + "from2": "2.3.0", + "parallel-transform": "1.1.0", + "pump": "1.0.2", + "pumpify": "1.3.5", + "stream-each": "1.2.0", + "through2": "2.0.3" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.2", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + } + } + }, + "duplexify": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.0.tgz", + "integrity": "sha1-GqdzAC4VeEV+nZ1KULDMquvL1gQ=", + "dev": true, + "requires": { + "end-of-stream": "1.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.2", + "stream-shift": "1.0.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.0.0.tgz", + "integrity": "sha1-1FlucCc0qT5A6a+GQxnqvZn/Lw4=", + "dev": true, + "requires": { + "once": "1.3.3" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + } + } + }, + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "flush-write-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.2.tgz", + "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.2" + } + }, + "parallel-transform": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", + "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "dev": true, + "requires": { + "cyclist": "0.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.2" + }, + "dependencies": { + "cyclist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", + "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "dev": true + } + } + }, + "pump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", + "integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "once": "1.4.0" + } + }, + "pumpify": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.3.5.tgz", + "integrity": "sha1-G2ccYZlAq8rqwK0OOjwWS+dgmTs=", + "dev": true, + "requires": { + "duplexify": "3.5.0", + "inherits": "2.0.3", + "pump": "1.0.2" + } + }, + "stream-each": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.0.tgz", + "integrity": "sha1-HpXUdXP1gNgU3A/4zQ9m8c5TyZE=", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + } + } + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.2", + "xtend": "4.0.1" + }, + "dependencies": { + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "1.1.2", + "copy-concurrently": "1.0.3", + "fs-write-stream-atomic": "1.0.10", + "mkdirp": "0.5.1", + "rimraf": "2.6.1", + "run-queue": "1.0.3" + }, + "dependencies": { + "copy-concurrently": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.3.tgz", + "integrity": "sha1-Rft4ZiSaHKiJqlcI5svSc+dbslA=", + "dev": true, + "requires": { + "aproba": "1.1.2", + "fs-write-stream-atomic": "1.0.10", + "iferr": "0.1.5", + "mkdirp": "0.5.1", + "rimraf": "2.6.1", + "run-queue": "1.0.3" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "1.1.2" + } + } + } + }, + "node-gyp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.2.tgz", + "integrity": "sha1-m/vlRWIoYoSDjnUOrAUpWFP6HGA=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "4.1.2", + "osenv": "0.1.4", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.2.14" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + } + } + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.1.0" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.3.0", + "validate-npm-package-license": "3.0.1" + }, + "dependencies": { + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + } + } + } + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", + "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", + "dev": true + }, + "npm-install-checks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-3.0.0.tgz", + "integrity": "sha1-1K7N/VGlPjcjt7L5Oy7ijjB7wNc=", + "dev": true, + "requires": { + "semver": "5.3.0" + } + }, + "npm-package-arg": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-5.1.2.tgz", + "integrity": "sha512-wJBsrf0qpypPT7A0LART18hCdyhpCMxeTtcb0X4IZO2jsP6Om7EHN1d9KSKiqD+KVH030RVNpWS9thk+pb7wzA==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "osenv": "0.1.4", + "semver": "5.3.0", + "validate-npm-package-name": "3.0.0" + } + }, + "npm-registry-client": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.4.0.tgz", + "integrity": "sha512-PVNfqq0lyRdFnE//nDmn3CC9uqTsr8Bya9KPLIevlXMfkP0m4RpCVyFFk0W1Gfx436kKwyhLA6J+lV+rgR81gQ==", + "dev": true, + "requires": { + "concat-stream": "1.6.0", + "graceful-fs": "4.1.11", + "normalize-package-data": "2.4.0", + "npm-package-arg": "5.1.2", + "npmlog": "4.1.2", + "once": "1.4.0", + "request": "2.81.0", + "retry": "0.10.1", + "semver": "5.3.0", + "slide": "1.1.6", + "ssri": "4.1.6" + }, + "dependencies": { + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.2", + "typedarray": "0.0.6" + }, + "dependencies": { + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + } + } + } + } + }, + "npm-user-validate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.0.tgz", + "integrity": "sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=", + "dev": true + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + }, + "dependencies": { + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "dev": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.2" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + } + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "1.1.2", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + } + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "dev": true, + "requires": { + "string-width": "1.0.2" + } + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", + "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "dev": true + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "dev": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + }, + "dependencies": { + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + } + } + }, + "pacote": { + "version": "2.7.38", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-2.7.38.tgz", + "integrity": "sha512-XxHUyHQB7QCVBxoXeVu0yKxT+2PvJucsc0+1E+6f95lMUxEAYERgSAc71ckYXrYr35Ew3xFU/LrhdIK21GQFFA==", + "dev": true, + "requires": { + "bluebird": "3.5.0", + "cacache": "9.2.9", + "glob": "7.1.2", + "lru-cache": "4.1.1", + "make-fetch-happen": "2.4.13", + "minimatch": "3.0.4", + "mississippi": "1.3.0", + "normalize-package-data": "2.4.0", + "npm-package-arg": "5.1.2", + "npm-pick-manifest": "1.0.4", + "osenv": "0.1.4", + "promise-inflight": "1.0.1", + "promise-retry": "1.1.1", + "protoduck": "4.0.0", + "safe-buffer": "5.1.1", + "semver": "5.3.0", + "ssri": "4.1.6", + "tar-fs": "1.15.3", + "tar-stream": "1.5.4", + "unique-filename": "1.1.0", + "which": "1.2.14" + }, + "dependencies": { + "make-fetch-happen": { + "version": "2.4.13", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-2.4.13.tgz", + "integrity": "sha512-73CsTlMRSLdGr7VvOE8iYl/ejOSIxyfRYg7jZhepGGEqIlgdq6FLe2DEAI5bo813Jdg5fS/Ku62SRQ/UpT6NJA==", + "dev": true, + "requires": { + "agentkeepalive": "3.3.0", + "cacache": "9.2.9", + "http-cache-semantics": "3.7.3", + "http-proxy-agent": "2.0.0", + "https-proxy-agent": "2.0.0", + "lru-cache": "4.1.1", + "mississippi": "1.3.0", + "node-fetch-npm": "2.0.1", + "promise-retry": "1.1.1", + "socks-proxy-agent": "3.0.0", + "ssri": "4.1.6" + }, + "dependencies": { + "agentkeepalive": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.3.0.tgz", + "integrity": "sha512-9yhcpXti2ZQE7bxuCsjjWNIZoQOd9sZ1ZBovHG0YeCRohFv73SLvcm73PC9T3olM4GyozaQb+4MGdQpcD8m7NQ==", + "dev": true, + "requires": { + "humanize-ms": "1.2.1" + }, + "dependencies": { + "humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "http-cache-semantics": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.7.3.tgz", + "integrity": "sha1-LzXFMuzSnx5UE7mvgztySjxvf3I=", + "dev": true + }, + "http-proxy-agent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.0.0.tgz", + "integrity": "sha1-RkgqLwUjpNYIJVFwn0acs+SoX/Q=", + "dev": true, + "requires": { + "agent-base": "4.1.0", + "debug": "2.6.8" + }, + "dependencies": { + "agent-base": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.1.0.tgz", + "integrity": "sha1-IOF0Ac1Js8B2v1akvGxbQ2/6jVU=", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.1.1" + }, + "dependencies": { + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "dev": true + } + } + } + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "https-proxy-agent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.0.0.tgz", + "integrity": "sha1-/6pLb69YasNAwYoUBDHna31/KUQ=", + "dev": true, + "requires": { + "agent-base": "4.1.0", + "debug": "2.6.8" + }, + "dependencies": { + "agent-base": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.1.0.tgz", + "integrity": "sha1-IOF0Ac1Js8B2v1akvGxbQ2/6jVU=", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.1.1" + }, + "dependencies": { + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "dev": true + } + } + } + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + } + } + }, + "node-fetch-npm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.1.tgz", + "integrity": "sha512-W3onhopST5tqpX0/MGSL47pDQLLKobNR83AvkiOWQKaw54h+uYUfzeLAxCiyhWlUOiuI+GIb4O9ojLaAFlhCCA==", + "dev": true, + "requires": { + "encoding": "0.1.12", + "json-parse-helpfulerror": "1.0.3", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "dev": true, + "requires": { + "iconv-lite": "0.4.18" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", + "dev": true + } + } + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "requires": { + "jju": "1.3.0" + }, + "dependencies": { + "jju": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", + "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=", + "dev": true + } + } + } + } + }, + "socks-proxy-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.0.tgz", + "integrity": "sha512-YJcT+SNNBgFoK/NpO20PChz0VnBOhkjG3X10BwlrYujd0NZlSsH1jbxSQ1S0njt3sOvzwQ2PvGqqUIvP4rNk/w==", + "dev": true, + "requires": { + "agent-base": "4.1.0", + "socks": "1.1.10" + }, + "dependencies": { + "agent-base": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.1.0.tgz", + "integrity": "sha1-IOF0Ac1Js8B2v1akvGxbQ2/6jVU=", + "dev": true, + "requires": { + "es6-promisify": "5.0.0" + }, + "dependencies": { + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "dev": true, + "requires": { + "es6-promise": "4.1.1" + }, + "dependencies": { + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "dev": true + } + } + } + } + }, + "socks": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", + "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "1.1.15" + }, + "dependencies": { + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "smart-buffer": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", + "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", + "dev": true + } + } + } + } + } + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + } + } + } + } + }, + "npm-pick-manifest": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-1.0.4.tgz", + "integrity": "sha512-MKxNdeyOZysPRTTbHtW0M5Fw38Jo/3ARsoGw5qjCfS+XGjvNB/Gb4qtAZUFmKPM2mVum+eX559eHvKywU856BQ==", + "dev": true, + "requires": { + "npm-package-arg": "5.1.2", + "semver": "5.3.0" + } + }, + "promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "dev": true, + "requires": { + "err-code": "1.1.2", + "retry": "0.10.1" + }, + "dependencies": { + "err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "dev": true + } + } + }, + "protoduck": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-4.0.0.tgz", + "integrity": "sha1-/kh02MeRM2bP2erRJFOiLNNlf44=", + "dev": true, + "requires": { + "genfun": "4.0.1" + }, + "dependencies": { + "genfun": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/genfun/-/genfun-4.0.1.tgz", + "integrity": "sha1-7RAEHy5KfxsKOEZtF6XD4n3x38E=", + "dev": true + } + } + }, + "tar-fs": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.15.3.tgz", + "integrity": "sha1-7M+TXpQUk9gVECjmNuUc5MPKfyA=", + "dev": true, + "requires": { + "chownr": "1.0.1", + "mkdirp": "0.5.1", + "pump": "1.0.2", + "tar-stream": "1.5.4" + }, + "dependencies": { + "pump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", + "integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "once": "1.4.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + } + } + } + } + }, + "tar-stream": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz", + "integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=", + "dev": true, + "requires": { + "bl": "1.2.1", + "end-of-stream": "1.4.0", + "readable-stream": "2.3.2", + "xtend": "4.0.1" + }, + "dependencies": { + "bl": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", + "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "dev": true, + "requires": { + "readable-stream": "2.3.2" + } + }, + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + } + } + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "0.0.7" + }, + "dependencies": { + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + } + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz", + "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", + "dev": true, + "requires": { + "debuglog": "1.0.1", + "graceful-fs": "4.1.11", + "read-package-json": "2.0.9", + "readdir-scoped-modules": "1.0.2", + "semver": "5.3.0", + "slide": "1.1.6", + "util-extend": "1.0.3" + }, + "dependencies": { + "util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "dev": true + } + } + }, + "read-package-json": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.9.tgz", + "integrity": "sha512-vuV8p921IgyelL4UOKv3FsRuRZSaRn30HanLAOKargsr8TbBEq+I3MgloSRXYuKhNdYP1wlEGilMWAIayA2RFg==", + "dev": true, + "requires": { + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "json-parse-helpfulerror": "1.0.3", + "normalize-package-data": "2.4.0" + }, + "dependencies": { + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "requires": { + "jju": "1.3.0" + }, + "dependencies": { + "jju": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", + "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=", + "dev": true + } + } + } + } + }, + "read-package-tree": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.1.6.tgz", + "integrity": "sha512-FCX1aT3GWyY658wzDICef4p+n0dB+ENRct8E/Qyvppj6xVpOYerBHfUu7OP5Rt1/393Tdglguf5ju5DEX4wZNg==", + "dev": true, + "requires": { + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "once": "1.4.0", + "read-package-json": "2.0.9", + "readdir-scoped-modules": "1.0.2" + } + }, + "readable-stream": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.2.tgz", + "integrity": "sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", + "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", + "dev": true, + "requires": { + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "graceful-fs": "4.1.11", + "once": "1.4.0" + } + }, + "request": { + "version": "2.81.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.1.0" + }, + "dependencies": { + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "dev": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + }, + "dependencies": { + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + } + } + }, + "har-validator": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", + "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + }, + "dependencies": { + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + }, + "dependencies": { + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + } + } + } + } + }, + "har-schema": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", + "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", + "dev": true + } + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + }, + "dependencies": { + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + } + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.1" + }, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "jsprim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", + "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "requires": { + "extsprintf": "1.0.2" + } + } + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", + "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", + "dev": true, + "requires": { + "mime-db": "1.27.0" + }, + "dependencies": { + "mime-db": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", + "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", + "dev": true + } + } + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", + "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", + "dev": true + }, + "qs": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", + "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", + "dev": true, + "requires": { + "punycode": "1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "dev": true + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "sha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sha/-/sha-2.0.1.tgz", + "integrity": "sha1-YDCCL70smCOUn49y7WQR7lzyWq4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "readable-stream": "2.3.2" + } + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "sorted-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.1.tgz", + "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz", + "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", + "dev": true, + "requires": { + "from2": "1.3.0", + "stream-iterate": "1.2.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-1.3.0.tgz", + "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "1.1.14" + }, + "dependencies": { + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + } + } + }, + "stream-iterate": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/stream-iterate/-/stream-iterate-1.2.0.tgz", + "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", + "dev": true, + "requires": { + "readable-stream": "2.3.2", + "stream-shift": "1.0.0" + }, + "dependencies": { + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + } + } + } + } + }, + "ssri": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-4.1.6.tgz", + "integrity": "sha512-WUbCdgSAMQjTFZRWvSPpauryvREEA+Krn19rx67UlJEJx/M192ZHxMmJXjZ4tkdFm+Sb0SXGlENeQVlA5wY7kA==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + }, + "dependencies": { + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true + }, + "umask": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", + "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", + "dev": true + }, + "unique-filename": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", + "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", + "dev": true, + "requires": { + "unique-slug": "2.0.0" + }, + "dependencies": { + "unique-slug": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", + "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "dev": true, + "requires": { + "imurmurhash": "0.1.4" + } + } + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "update-notifier": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.2.0.tgz", + "integrity": "sha1-G1g3z5DAc22IYncytmHBOPht5y8=", + "dev": true, + "requires": { + "boxen": "1.1.0", + "chalk": "1.1.3", + "configstore": "3.1.0", + "import-lazy": "2.1.0", + "is-npm": "1.0.0", + "latest-version": "3.1.0", + "semver-diff": "2.1.0", + "xdg-basedir": "3.0.0" + }, + "dependencies": { + "boxen": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.1.0.tgz", + "integrity": "sha1-sbad1SIwXoB6md7ud329blFnsQI=", + "dev": true, + "requires": { + "ansi-align": "2.0.0", + "camelcase": "4.1.0", + "chalk": "1.1.3", + "cli-boxes": "1.0.0", + "string-width": "2.1.0", + "term-size": "0.1.1", + "widest-line": "1.0.0" + }, + "dependencies": { + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "2.1.0" + } + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, + "string-width": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.0.tgz", + "integrity": "sha1-AwZkVh/BRslCPsfZeP4kV0N/5tA=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "term-size": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-0.1.1.tgz", + "integrity": "sha1-hzYLljlsq1dgljcUzaDQy+7K2co=", + "dev": true, + "requires": { + "execa": "0.4.0" + }, + "dependencies": { + "execa": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz", + "integrity": "sha1-TrZGejaglfq7KXD/nV4/t7zm68M=", + "dev": true, + "requires": { + "cross-spawn-async": "2.2.5", + "is-stream": "1.1.0", + "npm-run-path": "1.0.0", + "object-assign": "4.1.1", + "path-key": "1.0.0", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn-async": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz", + "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "which": "1.2.14" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "npm-run-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", + "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", + "dev": true, + "requires": { + "path-key": "1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "path-key": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + } + } + } + } + }, + "widest-line": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-1.0.0.tgz", + "integrity": "sha1-DAnIXCqUaD0Nfq+O4JfVZL8OEFw=", + "dev": true, + "requires": { + "string-width": "1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + }, + "dependencies": { + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + } + } + } + } + } + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "configstore": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.0.tgz", + "integrity": "sha1-Rd+QcHPibfoc9LLVL1tgVF6qEdE=", + "dev": true, + "requires": { + "dot-prop": "4.1.1", + "graceful-fs": "4.1.11", + "make-dir": "1.0.0", + "unique-string": "1.0.0", + "write-file-atomic": "2.1.0", + "xdg-basedir": "3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.1.1.tgz", + "integrity": "sha1-qEk/C3te7sglJbXHWH+n3nyoWcE=", + "dev": true, + "requires": { + "is-obj": "1.0.1" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + } + } + }, + "make-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.0.0.tgz", + "integrity": "sha1-l6ARdR6R3YfPre9Ygy67BJNt6Xg=", + "dev": true, + "requires": { + "pify": "2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "1.0.0" + }, + "dependencies": { + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + } + } + } + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "4.0.1" + }, + "dependencies": { + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "6.7.1", + "registry-auth-token": "3.3.1", + "registry-url": "3.1.0", + "semver": "5.3.0" + }, + "dependencies": { + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "3.0.2", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "is-redirect": "1.0.0", + "is-retry-allowed": "1.1.0", + "is-stream": "1.1.0", + "lowercase-keys": "1.0.0", + "safe-buffer": "5.1.1", + "timed-out": "4.0.1", + "unzip-response": "2.0.1", + "url-parse-lax": "1.0.0" + }, + "dependencies": { + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "1.0.0" + }, + "dependencies": { + "capture-stack-trace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", + "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "dev": true + } + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "dev": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "1.0.4" + }, + "dependencies": { + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + } + } + } + } + }, + "registry-auth-token": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.1.tgz", + "integrity": "sha1-+w0yie4Nmtosu1KvXf5mywcNMAY=", + "dev": true, + "requires": { + "rc": "1.2.1", + "safe-buffer": "5.1.1" + }, + "dependencies": { + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + } + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "1.2.1" + }, + "dependencies": { + "rc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", + "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", + "dev": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + } + } + } + } + } + } + }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "5.3.0" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + } + } + }, + "uuid": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", + "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + }, + "dependencies": { + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + }, + "dependencies": { + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + } + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + } + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", + "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "dev": true, + "requires": { + "builtins": "1.0.3" + }, + "dependencies": { + "builtins": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", + "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "dev": true + } + } + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "2.0.0" + }, + "dependencies": { + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + } + } + }, + "worker-farm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.3.1.tgz", + "integrity": "sha1-QzMRK7SbF6oFC4eJXKayys9A5f8=", + "dev": true, + "requires": { + "errno": "0.1.4", + "xtend": "4.0.1" + }, + "dependencies": { + "errno": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz", + "integrity": "sha1-uJbiOp5ei6M4cfyZar02NfyaHH0=", + "dev": true, + "requires": { + "prr": "0.0.0" + }, + "dependencies": { + "prr": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/prr/-/prr-0.0.0.tgz", + "integrity": "sha1-GoS4WQgyVQFBGFPQCB7j+obikmo=", + "dev": true + } + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.1.0.tgz", + "integrity": "sha512-0TZ20a+xcIl4u0+Mj5xDH2yOWdmQiXlKf9Hm+TgDXjTMsEYb+gDrmb8e8UNAzMCitX8NBqG4Z/FUQIyzv/R1JQ==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + } + } + }, + "npmi": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npmi/-/npmi-1.0.1.tgz", + "integrity": "sha1-FddpJzVHVF5oCdzwzhiu1IsCkOI=", + "dev": true, + "requires": { + "npm": "2.15.12", + "semver": "4.3.6" + }, + "dependencies": { + "npm": { + "version": "2.15.12", + "resolved": "https://registry.npmjs.org/npm/-/npm-2.15.12.tgz", + "integrity": "sha1-33w+1aJ3w/nUtdgZsFMR0QogCuY=", + "dev": true, + "requires": { + "abbrev": "1.0.9", + "ansi": "0.3.1", + "ansi-regex": "2.0.0", + "ansicolors": "0.3.2", + "ansistyles": "0.1.3", + "archy": "1.0.0", + "async-some": "1.0.2", + "block-stream": "0.0.9", + "char-spinner": "1.0.1", + "chmodr": "1.0.2", + "chownr": "1.0.1", + "cmd-shim": "2.0.2", + "columnify": "1.5.4", + "config-chain": "1.1.10", + "dezalgo": "1.0.3", + "editor": "1.0.0", + "fs-vacuum": "1.2.9", + "fs-write-stream-atomic": "1.0.8", + "fstream": "1.0.11", + "fstream-npm": "1.1.1", + "github-url-from-git": "1.4.0", + "github-url-from-username-repo": "1.0.2", + "glob": "7.0.6", + "graceful-fs": "4.1.6", + "hosted-git-info": "2.1.5", + "imurmurhash": "0.1.4", + "inflight": "1.0.5", + "inherits": "2.0.3", + "ini": "1.3.4", + "init-package-json": "1.9.4", + "lockfile": "1.0.1", + "lru-cache": "4.0.1", + "minimatch": "3.0.3", + "mkdirp": "0.5.1", + "node-gyp": "3.6.0", + "nopt": "3.0.6", + "normalize-git-url": "3.0.2", + "normalize-package-data": "2.3.5", + "npm-cache-filename": "1.0.2", + "npm-install-checks": "1.0.7", + "npm-package-arg": "4.1.0", + "npm-registry-client": "7.2.1", + "npm-user-validate": "0.1.5", + "npmlog": "2.0.4", + "once": "1.4.0", + "opener": "1.4.1", + "osenv": "0.1.3", + "path-is-inside": "1.0.2", + "read": "1.0.7", + "read-installed": "4.0.3", + "read-package-json": "2.0.4", + "readable-stream": "2.1.5", + "realize-package-specifier": "3.0.1", + "request": "2.74.0", + "retry": "0.10.0", + "rimraf": "2.5.4", + "semver": "5.1.0", + "sha": "2.0.1", + "slide": "1.1.6", + "sorted-object": "2.0.0", + "spdx-license-ids": "1.2.2", + "strip-ansi": "3.0.1", + "tar": "2.2.1", + "text-table": "0.2.0", + "uid-number": "0.0.6", + "umask": "1.1.0", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-name": "2.2.2", + "which": "1.2.11", + "wrappy": "1.0.2", + "write-file-atomic": "1.1.4" + }, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "ansi": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.1.tgz", + "integrity": "sha1-DELU+xcWDVqa8eSEus4cZpIsGyE=", + "dev": true + }, + "ansi-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", + "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=", + "dev": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "char-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/char-spinner/-/char-spinner-1.0.1.tgz", + "integrity": "sha1-5upnvSR+EHESmDt6sEee02KAAIE=", + "dev": true + }, + "chmodr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chmodr/-/chmodr-1.0.2.tgz", + "integrity": "sha1-BGYrky0PAuxm3qorDqQoEZaOPrk=", + "dev": true + }, + "chownr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "dev": true + }, + "cmd-shim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", + "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", + "dev": true, + "requires": { + "graceful-fs": "4.1.6", + "mkdirp": "0.5.1" + } + }, + "columnify": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", + "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "dev": true, + "requires": { + "strip-ansi": "3.0.1", + "wcwidth": "1.0.0" + }, + "dependencies": { + "wcwidth": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.0.tgz", + "integrity": "sha1-AtBZ/3qPx0Hg9rXaHmmytA2uym8=", + "dev": true, + "requires": { + "defaults": "1.0.3" + }, + "dependencies": { + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", + "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", + "dev": true + } + } + } + } + } + } + }, + "config-chain": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.10.tgz", + "integrity": "sha1-f8OD3g/MhNcRy0Zb0XZXnK1hI0Y=", + "dev": true, + "requires": { + "ini": "1.3.4", + "proto-list": "1.2.4" + }, + "dependencies": { + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + } + } + }, + "editor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/editor/-/editor-1.0.0.tgz", + "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", + "dev": true + }, + "fs-vacuum": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fs-vacuum/-/fs-vacuum-1.2.9.tgz", + "integrity": "sha1-T5AZOrjqAokJlbzU6ARlml02ay0=", + "dev": true, + "requires": { + "graceful-fs": "4.1.6", + "path-is-inside": "1.0.2", + "rimraf": "2.5.4" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.8.tgz", + "integrity": "sha1-5Jqt3yiPh9Rv+eiC8hahOrxAd4s=", + "dev": true, + "requires": { + "graceful-fs": "4.1.6", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "2.1.5" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + } + } + }, + "fstream-npm": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fstream-npm/-/fstream-npm-1.1.1.tgz", + "integrity": "sha1-a5F122I5qD2CCeIyQmxJTbspaQw=", + "dev": true, + "requires": { + "fstream-ignore": "1.0.5", + "inherits": "2.0.3" + }, + "dependencies": { + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.3" + } + } + } + }, + "github-url-from-git": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.4.0.tgz", + "integrity": "sha1-KF5rUggZABveEoZ0cEN55P8D4N4=", + "dev": true + }, + "glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.5", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.0" + }, + "dependencies": { + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha1-UUw4dysxvuLgi+3CGgrrOr9UwZ4=", + "dev": true + }, + "hosted-git-info": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.5.tgz", + "integrity": "sha1-C6gdkNouJas0ozLm7HeTbhWYEYs=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz", + "integrity": "sha1-2zIEzVqd4ubNiQuFxuL2a89PYgo=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "init-package-json": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.9.4.tgz", + "integrity": "sha1-tAU9C0Dwz4QqQZZpN8s9wPU06FY=", + "dev": true, + "requires": { + "glob": "6.0.4", + "npm-package-arg": "4.1.0", + "promzard": "0.3.0", + "read": "1.0.7", + "read-package-json": "2.0.4", + "semver": "5.1.0", + "validate-npm-package-license": "3.0.1", + "validate-npm-package-name": "2.2.2" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.5", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.0" + }, + "dependencies": { + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", + "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "dev": true, + "requires": { + "read": "1.0.7" + } + } + } + }, + "lockfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.1.tgz", + "integrity": "sha1-nTU+z+P1TRULtX+J1RdGk1o5xPU=", + "dev": true + }, + "lru-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", + "integrity": "sha1-E0OVXtry432bnn7nJB4nxLn7cr4=", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.0.0" + }, + "dependencies": { + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "yallist": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz", + "integrity": "sha1-MGxUODXwnuGkyyO3vOmrNByRzdQ=", + "dev": true + } + } + }, + "minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "1.1.6" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", + "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=", + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + } + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "node-gyp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.6.0.tgz", + "integrity": "sha1-dHT2OjoFARYd2gtjQfAi8UxCP6Y=", + "dev": true, + "requires": { + "fstream": "1.0.11", + "glob": "7.0.6", + "graceful-fs": "4.1.6", + "minimatch": "3.0.3", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "npmlog": "2.0.4", + "osenv": "0.1.3", + "request": "2.74.0", + "rimraf": "2.5.4", + "semver": "5.3.0", + "tar": "2.2.1", + "which": "1.2.11" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + } + } + }, + "normalize-git-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-git-url/-/normalize-git-url-3.0.2.tgz", + "integrity": "sha1-jl8Uvgva7bc+ByADEKpBbCc1D8Q=", + "dev": true + }, + "normalize-package-data": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz", + "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=", + "dev": true, + "requires": { + "hosted-git-info": "2.1.5", + "is-builtin-module": "1.0.0", + "semver": "5.1.0", + "validate-npm-package-license": "3.0.1" + }, + "dependencies": { + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.0" + }, + "dependencies": { + "builtin-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz", + "integrity": "sha1-EFOVX9mUpXRuUl5Kxxe4HK8HSRw=", + "dev": true + } + } + } + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz", + "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", + "dev": true + }, + "npm-install-checks": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-1.0.7.tgz", + "integrity": "sha1-bZGu2grJaAHx7Xqt7hFqbAoIalc=", + "dev": true, + "requires": { + "npmlog": "2.0.4", + "semver": "5.1.0" + } + }, + "npm-package-arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-4.1.0.tgz", + "integrity": "sha1-LgFfisAHN8uX+ZfJy/BZ9Cp0Un0=", + "dev": true, + "requires": { + "hosted-git-info": "2.1.5", + "semver": "5.1.0" + } + }, + "npm-registry-client": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-7.2.1.tgz", + "integrity": "sha1-x5ImawiMwxP4Ul5+NSSGJscj23U=", + "dev": true, + "requires": { + "concat-stream": "1.5.2", + "graceful-fs": "4.1.6", + "normalize-package-data": "2.3.5", + "npm-package-arg": "4.1.0", + "npmlog": "2.0.4", + "once": "1.4.0", + "request": "2.74.0", + "retry": "0.10.0", + "semver": "5.1.0", + "slide": "1.1.6" + }, + "dependencies": { + "concat-stream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + } + } + }, + "retry": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.0.tgz", + "integrity": "sha1-ZJ4VykCEItmDGBYZNef31lLUNd0=", + "dev": true + } + } + }, + "npm-user-validate": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-0.1.5.tgz", + "integrity": "sha1-UkZdUMLSApSlcSW5lrrtv1bFAEs=", + "dev": true + }, + "npmlog": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", + "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", + "dev": true, + "requires": { + "ansi": "0.3.1", + "are-we-there-yet": "1.1.2", + "gauge": "1.2.7" + }, + "dependencies": { + "are-we-there-yet": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz", + "integrity": "sha1-gORw6VoIR5T+GJkmLFZnxuiN4bM=", + "dev": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.1.5" + }, + "dependencies": { + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + } + } + }, + "gauge": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", + "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", + "dev": true, + "requires": { + "ansi": "0.3.1", + "has-unicode": "2.0.0", + "lodash.pad": "4.4.0", + "lodash.padend": "4.5.0", + "lodash.padstart": "4.5.0" + }, + "dependencies": { + "has-unicode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.0.tgz", + "integrity": "sha1-o82Wwwe6QdVZxaLuQIwSoRxMLsM=", + "dev": true + }, + "lodash._baseslice": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseslice/-/lodash._baseslice-4.0.0.tgz", + "integrity": "sha1-9c4d+YKUjsr/Y/IjhTQVt7l2NwQ=", + "dev": true + }, + "lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=", + "dev": true + }, + "lodash.pad": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.4.0.tgz", + "integrity": "sha1-+qON8mwKaexQhqgiRslY4VDcsas=", + "dev": true, + "requires": { + "lodash._baseslice": "4.0.0", + "lodash._basetostring": "4.12.0", + "lodash.tostring": "4.1.4" + } + }, + "lodash.padend": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.5.0.tgz", + "integrity": "sha1-oonpN37i5t6Lp/EfOo6zJgcLdhk=", + "dev": true, + "requires": { + "lodash._baseslice": "4.0.0", + "lodash._basetostring": "4.12.0", + "lodash.tostring": "4.1.4" + } + }, + "lodash.padstart": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.5.0.tgz", + "integrity": "sha1-PqGQ9nNIQcM2TSedEeBWcmtgp5o=", + "dev": true, + "requires": { + "lodash._baseslice": "4.0.0", + "lodash._basetostring": "4.12.0", + "lodash.tostring": "4.1.4" + } + }, + "lodash.tostring": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/lodash.tostring/-/lodash.tostring-4.1.4.tgz", + "integrity": "sha1-Vgwn0fjq3eA8LM4Zj+9cAx2CmPs=", + "dev": true + } + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "opener": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.1.tgz", + "integrity": "sha1-iXWQrNGu0zEbcDtYvMtNQ/VvKJU=", + "dev": true + }, + "osenv": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.3.tgz", + "integrity": "sha1-g88FxtZFj8TVrGNi6jJdkvJ1Qhc=", + "dev": true, + "requires": { + "os-homedir": "1.0.0", + "os-tmpdir": "1.0.1" + }, + "dependencies": { + "os-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.0.tgz", + "integrity": "sha1-43B4vGG1hpBjBTiXJX457BJhtwI=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz", + "integrity": "sha1-6bQjoe2vR5iCVi6S7XHXdDoHG24=", + "dev": true + } + } + }, + "read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "dev": true, + "requires": { + "mute-stream": "0.0.5" + }, + "dependencies": { + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + } + } + }, + "read-package-json": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.4.tgz", + "integrity": "sha1-Ye0bIlbqQ42ACIlQkL6EuOeZyFM=", + "dev": true, + "requires": { + "glob": "6.0.4", + "graceful-fs": "4.1.6", + "json-parse-helpfulerror": "1.0.3", + "normalize-package-data": "2.3.5" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.5", + "inherits": "2.0.3", + "minimatch": "3.0.3", + "once": "1.4.0", + "path-is-absolute": "1.0.0" + }, + "dependencies": { + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=", + "dev": true + } + } + }, + "json-parse-helpfulerror": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", + "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", + "dev": true, + "requires": { + "jju": "1.3.0" + }, + "dependencies": { + "jju": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", + "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=", + "dev": true + } + } + } + } + }, + "readable-stream": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz", + "integrity": "sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=", + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "buffer-shims": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } + } + }, + "realize-package-specifier": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/realize-package-specifier/-/realize-package-specifier-3.0.1.tgz", + "integrity": "sha1-/eMukmRI44+ZM02Vt7CNUeOpjZ8=", + "dev": true, + "requires": { + "dezalgo": "1.0.3", + "npm-package-arg": "4.1.0" + } + }, + "request": { + "version": "2.74.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", + "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.4.1", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "1.0.0-rc4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.11", + "node-uuid": "1.4.7", + "oauth-sign": "0.8.2", + "qs": "6.2.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.1", + "tunnel-agent": "0.4.3" + }, + "dependencies": { + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.4.1.tgz", + "integrity": "sha1-/efVKSRm0jDl7g9OA42d+qsI/GE=", + "dev": true + }, + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "dev": true, + "requires": { + "readable-stream": "2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + } + } + } + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + }, + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + } + } + }, + "extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "1.0.0-rc4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", + "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", + "dev": true, + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.5", + "mime-types": "2.1.11" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + } + } + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": "1.0.1" + }, + "dependencies": { + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + } + } + }, + "is-my-json-valid": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz", + "integrity": "sha1-1Vd4qC/rawlj/0vhEdXRaE6JBwc=", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "2.0.0", + "xtend": "4.0.1" + }, + "dependencies": { + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + }, + "dependencies": { + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + } + } + }, + "jsonpointer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz", + "integrity": "sha1-OvHdIP6FRjkQ1GmjheMwF9KgMNk=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + }, + "dependencies": { + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + } + } + } + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + }, + "dependencies": { + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + } + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.3.0", + "sshpk": "1.9.2" + }, + "dependencies": { + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "jsprim": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.3.0.tgz", + "integrity": "sha1-zi4b74NSBLTzCZkoxgL4tq5hVlA=", + "dev": true, + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.2", + "verror": "1.3.6" + }, + "dependencies": { + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", + "dev": true + }, + "json-schema": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", + "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=", + "dev": true + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "dev": true, + "requires": { + "extsprintf": "1.0.2" + } + } + } + }, + "sshpk": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.9.2.tgz", + "integrity": "sha1-O0E1G7rVw03fS9gRmTfv7jGkZ2U=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "dashdash": "1.14.0", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.6", + "jodid25519": "1.0.2", + "jsbn": "0.1.0", + "tweetnacl": "0.13.3" + }, + "dependencies": { + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "dashdash": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.0.tgz", + "integrity": "sha1-KeSGxUGL8PNWA0qZPVFoajPoQUE=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "getpass": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", + "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + } + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jsbn": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", + "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=", + "dev": true, + "optional": true + }, + "tweetnacl": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz", + "integrity": "sha1-1ii1bzvMPVrnS6nUwacE3vWrS1Y=", + "dev": true, + "optional": true + } + } + } + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "mime-types": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.11.tgz", + "integrity": "sha1-wlnEcb2oCKhdbNGTtDCl+uRHOzw=", + "dev": true, + "requires": { + "mime-db": "1.23.0" + }, + "dependencies": { + "mime-db": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.23.0.tgz", + "integrity": "sha1-oxtAcK2uon1zLqMzdApk0OyaZlk=", + "dev": true + } + } + }, + "node-uuid": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "qs": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz", + "integrity": "sha1-zgPF/wk1vB2daanxTL0Y5WjWdiU=", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.1.tgz", + "integrity": "sha1-mcd9+7fYBCSeiimdTLD9gf7wg/0=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + } + } + }, + "retry": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.0.tgz", + "integrity": "sha1-ZJ4VykCEItmDGBYZNef31lLUNd0=", + "dev": true + }, + "rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, + "requires": { + "glob": "7.0.6" + } + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=", + "dev": true + }, + "sha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/sha/-/sha-2.0.1.tgz", + "integrity": "sha1-YDCCL70smCOUn49y7WQR7lzyWq4=", + "dev": true, + "requires": { + "graceful-fs": "4.1.6", + "readable-stream": "2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz", + "integrity": "sha1-vsgb6ujPRVFovC5bKzH1vPrtmxs=", + "dev": true, + "requires": { + "core-util-is": "1.0.1", + "inherits": "2.0.3", + "isarray": "0.0.1", + "process-nextick-args": "1.0.3", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.1" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz", + "integrity": "sha1-awcIWu+aPMrG7lO/nT3wwVIaVTg=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz", + "integrity": "sha1-4nLu2CXV6fTqdNjXOx/jEcO+tjA=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz", + "integrity": "sha1-NVaj0TxMaqeYPX4kJUeBlxmbeIE=", + "dev": true + } + } + } + } + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "sorted-object": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sorted-object/-/sorted-object-2.0.0.tgz", + "integrity": "sha1-HP6pgWCQR9gEOAekkKnZmzF/r38=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.0.0" + } + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "dev": true + }, + "umask": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", + "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.2" + }, + "dependencies": { + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz", + "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=", + "dev": true, + "requires": { + "spdx-exceptions": "1.0.4", + "spdx-license-ids": "1.2.2" + }, + "dependencies": { + "spdx-exceptions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz", + "integrity": "sha1-IguEI5EZrpBFqJLbgag/TOFvgP0=", + "dev": true + } + } + } + } + }, + "which": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.11.tgz", + "integrity": "sha1-yLLu6muMFln6fB3U/aq+lTPcXos=", + "dev": true, + "requires": { + "isexe": "1.1.2" + }, + "dependencies": { + "isexe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", + "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.10", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "q": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.0.tgz", + "integrity": "sha1-3QG6ydBtMObyGa7LglPunr3DCPE=", + "dev": true + }, + "read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", + "dev": true, + "requires": { + "debuglog": "1.0.1", + "graceful-fs": "4.1.11", + "read-package-json": "2.0.10", + "readdir-scoped-modules": "1.0.2", + "semver": "5.3.0", + "slide": "1.1.6", + "util-extend": "1.0.3" + } + }, + "read-package-json": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.10.tgz", + "integrity": "sha512-iNWaEs9hW9nviu5rHADmkm/Ob5dvah5zajtTS1XbyERSzkWgSwWZ6Z12bION7bEAzVc2YRFWnAz8k/tAr+5/eg==", + "dev": true, + "requires": { + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "json-parse-helpfulerror": "1.0.3", + "normalize-package-data": "2.4.0" + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", + "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", + "dev": true, + "requires": { + "debuglog": "1.0.1", + "dezalgo": "1.0.3", + "graceful-fs": "4.1.11", + "once": "1.4.0" + } + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "dev": true + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "validate-npm-package-name": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz", + "integrity": "sha1-9laVsi9zJEQgGaPH+jmm5/0pkIU=", + "dev": true, + "requires": { + "builtins": "0.0.7" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.1.4.tgz", + "integrity": "sha1-sfUtwujcDjywTRh6JfdYo4qQyjs=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5c9b85e --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "icecore-hashids", + "version": "0.4.0", + "private": true, + "description": "A lightweight generator for short, unique, non-sequential and decodable Hashids from non-negative numbers.", + "author": { + "name": "Arctic Ice Studio", + "url": "http://arcticicestudio.com" + }, + "license": "Apache 2.0", + "dependencies": {}, + "devDependencies": { + "gitbook-cli": "2.3.2", + "hashids": "1.1.1" + }, + "scripts": { + "docs:build": "npm run docs:clean && gitbook install ./src/main/docs && gitbook build ./src/main/docs ./target/docs", + "docs:clean": "rm -rf ./target/docs", + "docs:serve": "npm run docs:build && gitbook serve ./src/main/docs ./target/docs" + }, + "engines": { + "node": "<=6.5" + } +} diff --git a/pom.xml b/pom.xml index a51aea1..415f371 100644 --- a/pom.xml +++ b/pom.xml @@ -1,24 +1,4 @@ - com.arcticicestudio icecore-hashids - 0.3.0 + 0.4.0 jar icecore-hashids - A lightweight generator for short, unique, non-sequential and decodable Hashids from positive unsigned (long) integer numbers. + A lightweight generator for short, unique, non-sequential and decodable Hashids from non-negative numbers. https://github.com/arcticicestudio/icecore-hashids 2016 @@ -87,9 +67,13 @@ JFrog Bintray - ${basedir}/src/main/assets/assembly-bin.xml 1.8 UTF-8 + 3.0.4 + 8.0 + 4.12 + 2.0.0.0 + v8.2.1 @@ -108,21 +92,14 @@ JFrog Bintray junit junit - 4.12 + ${junit.version} test org.hamcrest - hamcrest-core - 1.3 - test - - - - org.hamcrest - hamcrest-library - 1.3 + hamcrest-junit + ${hamcrest.version} test @@ -133,7 +110,7 @@ JFrog Bintray org.apache.maven.plugins maven-compiler-plugin - 3.5.1 + 3.6.1 ${java.version} ${java.version} @@ -182,7 +159,7 @@ JFrog Bintray org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.0.0-M1 attach-javadocs @@ -196,7 +173,7 @@ JFrog Bintray javadoc ${project.build.sourceEncoding} true - Copyright © ${project.inceptionYear} {organizationName} + Copyright © 2017 {organizationName} ${project.name} ${project.version} API ${project.name} ${project.version} API @@ -220,42 +197,55 @@ JFrog Bintray - net.ju-n.maven.plugins - checksum-maven-plugin - 1.3 + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + - checksum-artifacts - verify + validate + validate + + ${basedir}/src/main/resources/arcticicestudio-styleguide-java-checkstyle.xml + UTF-8 + true + true + - artifacts + check - - - SHA-1 - MD5 - - false - org.apache.maven.plugins - maven-install-plugin - 2.5.2 + maven-enforcer-plugin + 1.4.1 - install + enforce-versions - install + enforce + + + + (${maven.version.min},) + + + ${java.version} + + + - - true - @@ -296,29 +286,48 @@ JFrog Bintray - assemble + node org.apache.maven.plugins - maven-assembly-plugin - 2.6 + maven-clean-plugin + + + + node_modules + + + + + + + com.github.eirslett + frontend-maven-plugin + 1.5 - assemble - none + install-node-and-npm + + install-node-and-npm + + + ${node.version} + ${project.build.directory} + + + + npm install - single + npm + generate-resources + + ${project.build.directory} + install + - - true - false - - ${assembly.descriptor.bin} - - @@ -331,7 +340,7 @@ JFrog Bintray org.jacoco jacoco-maven-plugin - 0.7.7.201606060606 + 0.7.9 @@ -355,15 +364,15 @@ JFrog Bintray jfrog https://github.com/arcticicestudio/icecore-hashids - - bintray - OSS JFrog - http://oss.jfrog.org/artifactory/oss-snapshot-local - + + bintray + OSS JFrog + http://oss.jfrog.org/artifactory/oss-snapshot-local + - bintray - JFrog Bintray - https://api.bintray.com/maven/arcticicestudio/IceCore/icecore-hashids/;publish=1 + bintray + JFrog Bintray + https://api.bintray.com/maven/arcticicestudio/IceCore/icecore-hashids/;publish=1 @@ -390,7 +399,7 @@ JFrog Bintray org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.8 true ossrh diff --git a/src/main/assets/assembly-bin.xml b/src/main/assets/assembly-bin.xml deleted file mode 100644 index 9e5857a..0000000 --- a/src/main/assets/assembly-bin.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - bin - - tar.gz - zip - - - - ${project.basedir} - ${file.separator} - - CHANGELOG.md - LICENSE.md - README.md - - - - ${project.build.directory} - ${file.separator} - - *.jar - *.md5 - *.sha1 - pom.xml* - - - - diff --git a/src/main/docs/README.md b/src/main/docs/README.md new file mode 100644 index 0000000..8f58f82 --- /dev/null +++ b/src/main/docs/README.md @@ -0,0 +1,63 @@ +

+ +

+ +

+ +

+ +

A lightweight generator for short, unique, non-sequential and decodable Hashids from non-negative numbers.

+ +

Implementation of the Hashids algorithm.

+ +--- + +[Hashids][hashids] are obfuscated unique hashes of non-negative (long) integer numbers, but in contrast to cryptographic one-way hash algorithms they are can be decoded again. The algorithm can be used to either generate a hash from a single number or bundling several numbers into one to be stored as simple short UIDs. This design allows to use them for many use-cases like + +* URL shortening +* database ID protection +* shard numbers storage +* invitation-, authorization- and gift codes +* complex- or clustered system parameters + +Numbers like `347` are converted into strings like `yr8`, or an array of numbers like `[27, 986]` into `3kTMd`. + +## Features + +The **algorithm** provides the following features: + +* Generation of short, unique, case-sensitive and non-sequential [decodable][api-guide-decoding] hashes of natural numbers +* Additional entropy through [salt][api-guide-config-salt] usage +* Configurable [minimum hash length][api-guide-config-min-hash-length] and [alphabet][api-guide-config-alphabet] +* Combining of [several numbers to one hash][api-guide-encoding] +* Deterministic hash computation given the same input and parametrization/[instance configuration][api-instances] +* [Prevention of curse words][api-curse-word-prevention] through separator characters + +In addition, the **library** provides features to + +* pass `0x` or `0X` [prefixed hexadecimal numbers][api-guide-config-feature-hex-prefix] to the public API methods +* [handle exceptions][api-guide-config-feature-exception-handling] instead of returning empty values when invalid parameters are passed to any public API method +* [disable the maximum number size limit][api-guide-config-feature-no-max-number-size] which ensures the interoperability with the [algorithm reference implementation][hashids-js] and allows the usage of the Java `Long` [maximum value][long-max-value] + +> Please note that most features will break the interoperability with the [algorithm reference implementation][hashids-js]! + +--- + +

Copyright © 2017 +Arctic Ice Studio

+ +

+ +[api-curse-word-prevention]: api/curse-word-prevention.md +[api-guide-config-alphabet]: api/guide/configuration/index.md#determine-a-custom-alphabet +[api-guide-config-feature-exception-handling]: api/guide/configuration/features.md#exception-handling +[api-guide-config-feature-hex-prefix]: api/guide/configuration/features.md#allow-hexadecimal-number-prefixes +[api-guide-config-feature-no-max-number-size]: api/guide/configuration/features.md#no-number-size-limit +[api-guide-config-min-hash-length]: api/guide/configuration/index.md#defining-a-minimum-hash-length +[api-guide-config-salt]: api/guide/configuration/index.md#using-a-salt +[api-guide-decoding]: api/guide/decoding.md +[api-guide-encoding]: api/guide/encoding.md +[api-instances]: api/instances.md +[hashids]: http://hashids.org +[hashids-js]: https://github.com/ivanakimov/hashids.js +[long-max-value]: https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html#MAX_VALUE diff --git a/src/main/docs/SUMMARY.md b/src/main/docs/SUMMARY.md new file mode 100644 index 0000000..93d19ae --- /dev/null +++ b/src/main/docs/SUMMARY.md @@ -0,0 +1,63 @@ +# Summary + +### Introduction + - [Overview](README.md) + - [Features](README.md#features) + +### Getting Started + - [Requirements](getting-started/requirements.md) + - [Installation](getting-started/installation.md) + +### API + - [Overview](api/index.md) + - [Imports](api/imports.md) + - [Instances](api/instances.md) + - [Configuration](api/instances.md#configuration) + - [Features](api/instances.md#features) + - [Curse Word Prevention](api/curse-word-prevention.md) + - [Guide](api/guide/index.md) + - [Configuration](api/guide/configuration/index.md) + - [Using A Salt](api/guide/configuration/index.md#using-a-salt) + - [Defining A Minimum Hash Length](api/guide/configuration/index.md#defining-a-minimum-hash-length) + - [Determine A Custom Alphabet](api/guide/configuration/index.md#determine-a-custom-alphabet) + - [Enable Additional Features](api/guide/configuration/features.md) + - [Hexadecimal Number Prefixes](api/guide/configuration/features.md#allow-hexadecimal-number-prefixes) + - [Exception Handling](api/guide/configuration/features.md#exception-handling) + - [No Number Size Limit](api/guide/configuration/features.md#no-number-size-limit) + - [Encoding](api/guide/encoding.md) + - [Natural Numbers](api/guide/encoding.md#natural-numbers) + - [Hexadecimal Numbers](api/guide/encoding.md#hexadecimal-numbers) + - [Decoding](api/guide/decoding.md) + - [Natural Numbers](api/guide/decoding.md#natural-numbers) + - [Hexadecimal Numbers](api/guide/decoding.md#hexadecimal-numbers) + - [Single Number As Optional](api/guide/decoding.md#single-number-as-optional) + +### Development + - [Building](development/building.md) + - [From Source](development/building.md#from-source) + - [Documentations](development/building.md#documentations) + - [Testing](development/testing.md) + - [Unit Tests](development/testing.md#unit-tests) + - [Interoperability Tests](development/testing.md#interoperability-tests) + - [Contributing](development/contributing.md) + - [Getting Started](development/contributing.md#getting-started) + - [Bug Reports](development/contributing.md#bug-reports) + - [Enhancement Suggestions](development/contributing.md#enhancement-suggestions) + - [Pull Requests](development/contributing.md#pull-requests) + - [Documentations](development/contributing.md#documentations) + - [Branch Organization](development/contributing.md#branch-organization) + - [How else can I help?](development/contributing.md#how-else-can-i-help) + - [Improve Issues](development/contributing.md#improve-issues) + - [Give Feedback On Issues and Pull Requests](development/contributing.md#give-feedback-on-issues-and-pull-requests) + - [Styleguides](development/contributing.md#styleguides) + - [Java Code Style](development/contributing.md#java-code-style) + - [Git Commit Messages](development/contributing.md#git-commit-messages) + - [MCVE](development/contributing.md#mcve) + - [Versioning](development/contributing.md#versioning) + - [Credits](development/contributing.md#credits) + - [Changelog](development/changelog.md) + - [0.4.0](development/changelog.md#040) + - [0.3.0](development/changelog.md#030) + - [0.2.0](development/changelog.md#020) + - [0.1.0](development/changelog.md#010) + - [Code of Conduct](development/code-of-conduct.md) diff --git a/src/main/docs/api/curse-word-prevention.md b/src/main/docs/api/curse-word-prevention.md new file mode 100644 index 0000000..de6ba05 --- /dev/null +++ b/src/main/docs/api/curse-word-prevention.md @@ -0,0 +1,11 @@ +# Curse Word Prevention + +The algorithm was written with the intent of placing hashes in visible places like the URLs. It is possible that the created hash ends up accidentally contaning a bad or offensive word(s). + +Therefore, it tries to avoid generating most common english curse words with the [default alphabet][javadoc-gh-pages-default-alphabet] by using separators which will never be placed next to each other. + +``` +c, C, f, F, h, H, i, I, s, S, t, T, u, U +``` + +[javadoc-gh-pages-default-alphabet]: https://arcticicestudio.github.io/icecore-hashids/javadoc/com/arcticicestudio/icecore/hashids/Hashids.html#DEFAULT_ALPHABET diff --git a/src/main/docs/api/guide/configuration/features.md b/src/main/docs/api/guide/configuration/features.md new file mode 100644 index 0000000..2b4ebf5 --- /dev/null +++ b/src/main/docs/api/guide/configuration/features.md @@ -0,0 +1,48 @@ +# Enable Additional Features + +> Please note that most features will break the interoperability with the [algorithm reference implementation][hashids-js]! + +## Allow Hexadecimal Number Prefixes + +When this feature is enabled the public API methods [`encodeHex(String)`][guide-encode-hex] and [`decodeHex(String)`][guide-decode-hex] will allow to pass `0x` or `0X` prefixed hexadecimal numbers. + +```java +final Hashids hashids = new Hashids.Builder() + .features(HashidsFeature.ALLOW_HEXADECIMAL_NUMBER_PREFIX) + .build(); + +// Both will be "j2g9K4y" +final String hashHexPrefixLowercase = hashids.encodeHex("0x75bcd15"); +final String hashHexPrefixUppercase = hashids.encodeHex("0X75bcd15"); +``` + +## Exception Handling + +When this feature is enabled the instance will handle errors by throwing specific exceptions instead of returning empty values when invalid parameters are passed to any public API method. This allows a more accurate analysis and treatment of the error by using language specific advantages. + +This example will throw a `IllegalArgumentException` to handle negative numbers: + +```java +final Hashids hashids = new Hashids.Builder() + .features(HashidsFeature.EXCEPTION_HANDLING) + .build(); + +final long invalidNumber = -123L; +final String hash = hashids.encode(invalidNumber); +``` + +## No Number Size Limit + +This instance feature disables the maximum number size limit which ensures the interoperability with the [algorithm reference implementation][hashids-js] and allows the usage of the Java `Long` [maximum value][long-max-value]. + +```java +final Hashids hashids = new Hashids.Builder() + .features(HashidsFeature.NO_MAX_INTEROP_NUMBER_SIZE) + .build(); + +final String hash = hashids.encode(Hashids.MAX_INTEROP_NUMBER_SIZE + 1L); +``` + +[guide-decode-hex]: ../decoding.md#hexadecimal-numbers +[guide-encode-hex]: ../encoding.md#hexadecimal-numbers +[hashids-js]: https://github.com/ivanakimov/hashids.js diff --git a/src/main/docs/api/guide/configuration/index.md b/src/main/docs/api/guide/configuration/index.md new file mode 100644 index 0000000..c7ba86e --- /dev/null +++ b/src/main/docs/api/guide/configuration/index.md @@ -0,0 +1,49 @@ +# Configuration + +Instances are configured by using the `Hashids.Builder` which can be simply imported statically as described in the [imports][api-overview-imports] chapter. + +All configurations are compatible and can be combined. + +* [`salt(String)`](#using-a-salt) - Sets the salt to be used as entropy +* [`minLength(int)`](#defining-a-minimum-hash-length) - Sets the minimum hash length +* [`alphabet(String)`](#determine-a-custom-alphabet) - Sets the alphabet to be used for the hash generation +* [`features(HashidsFeature...)`][guide-config-features] - Enables the given instance feature + +## Using A Salt + +A salt adds additional entropy to the hash generation and can be used to increase the protection against unintentional hash decoding by third parties. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final String hash = hashids.encode(1234567L); // Result: "BbVrQ" +``` + +## Defining A Minimum Hash Length + +This configuration allows to determine the *minimum* length of the generated hash. By default the length is set to `0` which will generate hashes with the required length only depending on the given number(s). + +```java +final Hashids hashids = new Hashids.Builder() + .minHashLength(8) + .build(); + +final String hash = hashids.encode(42L); // Result: "WPe9xdLy" +``` + +## Determine A Custom Alphabet + +The alphabet for the hash generation can be customized but **must contain at least 16 unique characters**. + +```java +final Hashids hashids = new Hashids.Builder() + .alphabet("abcdefgh12345678") + .build(); + +final String hash = hashids.encode(1234567L); // Result: "edd6185" +``` + +[api-overview-imports]: ../../imports.md +[guide-config-features]: features.md diff --git a/src/main/docs/api/guide/decoding.md b/src/main/docs/api/guide/decoding.md new file mode 100644 index 0000000..8fd7150 --- /dev/null +++ b/src/main/docs/api/guide/decoding.md @@ -0,0 +1,72 @@ +# Decoding + +> In order to decode a hash the [instance algorithm configuration][algorithm-config] **must be equal to the configuration the hash has been encoded with**, otherwise the returned value will be empty! + +If the [exception handling][feature-exception-handling] feature is enabled a `IllegalArgumentException` will be thrown instead when the passed hash is not valid or a `NullPointerException` if the passed argument is `null`. + +This example will throw a `IllegalArgumentException` due to instance incompatibility: + +```java +final Hashids saltyHashids = new Hashids.Builder() + .salt("salt and pepper") + .features(HashidsFeature.EXCEPTION_HANDLING) + .build(); + +final Hashids fruityHashids = new Hashids.Builder() + .salt("coconut and yogurt") + .features(HashidsFeature.EXCEPTION_HANDLING) + .build(); + +final String hash = saltyHashids.encode(123L); +final long[] invalidNumbers = fruityHashids.decode(hash); // IllegalArgumentException +``` + +## Natural Numbers + +To decode a hash use the public API method `decode(String)`. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final long[] number = hashids.decode("Blk"); // Result: [123] +final long[] numbers = hashids.decode("9dTLhR"); // Result: [42, 5, 17] +``` + +## Hexadecimal Numbers + +Hexadecimal numbers can be decoded by using the public API method `decodeHex(String)`. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final String hexNumber = hashids.decodeHex("j2g9K4y"); // Result: "75bcd15" +``` + +## Single Number As Optional + +The `decodeOne(String)` public API method simplifies the use-case where the amount of resulting numbers is known before to handle the return value as single value instead of an array. + +It decodes into a single numeric representation and returns it as `Optional` if the given hash is valid, otherwise `Optional.EMPTY`. + +> The given hash **must resolve into a one number only**, otherwise the [`decode(String)`](#natural-numbers) public API method must be used. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final String singleNumberHash = hashids.encode(123L); +final String multipleNumberHash = hashids.encode(42L, 5L, 17L); + +final Optional number = hashids.decodeOne(singleNumberHash); // Optional[123] +final Optional empty = hashids.decodeOne(multipleNumberHash); // Optional.EMPTY +``` + + +[algorithm-config]: configuration/index.md +[feature-exception-handling]: configuration/features.md#exception-handling +[salt]: configuration/index.md#using-a-salt diff --git a/src/main/docs/api/guide/encoding.md b/src/main/docs/api/guide/encoding.md new file mode 100644 index 0000000..8b5afce --- /dev/null +++ b/src/main/docs/api/guide/encoding.md @@ -0,0 +1,52 @@ +# Encoding + +It is recommended to always use a [salt][salt] before encoding numbers. + +> All numbers **must be greater than or equal to zero**! + +If the [exception handling][feature-exception-handling] feature is enabled both public API encode methods will throw a `IllegalArgumentException` when no numbers are passed, any number is negative or greater than the maximum number size, unless the feature to allow numbers greater than the [maximum interoperability number size][feature-size-limit] is not enabled. When the passed argument is `null` a `NullPointerException` will be thrown. + +## Natural Numbers + +The public API method `encode(long...)` can be passed **one or more numbers**. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final String singleNumberHash = hashids.encode(123L); // Result: "Blk" +final String multipleNumberHash = hashids.encode(42L, 5L, 17L); // Result: "9dTLhR" +``` + +## Hexadecimal Numbers + +Hexadecimal numbers can be encoded by using the public API method `encodeHex(String)`. + +If the [exception handling][feature-exception-handling] feature is enabled the method will throw a `IllegalArgumentException` when the passed hexadecimal number format is not valid. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .build(); + +final String hash = hashids.encodeHex("75bcd15"); // Result: "j2g9K4y" +``` + +If the feature to [allow hexadecimal number prefixes][feature-hex-prefix] is enabled the passed value can optionally be prefixed with `0x` or `0X`. + +```java +final Hashids hashids = new Hashids.Builder() + .salt("salt and pepper") + .features(HashidsFeature.ALLOW_HEXADECIMAL_NUMBER_PREFIX) + .build(); + +// Both will be "j2g9K4y" +final String hashHexPrefixLowercase = hashids.encodeHex("0x75bcd15"); +final String hashHexPrefixUppercase = hashids.encodeHex("0X75bcd15"); +``` + +[feature-exception-handling]: configuration/features.md#allow-hexadecimal-number-prefixes +[feature-hex-prefix]: configuration/features.md#allow-hexadecimal-number-prefixes +[feature-size-limit]: configuration/features.md#no-number-size-limit +[salt]: configuration/index.md#using-a-salt diff --git a/src/main/docs/api/guide/index.md b/src/main/docs/api/guide/index.md new file mode 100644 index 0000000..a1546a3 --- /dev/null +++ b/src/main/docs/api/guide/index.md @@ -0,0 +1,7 @@ +# API Guide + +This is a basic guide to show the common usage of the IceCore Hashids API. + +The API reference documentation is provided through the [JavaDocs][javadoc-gh-pages]. + +[javadoc-gh-pages]: https://arcticicestudio.github.io/icecore-hashids/javadoc diff --git a/src/main/docs/api/imports.md b/src/main/docs/api/imports.md new file mode 100644 index 0000000..5e9bccf --- /dev/null +++ b/src/main/docs/api/imports.md @@ -0,0 +1,21 @@ +## Imports + +The `Hashids` class represents the public API located in the `com.arcticicestudio.icecore.hashids` package. + +```java +import com.arcticicestudio.icecore.hashids.Hashids; +``` + +For simplification the instance builder can be imported statically + +```java +import static com.arcticicestudio.icecore.hashids.Hashids.Builder; +``` + +which also applies to [feature](#features) enums. + +```java +import static com.arcticicestudio.icecore.hashids.HashidsFeature.ALLOW_HEXADECIMAL_NUMBER_PREFIX; +import static com.arcticicestudio.icecore.hashids.HashidsFeature.EXCEPTION_HANDLING; +import static com.arcticicestudio.icecore.hashids.HashidsFeature.NO_MAX_INTEROP_NUMBER_SIZE; +``` diff --git a/src/main/docs/api/index.md b/src/main/docs/api/index.md new file mode 100644 index 0000000..4d3bb89 --- /dev/null +++ b/src/main/docs/api/index.md @@ -0,0 +1,28 @@ +# IceCore Hashids API + +The IceCore Hashids API provides the methods to [encode numbers][guide-encoding] and [decode hashes][guide-decoding]. + +You can get started by reading the chapters about [imports][imports] and the [configuration of instances][guide-config-instances], their [opt-in features][instances-features] and [how to enable them][guide-config-features]. + +## Versions + +This documentation is based on the IceCore Hashids version {{book.api.version}}. + +You can get the public API version from the static method `getVersion()`. + +```java +Hashids.getVersion(); +``` + +The interoperability version can be obtained via the static method `getInteropVersion()`. + +```java +Hashids.getInteropVersion(); +``` + +[guide-config-features]: guide/configuration/features.md +[guide-config-instances]: guide/configuration/index.md +[guide-decoding]: guide/decoding.md +[guide-encoding]: guide/encoding.md +[imports]: imports.md +[instances-features]: instances.md#features diff --git a/src/main/docs/api/instances.md b/src/main/docs/api/instances.md new file mode 100644 index 0000000..d572a9a --- /dev/null +++ b/src/main/docs/api/instances.md @@ -0,0 +1,44 @@ +# Instances + +Default configured `Hashids` instances can be created by using the `Hashids.Builder` default instance + +```java +Hashids hashids = new Hashids.Builder().build(); +``` + +or the default constructor + +```java +Hashids hashids = new Hashids(); +``` + +### Configuration + +The `Hashids.Builder` instance provides methods to configure the algorithm features like the [entropy salt][guide-config-salt], the [minimum hash length][guide-config-min-hash-length] and the [customizable alphabet][guide-config-alphabet]. + +#### Default Values + +Below are the default values of the instance configurations which can also be found in the [JavaDoc][javadoc-gh-pages]. + +* `Hashids.Builder#salt(String)` - By default the value of the salt is empty which will generate hashes without entropy +* `Hashids.Builder#minLength(int)` By default the minimum hash length is `0` which will generate hashes with the required length only depending on the given number(s) +* `Hashids.Builder#alphabet(String)` - The default alphabet is `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890` which is defined in the `Hashids#DEFAULT_ALPHABET` constant + +### Features + +IceCore Hashids includes opt-in instance features to enable language specific advantages like + +* [exception handling][guide-feature-exception-handling] +* disabling [algorithm reference implementation][hashids-js] interoperability limitations like the [maximum number size][guide-feature-no-max-number-size] +* allowing to [prefix hexadecimal numbers][guide-feature-allow-hexadecimal-prefix] with `0x` or `0X` + +All public API instance features are represented by the `HashidsFeature` class located in the `com.arcticicestudio.icecore.hashids` package. + +[guide-config-alphabet]: guide/configuration/index.md#determine-a-custom-alphabet +[guide-config-min-hash-length]: guide/configuration/index.md#defining-a-minimum-hash-length +[guide-config-salt]: guide/configuration/index.md#using-a-salt +[guide-feature-allow-hexadecimal-prefix]: guide/configuration/features.md#allow-exadecimal-number-prefixes +[guide-feature-exception-handling]: guide/configuration/features.md#exception-handling +[guide-feature-no-max-number-size]: guide/configuration/features.md#no-number-size-limit +[hashids-js]: https://github.com/ivanakimov/hashids.js +[javadoc-gh-pages]: https://arcticicestudio.github.io/icecore-hashids/javadoc diff --git a/src/main/docs/assets/scrot-docs-testing-config-interop-tests.png b/src/main/docs/assets/scrot-docs-testing-config-interop-tests.png new file mode 100644 index 0000000..000e337 Binary files /dev/null and b/src/main/docs/assets/scrot-docs-testing-config-interop-tests.png differ diff --git a/src/main/docs/assets/scrot-docs-testing-config-unit-tests.png b/src/main/docs/assets/scrot-docs-testing-config-unit-tests.png new file mode 100644 index 0000000..8a9fd7e Binary files /dev/null and b/src/main/docs/assets/scrot-docs-testing-config-unit-tests.png differ diff --git a/src/main/docs/assets/scrot-docs-testing-window-interop-tests.png b/src/main/docs/assets/scrot-docs-testing-window-interop-tests.png new file mode 100644 index 0000000..746e87d Binary files /dev/null and b/src/main/docs/assets/scrot-docs-testing-window-interop-tests.png differ diff --git a/src/main/docs/assets/scrot-docs-testing-window-unit-tests.png b/src/main/docs/assets/scrot-docs-testing-window-unit-tests.png new file mode 100644 index 0000000..24c2099 Binary files /dev/null and b/src/main/docs/assets/scrot-docs-testing-window-unit-tests.png differ diff --git a/src/main/docs/book.json b/src/main/docs/book.json new file mode 100644 index 0000000..1bee44b --- /dev/null +++ b/src/main/docs/book.json @@ -0,0 +1,25 @@ +{ + "gitbook": "3.2.2", + "title": "IceCore Hashids", + "description": "A lightweight generator for short, unique, non-sequential and decodable Hashids from non-negative numbers.", + "author": "Arctic Ice Studio", + "plugins": ["anchorjs", "github", "toggle-chapters", "toggle-headers", "-sharing"], + "pluginsConfig": { + "github": { + "url": "https://github.com/arcticicestudio/icecore-hashids" + }, + "lunr": { + "maxIndexSize": 200000 + }, + "theme-default": { + "styles": { + "website": "./styles/website.css" + } + } + }, + "variables": { + "api": { + "version": "0.4.0" + } + } +} diff --git a/src/main/docs/development/building.md b/src/main/docs/development/building.md new file mode 100644 index 0000000..dc75d58 --- /dev/null +++ b/src/main/docs/development/building.md @@ -0,0 +1,45 @@ +# Building + +> Please make sure to complete the [requirement](requirements.md) steps first in order to build the project! + +Continuous integration builds are running at [Travis CI][travis-ci] and [Circle CI][circle-ci]. + +## From Source + +`icecore-hashids` can be build by running +```sh +mvn clean compile +``` + +To install `icecore-hashids` into your local repository without GPG signing run +```sh +mvn clean install +``` + +Signed artifacts can be installed by using the `sign-gpg` profile and providing the `gpg.keyname` property: +```sh +mvn clean install -Dgpg.keyname= +``` + +All output will be placed in the `target` directory. + +## Documentations + +In order to build the documentation install the currently tested and supported minimum version [NodeJS 6.5][nodejs] or higher. It comes prebundled with the package manager `npm` which can be used from the CLI. + +This documentation can be build by runnning +```sh +npm run docs:build +``` +from within the project root to bootstrap the build toolchain and install all dependencies. + +The output will be placed in the `target/docs` directory. + +To start the local hot reload server with browser live reload, using the default port `4000`, run +```sh +npm run docs:serve +``` + +[circle-ci]: https://circleci.com/bb/arcticicestudio/icecore-hashids +[nodejs]: https://nodejs.org/en/download/current +[travis-ci]: https://travis-ci.org/arcticicestudio/icecore-hashids diff --git a/src/main/docs/development/changelog.md b/src/main/docs/development/changelog.md new file mode 100644 index 0000000..4ec514c --- /dev/null +++ b/src/main/docs/development/changelog.md @@ -0,0 +1 @@ +{% include "git+https://github.com/arcticicestudio/icecore-hashids.git/CHANGELOG.md" %} diff --git a/src/main/docs/development/code-of-conduct.md b/src/main/docs/development/code-of-conduct.md new file mode 100644 index 0000000..766f09f --- /dev/null +++ b/src/main/docs/development/code-of-conduct.md @@ -0,0 +1 @@ +{% include "git+https://github.com/arcticicestudio/icecore-hashids.git/CODE_OF_CONDUCT.md" %} diff --git a/src/main/docs/development/contributing.md b/src/main/docs/development/contributing.md new file mode 100644 index 0000000..19fc635 --- /dev/null +++ b/src/main/docs/development/contributing.md @@ -0,0 +1 @@ +{% include "git+https://github.com/arcticicestudio/icecore-hashids.git/CONTRIBUTING.md" %} diff --git a/src/main/docs/development/testing.md b/src/main/docs/development/testing.md new file mode 100644 index 0000000..5c8660e --- /dev/null +++ b/src/main/docs/development/testing.md @@ -0,0 +1,70 @@ +# Testing + +> Please make sure to complete the [requirement](requirements.md) steps first in order to run the tests! + +Continuous integration builds are running at [Travis CI][travis-ci] and [Circle CI][circle-ci]. The code coverage results are processed via [Codecov][codecov]. + +To start all test types at once run + +```sh +mvn clean test +``` + +## Unit Tests + +The unit tests are implemented using [JUnit 4][junit] and can either be run via Maven + +```sh +mvn clean test -Dtest=HashidsTest +``` + +or by using the provided *Unit Tests* [run/debug configuration][intellij-run-config] from within IntelliJ IDEA. + +![][scrot-run-unit-tests] + +The unit tests can also be run in debug mode by using the bug icon next to the run configuration name or from the menu via *Run* > *Debug* > *Unit Tests*. + +The [test runner tab][intellij-test-runner-tab] will automatically toggle to show the test results. + +![][scrot-window-unit-tests] + +## Interoperability Tests + +The interoperability tests ensure that the library provides the same results as the reference algorithm implementation [*hashids.js*][hashids-js] by using + +* the Maven plugin [frontend-maven-plugin][maven-plugin-frontend] to locally download [NodeJS][nodejs] and install the required *devDependencies* via [*npm*][npm] +* the Java 8 JavaScript engine [Nashorn][nashorn] to execute the downloaded *hashids.js* script + +and then comparing the results of both libraries. + +The tests can either be run via Maven + +```sh +mvn clean test -P node -Dtest=InteropHashidsTest +``` + +or by using the provided *Interop Tests* [run/debug configuration][intellij-run-config] from within IntelliJ IDEA. + +![][scrot-run-interop-tests] + +The tests can also be run in debug mode by using the bug icon next to the run configuration name or from the menu via *Run* > *Debug* > *Interop Tests*. + +The [test runner tab][intellij-test-runner-tab] will automatically toggle to show the test results. + +![][scrot-window-interop-tests] + +[circle-ci]: https://circleci.com/bb/arcticicestudio/icecore-hashids +[codecov]: https://codecov.io/gh/arcticicestudio/icecore-hashids +[hashids-js]: https://github.com/ivanakimov/hashids.js +[intellij-test-runner-tab]: https://www.jetbrains.com/help/idea/test-runner-tab.html +[intellij-run-config]: https://www.jetbrains.com/help/pycharm/run-debug-configurations.html +[junit]: http://junit.org/junit4 +[maven-plugin-frontend]: https://github.com/eirslett/frontend-maven-plugin +[nashorn]: https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn +[nodejs]: https://nodejs.org +[npm]: https://www.npmjs.com +[scrot-run-interop-tests]: ../assets/scrot-docs-testing-config-interop-tests.png +[scrot-run-unit-tests]: ../assets/scrot-docs-testing-config-unit-tests.png +[scrot-window-interop-tests]: ../assets/scrot-docs-testing-window-interop-tests.png +[scrot-window-unit-tests]: ../assets/scrot-docs-testing-window-unit-tests.png +[travis-ci]: https://travis-ci.org/arcticicestudio/icecore-hashids diff --git a/src/main/docs/getting-started/installation.md b/src/main/docs/getting-started/installation.md new file mode 100644 index 0000000..2de5cb4 --- /dev/null +++ b/src/main/docs/getting-started/installation.md @@ -0,0 +1,31 @@ +# Installation + +To use `icecore-hashids` in your project it must be on the application's runtime classpath. + +[Download the latest version][latest-version] or add it as dependency to your build configuration from [Central Repository][central-repository] and [jCenter][jCenter]. + +Development snapshots are available via [OSS Sonatype][oss-sonatype] and [JFrog OSS Artifactory][artifactory]. + +###### Maven +```xml + + com.arcticicestudio + icecore-hashids + 0.4.0 + +``` + +###### Gradle +```java +compile(group: 'com.arcticicestudio', name: 'icecore-hashids', version: '0.4.0') +``` + +###### Ivy +```xml + +``` +[artifactory]: https://oss.jfrog.org/artifactory/webapp/#/artifacts/browse/tree/General/oss-snapshot-local/com/arcticicestudio/icecore-hashids +[central-repository]: https://oss.sonatype.org/content/repositories/releases/com/arcticicestudio/icecore-hashids +[jcenter]:https://bintray.com/arcticicestudio/IceCore/icecore-hashids +[latest-version]: https://github.com/arcticicestudio/icecore-hashids/releases/latest +[oss-sonatype]: https://oss.sonatype.org/content/repositories/snapshots/com/arcticicestudio/icecore-hashids diff --git a/src/main/docs/getting-started/requirements.md b/src/main/docs/getting-started/requirements.md new file mode 100644 index 0000000..1a8e6cc --- /dev/null +++ b/src/main/docs/getting-started/requirements.md @@ -0,0 +1,12 @@ +# Requirements + +### Java +IceCore Hashids requires [Java 8][java] or higher. Currently tested and supported SDKs are [Oracle JDK/JRE 8][oracle-java-8] and [OpenJDK 8][openjdk-8]. + +### Apache Maven +IceCore Hashids is compatible with [Apache Maven 3.0.4][maven] or higher. + +[java]: https://java.com +[maven]: https://maven.apache.org +[openjdk-8]: http://openjdk.java.net/projects/jdk8 +[oracle-java-8]: http://www.oracle.com/technetwork/java/javase/overview/java8-2100321.html diff --git a/src/main/docs/styles/website.css b/src/main/docs/styles/website.css new file mode 100644 index 0000000..74e6b4b --- /dev/null +++ b/src/main/docs/styles/website.css @@ -0,0 +1,24 @@ +/* Remove the sidebar link to the hosted gitbook service */ +.gitbook-link { + display: none !important; +} + +/* Sidebar links */ +.book .book-summary nav ul.summary li a { + color: #444444; + text-decoration: none; +} + +.book .book-summary nav ul.summary li a:hover, +.book .book-summary nav ul.summary li a:focus { + color: #000000; +} + +.book .book-summary nav ul.summary li.active > a { + color: #008cff; +} + +/* anchor.js hover icons */ +.book .book-body .page-wrapper .page-inner section.normal a.anchorjs-link { + color: #008cff; +} diff --git a/src/main/java/com/arcticicestudio/icecore/hashids/Hashid.java b/src/main/java/com/arcticicestudio/icecore/hashids/Hashid.java deleted file mode 100644 index c2a272b..0000000 --- a/src/main/java/com/arcticicestudio/icecore/hashids/Hashid.java +++ /dev/null @@ -1,90 +0,0 @@ -/* -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -title Hashid + -project icecore-hashids + -repository https://github.com/arcticicestudio/icecore-hashids + -author Arctic Ice Studio + -email development@arcticicestudio.com + -copyright Copyright (C) 2017 + -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -*/ -package com.arcticicestudio.icecore.hashids; - -import java.util.Arrays; -import java.util.Objects; - -/** - * Represents a hashid holding all numbers and the encoded string. - *

- * This class is immutable. - *

- * - * @author Arctic Ice Studio <development@arcticicestudio.com> - * @see Hashids - * @since 0.1.0 - */ -public final class Hashid { - - private final long[] numbers; - private final String hash; - - /** - * A empty hashid with no numbers and no hash. - */ - public static final Hashid EMPTY = new Hashid(new long[0], ""); - - /** - * Constructs a new hashid with the specified numbers and hash. - * - * @param longs the numbers of this hashid - * @param hash the hash of this hashid - */ - public Hashid(long[] longs, String hash) { - this.numbers = longs; - this.hash = hash; - } - - /** - * All numbers of this hashid. - * - * @return a new array with all numbers of this hashid - */ - public long[] numbers() { - long[] values = new long[numbers.length]; - System.arraycopy(numbers, 0, values, 0, numbers.length); - return values; - } - - /** - * Returns the hash of this hashid. - * - * @return the hash of this hashid - */ - @Override - public String toString() { - return hash; - } - - @Override - public int hashCode() { - int hashCode = 7; - hashCode = 97 * hashCode + Arrays.hashCode(this.numbers); - hashCode = 97 * hashCode + Objects.hashCode(this.hash); - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final Hashid other = (Hashid) obj; - return Arrays.equals(this.numbers, other.numbers) && Objects.equals(this.hash, other.hash); - } -} diff --git a/src/main/java/com/arcticicestudio/icecore/hashids/Hashids.java b/src/main/java/com/arcticicestudio/icecore/hashids/Hashids.java index dc1dab9..5d640c1 100644 --- a/src/main/java/com/arcticicestudio/icecore/hashids/Hashids.java +++ b/src/main/java/com/arcticicestudio/icecore/hashids/Hashids.java @@ -1,630 +1,758 @@ -/* -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -title Hashids Public API + -project icecore-hashids + -repository https://github.com/arcticicestudio/icecore-hashids + -author Arctic Ice Studio + -email development@arcticicestudio.com + -copyright Copyright (C) 2017 + -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -*/ package com.arcticicestudio.icecore.hashids; -import java.util.ArrayList; -import java.util.List; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.reducing; +import static java.util.stream.Collectors.toSet; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; /** - * Generates short, unique, non-sequential and decodable hashids from positive unsigned (long) integer numbers. - *

- * Serves as the entry point to the - * IceCore Hashids public API. - *

- *

- * The Hashids's {@code salt} is used as a secret to generate unique strings using a given {@code alphabet}. - * Generated strings can have a {@code minHashLength}. - *

- *

- * If used to obfuscates identities, make sure to not expose your {@code salt}, {@code alphabet} nor - * {@code separators} to a client, client-side is not safe. - *

- *

- * Only positive numbers are supported. - * All methods in this class will throw an {@link IllegalArgumentException} if a negative number is given. - * To use negative numbers prepend a hyphen character to the hash string. - * - * Note that this method is limited to single number hashes only and breaks the official Hashids definition! - * - * Example: - *

- *     Hashids hashids = new Hashids("salt");
- *     long number = -1234567890;
- *     String enc = (Math.abs(number) != number ? "-" : "") + hashids.encodeToString(Math.abs(number));
- *     long dec = enc.startsWith("-") ? hashids.decodeLongNumbers(enc.substring(1))[0] : hashids.decodeLongNumbers(enc)[0];
- *   
+ * A lightweight generator for short, unique, case-sensitive and non-sequential decodable hashes from positive unsigned (long) integer numbers. * - *

- * {@code Hashids} instances are thread-safe. - *

+ *

Implementation of the Hashids algorithm: + *

    + *
  • Generation of short, unique, case-sensitive and non-sequential decodable hashes of natural numbers
  • + *
  • Additional entropy through salt usage
  • + *
  • Configurable minimum hash size
  • + *
  • Combining of several numbers to one hash
  • + *
  • Deterministic hash computation given the same input and parametrization/configuration
  • + *
+ * + *

An instance with the default interoperable configurations is available through the {@link Hashids#Hashids() default constructor}. + * + *

Configured instances can be created via {@link Hashids.Builder} to set a {@link Hashids.Builder#salt salt}, define a + * ·{@link Hashids.Builder#minLength minimum hash length} or use a {@link Hashids.Builder#alphabet custom alphabet}. Optional {@link HashidsFeature features} + * can be enabled via the {@link Hashids.Builder#features features(HashidsFeature)} method. Please note that most features will break the + * interoperability with the origin algorithm implementation! + * + *

Instances of this class are thread-safe. * * @author Arctic Ice Studio <development@arcticicestudio.com> - * @see IceCore Hashids - * @see Hashids - * @version 0.3.0 + * @see IceCore Hashids GitHub Repository + * @version 0.4.0 */ public final class Hashids { - /** - * Holds the default alphabet. - */ - public static final String DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + private static final String VERSION = "0.4.0"; + private static final String VERSION_INTEROP = "1.0.0"; - /** - * Holds the default separators. - *

- * Used to prevent the generation of strings that contain bad, offensive and rude words. - *

- */ - public static final String DEFAULT_SEPARATORS = "cfhistuCFHISTU"; + private static final int LOTTERY_MOD = 100; + private static final double GUARD_THRESHOLD = 12; + private static final double SEPARATOR_THRESHOLD = 3.5; + private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}"); + private static final Pattern HEX_FORMAT_PATTERN = Pattern.compile("^[0-9a-fA-F]+$"); /** - * Holds the maximum number value. - *

- * This limit is mandatory in order to ensure interoperability. - *

- *

- * JavaScript equivalents used in hashids.js: - *

    - *
  • {@code 9_007_199_254_740_991}
  • - *
  • {@code 2^53-1}
  • - *
  • {@code Number.MAX_VALUE-1}
  • - *
- */ - public static final long MAX_NUMBER_VALUE = 9_007_199_254_740_992L - 1; - - /** - * The version of the public API. + * The maximum number size to ensure interoperability with the origin algorithm implementation hashids + * .js. + * + *

This limit is based on the + * JavaScript Number.MAX_SAFE_INTEGER + * constant which represents the maximum safe integer. * - * @since 0.3.0 + *

The limit can be canceled by enabling the {@link HashidsFeature#NO_MAX_INTEROP_NUMBER_SIZE NO_MAX_INTEROP_NUMBER_SIZE} feature. Please + * note that this will break the interoperability with the origin algorithm implementation! + * + * @see ECMAScript 2016 Language Specification */ - public static final String VERSION = "0.3.0"; - - private static final int GUARD_DIV = 12; - private static final int MIN_ALPHABET_LENGTH = 16; - private static final double SEP_DIV = 3.5; - private static final Pattern PATTERN_ENCODE_HEX = Pattern.compile("^[0-9a-fA-F]+$"); - private static final Pattern PATTERN_ALPHABET_REPLACE = Pattern.compile("\\s+"); - - private final String alphabet; - private final String guards; - private final int minHashLength; - private final String salt; - private final String separators; + public static final long MAX_INTEROP_NUMBER_SIZE = (long) Math.pow(2, 53) - 1; /** - * Constructs a new Hashid with all default values. - *

- *

    - *
  • no salt
  • - *
  • no minimal hash length
  • - *
  • {@link #DEFAULT_ALPHABET}
  • - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
+ * The minimum length of the hash generation alphabet. */ - public Hashids() { - this("", 0); - } + private static final int MIN_ALPHABET_LENGTH = 16; /** - * Constructs a new Hashid with the specified salt and the default minimal hash length, alphabet and separators. - *

- *

    - *
  • no minimal hash length
  • - *
  • {@link #DEFAULT_ALPHABET}
  • - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
+ * The default alphabet for the hash generation. * - * @param salt the salt value + *

A customized alphabet can be set via the instance builder {@link Hashids.Builder#alphabet alphabet} method. */ - public Hashids(String salt) { - this(salt, 0); - } + public static final char[] DEFAULT_ALPHABET = { + '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', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' + }; /** - * Constructs a new Hashid with the specified salt and the minimal hash length and the default alphabet and - * separators. - *

- *

    - *
  • {@link #DEFAULT_ALPHABET}
  • - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
- * - * @param salt the salt value - * @param minHashLength the minimal length of the hash + * The default separators to prevent bad, offensive and rude words in generated hashes. */ - public Hashids(String salt, int minHashLength) { - this(salt, minHashLength, DEFAULT_ALPHABET); - } + private static final char[] DEFAULT_SEPARATORS = {'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U'}; + + private final char[] alphabet; + private final char[] separators; + private final char[] salt; + private final char[] guards; + private final int minLength; + private final Set separatorsSet; /** - * Constructs a new Hashid with the specified salt and alphabet and the default minimal hash length and separators. - *

- *

    - *
  • no minimal hash length
  • - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
+ * A set of all enabled {@link HashidsFeature features}. * - * @param salt the salt value - * @param alphabet the alphabet value + * @since 0.4.0 */ - public Hashids(String salt, String alphabet) { - this(salt, 0, alphabet); - } + private final EnumSet features; /** - * Constructs a new Hashid with the specified salt, minimal hash length and alphabet and the default separators. - *

- *

    - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
- * - * @param salt the salt value - * @param minHashLength the minimal length of the hash - * @param alphabet the alphabet value + * Constructs a new instance without a salt, no minimum hash length, the {@link #DEFAULT_ALPHABET default alphabet} and no enabled + * {@link HashidsFeature features}. */ - public Hashids(String salt, int minHashLength, String alphabet) { - this(salt, minHashLength, alphabet, DEFAULT_SEPARATORS); + public Hashids() { + this(new char[0], 0, DEFAULT_ALPHABET, EnumSet.noneOf(HashidsFeature.class)); } /** - * Constructs a new Hashid with the specified salt, minimal hash length, alphabet and separators. + * Constructs a new instance with the given configuration and enabled {@link HashidsFeature features}. * - * @param salt the salt value - * @param minHashLength the minimal length of the hash - * @param alphabet the alphabet value - * @param separators the chained separators + * @param salt the salt to be used as entropy + * @param minLength the minimum hash length + * @param alphabet the alphabet to be used for the hash generation + * @param features the set of enabled Hashids features */ - public Hashids(String salt, int minHashLength, String alphabet, String separators) { - if (alphabet == null) { - throw new IllegalArgumentException("alphabet was null"); - } - if (alphabet.length() == 0) { - throw new IllegalArgumentException("alphabet was empty"); - } - - this.salt = salt == null ? "" : salt; - this.minHashLength = minHashLength < 0 ? 0 : minHashLength; - - StringBuilder uniqueAlphabet = new StringBuilder(); - for (int idx = 0; idx < alphabet.length(); idx++) { - if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(idx))) == -1) { - uniqueAlphabet.append(alphabet.charAt(idx)); - } - } - - if (uniqueAlphabet.length() < MIN_ALPHABET_LENGTH) { - throw new IllegalArgumentException( - "Alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters" - ); - } - - if (uniqueAlphabet.toString().contains(" ")) { - throw new IllegalArgumentException("Alphabet cannot contains spaces"); - } - - /* - * Separators should contain only characters present in alphabet. - * Alphabet should not contain separators. - */ - StringBuilder seps = new StringBuilder(separators == null ? "" : separators); - for (int sepIdx = 0; sepIdx < seps.length(); sepIdx++) { - int alphaIdx = uniqueAlphabet.indexOf(String.valueOf(seps.charAt(sepIdx))); - if (alphaIdx == -1) { - seps.replace(sepIdx, sepIdx + 1, " "); - } else { - uniqueAlphabet.replace(alphaIdx, alphaIdx + 1, " "); - } - } - - uniqueAlphabet.replace(0, uniqueAlphabet.length(), PATTERN_ALPHABET_REPLACE.matcher(uniqueAlphabet).replaceAll("")); - seps.replace(0, seps.length(), PATTERN_ALPHABET_REPLACE.matcher(seps).replaceAll("")); - seps.replace(0, seps.length(), consistentShuffle(seps.toString(), this.salt)); - - if (isEmpty(seps.toString()) || ((float)uniqueAlphabet.length() / seps.length()) > SEP_DIV) { - int sepsLen = (int) Math.ceil(uniqueAlphabet.length() / SEP_DIV); - if (sepsLen == 1) { - sepsLen++; - } - if (sepsLen > seps.length()) { - int diff = sepsLen - seps.length(); - seps.append(uniqueAlphabet.substring(0, diff)); - uniqueAlphabet.replace(0, uniqueAlphabet.length(), uniqueAlphabet.substring(diff)); - } else { - seps.replace(0, seps.length(), seps.substring(0, sepsLen)); + private Hashids(final char[] salt, final int minLength, final char[] alphabet, final EnumSet features) { + this.minLength = minLength; + this.salt = Arrays.copyOf(salt, salt.length); + this.features = features; + char[] tmpSeparators = shuffle(filterSeparators(DEFAULT_SEPARATORS, alphabet), this.salt); + char[] tmpAlphabet = validateAndFilterAlphabet(alphabet, tmpSeparators); + + // Check the separator threshold + if (tmpSeparators.length == 0 || (tmpAlphabet.length / tmpSeparators.length) > SEPARATOR_THRESHOLD) { + final int minSeparatorsSize = (int) Math.ceil(tmpAlphabet.length / SEPARATOR_THRESHOLD); + // Check the minimum size of the separators + if (minSeparatorsSize > tmpSeparators.length) { + // Fill the separators from the alphabet + final int missingSeparators = minSeparatorsSize - tmpSeparators.length; + tmpSeparators = Arrays.copyOf(tmpSeparators, tmpSeparators.length + missingSeparators); + System.arraycopy(tmpAlphabet, 0, tmpSeparators, tmpSeparators.length - missingSeparators, missingSeparators); + tmpAlphabet = Arrays.copyOfRange(tmpAlphabet, missingSeparators, tmpAlphabet.length); } } - uniqueAlphabet.replace(0, uniqueAlphabet.length(), consistentShuffle(uniqueAlphabet.toString(), this.salt)); - int guardCount = (int) Math.ceil((double)uniqueAlphabet.length() / GUARD_DIV); + // Shuffle the current alphabet + shuffle(tmpAlphabet, this.salt); - if (uniqueAlphabet.length() < 3) { - guards = seps.substring(0, guardCount); - seps.replace(0, seps.length(), seps.substring(guardCount)); + // Check the guards + this.guards = new char[(int) Math.ceil(tmpAlphabet.length / GUARD_THRESHOLD)]; + if (alphabet.length < 3) { + System.arraycopy(tmpSeparators, 0, guards, 0, guards.length); + this.separators = Arrays.copyOfRange(tmpSeparators, guards.length, tmpSeparators.length); + this.alphabet = tmpAlphabet; } else { - guards = uniqueAlphabet.substring(0, guardCount); - uniqueAlphabet.replace(0, uniqueAlphabet.length(), uniqueAlphabet.substring(guardCount)); + System.arraycopy(tmpAlphabet, 0, guards, 0, guards.length); + this.separators = tmpSeparators; + this.alphabet = Arrays.copyOfRange(tmpAlphabet, guards.length, tmpAlphabet.length); } - this.alphabet = uniqueAlphabet.toString(); - this.separators = seps.toString(); + // Populate the separators set + this.separatorsSet = IntStream.range(0, separators.length) + .mapToObj(idx -> separators[idx]) + .collect(toSet()); } /** - * An immutable and reusable {@link Hashids} builder. - *

- * Each method returns a new builder instance. - *

- *

- * Defaults to - *

    - *
  • no salt
  • - *
  • no minimum hash length
  • - *
  • {@link #DEFAULT_ALPHABET}
  • - *
  • {@link #DEFAULT_SEPARATORS}
  • - *
+ * An immutable {@link Hashids} instance builder with optional {@link HashidsFeature features}. */ public static final class Builder { - private final String salt; - private final String alphabet; - private final String separators; - private final int minHashLength; + private char[] salt; + private int minLength; + private char[] alphabet; + + private EnumSet features; /** - * Create a new {@link Hashids} builder. + * Constructs a new instance without a salt, no minimum hash length, the {@link #DEFAULT_ALPHABET default alphabet} and no enabled + * {@link HashidsFeature features}. */ public Builder() { - this.salt = ""; + this.salt = new char[0]; this.alphabet = DEFAULT_ALPHABET; - this.separators = DEFAULT_SEPARATORS; - this.minHashLength = 0; - } - - private Builder(String salt, String alphabet, String separators, int minHashLength) { - this.salt = salt; - this.alphabet = alphabet; - this.separators = separators; - this.minHashLength = minHashLength; + this.minLength = 0; + this.features = EnumSet.noneOf(HashidsFeature.class); } /** - * Sets the salt string. + * Sets the salt to be used as entropy. + * + *

By default the salt is empty. * - * @param salt The string to use as salt - * @return The builder instance with the specified salt + * @param salt the salt to be used as entropy + * @return a new builder instance with the given salt */ - public Builder salt(String salt) { - return new Builder(salt, alphabet, separators, minHashLength); + public Builder salt(final String salt) { + this.salt = salt.toCharArray(); + return this; } /** - * Sets the custom alphabet string. + * Sets the minimum hash length. * - * @param alphabet The string to use as custom alphabet - * @return The builder instance with the specified custom alphabet + *

The default value is {@code 0}. + * + * @param minLength the minimum hash length + * @return a new builder instance with the given minimum hash length */ - public Builder alphabet(String alphabet) { - return new Builder(salt, alphabet, separators, minHashLength); + public Builder minLength(final int minLength) { + this.minLength = minLength; + return this; } /** - * Sets the custom separators string. + * Sets the alphabet to be used for the hash generation. + * + *

The default value is the {@link #DEFAULT_ALPHABET default alphabet}. * - * @param separators The string to use as custom alphabet - * @return The builder instance with the specified custom separators + * @param alphabet the alphabet to be used for the hash generation + * @return a new builder instance with the given alphabet */ - public Builder separators(String separators) { - return new Builder(salt, alphabet, separators, minHashLength); + public Builder alphabet(final String alphabet) { + this.alphabet = alphabet.toCharArray(); + return this; } /** - * Sets the minimum hash length. + * Enables the given instance {@link HashidsFeature features}. * - * @param minHashLength The minimum length of the hash - * @return The builder instance with the minimum hash length + *

By default no features are enabled. + * + * @param features the features to be enabled for this instance + * @return a new builder instance with the enabled features + * @since 0.4.0 */ - public Builder minHashLength(int minHashLength) { - return new Builder(salt, alphabet, separators, minHashLength); + public Builder features(final HashidsFeature... features) { + this.features.addAll(Arrays.asList(features)); + return this; } /** - * Builds the {@link Hashids}. + * Builds a new configured {@link Hashids} instance. * - * @return The {@link Hashids} instance + * @return a new configured instance */ public Hashids build() { - return new Hashids(salt, minHashLength, alphabet, separators); + return new Hashids(salt, minLength, alphabet, features); } } /** - * Encode number(s). + * Encodes the given positive numbers based on this instance configuration. + * + *

The given numbers size must not be larger than the {@link #MAX_INTEROP_NUMBER_SIZE maximum interoperability size} unless the + * {@link HashidsFeature#NO_MAX_INTEROP_NUMBER_SIZE NO_MAX_INTEROP_NUMBER_SIZE} feature is enabled. Please + * note that this will break the interoperability with the origin algorithm implementation! * - * @param numbers the number(s) to encode - * @return the Hashid instance with the number(s) and the encoded string + * @param numbers the positive numbers to be encoded + * @return the resultant hash of the encoding of the numbers, empty otherwise + * @throws IllegalArgumentException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the total length of + * numbers is zero, any numbers size is invalid or larger than the {@link #MAX_INTEROP_NUMBER_SIZE maximum interoperability size} + * @throws NullPointerException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the given numbers are {@code null} */ - public Hashid encode(long... numbers) { - if (numbers.length == 0) { - return Hashid.EMPTY; + public String encode(final long... numbers) { + if (numbers == null) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new NullPointerException("numbers must not be null!"); + } + return ""; } - return doEncode(numbers); - } - /** - * Encode number(s) to string. - * - * @param numbers the number(s) to encode - * @return the encoded string - */ - public String encodeToString(long... numbers) { if (numbers.length == 0) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("length of numbers must be greater than or equal to one!"); + } return ""; } - return encode(numbers).toString(); + + for (long number : numbers) { + if (number < 0) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("number must not be less than zero: " + number); + } + return ""; + } + + if (number > MAX_INTEROP_NUMBER_SIZE && !features.contains(HashidsFeature.NO_MAX_INTEROP_NUMBER_SIZE)) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("number must not exceed the maximum number size: " + number + " > " + MAX_INTEROP_NUMBER_SIZE); + } + return ""; + } + } + + final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length); + + // Determine the lottery number + final long lotteryId = LongStream.range(0, numbers.length) + .reduce(0, (state, idx) -> state + numbers[(int) idx] % (idx + LOTTERY_MOD)); + final char lottery = currentAlphabet[(int) (lotteryId % currentAlphabet.length)]; + + // Encode each number + final StringBuilder global = new StringBuilder(); + IntStream.range(0, numbers.length) + .forEach(idx -> { + deriveNewAlphabet(currentAlphabet, salt, lottery); + final int initialLength = global.length(); + transform(numbers[idx], currentAlphabet, global, initialLength); + // Append the separator + if (idx + 1 < numbers.length) { + long n = numbers[idx] % (global.charAt(initialLength) + idx); + global.append(separators[(int) (n % separators.length)]); + } + }); + + // Prepend the lottery + global.insert(0, lottery); + + // Add the guards if there is any space left + if (minLength > global.length()) { + int guardIdx = (int) ((lotteryId + lottery) % guards.length); + global.insert(0, guards[guardIdx]); + if (minLength > global.length()) { + guardIdx = (int) ((lotteryId + global.charAt(2)) % guards.length); + global.append(guards[guardIdx]); + } + } + + // Add the necessary padding + int paddingLeft = minLength - global.length(); + while (paddingLeft > 0) { + shuffle(currentAlphabet, Arrays.copyOf(currentAlphabet, currentAlphabet.length)); + + final int alphabetHalfSize = currentAlphabet.length / 2; + final int initialSize = global.length(); + if (paddingLeft > currentAlphabet.length) { + int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); + + global.insert(0, currentAlphabet, alphabetHalfSize, offset); + global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize); + + paddingLeft -= currentAlphabet.length; + } else { + // Calculate the excess + final int excess = currentAlphabet.length + global.length() - minLength; + final int secondHalfStartOffset = alphabetHalfSize + Math.floorDiv(excess, 2); + final int secondHalfLength = currentAlphabet.length - secondHalfStartOffset; + final int firstHalfLength = paddingLeft - secondHalfLength; + + global.insert(0, currentAlphabet, secondHalfStartOffset, secondHalfLength); + global.insert(secondHalfLength + initialSize, currentAlphabet, 0, firstHalfLength); + + paddingLeft = 0; + } + } + + return global.toString(); } /** - * Encode number(s) to string. + * Encodes the given numbers in hexadecimal format based on this instance configuration. * - * @param numbers the number(s) to encode - * @return the encoded string + *

The given hexadecimal numbers must not be prefixed ({@code 0x} or {@code 0X}) unless the + * {@link HashidsFeature#ALLOW_HEXADECIMAL_NUMBER_PREFIX ALLOW_HEXADECIMAL_NUMBER_PREFIX} feature is enabled. Please + * note that this will break the interoperability with the origin algorithm implementation! + * + * @param hexNumbers the numbers in hexadecimal format to be encoded + * @return the resultant hash of the encoding of the hexadecimal numbers, empty otherwise + * @throws IllegalArgumentException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and any of the numbers is invalid + * @throws NullPointerException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the given numbers are {@code null} */ - public String encodeToString(int... numbers) { - if (numbers.length == 0) { + public String encodeHex(final String hexNumbers) { + if (hexNumbers == null) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new NullPointerException("hexNumbers must not be null!"); + } + return ""; + } + + final String hex; + if (hexNumbers.startsWith("0x") || hexNumbers.startsWith("0X")) { + if (features.contains(HashidsFeature.ALLOW_HEXADECIMAL_NUMBER_PREFIX)) { + hex = hexNumbers.substring(2); + } else { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("numbers must not contain a hexadecimal prefix: " + hexNumbers.substring(0, 2)); + } + return ""; + } + } else { + hex = hexNumbers; + } + + if (!HEX_FORMAT_PATTERN.matcher(hex).matches()) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("hexNumbers must be a valid hexadecimal number!"); + } return ""; } - long[] longs = new long[numbers.length]; - for (int idx = 0; idx < numbers.length; idx++) { - longs[idx] = numbers[idx]; + // Resolve the associated long value and encode it + LongStream values = LongStream.empty(); + final Matcher matcher = HEX_VALUES_PATTERN.matcher(hex); + while (matcher.find()) { + final long value = new BigInteger("1" + matcher.group(), 16).longValue(); + values = LongStream.concat(values, LongStream.of(value)); } - return doEncode(longs).toString(); + + return encode(values.toArray()); } /** - * Encode an hexadecimal string to string. + * Decodes the given hash into its numeric representation based on this instance configuration. * - * @param hex the hexadecimal string to encode - * @return the encoded string + * @param hash the hash to be decoded + * @return an array of long values with each numeric number present in the hash, empty otherwise + * @throws IllegalArgumentException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the hash is invalid + * @throws NullPointerException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the given hash is {@code null} */ - public String encodeHex(String hex) { - if (!PATTERN_ENCODE_HEX.matcher(hex).matches()) { - throw new IllegalArgumentException(String.format("%s is not a hex string", hex)); + public long[] decode(final String hash) { + if (hash == null) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new NullPointerException("hash must not be null!"); + } + return new long[0]; } - Matcher matcher = Pattern.compile("[\\w\\W]{1,12}").matcher(hex); - List matched = new ArrayList<>(); - while (matcher.find()) { - matched.add(Long.parseLong("1" + matcher.group(), 16)); + + // Validate that the hash only consists of valid characters + final Set validInputChars = new HashSet<>(alphabet.length + guards.length + separators.length); + Stream.of(alphabet, guards, separators) + .forEach(chars -> IntStream.range(0, chars.length) + .mapToObj(idx -> chars[idx]) + .forEach(validInputChars::add)); + if (!IntStream.range(0, hash.length()).allMatch(idx -> validInputChars.contains(hash.charAt(idx)))) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("invalid hash: " + hash); + } + return new long[0]; } - return doEncode(toArray(matched)).toString(); + + // Create a set of the guards and count the total amount + final Set guardsSet = IntStream.range(0, guards.length) + .mapToObj(idx -> guards[idx]) + .collect(toSet()); + final int[] guardsIdx = IntStream.range(0, hash.length()) + .filter(idx -> guardsSet.contains(hash.charAt(idx))) + .toArray(); + + // Calculate the start- and end index based on the guards count + final int startIdx; + final int endIdx; + if (guardsIdx.length > 0) { + startIdx = guardsIdx[0] + 1; + endIdx = guardsIdx.length > 1 ? guardsIdx[1] : hash.length(); + } else { + startIdx = 0; + endIdx = hash.length(); + } + + LongStream decoded = LongStream.empty(); + if (hash.length() > 0) { + final char lottery = hash.charAt(startIdx); + + // Create the initial accumulation string + final int length = hash.length() - guardsIdx.length - 1; + StringBuilder block = new StringBuilder(length); + + // Create the base salt + final char[] decodeSalt = new char[alphabet.length]; + decodeSalt[0] = lottery; + final int saltLength = salt.length >= alphabet.length ? alphabet.length - 1 : salt.length; + System.arraycopy(salt, 0, decodeSalt, 1, saltLength); + final int saltLeft = alphabet.length - saltLength - 1; + + final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length); + + for (int i = startIdx + 1; i < endIdx; i++) { + if (!separatorsSet.contains(hash.charAt(i))) { + block.append(hash.charAt(i)); + if (i < endIdx - 1) { + continue; + } + } + + if (block.length() > 0) { + // Create the salt + if (saltLeft > 0) { + System.arraycopy(currentAlphabet, 0, decodeSalt, alphabet.length - saltLeft, saltLeft); + } + + // Prepend the decoded value and create a new block + shuffle(currentAlphabet, decodeSalt); + final long number = transform(block.toString().toCharArray(), currentAlphabet); + decoded = LongStream.concat(decoded, LongStream.of(number)); + block = new StringBuilder(length); + } + } + } + + final long[] decodedValue = decoded.toArray(); + if (!Objects.equals(hash, encode(decodedValue))) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new IllegalArgumentException("invalid hash: " + hash); + } + return new long[0]; + } + + return decodedValue; } /** - * Decode an encoded string. + * Decodes the given hash into its hexadecimal representation based on this instance configuration. * - * @param hash the encoded string - * @return the Hashid instance with the decoded hash and decoded number(s) + * @param hash the hash to be decoded + * @return the hexadecimal representation of the hash values with each numeric number present in the hash, empty otherwise + * @throws NullPointerException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the given hash is {@code null} */ - public Hashid decode(String hash) { - if (isEmpty(hash)) { - return Hashid.EMPTY; + public String decodeHex(final String hash) { + if (hash == null) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new NullPointerException("hash must not be null!"); + } + return ""; } - return doDecode(hash, alphabet); + + final StringBuilder sb = new StringBuilder(); + Arrays.stream(decode(hash)) + .mapToObj(Long::toHexString) + .forEach(hex -> sb.append(hex, 1, hex.length())); + return sb.toString(); } /** - * Decode an encoded string to long numbers. + * Decodes the given valid hash into its single numeric representation based on this instance configuration. * - * @param hash the encoded string - * @return the decoded long numbers + *

Simplifies the use-case where the amount of resulting numbers is known before to handle the return value as single value instead of an array. + * The given hash must resolve into a one number only, otherwise see the {@link #decode(String) decode} method. + * + * @param hash the valid hash to be decoded + * @return the decoded number if the given hash is valid, empty otherwise + * @throws IllegalArgumentException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the hash is invalid + * @throws NullPointerException if the {@link HashidsFeature#EXCEPTION_HANDLING EXCEPTION_HANDLING} feature is enabled and the given hash is {@code null} + * @since 0.4.0 */ - public long[] decodeLongNumbers(String hash) { - if (isEmpty(hash)) { - return new long[0]; + public Optional decodeOne(final String hash) { + if (hash == null) { + if (features.contains(HashidsFeature.EXCEPTION_HANDLING)) { + throw new NullPointerException("hash must not be null!"); + } + return Optional.empty(); } - return doDecode(hash, alphabet).numbers(); + + long[] decoded = decode(hash); + return decoded.length == 1 ? Optional.of(decoded[0]) : Optional.empty(); } /** - * Decode an encoded string to integer numbers. + * Returns the version of the public API. * - * @param hash the encoded string - * @return the decoded integer numbers - * @throws IllegalArgumentException if the decoded number is out of the integer minimal- or maximal range + * @return the version of the public API + * @see Arctic Versioning Specification + * @see Semantic Versioning Specification + * @since 0.2.0 */ - public int[] decodeIntegerNumbers(String hash) { - if (isEmpty(hash)) { - return new int[0]; - } - long[] numbers = doDecode(hash, alphabet).numbers(); - int[] ints = new int[numbers.length]; - for (int idx = 0; idx < numbers.length; idx++) { - long number = numbers[idx]; - if (number < Integer.MIN_VALUE || number > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Number out of range"); - } - ints[idx] = (int) number; - } - return ints; + public static String getVersion() { + return VERSION; } /** - * Decode an string to hexadecimal numbers. + * Returns the version of the original algorithm implementation hashids + * .js with which the public API is interoperable. * - * @param hash the encoded string - * @return the decoded hexadecimal numbers string + * @return the version of the original algorithm implementation with which the public API is interoperable + * @see Hashids Compatibility + * @since 0.4.0 */ - public String decodeHex(String hash) { - StringBuilder sb = new StringBuilder(); - long[] numbers = decodeLongNumbers(hash); - for (long number : numbers) { - sb.append(Long.toHexString(number).substring(1)); - } - return sb.toString(); + public static String getInteropVersion() { + return VERSION_INTEROP; } - private Hashid doEncode(long... numbers) { - int numberHashInt = 0; - for (int idx = 0; idx < numbers.length; idx++) { - if (numbers[idx] < 0 || numbers[idx] > MAX_NUMBER_VALUE) { - throw new IllegalArgumentException("Number out of range"); - } - numberHashInt += numbers[idx] % (idx + 100); - } - String decodeAlphabet = alphabet; - final char lottery = decodeAlphabet.charAt(numberHashInt % decodeAlphabet.length()); - StringBuilder result = new StringBuilder(String.valueOf(lottery)); - - String buffer; - int sepsIdx, guardIdx; - for (int idx = 0; idx < numbers.length; idx++) { - long num = numbers[idx]; - buffer = lottery + salt + decodeAlphabet; - - decodeAlphabet = consistentShuffle(decodeAlphabet, buffer.substring(0, decodeAlphabet.length())); - final String last = hash(num, decodeAlphabet); - result.append(last); - - if (idx + 1 < numbers.length) { - num %= ((int) last.charAt(0) + idx); - sepsIdx = (int) (num % separators.length()); - result.append(separators.charAt(sepsIdx)); - } - } - - if (result.length() < minHashLength) { - guardIdx = (numberHashInt + (int) (result.charAt(0))) % guards.length(); - char guard = guards.charAt(guardIdx); - result.insert(0, guard); - - if (result.length() < minHashLength) { - guardIdx = (numberHashInt + (int) (result.charAt(2))) % guards.length(); - guard = guards.charAt(guardIdx); - result.append(guard); - } + @Override + public boolean equals(final Object otherObject) { + if (null == otherObject) { + return false; } - - final int halfLen = decodeAlphabet.length() / 2; - while (result.length() < minHashLength) { - decodeAlphabet = consistentShuffle(decodeAlphabet, decodeAlphabet); - result.insert(0, decodeAlphabet.substring(halfLen)).append(decodeAlphabet.substring(0, halfLen)); - final int excess = result.length() - minHashLength; - if (excess > 0) { - int startPos = excess / 2; - result.replace(0, result.length(), result.substring(startPos, startPos + minHashLength)); - } + if (this.getClass() != otherObject.getClass()) { + return false; } - return new Hashid(numbers, result.toString()); + final Hashids otherHashids = (Hashids) otherObject; + return Arrays.equals(salt, otherHashids.salt) + && Objects.equals(minLength, otherHashids.minLength) + && Arrays.equals(alphabet, otherHashids.alphabet) + && Objects.equals(features, otherHashids.features); } - private Hashid doDecode(String hash, String alphabet) { - final List result = new ArrayList<>(); - int idx = 0; - String[] hashArray = hash.replaceAll("[" + guards + "]", " ").split(" "); - if (hashArray.length == 3 || hashArray.length == 2) { - idx = 1; - } - String hashBreakdown = hashArray[idx]; - - if (!hashBreakdown.isEmpty()) { - final char lottery = hashBreakdown.charAt(0); - hashBreakdown = hashBreakdown.substring(1); - hashBreakdown = hashBreakdown.replaceAll("[" + separators + "]", " "); - hashArray = hashBreakdown.split(" "); - - String buffer; - for (String subHash : hashArray) { - buffer = lottery + salt + alphabet; - alphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length())); - result.add(unhash(subHash, alphabet)); - } - } - long[] resultArray = toArray(result); - return new Hashid(resultArray, hash); + @Override + public String toString() { + return "Hashids{" + + "salt=" + Arrays.toString(salt) + + ", minLength=" + minLength + + ", alphabet=" + Arrays.toString(alphabet) + + ", features=" + features + + '}'; } - private static String consistentShuffle(String alphabet, String salt) { - if (salt.length() <= 0) { - return alphabet; - } - final char[] saltChars = salt.toCharArray(); - int ascVal, j; - char [] tmpArr = alphabet.toCharArray(); - for (int idx = tmpArr.length - 1, v = 0, p = 0; idx > 0; idx--, v++) { - v %= salt.length(); - ascVal = (int) saltChars[v]; - p += ascVal; - j = (ascVal + v + p) % idx; + /** + * Derives a new alphabet using the given salt and lottery character. + * + * @param alphabet the current alphabet + * @param salt the salt to be used as entropy + * @param lottery the lottery character + * @return a new derived alphabet + * @since 0.4.0 + */ + private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final char lottery) { + final char[] newSalt = new char[alphabet.length]; + newSalt[0] = lottery; + int spaceLeft = newSalt.length - 1; + int offset = 1; - char tmp = tmpArr[j]; - tmpArr[j] = tmpArr[idx]; - tmpArr[idx] = tmp; + if (salt.length > 0 && spaceLeft > 0) { + int length = salt.length > spaceLeft ? spaceLeft : salt.length; + System.arraycopy(salt, 0, newSalt, offset, length); + spaceLeft -= length; + offset += length; + } + if (spaceLeft > 0) { + System.arraycopy(alphabet, 0, newSalt, offset, spaceLeft); } - return new String(tmpArr); + + return shuffle(alphabet, newSalt); } - private static String hash(long input, String alphabet) { - StringBuilder hash = new StringBuilder(); - final int alphabetLen = alphabet.length(); - final char[] alphabetChars = alphabet.toCharArray(); - do { - hash.insert(0, alphabetChars[(int) (input % alphabetLen)]); - input /= alphabetLen; - } - while (input > 0); - return hash.toString(); + /** + * Filters the given alphabet after the separators. + * + * @param separators the separators to be filtered + * @param alphabet the alphabet to be filtered + * @return the filtered alphabet + * @since 0.4.0 + */ + private char[] filterSeparators(final char[] separators, final char[] alphabet) { + final Set valid = IntStream.range(0, alphabet.length) + .mapToObj(idx -> alphabet[idx]) + .collect(toSet()); + + return IntStream.range(0, separators.length) + .mapToObj(idx -> (separators[idx])) + .filter(valid::contains) + .map(c -> Character.toString(c)) + .collect(joining()) + .toCharArray(); } - private static Long unhash(String input, String alphabet) { - long number = 0; - long pos; - final char[] inputChars = input.toCharArray(); - for (int idx = 0; idx < input.length(); idx++) { - pos = alphabet.indexOf(inputChars[idx]); - number += pos * Math.pow(alphabet.length(), input.length() - idx - 1); - } - return number; + /** + * Shuffles the alphabet with the given salt. + * + * @param alphabet the alphabet to be shuffled + * @param salt the salt with which the alphabet is shuffled + * @return the shuffled alphabet + */ + private char[] shuffle(final char[] alphabet, final char[] salt) { + for (int idx = alphabet.length - 1, mod = 0, idxChar = 0, idxMatch, num; salt.length > 0 && idx > 0; idx--, mod++) { + mod %= salt.length; + idxChar += num = salt[mod]; + idxMatch = (num + mod + idxChar) % idx; + final char tmp = alphabet[idxMatch]; + alphabet[idxMatch] = alphabet[idx]; + alphabet[idx] = tmp; + } + return alphabet; } - private long[] toArray(List longs) { - final long[] result = new long[longs.size()]; - int idx = 0; - for (Long aLong : longs) { - result[idx++] = aLong; - } - return result; + /** + * Transforms the hash into the encoded hash state using the given alphabet. + * + * @param number the number to be transformed into the encoded hash state + * @param alphabet the alphabet to be used for the transformation + * @param sb the string builder to prepend the transformed number + * @param start the start index for the given alphabet + * @return the given string builder with the prepended transformed number + */ + private StringBuilder transform(final long number, final char[] alphabet, final StringBuilder sb, final int start) { + long input = number; + do { + // Prepend the matched character and trim the input + sb.insert(start, alphabet[(int) (input % alphabet.length)]); + input = input / alphabet.length; + } while (input > 0); + + return sb; } /** - * Check if a string is {@code null} or empty. + * Transforms the hash into the decoded number state using the given alphabet. * - * @param value The string to check - * @return {@code true} if the string is {@code null} or empty, {@code false} otherwise + * @param hash the hash to be transformed into the decoded number state + * @param alphabet the alphabet to be used for the transformation + * @return the transformed hash in the decoded number state */ - private boolean isEmpty(String value) { - return value == null || value.length() == 0; + private long transform(final char[] hash, final char[] alphabet) { + long number = 0; + + final Map alphabetMapping = IntStream.range(0, alphabet.length) + .mapToObj(idx -> new Object[]{alphabet[idx], idx}) + .collect(groupingBy(arr -> (Character) arr[0], mapping(arr -> (Integer) arr[1], reducing(null, (a, b) -> a == null ? b : a)))); + + for (int idx = 0; idx < hash.length; ++idx) { + number += alphabetMapping.get(hash[idx]) * (long) Math.pow(alphabet.length, hash.length - idx - 1); + } + + return number; } /** - * Returns the ArcVer / SemVer version of the public API. + * Validates and filters the given alphabet. * - * @return the ArcVer/SemVer version string - * @see ArcVer - * @see SemVer - * @since 0.2.0 + * @param alphabet The alphabet to be validated and filtered + * @param separators the separators to be filtered + * @return the filtered alphabet + * @throws IllegalArgumentException if the given alphabet does not contain the {@link #MIN_ALPHABET_LENGTH minimum length of unique characters} or contains + * spaces. + * @since 0.4.0 */ - public static String getVersion() { - return VERSION; + private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] separators) { + if (alphabet.length < MIN_ALPHABET_LENGTH) { + throw new IllegalArgumentException("alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters: " + alphabet.length); + } + + final Set validated = new LinkedHashSet<>(alphabet.length); + final Set invalid = IntStream.range(0, separators.length) + .mapToObj(idx -> separators[idx]) + .collect(toSet()); + + // Add the validated characters + IntStream.range(0, alphabet.length) + .forEach(idx -> { + if (alphabet[idx] == ' ') { + throw new IllegalArgumentException("alphabet must not contain spaces: index " + idx); + } + final Character c = alphabet[idx]; + if (!invalid.contains(c)) { + validated.add(c); + } + }); + + // Create a new alphabet from the validated characters + final char[] uniqueAlphabet = new char[validated.size()]; + int idx = 0; + for (char c : validated) { + uniqueAlphabet[idx++] = c; + } + return uniqueAlphabet; } } diff --git a/src/main/java/com/arcticicestudio/icecore/hashids/HashidsFeature.java b/src/main/java/com/arcticicestudio/icecore/hashids/HashidsFeature.java new file mode 100644 index 0000000..1777e51 --- /dev/null +++ b/src/main/java/com/arcticicestudio/icecore/hashids/HashidsFeature.java @@ -0,0 +1,43 @@ +package com.arcticicestudio.icecore.hashids; + +import java.util.EnumSet; + +/** + * Enumeration that defines optional features for the {@link Hashids.Builder}. + * + *

Please note that almost every feature will break the interoperability with the origin algorithm implementation if enabled! + * + * @author Arctic Ice Studio <development@arcticicestudio.com> + * @since 0.4.0 + */ +public enum HashidsFeature { + /** + * Enables support for the {@code 0x} or {@code 0X} hexadecimal format prefixes of parameter values passed to the + * {@link Hashids#encodeHex(String) encodeHex(String)}and {@link Hashids#decodeHex(String) decodeHex(String)} methods. + * + *

Please note that this will break the interoperability with the origin algorithm implementation! + * + * @see Hexadecimal - Written representation + */ + ALLOW_HEXADECIMAL_NUMBER_PREFIX, + + /** + * Enables the handling of exceptions instead of returning empty values when invalid parameters are passed to any public API method. + * + *

Please note that this will break the interoperability with the origin algorithm implementation! + */ + EXCEPTION_HANDLING, + + /** + * Disables the {@link Hashids#MAX_INTEROP_NUMBER_SIZE maximum number size limit} which ensures the interoperability with the origin algorithm implementation + * hashids.js and allows the usage of the {@link Long#MAX_VALUE Java Long maxiumum value}. + * + *

Please note that this will break the interoperability with the origin algorithm implementation! + */ + NO_MAX_INTEROP_NUMBER_SIZE; + + /** + * Enables all features. + */ + public static final EnumSet ALL = EnumSet.allOf(HashidsFeature.class); +} diff --git a/src/main/java/com/arcticicestudio/icecore/hashids/package-info.java b/src/main/java/com/arcticicestudio/icecore/hashids/package-info.java index 01edaea..51f5a41 100644 --- a/src/main/java/com/arcticicestudio/icecore/hashids/package-info.java +++ b/src/main/java/com/arcticicestudio/icecore/hashids/package-info.java @@ -1,23 +1,7 @@ -/* -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -title Package Info + -project icecore-hashids + -repository https://github.com/arcticicestudio/icecore-hashids + -author Arctic Ice Studio + -email development@arcticicestudio.com + -copyright Copyright (C) 2017 + -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -*/ /** - * Represents the public API of the - * "IceCore Hashids" library. - *

- * The main entry point of the package is the {@link com.arcticicestudio.icecore.hashids.Hashids} class to to generate - * short, unique, non-sequential and decodable hashids from positive unsigned (long) integer numbers. - *

+ * Provides the {@link com.arcticicestudio.icecore.hashids.Hashids IceCore Hashids} public API. * * @author Arctic Ice Studio <development@arcticicestudio.com> - * @see Hashids - * @version 0.3.0 + * @version 0.4.0 */ package com.arcticicestudio.icecore.hashids; diff --git a/src/main/resources/arcticicestudio-styleguide-java-checkstyle.xml b/src/main/resources/arcticicestudio-styleguide-java-checkstyle.xml new file mode 100644 index 0000000..793bb1c --- /dev/null +++ b/src/main/resources/arcticicestudio-styleguide-java-checkstyle.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/com/arcticicestudio/icecore/hashids/AbstractHashidsTest.java b/src/test/java/com/arcticicestudio/icecore/hashids/AbstractHashidsTest.java new file mode 100644 index 0000000..31e5de2 --- /dev/null +++ b/src/test/java/com/arcticicestudio/icecore/hashids/AbstractHashidsTest.java @@ -0,0 +1,105 @@ +package com.arcticicestudio.icecore.hashids; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.LongStream; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +/** + * Sets up the reference implementation script and provides it as {@link HashidsJs} representation class. + * + * @author Arctic Ice Studio <development@arcticicestudio.com> + * @see hashids.js + * @version 0.4.0 + */ +abstract class AbstractHashidsTest { + + final Hashids algorithm; + final HashidsJs jsAlgorithm; + final long[] input; + + AbstractHashidsTest(final Supplier algorithm, final Supplier jsAlgorithm, final long[] input) { + this.algorithm = algorithm.get(); + this.jsAlgorithm = jsAlgorithm.get(); + this.input = input; + } + + static HashidsJs getHashidsJs() { + return getHashidsJs(""); + } + + static HashidsJs getHashidsJs(final String salt) { + return getHashidsJs(salt, 0); + } + + static HashidsJs getHashidsJs(final String salt, final int minLength) { + return getHashidsJs(salt, minLength, ""); + } + + static HashidsJs getHashidsJs(final String salt, final int minLength, final String alphabet) { + try { + final ScriptEngine nashorn = new ScriptEngineManager().getEngineByName("nashorn"); + try (FileInputStream hashidsScript = new FileInputStream(new File("node_modules/hashids/dist/hashids.js"))) { + nashorn.eval(new InputStreamReader(hashidsScript)); + } + + // Create the instantiation script with the constructor parameters + final StringBuilder script = new StringBuilder("var hashids = new Hashids("); + + if (salt != null && salt.length() > 0) { + script.append("'").append(salt).append("',"); + } else { + script.append("undefined,"); + } + script.append(minLength).append(","); + if (alphabet != null && alphabet.length() > 0) { + script.append("'").append(alphabet).append("'"); + } else { + script.append("undefined"); + } + script.append(");"); + + // Evaluate the script and return the algorithm bridge + nashorn.eval(script.toString()); + return new HashidsJs(nashorn); + } catch (final Exception e) { + throw new RuntimeException("unable to initialize hashids.js algorithm", e); + } + } + + /** + * Represents the reference implementation. + */ + static final class HashidsJs { + + private final ScriptEngine engine; + + private HashidsJs(final ScriptEngine engine) { + this.engine = engine; + } + + String encode(long[] numbers) { + final String params = LongStream.of(numbers) + .mapToObj(String::valueOf) + .collect(Collectors.joining(",")); + return eval("hashids.encode(" + params + ");"); + } + + long[] decode(String hash) { + return eval("hashids.decode('" + hash + "');"); + } + + @SuppressWarnings("unchecked") + private T eval(final String script) { + try { + return (T) engine.eval(script); + } catch (final Exception e) { + throw new AssertionError("unexpected exception during hashid.js algorithm execution evaluation", e); + } + } + } +} diff --git a/src/test/java/com/arcticicestudio/icecore/hashids/HashidTest.java b/src/test/java/com/arcticicestudio/icecore/hashids/HashidTest.java deleted file mode 100644 index 911d633..0000000 --- a/src/test/java/com/arcticicestudio/icecore/hashids/HashidTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -title Hashid Unit Tests + -project icecore-hashids + -repository https://github.com/arcticicestudio/icecore-hashids + -author Arctic Ice Studio + -email development@arcticicestudio.com + -copyright Copyright (C) 2017 + -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -*/ -package com.arcticicestudio.icecore.hashids; - - -import org.junit.Test; - -import static junit.framework.TestCase.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * Unit tests for {@link Hashid} implementation. - * - * @author Arctic Ice Studio <development@arcticicestudio.com> - * @since 0.3.0 - */ -public class HashidTest { - - @Test - public void testEqualsSymmetric() { - Hashid h1 = new Hashid(new long[] {2, 17, 92}, "MjhWikW"); - Hashid h2 = new Hashid(new long[] {2, 17, 92}, "MjhWikW"); - - assertTrue(h1.equals(h2) && h2.equals(h1)); - assertTrue(h1.hashCode() == h2.hashCode()); - assertTrue(h1.equals(h1)); - assertFalse(h1.equals(null)); - - Long unequalClassObject = 12L; - assertFalse(h1.equals(unequalClassObject)); - } -} diff --git a/src/test/java/com/arcticicestudio/icecore/hashids/HashidsTest.java b/src/test/java/com/arcticicestudio/icecore/hashids/HashidsTest.java index 39cee20..5e49793 100644 --- a/src/test/java/com/arcticicestudio/icecore/hashids/HashidsTest.java +++ b/src/test/java/com/arcticicestudio/icecore/hashids/HashidsTest.java @@ -1,262 +1,423 @@ -/* -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -title Hashids Public API Test + -project icecore-hashids + -repository https://github.com/arcticicestudio/icecore-hashids + -author Arctic Ice Studio + -email development@arcticicestudio.com + -copyright Copyright (C) 2017 + -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -*/ package com.arcticicestudio.icecore.hashids; -import org.junit.Test; +import static com.arcticicestudio.icecore.hashids.HashidsFeature.ALLOW_HEXADECIMAL_NUMBER_PREFIX; +import static com.arcticicestudio.icecore.hashids.HashidsFeature.EXCEPTION_HANDLING; +import static com.arcticicestudio.icecore.hashids.HashidsFeature.NO_MAX_INTEROP_NUMBER_SIZE; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; /** - * Tests the IceCore Hashids - * public API class {@link Hashids}. + * Units tests for the IceCore Hashids public API. * * @author Arctic Ice Studio <development@arcticicestudio.com> - * @see IceCore Hashids - * @see Hashids * @since 0.1.0 */ public class HashidsTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test - public void oneNumber() { - Hashids hashids = new Hashids("salt"); - long number = 12_345L; - String expected = "X4j1"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void defaultInstanceConfigurationEquality() { + final Hashids hashidsBuilder = new Hashids.Builder().build(); + final Hashids hashids = new Hashids(); + assertThat(hashidsBuilder, equalTo(hashids)); + assertThat(hashidsBuilder.toString(), equalTo(hashids.toString())); + } - assertEquals(hashids.encodeToString(number), expected); - assertEquals(decoded.length, 1); - assertEquals(decoded[0], number); + @Test + public void invalidAlphabetLength() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("alphabet must contain at least 16 unique characters: 6"); + final Hashids hashids = new Hashids.Builder() + .alphabet("abc123") + .build(); } @Test - public void oneLargeNumber() { - Hashids hashids = new Hashids("salt"); - long number = 9_007_199_254_740_991L; - String expected = "wQpRRqRX24R"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void alphabetWithSpaces() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("alphabet must not contain spaces: index 1"); + final Hashids hashids = new Hashids.Builder() + .salt("salt") + .alphabet("a bcdefg1234567890") + .build(); + } - assertEquals(hashids.encodeToString(number), expected); - assertEquals(decoded.length, 1); - assertEquals(decoded[0], number); + @Test + public void alphabetWithSymbols() { + Hashids hashids = new Hashids.Builder() + .alphabet("`~!@#$%^&*()-_=+|';:/?.>,<{[}]") + .build(); + assertThat(hashids.encode(123456L), equalTo("]]''?")); } @Test - public void oneLargeIntegerNumber() { - Hashids hashids = new Hashids("salt"); - int number = 2147483647; - String expected = "wqVYY1X"; - int[] decoded = hashids.decodeIntegerNumbers(hashids.encodeToString(number)); + public void transcode() { + Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode(123456L), equalTo("xkNDJ")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("x7SQJh8kQ")); + assertThat(hashids.decode("xkNDJ"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("x7SQJh8kQ"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("xkNDJ"), equalTo(Optional.of(123456L))); + } - assertEquals(hashids.encodeToString(number), expected); - assertEquals(decoded.length, 1); - assertEquals(decoded[0], number); + @Test + public void transcodeWithSalt() { + Hashids hashids = new Hashids.Builder() + .salt("salt") + .build(); + assertThat(hashids.encode(123456L), equalTo("7E1dX")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("7NfvoFvAJ")); + assertThat(hashids.decode("7E1dX"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("7NfvoFvAJ"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("7E1dX"), equalTo(Optional.of(123456L))); } - @Test(expected = IllegalArgumentException.class) - public void oneLargeNumberNotSupported() throws Exception { - long number = 9007199254740993L; - Hashids a = new Hashids("salt"); - a.encode(number); + @Test + public void transcodeWithMinLength() { + Hashids hashids = new Hashids.Builder() + .minLength(16) + .build(); + assertThat(hashids.encode(123456L), equalTo("VoJX7axkNDJeyv4E")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("vDLax7SQJh8kQe6p")); + assertThat(hashids.decode("VoJX7axkNDJeyv4E"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("vDLax7SQJh8kQe6p"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("VoJX7axkNDJeyv4E"), equalTo(Optional.of(123456L))); } @Test - public void oneNumberWithCustomMinimumHashLength() { - Hashids hashids = new Hashids("salt", 8); - long number = 12_345L; - String expected = "xkX4j1kZ"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void transcodeWithCustomAlphabet() { + Hashids hashids = new Hashids.Builder() + .alphabet("abcdefghij1234560") + .build(); + assertThat(hashids.encode(123456L), equalTo("e60655")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("e43i3j2hg6e4")); + assertThat(hashids.decode("e60655"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("e43i3j2hg6e4"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("e60655"), equalTo(Optional.of(123456L))); + + Hashids hashidsSymbols = new Hashids.Builder() + .alphabet("!#$%&'()*+,-./:;<=>?@[]^_`{|}~") + .build(); + assertThat(hashidsSymbols.encode(123456L), equalTo("~_--:")); + assertThat(hashidsSymbols.encode(11L, 222L, 3333L), equalTo("<~$-{$:;=")); + assertThat(hashidsSymbols.decode("~_--:"), equalTo(new long[] {123456L})); + assertThat(hashidsSymbols.decode("<~$-{$:;="), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashidsSymbols.decodeOne("~_--:"), equalTo(Optional.of(123456L))); + } - assertEquals(hashids.encodeToString(number), expected); - assertEquals(decoded.length, 1); - assertEquals(decoded[0], number); + @Test + public void transcodeWithSaltAndMinLength() { + Hashids hashids = new Hashids.Builder() + .salt("salt") + .minLength(16) + .build(); + assertThat(hashids.encode(123456L), equalTo("lgaRz37E1dXkB1XL")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("Wnj37NfvoFvAJmze")); + assertThat(hashids.decode("lgaRz37E1dXkB1XL"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("Wnj37NfvoFvAJmze"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("lgaRz37E1dXkB1XL"), equalTo(Optional.of(123456L))); } @Test - public void oneNumberWithoutSeparators() { - Hashids hashids = new Hashids("salt", 0, Hashids.DEFAULT_ALPHABET, null); - long number = 12_345L; - String hash = hashids.encodeToString(number); - long[] decoded = hashids.decodeLongNumbers(hash); + public void transcodeWithSaltAndCustomAlphabet() { + Hashids hashids = new Hashids.Builder() + .salt("salt") + .alphabet("abcdefghij1234560") + .build(); + assertThat(hashids.encode(123456L), equalTo("534300")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("5eahg15cbea1")); + assertThat(hashids.decode("534300"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("5eahg15cbea1"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("534300"), equalTo(Optional.of(123456L))); + } - assertEquals(1, decoded.length); - assertEquals(number, decoded[0]); + @Test + public void transcodeWithMinLengthAndCustomAlphabet() { + Hashids hashids = new Hashids.Builder() + .minLength(16) + .alphabet("abcdefghij1234560") + .build(); + assertThat(hashids.encode(123456L), equalTo("6125ebe60655adg0")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("e1be43i3j2hg6e4a")); + assertThat(hashids.decode("6125ebe60655adg0"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("e1be43i3j2hg6e4a"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("6125ebe60655adg0"), equalTo(Optional.of(123456L))); } @Test - public void oneNumberWithCustomSeparators() { - Hashids hashids = new Hashids("salt", 0, Hashids.DEFAULT_ALPHABET, "abcdefgABCDEFG1234567"); - long number = 12_345L; - String hash = hashids.encodeToString(number); - long[] decoded = hashids.decodeLongNumbers(hash); + public void transcodeWithSaltAndMinLengthAndCustomAlphabet() { + Hashids hashids = new Hashids.Builder() + .salt("salt") + .minLength(16) + .alphabet("abcdefghij1234560") + .build(); + assertThat(hashids.encode(123456L), equalTo("61a0425343002gd3")); + assertThat(hashids.encode(11L, 222L, 3333L), equalTo("4d25eahg15cbea12")); + assertThat(hashids.decode("61a0425343002gd3"), equalTo(new long[] {123456L})); + assertThat(hashids.decode("4d25eahg15cbea12"), equalTo(new long[] {11L, 222L, 3333L})); + assertThat(hashids.decodeOne("61a0425343002gd3"), equalTo(Optional.of(123456L))); + } - assertEquals(1, decoded.length); - assertEquals(number, decoded[0]); + @Test + public void transcodeMaximumNumberSize() { + Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode(Hashids.MAX_INTEROP_NUMBER_SIZE), equalTo("lEW77X7g527")); + assertThat(hashids.decode("lEW77X7g527"), equalTo(new long[] {Hashids.MAX_INTEROP_NUMBER_SIZE})); + assertThat(hashids.decodeOne("lEW77X7g527"), equalTo(Optional.of(Hashids.MAX_INTEROP_NUMBER_SIZE))); } @Test - public void oneNumberWithLargeCustomMinimumHashLength() { - Hashids hashids = new Hashids("salt", 1000); - long number = 12_345; - String expected = "lPaNoKpAjg2O9bdQ5K6l9Yvj0o7MWXwZPNyZ90jxQp7WYlA5gJ4LvwEMGnwDNBZjpxQY692dXoPOMyrVrgXDGE0BnaeZdM" + - "YO1WxRlevqJK0BloZNbYG29dP1paDDw7gpRqBzjy5OedVZlEJnNjPQ4LXZye5REY0qA2wW1pOnJNjvMbLKg6rexwWDVyl74ZW74pVdEYQBa6zv" + - "15eDbwlVqpzoDZ14AwlxbGL0RyE9P250JQ4egxYbwaoz17vXZENyrgxzn6V2pW5eA9vMPRdBoa7w5Vo9GMge1nPqQYJxlEZVyrxYKONAepM0lw" + - "L42g7PdgK6V0xvdQlao1ANOG92LpYONBLEyYvqeZwnDA6WMaGb7jaQOVlPerdbJ7B1LzDyY59b4AJOeo6l9RpgGWVnzZqjNVDpraMYz2bQxdKv" + - "y5PEwe7OP7WZDzeQANv9o6ga0jG2dDYj4xlKLZpa5qBbzMyGvrNp9nl4bLoy7E2dPB6AGYqzxkX4j1kZWaevwVKNR01OrMX5DgjJQAdPXJ9W16" + - "OwQeR2go07VEnb1MqKxJ4YLr5XEVlwpnRyBqOlZG9Rg1AnN4o6XLBJ0jWP2yrvYX7QMB0a5LdxKwED1g0KMRpWnXv26oZx4GEqAwNdVg9PR25z" + - "rJQjxKoXl104pzZeqEb7J5Pyw4BXWjRMnrD5RqWnoa1EGzX6JvDQbjBZ9NB6Av2bjd4LRy0WOXzrKDp0DlXKJZL7YNjOwQqbE1a4GpMLnj6RKy" + - "PDrWVB9lOGdqAQenj5aYBM7KWdNr6JXOgv2M9XNL0oAxJ2RqrGOngPjKyq2oPpEzdA5XORGQZa91Y0BlNxz96Bba7oVDnMgdrJvGKaMYKo9bvQ" + - "6r04xXL1GP2AWOWR7x46rMLgjXyQnVAEw5zL76Aboq49vK2pQz5jNJywPlGg1KAV0aLWzJ5bRevE74qBPoy6Xzbqre1RnKa2NdVODOVnBaDERz" + - "AgJGx41qe2LrpdWGE4QbxLJez6"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void transcodeNullInput() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode((long[]) null), emptyString()); + assertThat(hashids.decode(null), equalTo(new long[0])); + assertThat(hashids.decodeOne(null), equalTo(Optional.empty())); + } - assertEquals(expected, hashids.encodeToString(number)); - assertEquals(1, decoded.length); - assertEquals(number, decoded[0]); + @Test + public void transcodeHex() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encodeHex("75bcd15"), equalTo("j2g9K4y")); + assertThat(hashids.decodeHex("j2g9K4y"), equalTo("75bcd15")); } @Test - public void oneNumberWithCustomUpperCaseAndNumbersAlphabet() { - Hashids hashids = new Hashids("salt", "ABCDEFGHIJKLMNPQRSTUVWXYZ123456789"); - long number = 12_345L; - String expected = "KQL1R"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void transcodeHexPrefixedInput() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encodeHex("0x75bcd15"), emptyString()); + assertThat(hashids.encodeHex("0X75bcd15"), emptyString()); + assertThat(hashids.decodeHex("0x75bcd15"), emptyString()); + assertThat(hashids.decodeHex("0X75bcd15"), emptyString()); + } + + @Test + public void transcodeHexWithAllowedHexadecimalNumberPrefix() { + final Hashids hashids = new Hashids.Builder() + .features(ALLOW_HEXADECIMAL_NUMBER_PREFIX) + .build(); + final String hex = hashids.encodeHex("75bcd15"); + final String hexPrefixLowercase = hashids.encodeHex("0x75bcd15"); + final String hexPrefixUppercase = hashids.encodeHex("0X75bcd15"); + assertThat(hex, equalTo("j2g9K4y")); + assertThat(hex, equalTo(hexPrefixLowercase)); + assertThat(hex, equalTo(hexPrefixUppercase)); + assertThat(hashids.decodeHex(hex), equalTo("75bcd15")); + } - assertEquals(expected, hashids.encodeToString(number)); - assertEquals(decoded.length, 1); - assertEquals(decoded[0], number); + @Test + public void transcodeHexNullInput() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encodeHex(null), emptyString()); + assertThat(hashids.decodeHex(null), emptyString()); } @Test - public void oneNumberWithCustomLowerCaseAndNumbersAlphabet() { - Hashids hashids = new Hashids("salt", "abcdefghijklmnpqrstuvwxyz123456789"); - long number = 12_345L; - String expected = "kzkvx"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + public void encodeMaximumNumberSizeExceeded() { + Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode(Hashids.MAX_INTEROP_NUMBER_SIZE + 1L), emptyString()); + } - assertEquals(expected, hashids.encodeToString(number)); - assertEquals(1, decoded.length); - assertEquals(number, decoded[0]); + @Test + public void encodeMaximumNumberSizeExceededWithExceptionHandling() { + Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("number must not exceed the maximum number size: 9007199254740992 > 9007199254740991"); + hashids.encode(Hashids.MAX_INTEROP_NUMBER_SIZE + 1L); } @Test - public void severalNumbers() { - Hashids hashids = new Hashids("salt"); - long[] numbers = {683L, 94_108L, 123L, 5L}; - String expected = "1eMToyKzsRAfO"; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(numbers)); + public void encodeWithNoMaximumNumberSizeLimit() { + Hashids hashids = new Hashids.Builder() + .features(NO_MAX_INTEROP_NUMBER_SIZE) + .build(); + assertThat(hashids.encode(Hashids.MAX_INTEROP_NUMBER_SIZE + 1L), not(emptyString())); + } - assertEquals(hashids.encodeToString(numbers), expected); - assertEquals(decoded.length, numbers.length); - assertArrayEquals(decoded, numbers); + @Test + public void encodeNegativeInput() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode(-123456L), emptyString()); + assertThat(hashids.encode(-10L, -222L, -3333L), emptyString()); } @Test - public void severalNumbersRandomness() { - Hashids hashids = new Hashids("salt"); - long[] numbers = {5L, 5L, 5L, 5L}; - String expected = "YBF7FKFz"; - long[] decoded = hashids.decodeLongNumbers(expected); + public void encodeNegativeInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("number must not be less than zero: -1"); + hashids.encode(-1L); + } - assertEquals(expected, hashids.encodeToString(numbers)); - assertEquals(numbers.length, decoded.length); - assertArrayEquals(numbers, decoded); + @Test + public void encodeNullInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(NullPointerException.class); + thrown.expectMessage("numbers must not be null!"); + hashids.encode((long[]) null); } @Test - public void severalIncrementingNumbersRandomness() { - Hashids hashids = new Hashids("salt"); - long[] numbers = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L}; - String expected = "rjiJulUECaFAS1TBhzcX"; - long[] decoded = hashids.decodeLongNumbers(expected); + public void decodeNullInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(NullPointerException.class); + thrown.expectMessage("hash must not be null!"); + hashids.decode(null); + } - assertEquals(expected, hashids.encodeToString(numbers)); - assertEquals(numbers.length, decoded.length); - assertArrayEquals(numbers, decoded); + @Test + public void decodeOneNullInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(NullPointerException.class); + thrown.expectMessage("hash must not be null!"); + hashids.decodeOne(null); } @Test - public void invalidSaltCollision() { - Hashids hashidsA = new Hashids("salt and pepper", 4); - Hashids hashidsB = new Hashids("salt", 4); - long number = 123L; - String token = hashidsA.encodeToString(number); + public void encodeNoInput() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encode(), emptyString()); + } - long[] decodedWrongSalt = hashidsB.decodeLongNumbers(token); - long[] decodedCorrectSalt = hashidsA.decodeLongNumbers(token); - assertThat(decodedWrongSalt, not(equalTo(decodedCorrectSalt))); + @Test + public void encodeNoInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("length of numbers must be greater than or equal to one!"); + hashids.encode(); } @Test - public void hexString() { - Hashids hashids = new Hashids("salt"); - String hex = "75bcd15"; - String hash = hashids.encodeHex(hex); - String returnedHex = hashids.decodeHex(hash); + public void encodeHexInvalidHexadecimalFormat() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.encodeHex("z"), emptyString()); + } - assertNotNull(hash); - assertNotEquals(0, hash.length()); - assertEquals(hex, returnedHex); + @Test + public void encodeHexInvalidHexadecimalFormatWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("hexNumbers must be a valid hexadecimal number!"); + hashids.encodeHex("z"); } @Test - public void hexStringWithCustomMinimumHashLength() { - Hashids hashids = new Hashids("salt", 8); - String hex = "75bcd15"; - String hash = hashids.encodeHex(hex); - String returnedHex = hashids.decodeHex(hash); + public void encodeHexLowercasePrefixedInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("numbers must not contain a hexadecimal prefix: 0x"); + hashids.encodeHex("0x75bcd15"); + } - assertNotNull(hash); - assertNotEquals(0, hash.length()); - assertEquals(hex, returnedHex); - assertEquals(8, hash.length()); + @Test + public void encodeHexUppercasePrefixedInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("numbers must not contain a hexadecimal prefix: 0X"); + hashids.encodeHex("0X75bcd15"); } @Test - public void longHexString() { - Hashids hashids = new Hashids("salt"); - String hex = "f000000000000000000000000000000000000000000000000000000000000000000000000000000000000f"; - String hash = hashids.encodeHex(hex); - String returnedHex = hashids.decodeHex(hash); + public void encodeHexNullInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(NullPointerException.class); + thrown.expectMessage("hexNumbers must not be null!"); + hashids.encodeHex(null); + } - assertNotNull(hash); - assertNotEquals(0, hash.length()); - assertEquals(hex, returnedHex); + @Test + public void decodeHexNullInputWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(NullPointerException.class); + thrown.expectMessage("hash must not be null!"); + hashids.decodeHex(null); } - @Test(expected = IllegalArgumentException.class) - public void negativeNumber() { - Hashids hashids = new Hashids("salt"); - long number = -1L; - hashids.encodeToString(number); + @Test + public void decodeInvalidHash() { + final Hashids hashids = new Hashids.Builder().build(); + assertThat(hashids.decode("yogurt"), equalTo(new long[0])); + assertThat(hashids.decode("()"), equalTo(new long[0])); + assertThat(hashids.decode("[]"), equalTo(new long[0])); + assertThat(hashids.decodeOne("yogurt"), equalTo(Optional.empty())); + assertThat(hashids.decodeOne("()"), equalTo(Optional.empty())); + assertThat(hashids.decodeOne("[]"), equalTo(Optional.empty())); } @Test - public void hashidsBuilder() { - Hashids hashids = new Hashids.Builder() + public void decodeInvalidSalt() { + final Hashids hashidsSalt = new Hashids.Builder() .salt("salt") - .minHashLength(16) - .alphabet(Hashids.DEFAULT_ALPHABET) - .separators(Hashids.DEFAULT_SEPARATORS) .build(); - long number = 12_345L; - long[] decoded = hashids.decodeLongNumbers(hashids.encodeToString(number)); + final Hashids hashidsPepper = new Hashids.Builder() + .salt("pepper") + .build(); + String salt = hashidsSalt.encode(123456L); + String pepper = hashidsPepper.encode(123456L); + assertThat(salt, not(equalTo(pepper))); + assertThat(hashidsSalt.decode(pepper), equalTo(new long[0])); + assertThat(hashidsSalt.decodeOne(pepper), equalTo(Optional.empty())); + assertThat(hashidsPepper.decode(salt), equalTo(new long[0])); + assertThat(hashidsPepper.decodeOne(salt), equalTo(Optional.empty())); + } - assertEquals(1, decoded.length); - assertEquals(number, decoded[0]); + @Test + public void decodeInvalidHashWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("invalid hash: yogurt"); + hashids.decode("yogurt"); + } + + @Test + public void decodeOneInvalidHashWithExceptionHandling() { + final Hashids hashids = new Hashids.Builder() + .features(EXCEPTION_HANDLING) + .build(); + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("invalid hash: yogurt"); + hashids.decodeOne("yogurt"); + } + + @Test + public void validVersion() { + assertThat(Hashids.getVersion(), equalTo("0.4.0")); + } + + @Test + public void validInteropVersion() { + assertThat(Hashids.getInteropVersion(), equalTo("1.0.0")); } } diff --git a/src/test/java/com/arcticicestudio/icecore/hashids/InteropHashidsTest.java b/src/test/java/com/arcticicestudio/icecore/hashids/InteropHashidsTest.java new file mode 100644 index 0000000..6334bd8 --- /dev/null +++ b/src/test/java/com/arcticicestudio/icecore/hashids/InteropHashidsTest.java @@ -0,0 +1,121 @@ +package com.arcticicestudio.icecore.hashids; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Interoperability tests for the {@link Hashids} algorithm and the reference implementation represented by the {@link HashidsJs} class. + * + * @author Arctic Ice Studio <development@arcticicestudio.com> + * @see hashids.js + * @version 0.4.0 + */ +@RunWith(Parameterized.class) +public class InteropHashidsTest extends AbstractHashidsTest { + + /** + * Generates test data with incremented numbers from 1 to 32. + * + * @return the generated test data + */ + @Parameterized.Parameters(name = "{index}: {0}") + public static Collection data() { + + // generate a 1-to-32 sized arrays + final List input = new ArrayList<>(); + for (long i = 0; i < 32; i++) { + if (input.isEmpty()) { + input.add(new long[]{i}); + } else { + final long[] prev = input.get(input.size() - 1); + final long[] curr = Arrays.copyOf(prev, prev.length + 1); + curr[prev.length] = i; + input.add(curr); + } + } + + // generate a supplier for all algorithm combinations + final List[]> algorithms = new ArrayList<>(); + algorithms.add(new Supplier[]{ + () -> "NoSalt|NoMinLength|NoAlphabet", + Hashids::new, + AbstractHashidsTest::getHashidsJs + }); + + algorithms.add(new Supplier[]{ + () -> "Salt|NoMinLength|NoAlphabet", + () -> new Hashids.Builder().salt("salt and pepper").build(), + () -> getHashidsJs("salt and pepper") + }); + + algorithms.add(new Supplier[]{ + () -> "NoSalt|NoMinLength|Alphabet", + () -> new Hashids.Builder().alphabet("abcdef1234567890").build(), + () -> getHashidsJs("", 0, "abcdef1234567890") + }); + + algorithms.add(new Supplier[]{ + () -> "NoSalt|MinLength|NoAlphabet", + () -> new Hashids.Builder().minLength(32).build(), + () -> getHashidsJs("", 32) + }); + + algorithms.add(new Supplier[]{ + () -> "Salt|NoMinLength|Alphabet", + () -> new Hashids.Builder().salt("salt and pepper").alphabet("abcdef1234567890").build(), + () -> getHashidsJs("salt and pepper", 0, "abcdef1234567890") + }); + + algorithms.add(new Supplier[]{ + () -> "NoSalt|MinLength|Alphabet", + () -> new Hashids.Builder().minLength(32).alphabet("abcdef1234567890").build(), + () -> getHashidsJs("", 32, "abcdef1234567890") + }); + + algorithms.add(new Supplier[]{ + () -> "Salt|MinLength|NoAlphabet", + () -> new Hashids.Builder().salt("salt and pepper").minLength(32).build(), + () -> getHashidsJs("salt and pepper", 32) + }); + + algorithms.add(new Supplier[]{ + () -> "Salt|MinLength|Alphabet", + () -> new Hashids.Builder().salt("salt and pepper").minLength(32).alphabet("abcdef1234567890").build(), + () -> getHashidsJs("salt and pepper", 32, "abcdef1234567890") + }); + + // generate the input for each hash algorithm + return algorithms.stream() + .flatMap(algorithm -> input.stream().map(numbers -> new Object[]{ + // name + String.format("algorithm(%s) with input(%s)", algorithm[0].get(), Arrays.toString(numbers)), + // algorithm, jsAlgorithm, input + algorithm[1], algorithm[2], numbers + })) + .collect(Collectors.toList()); + } + + @SuppressWarnings("unused") + public InteropHashidsTest(final String name, final Supplier hashids, final Supplier hashidsJs, final long[] input) { + super(hashids, hashidsJs, input); + } + + @Test + public void runInteropTests() { + final String encoded = algorithm.encode(input); + assertThat("encoding mismatch", encoded, equalTo(jsAlgorithm.encode(input))); + final long[] original = algorithm.decode(encoded); + assertThat(original, equalTo(input)); + } +}