diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c66ad55 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,154 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2017 The Stdlib Authors. +# +# 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. +#/ + +# EditorConfig configuration file (see ). + +# Indicate that this file is a root-level configuration file: +root = true + +# Set properties for all files: +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Set properties for JavaScript files: +[*.js] +indent_style = tab + +# Set properties for TypeScript files: +[*.ts] +indent_style = tab + +# Set properties for Python files: +[*.py] +indent_style = space +indent_size = 4 + +# Set properties for Julia files: +[*.jl] +indent_style = tab + +# Set properties for R files: +[*.R] +indent_style = tab + +# Set properties for C files: +[*.c] +indent_style = tab + +# Set properties for C header files: +[*.h] +indent_style = tab + +# Set properties for C++ files: +[*.cpp] +indent_style = tab + +# Set properties for C++ header files: +[*.hpp] +indent_style = tab + +# Set properties for Fortran files: +[*.f] +indent_style = space +indent_size = 2 +insert_final_newline = false + +# Set properties for shell files: +[*.sh] +indent_style = tab + +# Set properties for AWK files: +[*.awk] +indent_style = tab + +# Set properties for HTML files: +[*.html] +indent_style = tab +tab_width = 2 + +# Set properties for CSS files: +[*.css] +indent_style = tab + +# Set properties for Makefiles: +[Makefile] +indent_style = tab + +[*.mk] +indent_style = tab + +# Set properties for Markdown files: +[*.md] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false + +# Set properties for `usage.txt` files: +[usage.txt] +indent_style = space +indent_size = 2 + +# Set properties for `repl.txt` files: +[repl.txt] +indent_style = space +indent_size = 4 + +# Set properties for `package.json` files: +[package.json] +indent_style = space +indent_size = 2 + +# Set properties for `datapackage.json` files: +[datapackage.json] +indent_style = space +indent_size = 2 + +# Set properties for `tslint.json` files: +[tslint.json] +indent_style = space +indent_size = 2 + +# Set properties for `tsconfig.json` files: +[tsconfig.json] +indent_style = space +indent_size = 2 + +# Set properties for LaTeX files: +[*.tex] +indent_style = tab + +# Set properties for LaTeX Bibliography files: +[*.bib] +indent_style = tab + +# Set properties for YAML files: +[*.yml] +indent_style = space +indent_size = 2 + +# Set properties for GYP files: +[binding.gyp] +indent_style = space +indent_size = 2 + +[*.gypi] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7212d81 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2017 The Stdlib Authors. +# +# 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. +#/ + +# Configuration file which assigns attributes to pathnames. +# +# [1]: https://git-scm.com/docs/gitattributes + +# Automatically normalize the line endings of any committed text files: +* text=auto + +# Override what is considered "vendored" by GitHub's linguist: +/deps/** linguist-vendored=false +/lib/node_modules/** linguist-vendored=false linguist-generated=false +test/fixtures/** linguist-vendored=false +tools/** linguist-vendored=false + +# Override what is considered "documentation" by GitHub's linguist: +examples/** linguist-documentation=false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e6a7232 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ + + +We are excited about your pull request, but unfortunately we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/stdlib-js/stdlib). We kindly request that you submit this pull request against the [respective directory](https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/net/disposable-http-server) of the main repository where we’ll review and provide feedback. + +If this is your first stdlib contribution, be sure to read the [contributing guide](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions. You may also consult the [development guide](https://github.com/stdlib-js/stdlib/blob/develop/docs/development.md) for help on developing stdlib. + +We look forward to receiving your contribution! :smiley: \ No newline at end of file diff --git a/.github/workflows/close_pull_requests.yml b/.github/workflows/close_pull_requests.yml new file mode 100644 index 0000000..f17fb67 --- /dev/null +++ b/.github/workflows/close_pull_requests.yml @@ -0,0 +1,23 @@ +name: Close Pull Requests + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thank you for submitting a pull request. :raised_hands: + + We greatly appreciate your willingness to submit a contribution. However, we are not accepting pull requests against this repository, as all development happens on the [main project repository](https://github.com/stdlib-js/stdlib). + + We kindly request that you submit this pull request against the [respective directory](https://github.com/stdlib-js/stdlib/tree/develop/lib/node_modules/%40stdlib/net/disposable-http-server) of the main repository where we’ll review and provide feedback. If this is your first stdlib contribution, be sure to read the [contributing guide](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) which provides guidelines and instructions for submitting contributions. + + Thank you again, and we look forward to receiving your contribution! :smiley: + + Best, + The stdlib team \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 0000000..7668c70 --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,19 @@ +name: examples + +on: + workflow_dispatch: + +jobs: + examples: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Install production and development dependencies + run: | + npm install + - name: Run examples + run: | + npm run examples \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..164619b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,34 @@ +name: Publish Package + +on: push + +jobs: + publish: + runs-on: ubuntu-latest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Increment version + run: | + git config --local user.email "noreply@stdlib.io" + git config --local user.name "stdlib-bot" + npm version patch + - name: Publish package to npm + uses: JS-DevTools/npm-publish@v1 + with: + token: ${{ secrets.NPM_TOKEN }} + access: public + - name: Push changes + run: | + git push origin main + git push --tags + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: '#npm-ci' + if: failure() \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..72b66f4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: build + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Install production and development dependencies + id: install + run: | + npm install + - name: Run tests + id: tests + run: | + npm test + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: '#npm-ci' + if: failure() \ No newline at end of file diff --git a/.github/workflows/test_coverage.yml b/.github/workflows/test_coverage.yml new file mode 100644 index 0000000..debbfd5 --- /dev/null +++ b/.github/workflows/test_coverage.yml @@ -0,0 +1,24 @@ +name: coverage + +on: + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Install production and development dependencies + run: | + npm install + - name: Calculate test coverage + run: | + npm run test-cov + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + directory: reports/coverage + flags: unittests \ No newline at end of file diff --git a/.github/workflows/test_install.yml b/.github/workflows/test_install.yml new file mode 100644 index 0000000..039afb6 --- /dev/null +++ b/.github/workflows/test_install.yml @@ -0,0 +1,27 @@ +name: Test Installing Dependencies + +on: + workflow_run: + workflows: ["Publish Package"] + types: [completed] + +jobs: + on-success: + runs-on: ubuntu-latest + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 14 + - name: Install production dependencies via npm + run: | + npm install --only=prod + - uses: act10ns/slack@v1 + with: + status: ${{ job.status }} + steps: ${{ toJson(steps) }} + channel: '#npm-ci' + if: failure() \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1475963 --- /dev/null +++ b/.gitignore @@ -0,0 +1,181 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2017 The Stdlib Authors. +# +# 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. +#/ + +# Files # +######### +.postinstall.json + +# Directories # +############### +build/ +downloads/ +reports/ +tmp/ + +# Compiled source # +################### +*.com +*.class +*.dll +*.o +*.so +*.slo +*.lo +*.obj +*.dylib +*.lai +*.la +*.a +*.lib +*.ko +*.elf +*.node + +# Precompiled headers # +####################### +*.gch +*.pch + +# Executables # +############### +*.exe +*.out +*.app + +# Packages # +############ +# It is better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Make an exception for compressed distributable files: +!dist/*.gz + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db +Desktop.ini + +# Temporary files # +################### +*~ + +# Node.js # +########### +/node_modules/ +lib/node_modules/**/node_modules/ +docs/**/node_modules/ +pids +*.pid +*.seed + +# Typescript # +############## +*.tsbuildinfo +lib/node_modules/**/tsconfig.json +lib/node_modules/**/tslint.json + +# Matlab # +########## +*.asv +*.mex* + +# Fortran # +########### +*.mod + +# R # +##### +.Rhistory +.Rapp.history +.Rproj.user/ + +# Python # +########## +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ + +# TeX # +####### +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.dvi +*-converted-to.* +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.brf +*.run.xml +*.fdb_latexmk +*.synctex +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.alg +*.loa +acs-*.bib +*.thm +*.nav +*.snm +*.vrb +*.acn +*.acr +*.glg +*.glo +*.gls +*-concordance.tex +*.tikz +*-tikzDictionary +*.idx +*.ilg +*.ind +*.ist + +# Visual Studio # +################# +.vscode/ +jsconfig.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..401aa76 --- /dev/null +++ b/.npmignore @@ -0,0 +1,225 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2017 The Stdlib Authors. +# +# 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. +#/ + +# Files # +######### +CODE_OF_CONDUCT.md +CONTRIBUTING.md +CONTRIBUTORS +TODO.md +ROADMAP.md +.postinstall.json + +# Directories # +############### +.circleci/ +.github/ +**/benchmark/ +**/build/ +**/examples/ +reports/ +support/ +**/tmp/ +workshops/ + +# Ignore test directories, except for testing dependency installation: +**/test/ +!/deps/test/ + +# Only top-level directories: +/etc/ +/docs/ + +# Compiled source # +################### +*.com +*.class +*.dll +*.o +*.so +*.slo +*.lo +*.obj +*.dylib +*.lai +*.la +*.a +*.lib +*.ko +*.elf +*.node + +# Precompiled headers # +####################### +*.gch +*.pch + +# Executables # +############### +*.exe +*.out +*.app + +# Packages # +############ +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Make an exception for compressed distributable files: +!dist/*.gz + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db +Desktop.ini + +# Temporary files # +################### +*~ + +# Node.js # +########### +.npmignore + +# Only top-level node_modules: +/node_modules/ + +# TypeScript # +############## +tsconfig.json +tslint.json +*.tsbuildinfo + +# Matlab # +########## +*.asv +*.mex* + +# Fortran # +########### +*.mod + +# R # +##### +.Rhistory +.Rapp.history +.Rproj.user/ + +# Python # +########## +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +.ipynb_checkpoints +setup.cfg +setup.py + +# TeX # +####### +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.dvi +*-converted-to.* +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.brf +*.run.xml +*.fdb_latexmk +*.synctex +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.alg +*.loa +acs-*.bib +*.thm +*.nav +*.snm +*.vrb +*.acn +*.acr +*.glg +*.glo +*.gls +*-concordance.tex +*.tikz +*-tikzDictionary +*.idx +*.ilg +*.ind +*.ist + +# Git # +####### +.git* +.mailmap + +# Visual Studio # +################# +.vscode/ +jsconfig.json + +# Utilities # +############# +.jshintrc +.jshintignore +.eslintrc* +.eslintignore + +.pylintrc +.pycodestyle +.pydocstyle + +.travis.yml +circle.yml +appveyor.yml +azure-pipelines.yml + +.editorconfig +.codeclimate.yml +.codecov.yml + +.rtlintrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..36f5bef --- /dev/null +++ b/.npmrc @@ -0,0 +1,28 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2017 The Stdlib Authors. +# +# 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. +#/ + +# Configuration for [npm][1]. +# +# [1]: https://docs.npmjs.com/files/npmrc + +# Disable the creation of a lock file: +package-lock = false +shrinkwrap = false + +# Disable automatically "saving" dependencies on install: +save = false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..35b70c9 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +stdlib expects community participants to adhere to the project Code of Conduct. The [full text](https://github.com/stdlib-js/stdlib/blob/develop/CODE_OF_CONDUCT.md) is available in the main project repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5f59443 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contribution Guidelines + +Woot woot! If you are new to stdlib, welcome! And thanks for your interest! Guidelines for how to contribute to the project are [available](https://github.com/stdlib-js/stdlib/blob/develop/CONTRIBUTING.md) in the main project repository. diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..da469e5 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,24 @@ +# This file is generated by tools/scripts/update_contributors. +# +# Contributors listed in alphabetical order. + +Athan Reines +Brendan Graetz +Bruno Fenzl +Christopher Dambamuromo +Dominik Moritz +Frank Kovacs +James +Jithin KS +Joey Reed +Joris Labie +Justin Dennison +Marcus +Matt Cochrane +Milan Raj +Ognjen Jevremović +Philipp Burckhardt +Ricky Reusser +Ryan Seal +Shraddheya Shendre +rei2hu diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fcc9934 --- /dev/null +++ b/LICENSE @@ -0,0 +1,481 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by this +license (the "Software") to use, reproduce, display, distribute, execute, and +transmit the Software, and to prepare derivative works of the Software, and to +permit third-parties to whom the Software is furnished to do so, all subject to +the following: + +The copyright notices in the Software and this entire statement, including the +above license grant, this restriction and the following disclaimer, must be +included in all copies of the Software, in whole or in part, and all derivative +works of the Software, unless such copies or derivative works are solely in the +form of machine-executable object code generated by a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES +OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + + +DEPENDENCIES + +The library links against the following external libraries, which have their own +licenses: + +* OpenBLAS + +Copyright (c) 2011-2014, The OpenBLAS Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. Neither the name of the OpenBLAS project nor the names of + its contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +* Electron + +Copyright (c) 2013-2017 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +* Boost + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +* Cephes + +Copyright (c) 1984-2000 Stephen L. Moshier + +Some software in this archive may be from the book _Methods and Programs for +Mathematical Functions_ (Prentice-Hall or Simon & Schuster International, 1989) +or from the Cephes Mathematical Library, a commercial product. In either event, +it is copyrighted by the author. What you see here may be used freely but it +comes with no support or guarantee. + +Stephen L. Moshier +moshier@na-net.ornl.gov + + + +ATTRIBUTION + +The library contains implementations from the following external libraries, +which have their own licenses: + +* FreeBSD + +Copyright (C) 1993-2004 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunPro, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice +is preserved. + + +* FDLIBM + +Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunPro, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice +is preserved. + + +* Go + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +* SLATEC Common Mathematical Library + +Public domain. + + +* ESLint + +Copyright JS Foundation and other contributors, https://js.foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +* StatsFuns.jl + +Copyright (c) 2015: Dahua Lin. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +* SpecialFunctions.jl + +The MIT License (MIT) + +Copyright (c) 2017 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and others: + +https://github.com/JuliaMath/SpecialFunctions.jl/graphs/contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +* MT19937 + +Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1c7d53e --- /dev/null +++ b/Makefile @@ -0,0 +1,534 @@ +#/ +# @license Apache-2.0 +# +# Copyright (c) 2021 The Stdlib Authors. +# +# 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. +#/ + +# USER VARIABLES # + +ifndef VERBOSE + QUIET := @ +else + QUIET := +endif + +# Indicate whether to "fast" fail when linting, running tests, etc: +ifndef FAST_FAIL + FAIL_FAST := true +else +ifeq ($(FAST_FAIL), 0) + FAIL_FAST := false +else + FAIL_FAST := true +endif +endif + +# Define the `NODE_PATH` environment variable: +NODE_PATH ?= + +# Define the `NODE_ENV` environment variable: +NODE_ENV ?= + + +# INTERNAL VARIABLES # + +# Instruct make to warn us when we use an undefined variable (e.g., misspellings). +MAKEFLAGS += --warn-undefined-variables + +# Define the default target: +.DEFAULT_GOAL := all + +# Define the `SHELL` variable to avoid issues on systems where the variable may be inherited from the environment. +# +# ## Notes +# +# - We use `bash` so that we can use `pipefail`. +# +# +# [1]: https://www.gnu.org/prep/standards/html_node/Makefile-Basics.html#Makefile-Basics +# [2]: http://clarkgrubb.com/makefile-style-guide +SHELL := bash + +# Define shell flags. +# +# ## Notes +# +# - `.SHELLFLAGS` was introduced in GNU Make 3.82 and has no effect on the version of GNU Make installed on Mac OS X, which is 3.81. +# - The `-e` flag causes `bash` to exit immediately if a `bash` executed command fails. +# - The `-u` flag causes `bash` to exit with an error message if a variable is accessed without being defined. +# - The `pipefail` option specifies that, if any of the commands in a pipeline fail, the entire pipeline fails. Otherwise the return value of a pipeline is the return value of the last command. +# - The `-c` flag is in the default value of `.SHELLFLAGS`, which must be preserved, as this is how `make` passes the script to be executed to `bash`. +# +.SHELLFLAGS := -eu -o pipefail -c + +# Remove targets if its recipe fails. +# +# ## Notes +# +# - Mentioning this target anywhere in a Makefile prevents a user from re-running make and using an incomplete or invalid target. +# - When debugging, it may be necessary to comment this line out so the incomplete or invalid target can be inspected. +# +# [1]: https://www.gnu.org/software/make/manual/html_node/Special-Targets.html +.DELETE_ON_ERROR: + +# Remove all the default suffixes, preferring to define all rules explicitly. +# +# [1]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules +# [2]: https://www.gnu.org/software/make/manual/html_node/Suffix-Rules.html#Suffix-Rules +.SUFFIXES: + +# Determine the OS ([1][1], [2][2]). +# +# [1]: https://en.wikipedia.org/wiki/Uname#Examples +# [2]: http://stackoverflow.com/a/27776822/2225624 +OS ?= $(shell uname) +ifneq (, $(findstring MINGW,$(OS))) + OS := WINNT +else +ifneq (, $(findstring MSYS,$(OS))) + OS := WINNT +else +ifneq (, $(findstring CYGWIN,$(OS))) + OS := WINNT +else +ifneq (, $(findstring Windows_NT,$(OS))) + OS := WINNT +endif +endif +endif +endif + +# Determine the filename: +this_file := $(lastword $(MAKEFILE_LIST)) + +# Determine the absolute path of the Makefile (see http://blog.jgc.org/2007/01/what-makefile-am-i-in.html): +this_dir := $(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))) + +# Remove the trailing slash: +this_dir := $(patsubst %/,%,$(this_dir)) + +# Determine root directory: +ROOT_DIR = $(this_dir) + +# Define the root build directory: +BUILD_DIR ?= $(ROOT_DIR)/build + +# Define the root directory for storing distributable files: +DIST_DIR ?= $(ROOT_DIR)/dist + +# Define the root directory for storing temporary files: +TMP_DIR ?= $(ROOT_DIR)/tmp + +# Define the directories for writing reports, including code coverage: +REPORTS_DIR ?= $(ROOT_DIR)/reports +COVERAGE_DIR ?= $(REPORTS_DIR)/coverage + +# Define the top-level directory containing node module dependencies: +NODE_MODULES ?= $(ROOT_DIR)/node_modules + +# Define the top-level directory containing node module executables: +BIN_DIR ?= $(NODE_MODULES)/.bin + +# Define the path to the root `package.json`: +ROOT_PACKAGE_JSON ?= $(ROOT_DIR)/package.json + +# Define the folder name convention for source files requiring compilation: +SRC_FOLDER ?= src + +# Define the folder name convention for documentation files: +DOCUMENTATION_FOLDER ?= docs + +# Define the folder name convention for configuration files: +CONFIG_FOLDER ?= etc + +# Define the folder name convention for benchmark files: +BENCHMARKS_FOLDER ?= benchmark + +# Define the folder name convention for benchmark fixtures: +BENCHMARKS_FIXTURES_FOLDER ?= $(BENCHMARKS_FOLDER)/fixtures + +# Define the folder name convention for examples files: +EXAMPLES_FOLDER ?= examples + +# Define the folder name convention for examples fixtures: +EXAMPLES_FIXTURES_FOLDER ?= $(EXAMPLES_FOLDER)/fixtures + +# Define the folder name convention for test files: +TESTS_FOLDER ?= test + +# Define the folder name convention for test fixtures: +TESTS_FIXTURES_FOLDER ?= $(TESTS_FOLDER)/fixtures + +# Define a filepath pattern for benchmark files: +BENCHMARKS_FILTER ?= .*/.* + +# Define a filepath pattern for example files: +EXAMPLES_FILTER ?= .*/.* + +# Define a filepath pattern for test files: +TESTS_FILTER ?= .*/.* + +# Define a filename pattern for benchmark files: +BENCHMARKS_PATTERN ?= benchmark*.js + +# Define a filename pattern for example files: +EXAMPLES_PATTERN ?= *.js + +# Define a filename pattern for test files: +TESTS_PATTERN ?= test*.js + +# Define Node environments: +ifdef NODE_ENV + NODE_ENV_BENCHMARK := $(NODE_ENV) + NODE_ENV_EXAMPLES := $(NODE_ENV) + NODE_ENV_TEST := $(NODE_ENV) +else + NODE_ENV ?= + NODE_ENV_BENCHMARK ?= benchmark + NODE_ENV_EXAMPLES ?= examples + NODE_ENV_TEST ?= test +endif + +# Define whether delete operations should be safe (i.e., deleted items are sent to trash, rather than permanently deleted): +SAFE_DELETE ?= false + +# Define the delete command: +ifeq ($(SAFE_DELETE), true) + # FIXME: -rm -rf + DELETE := -rm + DELETE_FLAGS := -rf +else + DELETE ?= -rm + DELETE_FLAGS ?= -rf +endif + +# Determine the `open` command: +ifeq ($(OS), Darwin) + OPEN ?= open +else + OPEN ?= xdg-open +endif +# TODO: add Windows command + +# Define the command for `node`: +NODE ?= node + +# Define the command for `npm`: +NPM ?= npm + +# Define the path to a JavaScript test runner. +# +# ## Notes +# +# - We reference the `bin` file directly in order to support using `istanbul` for code coverage on Windows (https://github.com/gotwarlost/istanbul#usage-on-windows) +JAVASCRIPT_TEST ?= $(NODE_MODULES)/tape/bin/tape + +# Define any command-line options to use when invoking the test runner: +JAVASCRIPT_TEST_FLAGS ?= + +# Define the path to the executable for parsing TAP output: +TAP_REPORTER ?= $(BIN_DIR)/tap-spec + +# Define the path to the Istanbul executable: +ISTANBUL ?= $(BIN_DIR)/istanbul + +# Define which files and directories to exclude from coverage instrumentation: +ISTANBUL_EXCLUDES_FLAGS ?= \ + --no-default-excludes \ + -x 'node_modules/**' \ + -x 'reports/**' \ + -x 'tmp/**' \ + -x 'deps/**' \ + -x 'dist/**' \ + -x "**/$(SRC_FOLDER)/**" \ + -x "**/$(TESTS_FOLDER)/**" \ + -x "**/$(EXAMPLES_FOLDER)/**" \ + -x "**/$(BENCHMARKS_FOLDER)/**" \ + -x "**/$(CONFIG_FOLDER)/**" \ + -x "**/$(DOCUMENTATION_FOLDER)/**" + +# Define the command to generate test coverage: +ISTANBUL_COVER ?= $(ISTANBUL) cover + +# Define the type of report Istanbul should produce: +ISTANBUL_COVER_REPORT_FORMAT ?= lcov + +# Define the command-line options to be used when generating code coverage: +ISTANBUL_COVER_FLAGS ?= \ + $(ISTANBUL_EXCLUDES_FLAGS) \ + --dir $(COVERAGE_DIR) \ + --report $(ISTANBUL_COVER_REPORT_FORMAT) + +# On Mac OSX, in order to use `|` and other regular expression operators, we need to use enhanced regular expression syntax (-E); see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man7/re_format.7.html#//apple_ref/doc/man/7/re_format. +ifeq ($(OS), Darwin) + find_kernel_prefix := -E +else + find_kernel_prefix := +endif + +# Common exclude flags that most recipes for finding package files should use (Note: order does matter to some degree): +FIND_COMMON_EXCLUDE_FLAGS ?= \ + -not -path "$(ROOT_DIR)/.*" \ + -not -path "$(NODE_MODULES)/*" \ + -not -path "$(BUILD_DIR)/*" \ + -not -path "$(REPORTS_DIR)/*" \ + +# Define exclusion flags to use when searching for benchmark files: +FIND_BENCHMARKS_EXCLUDE_FLAGS ?= \ + $(FIND_COMMON_EXCLUDE_FLAGS) \ + -not -path "$(ROOT_DIR)/**/$(BENCHMARKS_FIXTURES_FOLDER)/*" + +# Define flags for finding benchmark files: +FIND_BENCHMARKS_FLAGS ?= \ + -type f \ + -name "$(BENCHMARKS_PATTERN)" \ + -path "$(ROOT_DIR)/**/$(BENCHMARKS_FOLDER)/**" \ + -regex "$(BENCHMARKS_FILTER)" \ + $(FIND_BENCHMARKS_EXCLUDE_FLAGS) + +ifneq ($(OS), Darwin) + FIND_BENCHMARKS_FLAGS := -regextype posix-extended $(FIND_BENCHMARKS_FLAGS) +endif + +# Define a command to list benchmark files: +FIND_BENCHMARKS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_BENCHMARKS_FLAGS) + +# Define exclusion flags to use when searching for examples files: +FIND_EXAMPLES_EXCLUDE_FLAGS ?= \ + $(FIND_COMMON_EXCLUDE_FLAGS) \ + -not -path "$(ROOT_DIR)/**/$(EXAMPLES_FIXTURES_FOLDER)/*" + +# Define flags for finding examples files: +FIND_EXAMPLES_FLAGS ?= \ + -type f \ + -name "$(EXAMPLES_PATTERN)" \ + -path "$(ROOT_DIR)/**/$(EXAMPLES_FOLDER)/**" \ + -regex "$(EXAMPLES_FILTER)" \ + $(FIND_EXAMPLES_EXCLUDE_FLAGS) + +ifneq ($(OS), Darwin) + FIND_EXAMPLES_FLAGS := -regextype posix-extended $(FIND_EXAMPLES_FLAGS) +endif + +# Define a command to list example files: +FIND_EXAMPLES_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_EXAMPLES_FLAGS) + +# Define exclusion flags to use when searching for test files: +FIND_TESTS_EXCLUDE_FLAGS ?= \ + $(FIND_COMMON_EXCLUDE_FLAGS) \ + -not -path "$(ROOT_DIR)/**/$(TESTS_FIXTURES_FOLDER)/*" + +# Define flags for finding test files: +FIND_TESTS_FLAGS ?= \ + -type f \ + -name "$(TESTS_PATTERN)" \ + -regex "$(TESTS_FILTER)" \ + $(FIND_TESTS_EXCLUDE_FLAGS) + +ifneq ($(OS), Darwin) + FIND_TESTS_FLAGS := -regextype posix-extended $(FIND_TESTS_FLAGS) +endif + +# Define a command to list test files: +FIND_TESTS_CMD ?= find $(find_kernel_prefix) $(ROOT_DIR) $(FIND_TESTS_FLAGS) + + +# RULES # + +#/ +# Default target. +# +# @example +# make +# +# @example +# make all +#/ +all: help + +.PHONY: all + +#/ +# Prints a `Makefile` help message. +# +# @example +# make help +#/ +help: + $(QUIET) echo 'Read the Makefile to see the list of available commands.' + $(QUIET) echo '' + +.PHONY: help + +#/ +# Prints the runtime value of a `Makefile` variable. +# +# ## Notes +# +# - The rule uses the following format: +# +# ```bash +# $ make inspect. +# ``` +# +# @example +# make inspect.ROOT_DIR +# +# @example +# make inspect.CC +#/ +inspect.%: + $(QUIET) echo '$*=$($*)' + +#/ +# Runs the project's install sequence. +# +# @example +# make install +#/ +install: + $(NPM) install + +.PHONY: install + +#/ +# Removes node module dependencies. +# +# @example +# make clean-node +#/ +clean-node: + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(NODE_MODULES) + +#/ +# Runs the project's cleanup sequence. +# +# @example +# make clean +#/ +clean: clean-node clean-cov + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(BUILD_DIR) + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(REPORTS_DIR) + +.PHONY: clean + +#/ +# Runs JavaScript benchmarks consecutively. +# +# ## Notes +# +# - The recipe assumes that benchmark files can be run via Node.js. +# - This rule is useful when wanting to glob for JavaScript benchmark files (e.g., run all JavaScript benchmarks for a particular package). +# +# +# @param {string} [BENCHMARKS_FILTER] - file path pattern (e.g., `.*/utils/group-by/.*`) +# +# @example +# make benchmark +# +# @example +# make benchmark BENCHMARKS_FILTER=".*/utils/group-by/.*" +#/ +benchmark: $(NODE_MODULES) + $(QUIET) $(FIND_BENCHMARKS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \ + echo ""; \ + echo "Running benchmark: $$file"; \ + NODE_ENV="$(NODE_ENV_BENCHMARK)" \ + NODE_PATH="$(NODE_PATH)" \ + $(NODE) $$file || exit 1; \ + done + +.PHONY: benchmark + +#/ +# Runs JavaScript examples consecutively. +# +# ## Notes +# +# - This rule is useful when wanting to glob for JavaScript examples files (e.g., run all JavaScript examples for a particular package). +# - This rule **assumes** that examples files can be run using Node.js. +# +# +# @param {string} [EXAMPLES_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`) +# +# @example +# make examples +# +# @example +# make examples EXAMPLES_FILTER=".*/strided/common/.*" +#/ +examples: $(NODE_MODULES) + $(QUIET) $(FIND_EXAMPLES_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r file; do \ + echo ""; \ + echo "Running example: $$file"; \ + NODE_ENV="$(NODE_ENV_EXAMPLES)" \ + NODE_PATH="$(NODE_PATH)" \ + $(NODE) $$file || exit 1; \ + done + +.PHONY: examples + +#/ +# Runs JavaScript tests consecutively. +# +# ## Notes +# +# - This rule is useful when wanting to glob for JavaScript test files (e.g., run all JavaScript tests for a particular package). +# - This rule **assumes** that test files can be run using Node.js. +# +# +# @param {string} [TEST_FILTER] - file path pattern (e.g., `.*/math/base/special/abs/.*`) +# +# @example +# make test +# +# @example +# make test TESTS_FILTER=".*/strided/common/.*" +#/ +test: $(NODE_MODULES) + $(QUIET) $(FIND_TESTS_CMD) | grep '^[\/]\|^[a-zA-Z]:[/\]' | while read -r test; do \ + echo ''; \ + echo "Running test: $$test"; \ + NODE_ENV="$(NODE_ENV_TEST)" \ + NODE_PATH="$(NODE_PATH)" \ + $(JAVASCRIPT_TEST) \ + $(JAVASCRIPT_TEST_FLAGS) \ + $$test \ + | $(TAP_REPORTER) || exit 1; \ + done + +.PHONY: test + +#/ +# Runs unit tests and generate a test coverage report. +# +# @example +# make test-cov +#/ +test-cov: clean-cov + $(QUIET) NODE_ENV="$(NODE_ENV_TEST)" \ + NODE_PATH="$(NODE_PATH)" \ + $(ISTANBUL_COVER) $(ISTANBUL_COVER_FLAGS) $(JAVASCRIPT_TEST) -- $$( $(FIND_TESTS_CMD) ) + +.PHONY: test-cov + +#/ +# Removes a test coverage directory. +# +# @example +# make clean-cov +#/ +clean-cov: + $(QUIET) $(DELETE) $(DELETE_FLAGS) $(COVERAGE_DIR) diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..f5374f6 --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +Copyright (c) 2016-2021 The Stdlib Authors. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a8875af --- /dev/null +++ b/README.md @@ -0,0 +1,361 @@ + + +# Disposable HTTP Server + +[![NPM version][npm-image]][npm-url] [![Build Status][test-image]][test-url] [![Coverage Status][coverage-image]][coverage-url] [![dependencies][dependencies-image]][dependencies-url] + +> Create a disposable HTTP server. + +
+ +## Installation + +```bash +npm install @stdlib/net-disposable-http-server +``` + +
+ +
+ +## Usage + + + +```javascript +var httpServer = require( '@stdlib/net-disposable-http-server' ); +``` + +#### httpServer( options\[, clbk] ) + +Creates a disposable HTTP server; i.e., the server closes immediately after serving provided content. + + + +```javascript +var opts = { + 'html': '', + 'javascript': 'console.log( "Boop" );' +}; + +httpServer( opts ); +``` + +The function accepts the following options: + +- **html**: `buffer` or `string` to serve as HTML content. +- **javascript**: `buffer` or `string` to serve as JavaScript. +- **port**: server port. Default: `0` (i.e., randomly assigned). +- **maxport**: max server port (used when port hunting). Default: `=port`. +- **hostname**: server hostname. +- **address**: server address. Default: `"0.0.0.0"`. +- **open**: `boolean` indicating whether to launch a web browser. Default: `false`. + +To serve HTML content, set the `html` option. Once the content is requested, the server will close. + + + +```javascript +var opts = { + 'html': '

Beep

' +}; + +httpServer( opts ); +``` + +To serve JavaScript, set the `javascript` option. If no HTML is provided, an HTML boilerplate is served and the JavaScript is served as `/bundle.js`. Once the content is requested, the server will close. + + + +```javascript +var opts = { + 'javascript': 'console.log( "Boop" );' +}; + +httpServer( opts ); +``` + +If HTML and JavaScript are provided, in order for the JavaScript to be served, the HTML content should request the file `/bundle.js`. + + + +```javascript +var opts = { + 'html': '', + 'javascript': 'console.log( "Boop" );' +}; + +httpServer( opts ); +``` + +To obtain the `server` handle, provide a callback. + + + +```javascript +var nextTick = require( '@stdlib/utils-next-tick' ); + +function onReady( error, server ) { + if ( error ) { + throw error; + } + nextTick( close ); + function close() { + server.close(); + } +} + +var opts = { + 'html': html, + 'javascript': 'console.log( "Boop" );' +}; + +httpServer( opts, onReady ); +``` + +
+ + + +
+ +## Notes + +- If neither the `html` or `javascript` option is set, the server serves an HTML boilerplate and then closes. + +
+ + + +
+ +## Examples + + + + + +```javascript +var join = require( 'path' ).join; +var readFileSync = require( '@stdlib/fs-read-file' ).sync; +var httpServer = require( '@stdlib/net-disposable-http-server' ); + +var html = join( __dirname, 'examples', 'fixtures', 'index.html' ); +var js = join( __dirname, 'examples', 'fixtures', 'script.js' ); + +var opts = { + 'html': readFileSync( html ), + 'javascript': readFileSync( js ), + 'port': 7331, + 'hostname': 'localhost', + 'open': false +}; + +httpServer( opts, clbk ); + +function clbk( error, server ) { + if ( error ) { + throw error; + } + // Give the user a few seconds to open her web browser before closing the server... + setTimeout( onTimeout, 5000 ); + + function onTimeout() { + server.close(); + } +} +``` + +
+ + + +* * * + +
+ +## CLI + +
+ +## Installation + +To use the module as a general utility, install the module globally + +```bash +npm install -g @stdlib/net-disposable-http-server +``` + +
+ +
+ +### Usage + +```text +Usage: temp-http-server [options] (--html path | --js path | --stdin type) + +Options: + + -h, --help Print this message. + -V, --version Print the package version. + --html path Serve HTML. + --js, --javascript path Serve JavaScript. + --stdin type Type of content: html or javascript. + -p, --port port Server port. Default: 0. + --maxport maxport Max server port. Default: `port`. + --hostname hostname Server hostname. + --address address Server address. Default: 0.0.0.0. + --open Launch a browser once server is ready. +``` + +The application recognizes the following [environment variables][environment-variable]: + +- `DEBUG`: enable verbose logging. +- `PORT`: server port. +- `MAXPORT`: max server port. +- `HOSTNAME`: server hostname. +- `ADDRESS`: server address. + +
+ + + +
+ +### Notes + +- Command-line arguments **always** take precedence over [environment variables][environment-variable]. +- If either the `--html` or `--javascript` command-line flag is set, `stdin` is assumed to be of the other type. Accordingly, the `--stdin` flag may be omitted. + +
+ + + +
+ +### Examples + +To serve an HTML file, + +```bash +$ DEBUG=* temp-http-server --html ./examples/fixtures/index.html +... +``` + +To serve a JavaScript file (and a default HTML boilerplate), + +```bash +$ DEBUG=* temp-http-server --javascript ./examples/fixtures/script.js +... +``` + +In addition to file input, the application accepts [standard input][standard-streams]. To pipe HTML, + +```bash +$ cat ./examples/fixtures/index.html | DEBUG=* temp-http-server --port 7331 --stdin html +... +``` + +To pipe HTML and load a JavaScript file, + +```bash +$ cat ./examples/fixtures/index.html | DEBUG=* temp-http-server --port 7331 --javascript ./examples/fixtures/script.js +... +``` + +To pipe JavaScript (and serve a default HTML boilerplate), + +```bash +$ cat ./examples/fixtures/script.js | DEBUG=* temp-http-server --address '127.0.0.1' --stdin javascript +... +``` + +To pipe JavaScript and serve custom HTML content which requests a `/bundle.js` file, + +```bash +$ cat ./examples/fixtures/script.js | DEBUG=* temp-http-server --html ./examples/fixtures/index.html +... +``` + +
+ + + +
+ + + + +
+ +* * * + +## Notice + +This package is part of [stdlib][stdlib], a standard library for JavaScript and Node.js, with an emphasis on numerical and scientific computing. The library provides a collection of robust, high performance libraries for mathematics, statistics, streams, utilities, and more. + +For more information on the project, filing bug reports and feature requests, and guidance on how to develop [stdlib][stdlib], see the main project [repository][stdlib]. + +--- + +## License + +See [LICENSE][stdlib-license]. + + +## Copyright + +Copyright © 2016-2021. The Stdlib [Authors][stdlib-authors]. + +
+ + + + + + + + diff --git a/bin/cli b/bin/cli new file mode 100644 index 0000000..683237a --- /dev/null +++ b/bin/cli @@ -0,0 +1,241 @@ +#!/usr/bin/env node + +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var fs = require( 'fs' ); +var path = require( 'path' ); +var parseArgs = require( 'minimist' ); +var notifier = require( 'update-notifier' ); +var ENV = require( '@stdlib/process/env' ); +var cwd = require( '@stdlib/process/cwd' ); +var stdin = require( '@stdlib/process/read-stdin' ); +var readFileSync = require( '@stdlib/fs/read-file' ).sync; +var pkg = require( './../package.json' ); +var opts = require( './opts.json' ); +var httpServer = require( './../lib' ); + + +// FUNCTIONS // + +/** +* Performs initialization tasks. +* +* @private +* @example +* init(); +*/ +function init() { + var opts; + + // Check if newer versions exist for this package: + opts = { + 'pkg': pkg + }; + notifier( opts ).notify(); + + // Set the process title to allow the process to be more easily identified: + process.title = pkg.name; + process.stdout.on( 'error', process.exit ); +} + +/** +* Prints usage information. +* +* @private +* @example +* help(); +* // => '...' +*/ +function help() { + var fpath = path.join( __dirname, 'usage.txt' ); + fs.createReadStream( fpath ) + .pipe( process.stderr ) + .on( 'close', onClose ); + + function onClose() { + process.exit( 0 ); + } +} + +/** +* Prints the package version. +* +* @private +* @example +* version(); +* // => '#.#.#' +*/ +function version() { + var msg = pkg.version.toString()+'\n'; + process.stdout.write( msg, 'utf8' ); + process.exit( 0 ); +} + +/** +* Writes an error message to `stderr` and then exits. +* +* @private +* @param {string} msg - error message +*/ +function exit( msg ) { + process.stderr.write( msg+'\n', 'utf8' ); + process.exit( 1 ); +} + + +// VARIABLES // + +var fpath; +var file; +var args; +var err; +var v; + + +// MAIN // + +init(); + +// Parse command-line arguments: +args = parseArgs( process.argv.slice( 2 ), opts ); + +if ( args.help ) { + return help(); +} +if ( args.version ) { + return version(); +} + +opts = {}; + +v = args.port || ENV.PORT; +if ( v ) { + opts.port = parseInt( v, 10 ); +} +v = args.maxport || ENV.MAXPORT; +if ( v ) { + opts.maxport = parseInt( v, 10 ); +} +v = args.hostname || ENV.HOSTNAME; +if ( v ) { + opts.hostname = v; +} +v = args.address || ENV.ADDRESS; +if ( v ) { + opts.address = v; +} +if ( args.open ) { + opts.open = true; +} + +// Load an HTML file... +if ( args.html ) { + fpath = path.resolve( cwd(), args.html ); + file = readFileSync( fpath ); + if ( file instanceof Error ) { + return exit( file.message ); + } + opts.html = file; +} +// Load a JavaScript file... +if ( args.javascript ) { + fpath = path.resolve( cwd(), args.javascript ); + file = readFileSync( fpath ); + if ( file instanceof Error ) { + return exit( file.message ); + } + opts.javascript = file; +} +// Determine if we need to read data from `stdin`... +if ( args.stdin ) { + if ( args.stdin === 'html' ) { + return stdin( html ); + } else if ( + args.stdin === 'javascript' || + args.stdin === 'js' + ) { + return stdin( javascript ); + } + err = new Error( 'invalid flag. Unrecognized/unsupported `stdin` type: '+args.stdin+'.' ); + return exit( err.message ); +} +// If the `stdin` flag has not been set and we're receiving data (i.e., not running in a terminal context), assume a content type based on either an `--html` or `--javascript` flag being set... +else if ( !process.stdin.isTTY ) { + if ( args.html ) { + return stdin( javascript ); + } else if ( args.javascript ) { + return stdin( html ); + } + // Neither `--html` or `--javascript` was set... + err = new Error( 'invalid invocation. Must specify a `stdin` content type.' ); + return exit( err.message ); +} + +return process.nextTick( next ); + +/** +* Callback invoked after attempting to read HTML from `stdin`. +* +* @private +* @param {(Error|null)} error - error object +* @param {Buffer} data - `stdin` data +*/ +function html( error, data ) { + if ( error ) { + return exit( error.message ); + } + if ( data.toString() === '' ) { + error = new Error( 'no input data. Provide either a path to an HTML file or provide HTML via `stdin`.' ); + return exit( error.message ); + } + opts.html = data; + next(); +} + +/** +* Callback invoked after attempting to read JavaScript from `stdin`. +* +* @private +* @param {(Error|null)} error - error object +* @param {Buffer} data - `stdin` data +*/ +function javascript( error, data ) { + if ( error ) { + return exit( error.message ); + } + if ( data.toString() === '' ) { + error = new Error( 'no input data. Provide either a path to a JavaScript file or provide JavaScript via `stdin`.' ); + return exit( error.message ); + } + opts.javascript = data; + next(); +} + +/** +* Callback invoked once ready to run an HTTP server. +* +* @private +*/ +function next() { + httpServer( opts ); +} diff --git a/bin/opts.json b/bin/opts.json new file mode 100644 index 0000000..936443c --- /dev/null +++ b/bin/opts.json @@ -0,0 +1,30 @@ +{ + "string": [ + "port", + "maxport", + "hostname", + "address", + "html", + "javascript", + "stdin" + ], + "boolean": [ + "help", + "version", + "open" + ], + "alias": { + "help": [ + "h" + ], + "version": [ + "V" + ], + "port": [ + "p" + ], + "javascript": [ + "js" + ] + } +} diff --git a/bin/usage.txt b/bin/usage.txt new file mode 100644 index 0000000..5fb3329 --- /dev/null +++ b/bin/usage.txt @@ -0,0 +1,16 @@ + +Usage: temp-http-server [options] (--html path | --js path | --stdin type) + +Options: + + -h, --help Print this message. + -V, --version Print the package version. + --html path Serve HTML. + --js, --javascript path Serve JavaScript. + --stdin type Type of content. Either: html or javascript. + -p, --port port Server port. Default: 0. + --maxport maxport Max server port. Default: `port`. + --hostname hostname Server hostname. + --address address Server address. Default: 0.0.0.0. + --open Launch a browser once server is ready. + diff --git a/docs/types/index.d.ts b/docs/types/index.d.ts new file mode 100644 index 0000000..0117f05 --- /dev/null +++ b/docs/types/index.d.ts @@ -0,0 +1,127 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2021 The Stdlib Authors. +* +* 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. +*/ + +// TypeScript Version: 2.0 + +/// + +import { Buffer } from 'buffer'; + +/** +* Interface defining function options. +*/ +interface Options { + /** + * HTML content to serve. + */ + html?: Buffer | string; + + /** + * JavaScript to serve. + */ + javascript?: Buffer | string; + + /** + * Server port (default: 0). + */ + port?: number; + + /** + * Max server port. + */ + maxport?: number; + + /** + * Server hostname. + */ + hostname?: string; + + /** + * Server address (default: '0.0.0.0'). + */ + address?: string; + + /** + * Indicates whether to launch a web browser (default: false). + */ + open?: boolean; +} + +/** +* Callback invoked after creating a server. +*/ +type Nullary = () => void; + +/** +* Callback invoked after creating a server. +* +* @param error - error object or null +*/ +type Unary = ( error: Error | null ) => void; + +/** +* Callback invoked after creating a server. +* +* @param error - error object or null +* @param server - server object +*/ +type Binary = ( error: Error | null, server: any ) => void; + +/** +* Callback invoked after creating a server. +* +* @param error - error object or null +* @param server - server object +*/ +type Callback = Nullary | Unary | Binary; + +/** +* Creates a disposable HTTP server. +* +* @param options - server options +* @param options.html - HTML content to serve +* @param options.javascript - JavaScript to serve +* @param options.port - server port (default: 0) +* @param options.maxport - max server port +* @param options.hostname - server hostname +* @param options.address - server address (default: '0.0.0.0') +* @param options.open - boolean indicating whether to launch a web browser (default: false) +* @param clbk - callback to invoke upon creating a server +* @throws must provide valid options +* @throws error encountered when starting server +* +* @example +* var opts = { +* 'html': '

Beep

' +* }; +* +* httpServer( opts, onReady ); +* +* function onReady( error, server ) { +* if ( error ) { +* throw error; +* } +* server.close(); +* } +*/ +declare function httpServer( options: Options, clbk?: Callback ): void; + + +// EXPORTS // + +export = httpServer; diff --git a/docs/types/test.ts b/docs/types/test.ts new file mode 100644 index 0000000..03645ca --- /dev/null +++ b/docs/types/test.ts @@ -0,0 +1,143 @@ +/* +* @license Apache-2.0 +* +* Copyright (c) 2021 The Stdlib Authors. +* +* 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. +*/ + +/* tslint:disable:no-unsafe-any */ + +import httpServer = require( './index' ); + +/** +* Callback invoked after creating a server. +* +* @param error - error object or null +* @param server - server object +*/ +function done( error: Error | null, server: any ) { + if ( error ) { + throw error; + } + server.close(); +} + + +// TESTS // + +// The function returns a function to create a server... +{ + httpServer( { 'html': '

Beep

' }, done ); // $ExpectType void +} + +// The compiler throws an error if the function is provided a first argument which is not an options object... +{ + httpServer( 'abc' ); // $ExpectError + httpServer( true ); // $ExpectError + httpServer( false ); // $ExpectError + httpServer( 5 ); // $ExpectError + httpServer( [] ); // $ExpectError +} + +// The compiler throws an error if the function is provided a second argument which is not a callback function... +{ + httpServer( {}, false ); // $ExpectError + httpServer( {}, true ); // $ExpectError + httpServer( {}, 123 ); // $ExpectError + httpServer( {}, null ); // $ExpectError + httpServer( {}, 'abc' ); // $ExpectError + httpServer( {}, [] ); // $ExpectError + httpServer( {}, {} ); // $ExpectError +} + +// The compiler throws an error if the function is provided a `html` option which is not a string or buffer... +{ + httpServer( { 'html': 123 }, done ); // $ExpectError + httpServer( { 'html': true }, done ); // $ExpectError + httpServer( { 'html': false }, done ); // $ExpectError + httpServer( { 'html': null }, done ); // $ExpectError + httpServer( { 'html': [] }, done ); // $ExpectError + httpServer( { 'html': {} }, done ); // $ExpectError + httpServer( { 'html': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided a `javascript` option which is not a string or buffer... +{ + httpServer( { 'javascript': 123 }, done ); // $ExpectError + httpServer( { 'javascript': true }, done ); // $ExpectError + httpServer( { 'javascript': false }, done ); // $ExpectError + httpServer( { 'javascript': null }, done ); // $ExpectError + httpServer( { 'javascript': [] }, done ); // $ExpectError + httpServer( { 'javascript': {} }, done ); // $ExpectError + httpServer( { 'javascript': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided a `port` option which is not a number... +{ + httpServer( { 'port': 'abc' }, done ); // $ExpectError + httpServer( { 'port': true }, done ); // $ExpectError + httpServer( { 'port': false }, done ); // $ExpectError + httpServer( { 'port': null }, done ); // $ExpectError + httpServer( { 'port': [] }, done ); // $ExpectError + httpServer( { 'port': {} }, done ); // $ExpectError + httpServer( { 'port': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided a `maxport` option which is not a number... +{ + httpServer( { 'maxport': 'abc' }, done ); // $ExpectError + httpServer( { 'maxport': true }, done ); // $ExpectError + httpServer( { 'maxport': false }, done ); // $ExpectError + httpServer( { 'maxport': null }, done ); // $ExpectError + httpServer( { 'maxport': [] }, done ); // $ExpectError + httpServer( { 'maxport': {} }, done ); // $ExpectError + httpServer( { 'maxport': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided a `hostname` option which is not a string... +{ + httpServer( { 'hostname': 123 }, done ); // $ExpectError + httpServer( { 'hostname': true }, done ); // $ExpectError + httpServer( { 'hostname': false }, done ); // $ExpectError + httpServer( { 'hostname': null }, done ); // $ExpectError + httpServer( { 'hostname': [] }, done ); // $ExpectError + httpServer( { 'hostname': {} }, done ); // $ExpectError + httpServer( { 'hostname': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided an `address` option which is not a string... +{ + httpServer( { 'address': 123 }, done ); // $ExpectError + httpServer( { 'address': true }, done ); // $ExpectError + httpServer( { 'address': false }, done ); // $ExpectError + httpServer( { 'address': null }, done ); // $ExpectError + httpServer( { 'address': [] }, done ); // $ExpectError + httpServer( { 'address': {} }, done ); // $ExpectError + httpServer( { 'address': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided an `open` option which is not a boolean... +{ + httpServer( { 'open': 123 }, done ); // $ExpectError + httpServer( { 'open': 'abc' }, done ); // $ExpectError + httpServer( { 'open': null }, done ); // $ExpectError + httpServer( { 'open': [] }, done ); // $ExpectError + httpServer( { 'open': {} }, done ); // $ExpectError + httpServer( { 'open': ( x: number ): number => x }, done ); // $ExpectError +} + +// The compiler throws an error if the function is provided an invalid number of arguments... +{ + httpServer( {}, done, {} ); // $ExpectError +} diff --git a/examples/fixtures/index.html b/examples/fixtures/index.html new file mode 100644 index 0000000..18b87af --- /dev/null +++ b/examples/fixtures/index.html @@ -0,0 +1,35 @@ + + + + + + + + + Disposable HTTP Server Example + + +

Beep

+ + + + + diff --git a/examples/fixtures/script.js b/examples/fixtures/script.js new file mode 100644 index 0000000..d3f364e --- /dev/null +++ b/examples/fixtures/script.js @@ -0,0 +1,39 @@ +/* eslint-disable */ + +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +(function() { + 'use strict'; + + var idx; + var str; + var el; + + el = document.querySelector( 'h1' ); + + str = [ 'Beep', 'Boop' ]; + idx = 0; + + setInterval( update, 1000 ); + + function update() { + idx = (idx+1) % 2; + el.innerHTML = str[ idx ]; + } +})(); diff --git a/examples/index.js b/examples/index.js new file mode 100644 index 0000000..d0fac7e --- /dev/null +++ b/examples/index.js @@ -0,0 +1,48 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +var join = require( 'path' ).join; +var readFileSync = require( '@stdlib/fs-read-file' ).sync; +var httpServer = require( './../lib' ); + +var html = join( __dirname, 'fixtures', 'index.html' ); +var js = join( __dirname, 'fixtures', 'script.js' ); + +var opts = { + 'html': readFileSync( html ), + 'javascript': readFileSync( js ), + 'port': 7331, + 'hostname': 'localhost', + 'open': false +}; + +httpServer( opts, clbk ); + +function clbk( error, server ) { + if ( error ) { + throw error; + } + // Give the user a few seconds to open her web browser before closing the server... + setTimeout( onTimeout, 5000 ); + + function onTimeout() { + server.close(); + } +} diff --git a/lib/connections_store.js b/lib/connections_store.js new file mode 100644 index 0000000..93c0bcf --- /dev/null +++ b/lib/connections_store.js @@ -0,0 +1,34 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +/** +* Returns a new connections store. +* +* @private +* @returns {Object} store +*/ +function create() { + return {}; +} + + +// EXPORTS // + +module.exports = create; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..657f924 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,50 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +/** +* Create a disposable HTTP server. +* +* @module @stdlib/net/disposable-http-server +* +* @example +* var httpServer = require( '@stdlib/net-disposable-http-server' ); +* +* var opts = { +* 'html': '

Beep

' +* }; +* +* httpServer( opts, onReady ); +* +* function onReady( error, server ) { +* if ( error ) { +* throw error; +* } +* server.close(); +* } +*/ + +// MODULES // + +var httpServer = require( './server.js' ); + + +// EXPORTS // + +module.exports = httpServer; diff --git a/lib/opts.js b/lib/opts.js new file mode 100644 index 0000000..3cf374b --- /dev/null +++ b/lib/opts.js @@ -0,0 +1,69 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var hasOwnProp = require( '@stdlib/assert-has-own-property' ); + + +// MAIN // + +/** +* Extracts HTTP server options from input function options. +* +* @private +* @param {Options} options - function options +* @param {NonNegativeInteger} [options.port] - server port +* @param {NonNegativeInteger} [options.maxport] - max server port +* @param {string} [options.hostname] - server hostname +* @param {string} [options.address] - server address +* @returns {Options} server options +* +* @example +* var options = { +* 'html': '

beep

', +* 'open': true, +* 'port': 7331, +* 'address': '127.0.0.1' +* }; +* var out = opts( options ); +* // returns {'port': 7331, 'address': '127.0.0.1'} +*/ +function opts( options ) { + var out = {}; + if ( hasOwnProp( options, 'port' ) ) { + out.port = options.port; + } + if ( hasOwnProp( options, 'maxport' ) ) { + out.maxport = options.maxport; + } + if ( hasOwnProp( options, 'hostname' ) ) { + out.hostname = options.hostname; + } + if ( hasOwnProp( options, 'address' ) ) { + out.address = options.address; + } + return out; +} + + +// EXPORTS // + +module.exports = opts; diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..286a809 --- /dev/null +++ b/lib/server.js @@ -0,0 +1,328 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var path = require( 'path' ); +var logger = require( 'debug' ); +var objectKeys = require( '@stdlib/utils-keys' ); +var createServer = require( '@stdlib/net-http-server' ); +var readFileSync = require( '@stdlib/fs-read-file' ).sync; +var isString = require( '@stdlib/assert-is-string' ).isPrimitive; +var isFunction = require( '@stdlib/assert-is-function' ); +var openURL = require( '@stdlib/utils-open-url' ); +var noop = require( '@stdlib/utils-noop' ); +var Buffer = require( '@stdlib/buffer-ctor' ); +var string2buffer = require( '@stdlib/buffer-from-string' ); +var nextTick = require( '@stdlib/utils-next-tick' ); +var validate = require( './validate.js' ); +var serverOpts = require( './opts.js' ); +var createStore = require( './connections_store.js' ); + + +// VARIABLES // + +var debug = logger( 'disposable-http-server' ); + + +// MAIN // + +/** +* Creates a disposable HTTP server. +* +* @param {Options} options - server options +* @param {(Buffer|string)} [options.html] - HTML content to serve +* @param {(Buffer|string)} [options.javascript] - JavaScript to serve +* @param {NonNegativeInteger} [options.port=0] - server port +* @param {NonNegativeInteger} [options.maxport] - max server port +* @param {string} [options.hostname] - server hostname +* @param {string} [options.address="0.0.0.0"] - server address +* @param {boolean} [options.open=false] - boolean indicating whether to launch a web browser +* @param {Callback} [clbk] - callback to invoke upon creating a server +* @throws {TypeError} must provide valid options +* @throws {Error} error encountered when starting server +* +* @example +* var opts = { +* 'html': '

Beep

' +* }; +* +* httpServer( opts, onReady ); +* +* function onReady( error, server ) { +* if ( error ) { +* throw error; +* } +* server.close(); +* } +*/ +function httpServer( options ) { + var connections; + var isClosing; + var server; + var sopts; + var fpath; + var clbk; + var boot; + var opts; + var err; + + opts = {}; + if ( arguments.length > 1 ) { + clbk = arguments[ 1 ]; + if ( !isFunction( clbk ) ) { + throw new TypeError( 'invalid argument. Callback argument must be a function. Value: `' + clbk + '`.' ); + } + } else { + clbk = noop; + } + err = validate( opts, options ); + if ( err ) { + throw err; + } + // If provided HTML and/or JavaScript as `strings`, convert to `buffers`... + if ( opts.html && isString( opts.html ) ) { + opts.html = string2buffer( opts.html ); + } + if ( opts.javascript && isString( opts.javascript ) ) { + opts.javascript = string2buffer( opts.javascript ); + } + // Extract server options: + sopts = serverOpts( options ); + + debug( 'Serving provided content.' ); + if ( !opts.html ) { + debug( 'No HTML content provided.' ); + debug( 'Loading a boilerplate HTML page...' ); + fpath = path.resolve( __dirname, '../static/index.html' ); + opts.html = readFileSync( fpath ); + } + // Create a function to boot a server... + boot = createServer( sopts, requestListener ); + + debug( 'Starting server...' ); + boot( onServer ); + + // Initialize a connections store: + connections = createStore(); + + /** + * Callback invoked upon creating a server. + * + * @private + * @param {(Error|null)} error - error object + * @param {Server} _server - server instance + * @throws {Error} error encountered when starting server + */ + function onServer( error, _server ) { + var addr; + if ( error ) { + throw error; + } + debug( 'Server started.' ); + server = _server; + server.on( 'connection', onConnection ); + server.once( 'close', onClose ); + if ( opts.open ) { + addr = server.address(); + openURL( 'http://'+addr.address+':'+addr.port ); + } + clbk( null, server ); + } + + /** + * Callback invoked upon receiving a socket connection. + * + * @private + * @param {Socket} socket - socket connection + */ + function onConnection( socket ) { + var key = socket.remoteAddress + ':' + socket.remotePort; + + debug( 'Received a socket connection: %s.', key ); + connections[ key ] = socket; + socket.on( 'close', onClose ); + + /** + * Callback invoked once a socket connection closes. + * + * @private + */ + function onClose() { + debug( 'Socket connection closed: %s.', key ); + delete connections[ key ]; + } + } + + /** + * Callback invoked upon receiving an HTTP request for provided content. + * + * @private + * @param {IncomingMessage} request - HTTP request object + * @param {ServerResponse} response - HTTP response object + * @returns {void} + */ + function requestListener( request, response ) { + debug( 'Received a request for %s', request.url ); + + if ( isClosing ) { + return unavailable( request, response ); + } + if ( request.url === '/bundle.js' ) { + nextTick( onTick( sendJavaScript ) ); + return response.once( 'finish', onFinish ); + } + if ( request.url !== '/' && request.url !== '/index.html' ) { + return notFound( request, response ); + } + nextTick( onTick( sendHTML ) ); + if ( !opts.javascript ) { + response.once( 'finish', onFinish ); + } + + /** + * Returns a callback to return a response on the next tick. Note that this is a workaround for a race condition bug in Node v0.10 (see [nodejs/node#1309][1]). + * + * [1]: https://github.com/nodejs/node/issues/1309 + * + * @private + * @param {Function} fcn - response function + * @returns {Callback} callback to invoke on next tick + */ + function onTick( fcn ) { + return next; + + /** + * Callback to return on a response. + * + * @private + */ + function next() { + fcn( request, response ); + } + } + } + + /** + * Sends a 404 response. + * + * @private + * @param {IncomingMessage} request - HTTP request object + * @param {ServerResponse} response - HTTP response object + */ + function notFound( request, response ) { + debug( 'Sending 404 response...' ); + response.statusCode = 404; + response.end(); + } + + /** + * Sends a 503 response. + * + * @private + * @param {IncomingMessage} request - HTTP request object + * @param {ServerResponse} response - HTTP response object + */ + function unavailable( request, response ) { + debug( 'Sending 503 response...' ); + response.statusCode = 503; + response.end(); + } + + /** + * Sends HTML content in response to a client request. + * + * @private + * @param {IncomingMessage} request - HTTP request object + * @param {ServerResponse} response - HTTP response object + */ + function sendHTML( request, response ) { + debug( 'Sending HTML...' ); + response.statusCode = 200; + response.setHeader( 'Content-Type', 'text/html' ); + + // TODO: we have to convert to a `string` because Node v0.10 requires a `string`. Subsequent versions support providing a `Buffer` object. Ideally, we would sniff `Buffer` support and only convert to a `string` if necessary. + response.setHeader( 'Content-Length', Buffer.byteLength( opts.html.toString() ) ); + response.end( opts.html ); + } + + /** + * Sends JavaScript content in response to a client request. + * + * @private + * @param {IncomingMessage} request - HTTP request object + * @param {ServerResponse} response - HTTP response object + */ + function sendJavaScript( request, response ) { + debug( 'Sending JavaScript...' ); + response.statusCode = 200; + response.setHeader( 'Content-Type', 'text/javascript' ); + + // TODO: we have to convert to a `string` because Node v0.10 requires a `string`. Subsequent versions support providing a `Buffer` object. Ideally, we would sniff `Buffer` support and only convert to a `string` if necessary. + response.setHeader( 'Content-Length', Buffer.byteLength( opts.javascript.toString() ) ); + response.end( opts.javascript ); + } + + /** + * Callback invoked once the server should close. + * + * @private + */ + function onFinish() { + debug( 'Finished serving content.' ); + isClosing = true; + + debug( 'Closing the server...' ); + server.close(); + + setTimeout( destroyConnections, 5000 ); + } + + /** + * Destroys all connections. + * + * @private + */ + function destroyConnections() { + var keys; + var i; + + debug( 'Destroying all connections...' ); + keys = objectKeys( connections ); + for ( i = 0; i < keys.length; i++ ) { + debug( 'Destroying connection %s...', keys[i] ); + connections[ keys[i] ].destroy(); + } + } + + /** + * Callback invoked once a server closes. + * + * @private + */ + function onClose() { + debug( 'Server closed.' ); + } +} + + +// EXPORTS // + +module.exports = httpServer; diff --git a/lib/validate.js b/lib/validate.js new file mode 100644 index 0000000..19e606d --- /dev/null +++ b/lib/validate.js @@ -0,0 +1,83 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var isBuffer = require( '@stdlib/assert-is-buffer' ); +var isBoolean = require( '@stdlib/assert-is-boolean' ).isPrimitive; +var isString = require( '@stdlib/assert-is-string' ).isPrimitive; +var isObject = require( '@stdlib/assert-is-plain-object' ); +var hasOwnProp = require( '@stdlib/assert-has-own-property' ); + + +// MAIN // + +/** +* Validates function options. +* +* @private +* @param {Object} opts - destination object +* @param {Options} options - function options +* @param {(Buffer|string)} [options.html] - HTML content to serve +* @param {(Buffer|string)} [options.javascript] - JavaScript script to serve +* @param {boolean} [options.open] - indicates whether to launch a web browser +* @returns {Error|null} error or null +* +* @example +* var options = { +* 'port': 7331, +* 'address': '127.0.0.1', +* 'open': false +* }; +* var opts = {}; +* var err = validate( opts, options ); +* if ( err ) { +* throw err; +* } +*/ +function validate( opts, options ) { + if ( !isObject( options ) ) { + return new TypeError( 'invalid argument. Options argument must be an object. Value: `' + options + '`.' ); + } + if ( hasOwnProp( options, 'html' ) ) { + opts.html = options.html; + if ( !isBuffer( opts.html ) && !isString( opts.html ) ) { + return new TypeError( 'invalid option. `html` option must be either a `buffer` or a primitive string. Option: `' + opts.html + '`.' ); + } + } + if ( hasOwnProp( options, 'javascript' ) ) { + opts.javascript = options.javascript; + if ( !isBuffer( opts.javascript ) && !isString( opts.javascript ) ) { + return new TypeError( 'invalid option. `javascript` option must be either a `buffer` or a primitive string. Option: `' + opts.javascript + '`.' ); + } + } + if ( hasOwnProp( options, 'open' ) ) { + opts.open = options.open; + if ( !isBoolean( opts.open ) ) { + return new TypeError( 'invalid option. `open` option must be a primitive boolean. Option: `' + opts.open + '`.' ); + } + } + return null; +} + + +// EXPORTS // + +module.exports = validate; diff --git a/package.json b/package.json new file mode 100644 index 0000000..12ea759 --- /dev/null +++ b/package.json @@ -0,0 +1,109 @@ +{ + "name": "@stdlib/net-disposable-http-server", + "version": "0.0.0", + "description": "Disposable HTTP server.", + "license": "Apache-2.0", + "author": { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + }, + "contributors": [ + { + "name": "The Stdlib Authors", + "url": "https://github.com/stdlib-js/stdlib/graphs/contributors" + } + ], + "bin": { + "temp-http-server": "./bin/cli" + }, + "main": "./lib", + "directories": { + "bin": "./bin", + "example": "./examples", + "lib": "./lib", + "test": "./test" + }, + "types": "./docs/types", + "scripts": { + "test": "make test", + "test-cov": "make test-cov", + "examples": "make examples" + }, + "homepage": "https://github.com/stdlib-js/stdlib", + "repository": { + "type": "git", + "url": "git://github.com/stdlib-js/net-disposable-http-server.git" + }, + "bugs": { + "url": "https://github.com/stdlib-js/stdlib/issues" + }, + "dependencies": { + "@stdlib/assert-has-own-property": "^0.0.x", + "@stdlib/assert-is-boolean": "^0.0.x", + "@stdlib/assert-is-buffer": "^0.0.x", + "@stdlib/assert-is-function": "^0.0.x", + "@stdlib/assert-is-plain-object": "^0.0.x", + "@stdlib/assert-is-string": "^0.0.x", + "@stdlib/buffer-ctor": "^0.0.x", + "@stdlib/buffer-from-string": "^0.0.x", + "@stdlib/fs-read-file": "^0.0.x", + "@stdlib/net-http-server": "^0.0.x", + "@stdlib/process-cwd": "^0.0.x", + "@stdlib/process-env": "^0.0.x", + "@stdlib/process-read-stdin": "^0.0.x", + "@stdlib/utils-keys": "^0.0.x", + "@stdlib/utils-next-tick": "^0.0.x", + "@stdlib/utils-noop": "^0.0.x", + "@stdlib/utils-open-url": "^0.0.x", + "debug": "^2.6.9", + "minimist": "^1.2.0", + "update-notifier": "^1.0.0" + }, + "devDependencies": { + "proxyquire": "^2.0.0", + "tape": "git+https://github.com/kgryte/tape.git#fix/globby", + "istanbul": "^0.4.1", + "tap-spec": "5.x.x" + }, + "engines": { + "node": ">=0.10.0", + "npm": ">2.7.0" + }, + "os": [ + "aix", + "darwin", + "freebsd", + "linux", + "macos", + "openbsd", + "sunos", + "win32", + "windows" + ], + "keywords": [ + "stdlib", + "net", + "network", + "networking", + "http", + "server", + "create", + "serve", + "static", + "disposable", + "once", + "one-time", + "throwaway", + "temporary", + "temp" + ], + "__stdlib__": { + "envs": { + "browser": false + } + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/athan" + } +} diff --git a/test/fixtures/request.js b/test/fixtures/request.js new file mode 100644 index 0000000..7b2edbe --- /dev/null +++ b/test/fixtures/request.js @@ -0,0 +1,68 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var http = require( 'http' ); + + +// MAIN // + +/** +* Makes an HTTP `GET` request. +* +* @private +* @param {Options} opts - request options +* @param {Callback} clbk - callback to invoke after receiving an HTTP response +*/ +function request( opts, clbk ) { + var body; + var req; + var res; + + req = http.request( opts, onResponse ); + req.on( 'error', onError ); + req.end(); + + function onError( error ) { + clbk( error ); + } + + function onResponse( response ) { + body = ''; + response.setEncoding( 'utf8' ); + response.on( 'data', onData ); + response.on( 'end', onEnd ); + res = response; + } + + function onData( chunk ) { + body += chunk; + } + + function onEnd() { + clbk( null, res, body ); + } +} + + +// EXPORTS // + +module.exports = request; diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..cbf8925 --- /dev/null +++ b/test/test.js @@ -0,0 +1,33 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var httpServer = require( './../lib' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof httpServer, 'function', 'main export is a function' ); + t.end(); +}); diff --git a/test/test.opts.js b/test/test.opts.js new file mode 100644 index 0000000..57ec2c8 --- /dev/null +++ b/test/test.opts.js @@ -0,0 +1,144 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var getOpts = require( './../lib/opts.js' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof getOpts, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function extracts a `port` option', function test( t ) { + var expected; + var actual; + var opts; + + opts = { + 'html': '

beep

', + 'open': true, + 'port': 7331 + }; + + expected = { + 'port': opts.port + }; + + actual = getOpts( opts ); + + t.deepEqual( actual, expected, 'extracts server options' ); + t.end(); +}); + +tape( 'the function extracts a `maxport` option', function test( t ) { + var expected; + var actual; + var opts; + + opts = { + 'html': '

beep

', + 'open': true, + 'maxport': 8000 + }; + + expected = { + 'maxport': opts.maxport + }; + + actual = getOpts( opts ); + + t.deepEqual( actual, expected, 'extracts server options' ); + t.end(); +}); + +tape( 'the function extracts a `hostname` option', function test( t ) { + var expected; + var actual; + var opts; + + opts = { + 'html': '

beep

', + 'open': true, + 'hostname': 'localhost' + }; + + expected = { + 'hostname': opts.hostname + }; + + actual = getOpts( opts ); + + t.deepEqual( actual, expected, 'extracts server options' ); + t.end(); +}); + +tape( 'the function extracts an `address` option', function test( t ) { + var expected; + var actual; + var opts; + + opts = { + 'html': '

beep

', + 'open': true, + 'address': '127.0.0.1' + }; + + expected = { + 'address': opts.address + }; + + actual = getOpts( opts ); + + t.deepEqual( actual, expected, 'extracts server options' ); + t.end(); +}); + +tape( 'the function ignores non HTTP server options', function test( t ) { + var expected; + var actual; + var opts; + + opts = { + 'html': '

beep

', + 'open': true, + 'port': 7331, + 'maxport': 8000, + 'address': '127.0.0.1', + 'hostname': 'localhost' + }; + + expected = { + 'port': opts.port, + 'maxport': opts.maxport, + 'address': opts.address, + 'hostname': opts.hostname + }; + + actual = getOpts( opts ); + + t.deepEqual( actual, expected, 'extracts server options' ); + t.end(); +}); diff --git a/test/test.server.js b/test/test.server.js new file mode 100644 index 0000000..cdc2129 --- /dev/null +++ b/test/test.server.js @@ -0,0 +1,711 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var path = require( 'path' ); +var tape = require( 'tape' ); +var proxyquire = require( 'proxyquire' ); +var readFileSync = require( '@stdlib/fs-read-file' ).sync; +var noop = require( '@stdlib/utils-noop' ); +var string2buffer = require( '@stdlib/buffer-from-string' ); +var httpServer = require( './../lib/server.js' ); + + +// FIXTURES // + +var request = require( './fixtures/request.js' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof httpServer, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function requires an options object', function test( t ) { + t.throws( foo, Error, 'throws an error' ); + t.end(); + + function foo() { + httpServer(); + } +}); + +tape( 'the function throws a type error if provided an invalid option', function test( t ) { + t.throws( foo, TypeError, 'throws a type error' ); + t.end(); + + function foo() { + httpServer({ + 'open': null + }); + } +}); + +tape( 'the function throws a type error if provided a callback argument which is not a function', function test( t ) { + var values; + var i; + + values = [ + '5', + 5, + NaN, + null, + undefined, + true, + [], + {} + ]; + + for ( i = 0; i < values.length; i++ ) { + t.throws( badValue( values[i] ), TypeError, 'throws a type error when provided '+values[i] ); + } + t.end(); + + function badValue( value ) { + return function badValue() { + httpServer( {}, value ); + }; + } +}); + +tape( 'the function throws an error if an error is encountered while attempting to start a server', function test( t ) { + var httpServer = proxyquire( './../lib/server.js', { + '@stdlib/net/http-server': createServer, + '@stdlib/utils/open-url': noop + }); + + t.throws( foo, Error, 'throws an error' ); + t.end(); + + function createServer() { + return function boot( clbk ) { + clbk( new Error( 'beep' ) ); + }; + } + + function foo() { + httpServer( {} ); + } +}); + +tape( 'if only provided HTML, the server serves the HTML content once and then closes', function test( t ) { + var server; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': '

Beep

' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + var options; + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + + options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + request( options, onResponse ); + } + + function onResponse( error, res, body ) { + if ( error ) { + t.ok( false, error.message ); + } + t.equal( body, opts.html, 'returns content' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'if only provided JavaScript, the server serves an HTML boilerplate and then the JavaScript and then closes', function test( t ) { + var server; + var fpath; + var html; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'javascript': 'console.log("Beep");' + }; + + fpath = path.resolve( __dirname, '../static/index.html' ); + html = readFileSync( fpath, 'utf8' ); + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + getHTML(); + } + + function getHTML() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + request( options, onHTML ); + } + + function onHTML( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, html, 'returns HTML boilerplate' ); + getJavaScript(); + } + + function getJavaScript() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/bundle.js', + 'method': 'GET' + }; + request( options, onJavaScript ); + } + + function onJavaScript( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.javascript, 'returns JavaScript' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'if provided HTML and JavaScript, the server serves the HTML and then the JavaScript and then closes', function test( t ) { + var server; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': '', + 'javascript': 'console.log("Beep");' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + getHTML(); + } + + function getHTML() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + request( options, onHTML ); + } + + function onHTML( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.html, 'returns HTML' ); + getJavaScript(); + } + + function getJavaScript() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/bundle.js', + 'method': 'GET' + }; + request( options, onJavaScript ); + } + + function onJavaScript( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.javascript, 'returns JavaScript' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'if a client only requests JavaScript, the server serves the JavaScript and then closes', function test( t ) { + var server; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'javascript': 'console.log("Beep");' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + getJavaScript(); + } + + function getJavaScript() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/bundle.js', + 'method': 'GET' + }; + request( options, onJavaScript ); + } + + function onJavaScript( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.javascript, 'returns JavaScript' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'if the server receives a request for something other than the JavaScript bundle or base HTML page, the server returns a `404` response', function test( t ) { + var server; + var opts; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': '

Beep

' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + var options; + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + + options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/beep/boop.js', + 'method': 'GET' + }; + request( options, onResponse ); + } + + function onResponse( error, res ) { + if ( error ) { + t.ok( false, error.message ); + } + t.equal( res.statusCode, 404, 'returns 404' ); + server.close(); + } + + function onClose() { + t.ok( true, 'server closed' ); + t.end(); + } +}); + +tape( 'if the server receives a request while closing, the server returns a `503` response', function test( t ) { + var server; + var close; + var opts; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': 'Beep!' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + + // Intercept the close method: + close = server.close; + close = close.bind( server ); + server.close = noop; + + getHTML(); + } + + function getHTML() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + + request( options, onHTML ); + } + + function onHTML( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.html, 'returns HTML' ); + next(); + } + + function next() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/bundle.js', + 'method': 'GET' + }; + request( options, onResponse ); + } + + function onResponse( error, res ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( res.statusCode, 503, 'returns 503 response' ); + + // Restore the close method: + server.close = close; + server.close(); + + t.end(); + } +}); + +tape( 'the function accepts `Buffer` objects for both HTML and JavaScript', function test( t ) { + var server; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': string2buffer( '' ), + 'javascript': string2buffer( 'console.log("Beep");' ) + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + getHTML(); + } + + function getHTML() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + request( options, onHTML ); + } + + function onHTML( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.html.toString(), 'returns HTML' ); + getJavaScript(); + } + + function getJavaScript() { + var options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/bundle.js', + 'method': 'GET' + }; + request( options, onJavaScript ); + } + + function onJavaScript( error, res, data ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + t.equal( data, opts.javascript.toString(), 'returns JavaScript' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'the server will close any persistent connections on close', function test( t ) { + var httpServer; + var server; + var opts; + var flg; + + httpServer = proxyquire( './../lib/server.js', { + './connections_store.js': store + }); + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': '

Beep

' + }; + + httpServer( opts, onReady ); + + function onReady( error, _server ) { + var options; + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + + options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + request( options, noop ); + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function store() { + return { + '1234': { + 'destroy': destroy + } + }; + } + + function destroy() { + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); + +tape( 'if the `open` option is `true`, the function will attempt to open the content in a user\'s default web browser', function test( t ) { + var httpServer; + var server; + var opts; + var flg; + + opts = { + 'port': 7331, + 'address': '127.0.0.1', + 'html': '

Beep

', + 'open': true + }; + + httpServer = proxyquire( './../lib/server.js', { + '@stdlib/utils/open-url': openURL + }); + + httpServer( opts, onReady ); + + function openURL( url ) { + var options; + + t.equal( url, 'http://'+opts.address+':'+opts.port, 'attempts to open URL' ); + + options = { + 'protocol': 'http:', + 'hostname': opts.address, + 'port': opts.port, + 'path': '/', + 'method': 'GET' + }; + setTimeout( onTimeout, 0 ); + + function onTimeout() { + request( options, onResponse ); + } + } + + function onReady( error, _server ) { + if ( error ) { + t.ok( false, error.message ); + return t.end(); + } + server = _server; + server.on( 'close', onClose ); + } + + function onResponse( error, res, body ) { + if ( error ) { + t.ok( false, error.message ); + } + t.equal( body, opts.html, 'returns content' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } + + function onClose() { + t.ok( true, 'server closed' ); + if ( flg ) { + t.end(); + } else { + flg = true; + } + } +}); diff --git a/test/test.validate.js b/test/test.validate.js new file mode 100644 index 0000000..081bdfb --- /dev/null +++ b/test/test.validate.js @@ -0,0 +1,179 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2018 The Stdlib Authors. +* +* 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. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var validate = require( './../lib/validate.js' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.equal( typeof validate, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'the function returns a type error if not provided an options object', function test( t ) { + var values; + var err; + var i; + + values = [ + '5', + 5, + NaN, + true, + null, + undefined, + [], + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, values[i] ); + t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] ); + } + t.end(); +}); + +tape( 'the function returns a type error if provided an `html` option which is neither a `Buffer` or a `string`', function test( t ) { + var values; + var err; + var i; + + values = [ + 5, + NaN, + true, + null, + undefined, + [], + {}, + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'html': values[i] + }); + t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] ); + } + t.end(); +}); + +tape( 'the function returns a type error if provided a `javascript` option which is neither a `Buffer` or a `string`', function test( t ) { + var values; + var err; + var i; + + values = [ + 5, + NaN, + true, + null, + undefined, + [], + {}, + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'javascript': values[i] + }); + t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] ); + } + t.end(); +}); + +tape( 'the function returns a type error if provided an `open` option which is not a `boolean` primitive', function test( t ) { + var values; + var err; + var i; + + values = [ + '5', + 5, + NaN, + null, + undefined, + [], + {}, + function noop() {} + ]; + + for ( i = 0; i < values.length; i++ ) { + err = validate( {}, { + 'open': values[i] + }); + t.equal( err instanceof TypeError, true, 'returns a TypeError when provided '+values[i] ); + } + t.end(); +}); + +tape( 'the function returns `null` if all options are valid', function test( t ) { + var expected; + var options; + var opts; + var err; + + options = { + 'html': 'beep', + 'javascript': 'console.log("beep");', + 'open': true + }; + opts = {}; + + expected = { + 'html': options.html, + 'javascript': options.javascript, + 'open': options.open + }; + + err = validate( opts, options ); + + t.equal( err, null, 'returns null' ); + t.deepEqual( opts, expected, 'extracts options' ); + + t.end(); +}); + +tape( 'the function ignores unrecognized options', function test( t ) { + var options; + var opts; + var err; + + options = { + 'beep': 'boop', + 'foo': 5, + 'bar': {} + }; + + opts = {}; + + err = validate( opts, options ); + + t.equal( err, null, 'returns null' ); + t.deepEqual( opts, {}, 'ignores unrecognized options' ); + + t.end(); +});