Add extensions

This commit is contained in:
2024-04-25 13:51:46 +01:00
parent 794e7b6aed
commit 8481dcc050
178 changed files with 30454 additions and 1 deletions

View File

@@ -1 +1 @@
[{"identifier":{"id":"piousdeer.adwaita-theme","uuid":"93fbc635-4a9a-4ff1-88ba-bf017484c602"},"version":"1.1.0","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/piousdeer.adwaita-theme-1.1.0-universal","scheme":"file"},"relativeLocation":"piousdeer.adwaita-theme-1.1.0-universal","metadata":{"id":"93fbc635-4a9a-4ff1-88ba-bf017484c602","publisherId":"93befe42-7314-4d14-8724-19419a27ed64","publisherDisplayName":"piousdeer","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714048423200,"pinned":false,"source":"gallery"}},{"identifier":{"id":"oderwat.indent-rainbow","uuid":"eaa2127d-cb69-4ab9-8505-a60c9ee5f28b"},"version":"8.3.1","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/oderwat.indent-rainbow-8.3.1-universal","scheme":"file"},"relativeLocation":"oderwat.indent-rainbow-8.3.1-universal","metadata":{"id":"eaa2127d-cb69-4ab9-8505-a60c9ee5f28b","publisherId":"ac064ae0-224d-4351-9aa4-45ef7bf3ed21","publisherDisplayName":"oderwat","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714048433635,"pinned":false,"source":"gallery"}},{"identifier":{"id":"yandeu.five-server","uuid":"a18a6705-5a6a-4e14-a4a7-3efa6b7647d5"},"version":"0.3.1","location":{"$mid":1,"fsPath":"/home/trude/.vscode-oss/extensions/yandeu.five-server-0.3.1-universal","external":"file:///home/trude/.vscode-oss/extensions/yandeu.five-server-0.3.1-universal","path":"/home/trude/.vscode-oss/extensions/yandeu.five-server-0.3.1-universal","scheme":"file"},"relativeLocation":"yandeu.five-server-0.3.1-universal","metadata":{"id":"a18a6705-5a6a-4e14-a4a7-3efa6b7647d5","publisherId":"a342b7ab-2d58-4313-9c2d-f506bb2aa10c","publisherDisplayName":"yandeu","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714049361078,"pinned":false,"source":"gallery"}}]
[{"identifier":{"id":"piousdeer.adwaita-theme","uuid":"93fbc635-4a9a-4ff1-88ba-bf017484c602"},"version":"1.1.0","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/piousdeer.adwaita-theme-1.1.0-universal","scheme":"file"},"relativeLocation":"piousdeer.adwaita-theme-1.1.0-universal","metadata":{"id":"93fbc635-4a9a-4ff1-88ba-bf017484c602","publisherId":"93befe42-7314-4d14-8724-19419a27ed64","publisherDisplayName":"piousdeer","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714048423200,"pinned":false,"source":"gallery"}},{"identifier":{"id":"oderwat.indent-rainbow","uuid":"eaa2127d-cb69-4ab9-8505-a60c9ee5f28b"},"version":"8.3.1","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/oderwat.indent-rainbow-8.3.1-universal","scheme":"file"},"relativeLocation":"oderwat.indent-rainbow-8.3.1-universal","metadata":{"id":"eaa2127d-cb69-4ab9-8505-a60c9ee5f28b","publisherId":"ac064ae0-224d-4351-9aa4-45ef7bf3ed21","publisherDisplayName":"oderwat","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714048433635,"pinned":false,"source":"gallery"}},{"identifier":{"id":"yandeu.five-server","uuid":"a18a6705-5a6a-4e14-a4a7-3efa6b7647d5"},"version":"0.3.1","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/yandeu.five-server-0.3.1-universal","scheme":"file"},"relativeLocation":"yandeu.five-server-0.3.1-universal","metadata":{"id":"a18a6705-5a6a-4e14-a4a7-3efa6b7647d5","publisherId":"a342b7ab-2d58-4313-9c2d-f506bb2aa10c","publisherDisplayName":"yandeu","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714049361078,"pinned":false,"source":"gallery"}},{"identifier":{"id":"rust-lang.rust-analyzer","uuid":"06574cb4-e5dc-4631-8174-a543a4533621"},"version":"0.3.1932","location":{"$mid":1,"path":"/home/trude/.vscode-oss/extensions/rust-lang.rust-analyzer-0.3.1932-linux-x64","scheme":"file"},"relativeLocation":"rust-lang.rust-analyzer-0.3.1932-linux-x64","metadata":{"id":"06574cb4-e5dc-4631-8174-a543a4533621","publisherId":"cb14a7a7-a188-40bd-a953-e0a20757c5dd","publisherDisplayName":"rust-lang","targetPlatform":"linux-x64","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714049437810,"pinned":false,"source":"gallery"}},{"identifier":{"id":"formulahendry.code-runner","uuid":"a6a0c5b2-d078-4bf5-a9ee-4e37054414b3"},"version":"0.12.2","location":{"$mid":1,"fsPath":"/home/trude/.vscode-oss/extensions/formulahendry.code-runner-0.12.2-universal","external":"file:///home/trude/.vscode-oss/extensions/formulahendry.code-runner-0.12.2-universal","path":"/home/trude/.vscode-oss/extensions/formulahendry.code-runner-0.12.2-universal","scheme":"file"},"relativeLocation":"formulahendry.code-runner-0.12.2-universal","metadata":{"id":"a6a0c5b2-d078-4bf5-a9ee-4e37054414b3","publisherId":"38bbe3f0-5204-4170-845e-c2f966d979b8","publisherDisplayName":"formulahendry","targetPlatform":"universal","updated":false,"isPreReleaseVersion":false,"hasPreReleaseVersion":false,"installedTimestamp":1714049439694,"pinned":false,"source":"gallery"}}]

View File

@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: ''
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- FYI issues: https://github.com/formulahendry/vscode-code-runner/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Afyi -->
- VS Code Version:
- OS Version:
- Code Runner Version:
**Describe the bug**
Description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
**Actual behavior**
Description of what happened actually.
**Expected behavior**
Description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.

View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://stackoverflow.com/questions/tagged/vscode-code-runner
about: Please ask and answer questions here.

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature request]"
labels: ''
assignees: ''
---
<!-- Please search existing issues to avoid creating duplicates. -->
<!-- FYI issues: https://github.com/formulahendry/vscode-code-runner/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Afyi -->
**Is your feature request related to a problem? Please describe.**
Description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
Description of what you want to happen.

View File

@@ -0,0 +1,28 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 16.x
- run: npm install
- run: npm run vscode:prepublish
- run: npm run tslint

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Language="en-US" Id="code-runner" Version="0.12.2" Publisher="formulahendry" />
<DisplayName>Code Runner</DisplayName>
<Description xml:space="preserve">Run C, C++, Java, JS, PHP, Python, Perl, Ruby, Go, Lua, Groovy, PowerShell, CMD, BASH, F#, C#, VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml, R, AppleScript, Elixir, VB.NET, Clojure, Haxe, Obj-C, Rust, Racket, Scheme, AutoHotkey, AutoIt, Kotlin, Dart, Pascal, Haskell, Nim, D, Lisp, Kit, V, SCSS, Sass, CUDA, Less, Fortran, Ring, Standard ML, Zig, Mojo, Erlang, SPWN, Pkl, Gleam</Description>
<Tags>javascript,php,python,perl,ruby,multi-root ready,keybindings,code-runner-output,c++,java,erlang,c#,haskell,powershell,rust,__sponsor_extension</Tags>
<Categories>Programming Languages,Other</Categories>
<GalleryFlags>Public</GalleryFlags>
<Properties>
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="^1.56.0" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionKind" Value="workspace" />
<Property Id="Microsoft.VisualStudio.Code.LocalizedLanguages" Value="" />
<Property Id="Microsoft.VisualStudio.Code.SponsorLink" Value="https://www.patreon.com/junhan" />
<Property Id="Microsoft.VisualStudio.Services.Links.Source" Value="https://github.com/formulahendry/vscode-code-runner.git" />
<Property Id="Microsoft.VisualStudio.Services.Links.Getstarted" Value="https://github.com/formulahendry/vscode-code-runner.git" />
<Property Id="Microsoft.VisualStudio.Services.Links.GitHub" Value="https://github.com/formulahendry/vscode-code-runner.git" />
<Property Id="Microsoft.VisualStudio.Services.Links.Support" Value="https://github.com/formulahendry/vscode-code-runner/issues" />
<Property Id="Microsoft.VisualStudio.Services.Links.Learn" Value="https://github.com/formulahendry/vscode-code-runner/blob/master/README.md" />
<Property Id="Microsoft.VisualStudio.Services.GitHubFlavoredMarkdown" Value="true" />
<Property Id="Microsoft.VisualStudio.Services.Content.Pricing" Value="Free"/>
</Properties>
<License>extension/LICENSE.txt</License>
<Icon>extension/images/logo.png</Icon>
</Metadata>
<Installation>
<InstallationTarget Id="Microsoft.VisualStudio.Code"/>
</Installation>
<Dependencies/>
<Assets>
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
<Asset Type="Microsoft.VisualStudio.Services.Content.Details" Path="extension/README.md" Addressable="true" />
<Asset Type="Microsoft.VisualStudio.Services.Content.Changelog" Path="extension/CHANGELOG.md" Addressable="true" />
<Asset Type="Microsoft.VisualStudio.Services.Content.License" Path="extension/LICENSE.txt" Addressable="true" />
<Asset Type="Microsoft.VisualStudio.Services.Icons.Default" Path="extension/images/logo.png" Addressable="true" />
</Assets>
</PackageManifest>

View File

@@ -0,0 +1,3 @@
<h2 align="center">Backers</h2>
- Daniel Wotapka

View File

@@ -0,0 +1,336 @@
### 0.12.2 (2024-04-05)
* Add support for Erlang
* Add support for SPWN
* Add support for Pkl
* Add support for Gleam
### 0.12.1 (2023-10-08)
* Add support for Mojo language
### 0.12.0 (2023-01-23)
* Add support for Zig language
* Use `runghc` to replace `runhaskell` for Haskell
### 0.11.8 (2022-06-11)
* Add support for Standard ML
* Adopt extension sponsorship
### 0.11.7 (2022-02-08)
* Update run/stop icons
### 0.11.6 (2021-10-10)
* Add support for Ring
### 0.11.5 (2021-07-10)
* [#776](https://github.com/formulahendry/vscode-code-runner/pull/776): Add stop button in editor title bar
### 0.11.4 (2021-05-14)
* [#783](https://github.com/formulahendry/vscode-code-runner/issues/783): Not detect shell on Windows correctly in VS Code 1.56
### 0.11.3 (2021-03-11)
* Adopt 'run' menu in editor title
### 0.11.2 (2021-01-05)
* Activate extension after VS Code starts up
### 0.11.1 (2020-10-11)
* Add support for Fortran
### 0.11.0 (2020-07-07)
* Integrate with new Python Interpreter Path API V2 of Python extension
* Add support for Less
### 0.10.0 (2020-05-02)
* Integrate with new Python Interpreter Path API of Python extension
### 0.9.17 (2020-03-11)
* Add support for CUDA
### 0.9.16 (2020-02-20)
* Add support for SCSS and Sass
### 0.9.15 (2019-11-21)
* Add support for [V Programming Language](https://vlang.io/)
### 0.9.14 (2019-08-17)
* [#516](https://github.com/formulahendry/vscode-code-runner/pull/516): Update "Run" icon to match new icon style
### 0.9.13 (2019-08-14)
* [#428](https://github.com/formulahendry/vscode-code-runner/pull/428): Use spawn to avoid stdout buffer exceeded
### 0.9.12 (2019-08-02)
* Add support for Scheme using [CHICKEN Scheme](https://www.call-cc.org/)
### 0.9.11 (2019-06-12)
* [#491](https://github.com/formulahendry/vscode-code-runner/issues/491): Fix terminal detection due to VS Code's change in 1.35
### 0.9.10 (2019-06-02)
* [#484](https://github.com/formulahendry/vscode-code-runner/pull/484): Fix Rust attributes considered as Shebang
### 0.9.9 (2019-05-04)
* Fix tempCodeRunnerFile not being deleted
* Add option to hide "Run Code" from explorer context menu
### 0.9.8 (2019-04-07)
* Add support for [Kit](https://www.kitlang.org/)
### 0.9.7 (2019-01-29)
* Add config entry to set the executor per filename glob
* Support Maven project (pom.xml)
* Add [CODING](https://e.coding.net/?utm_source=hendry-code-runner&utm_medium=cpc&utm_campaign=hendry-code-runner) as our second sponsor
### 0.9.6 (2019-01-16)
* Add support for Lisp
### 0.9.5 (2018-10-29)
* We have our first sponsor now: [CodeStream](https://codestream.com/?utm_source=vscmarket&utm_medium=banner&utm_campaign=coderunner)
### 0.9.4 (2018-08-03)
* [#70](https://github.com/formulahendry/vscode-code-runner/issues/70): Use `-u` flag for Python to force stdin, stdout and stderr to be totally unbuffered.
### 0.9.3 (2018-03-19)
* [#273](https://github.com/formulahendry/vscode-code-runner/issues/273): Could not run file without extension
### 0.9.2 (2018-03-13)
* Add $pythonPath customized parameter to respect `python.pythonPath` setting
### 0.9.1 (2018-03-08)
* Add option to respect Shebang or not
### 0.9.0 (2018-03-04)
* Add support for Shebang
### 0.8.7 (2017-12-30)
* Add support for Perl 6
### 0.8.6 (2017-12-22)
* Fix kotlin script execution
### 0.8.5 (2017-11-30)
* Add support for F# (.NET Core)
### 0.8.4 (2017-11-17)
* [#207](https://github.com/formulahendry/vscode-code-runner/issues/207): Add option to hide "Run Code" from editor context menu
### 0.8.3 (2017-11-04)
* Add support for C# (.NET Core)
### 0.8.2 (2017-10-26)
* [#196](https://github.com/formulahendry/vscode-code-runner/issues/196): Fix 'run code' hotkey not working
### 0.8.1 (2017-10-26)
* Distinguish whether it is running from file explorer
### 0.8.0 (2017-10-25)
* [#88](https://github.com/formulahendry/vscode-code-runner/issues/88): Run file through context menu of file explorer
### 0.7.4 (2017-10-20)
* [#191](https://github.com/formulahendry/vscode-code-runner/issues/191): Stop running code when VS Code is closed
### 0.7.3 (2017-10-14)
* Add support for Multi Root Workspaces
### 0.7.2 (2017-09-29)
* [#182](https://github.com/formulahendry/vscode-code-runner/issues/182): Add $driveLetter customized parameter
### 0.7.1 (2017-08-24)
* [#98](https://github.com/formulahendry/vscode-code-runner/issues/98): Add .ts extension mapping for typescript
### 0.7.0 (2017-08-20)
* [#164](https://github.com/formulahendry/vscode-code-runner/issues/164): Make temporary file name not random by default
* Support running code snippet in terminal
### 0.6.33 (2017-08-10)
* [#149](https://github.com/formulahendry/vscode-code-runner/issues/149): Set custom terminal root path for Linux Shell on Windows
### 0.6.32 (2017-08-05)
* [#146](https://github.com/formulahendry/vscode-code-runner/issues/146): Enable fileDirectoryAsCwd in Terminal
### 0.6.31 (2017-08-03)
* Fix code running in PowerShell
### 0.6.30 (2017-08-01)
* [#152](https://github.com/formulahendry/vscode-code-runner/issues/152): Clear previous output when running in terminal
### 0.6.29 (2017-07-28)
* [#132](https://github.com/formulahendry/vscode-code-runner/issues/132): Refine 'Run Code' button
### 0.6.28 (2017-07-22)
* [#106](https://github.com/formulahendry/vscode-code-runner/issues/106): [Continuous Fix for Bash on Windows] Handle multiple file path and case-insensitive bash path
### 0.6.27 (2017-07-20)
* [#106](https://github.com/formulahendry/vscode-code-runner/issues/106): Handle file path for Bash on Windows
### 0.6.26 (2017-07-13)
* [#140](https://github.com/formulahendry/vscode-code-runner/issues/140): Add support for Nim
### 0.6.25 (2017-07-12)
* [#130](https://github.com/formulahendry/vscode-code-runner/issues/130): Add option to save all files before running
### 0.6.24 (2017-06-22)
* Add support for Haskell
### 0.6.23 (2017-06-21)
* [#131](https://github.com/formulahendry/vscode-code-runner/issues/131): Option to hide 'Run Code' button
* Refine 'Run Code' button
### 0.6.22 (2017-06-20)
* [#128](https://github.com/formulahendry/vscode-code-runner/issues/128): Add 'Run Code' buuton in editor title menu
### 0.6.21 (2017-06-18)
* Add support for D
### 0.6.20 (2017-06-14)
* Resolve [GitHub issue#126](https://github.com/formulahendry/vscode-code-runner/issues/126): Add support for Free Pascal
### 0.6.19 (2017-06-13)
* Add support for Dart
### 0.6.18 (2017-06-02)
* Add support for Kotlin script (.kts)
### 0.6.17 (2017-05-20)
* Resolve [GitHub issue#113](https://github.com/formulahendry/vscode-code-runner/issues/113): Add support for Kotlin
### 0.6.16 (2017-04-15)
* Resolve [GitHub issue#102](https://github.com/formulahendry/vscode-code-runner/issues/102): Add config entry to set whether to ignore selection to always run entire file
### 0.6.15 (2017-03-26)
* Add support for AutoIt
### 0.6.14 (2017-03-12)
* Resolve [GitHub issue#93](https://github.com/formulahendry/vscode-code-runner/issues/93): Add config entry to set whether to preserve focus on code editor after code run is triggered
### 0.6.13 (2017-03-05)
* Add support for AutoHotkey
### 0.6.12
* Add support for Racket
### 0.6.11
* Add support for Rust
* Set cwd as the directory of the code file if no folder has been opened
### 0.6.10
* Resolve [GitHub issue#77](https://github.com/formulahendry/vscode-code-runner/issues/77): Add support for Objective-C
### 0.6.9
* Change executor if the Integrated Terminal is PowerShell on Windows
### 0.6.8
* Add support for Haxe
### 0.6.7
* Resolve [GitHub issue#57](https://github.com/formulahendry/vscode-code-runner/issues/57): Add support for Clojure
* Fix output color due to changed VS Code 1.9
* Improve output color for numeric
### 0.6.6
* Resolve [GitHub issue#54](https://github.com/formulahendry/vscode-code-runner/issues/54): Add support for VB.NET
* Resolve [GitHub issue#51](https://github.com/formulahendry/vscode-code-runner/issues/51): Add support for $workspaceRoot
### 0.6.5
* Resolve [GitHub issue#43](https://github.com/formulahendry/vscode-code-runner/issues/43): Add support for Elixir
* Upgrade applicationinsights npm since [telemetry data requires HTTPS](https://azure.microsoft.com/en-us/updates/application-insights-telemetry-data-now-requires-https-with-shutdown-of-http-data-collectors/)
### 0.6.4
* Resolve [GitHub issue#41](https://github.com/formulahendry/vscode-code-runner/issues/41): Fix running C/C++ in Windows
### 0.6.3
* Add support for AppleScript
* Add PayPal link in donation section
### 0.6.2
* Add support for R Language
* Add donation section in README.md
### 0.6.1
* Minor fix for running custom command when there is no active editor window
### 0.6.0
* Add support to run custom command
### 0.5.1
* Resolve [GitHub issue#21](https://github.com/formulahendry/vscode-code-runner/issues/21): Remove "Run Code" in Output Channel
* Add "Stop Code Run" in Output Channel
* Resolve [GitHub issue#32](https://github.com/formulahendry/vscode-code-runner/issues/32): Preserve focus of Text Editor after code is running
* Not add quote for $fileNameWithoutExt and $fileName
* Not add extra space for placeholders
### 0.5.0
* Add placeholders into configuration for compiled language
* Add support for C, C++, Java
### 0.4.2
* Add support for OCaml Script
### 0.4.1
* Avoid running code in Integrated Terminal when it is untitled file or code snippet
### 0.4.0
* Add support to run code in Integrated Terminal
### 0.3.4
* Resolve [GitHub issue#24](https://github.com/formulahendry/vscode-code-runner/issues/24): Add config entry to set whether to show extra execution message
### 0.3.3
* Add support to run by language from a suggestion list
### 0.3.2
* Add support for Swift, Julia, Crystal
### 0.3.1
* Update README.md about running TypeScript with ts-node
### 0.3.0
* Add support for TypeScript, CoffeeScript, Scala
### 0.2.4
* Resolve [GitHub issue#20](https://github.com/formulahendry/vscode-code-runner/issues/20): Add config entry to determine whether to use the directory of the file to be executed as the working directory
### 0.2.3
* Resolve [GitHub issue#18](https://github.com/formulahendry/vscode-code-runner/issues/18): Fix output highlight when execution time is greater than 10 seconds
### 0.2.2
* Resolve [GitHub issue#12](https://github.com/formulahendry/vscode-code-runner/issues/12): Add 'Run Code' entry to editor context menu
### 0.2.1
* Resolve [GitHub issue#8](https://github.com/formulahendry/vscode-code-runner/issues/8): Fix output highlight
* Resolve [GitHub issue#10](https://github.com/formulahendry/vscode-code-runner/issues/10): Add option to save the file before running
* Resolve [GitHub issue#11](https://github.com/formulahendry/vscode-code-runner/issues/11): Set the mapping of languageId to file extension
* Add Application Insights to track telemetry data to improve this extension
### 0.2.0
* Resolve [GitHub issue#6](https://github.com/formulahendry/vscode-code-runner/issues/6): Add config entry to set whether to clear previous output before each run
* Resolve [GitHub issue#7](https://github.com/formulahendry/vscode-code-runner/issues/7): Add colorizer for output and refine output
* Use the current file to run if there is no selection and it is not untitled
* If there is selection and the file is not untitled, create tmp file in the file folder to run the code sinnpet
### 0.1.3
* Resolve [GitHub issue#5](https://github.com/formulahendry/vscode-code-runner/issues/5): Put the temp code file in working directory instead of os temp folder
### 0.1.2
* Resolve [GitHub issue#5](https://github.com/formulahendry/vscode-code-runner/issues/5): Add support to set working directory
### 0.1.1
* Add support for VBScript
* Add config entry to set the executor per file extension
### 0.1.0
* Add support to set default language to run
* Add support to select language to run
### 0.0.5
* Add support for C# script
### 0.0.4
* Add support for F# script
### 0.0.3
* Add support for powershell, bat/cmd and bash/sh
### 0.0.2
* Add support for lua and groovy
### 0.0.1
* Initial Release

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Jun Han
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.

View File

@@ -0,0 +1,222 @@
# Code Runner
[![Join the chat at https://gitter.im/formulahendry/vscode-code-runner](https://badges.gitter.im/formulahendry/vscode-code-runner.svg)](https://gitter.im/formulahendry/vscode-code-runner?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ![Downloads](https://img.shields.io/visual-studio-marketplace/d/formulahendry.code-runner) ![Rating](https://img.shields.io/visual-studio-marketplace/r/formulahendry.code-runner) [![Actions Status](https://github.com/formulahendry/vscode-code-runner/actions/workflows/main.yml/badge.svg)](https://github.com/formulahendry/vscode-code-runner/actions/workflows/main.yml)
Run code snippet or code file for multiple languages: **C, C++, Java, JavaScript, PHP, Python, Perl, Perl 6, Ruby, Go, Lua, Groovy, PowerShell, BAT/CMD, BASH/SH, F# Script, F# (.NET Core), C# Script, C# (.NET Core), VBScript, TypeScript, CoffeeScript, Scala, Swift, Julia, Crystal, OCaml Script, R, AppleScript, Elixir, Visual Basic .NET, Clojure, Haxe, Objective-C, Rust, Racket, Scheme, AutoHotkey, AutoIt, Kotlin, Dart, Free Pascal, Haskell, Nim, D, Lisp, Kit, V, SCSS, Sass, CUDA, Less, Fortran, Ring, Standard ML, Zig, Mojo, Erlang, SPWN, Pkl, Gleam**, and custom command
## Book for VS Code
[《Visual Studio Code 权威指南》](https://union-click.jd.com/jdc?e=jdext-1261348777639735296-0&p=AyIGZRhbHQsWAVIaXxEyEgRdG1sRBxU3EUQDS10iXhBeGlcJDBkNXg9JHUlSSkkFSRwSBF0bWxEHFRgMXgdIMkRxFAUJD1RQZT0cBnwKDE4%2BaDpgB2ILWStbHAIQD1QaWxIBIgdUGlsRBxEEUxprJQIXNwd1g6O0yqLkB4%2B%2FjcePwitaJQIWD1cfWhwKGwVSG1wlAhoDZc31gdeauIyr%2FsOovNLYq46cqca50ytrJQEiXABPElAeEgRSG1kQCxQBUxxZHQQQA1YTXAkDIgdUGlscChECXRs1FGwSD1UbWRALFwRWK1slASJZOxoLRlUXU1NONU9QEkdXWRlJbBUDVB9TFgAVN1caWhcA):带你深入浅出 VS Code
![Book](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/book.jpg)
## WeChat Official Account
VS Code 的热门文章、使用技巧、插件推荐、插件开发攻略等,请关注“**玩转VS Code**”公众号!
![WeChat](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/WeChatPublicAccount.jpg)
## Donation
If you like this extension, you could become a backer or sponsor via **[Patreon](https://www.patreon.com/junhan)**, donate via **[PayPal](https://www.paypal.me/junhanme)**, or scan below QR code to donate via **Alipay**. Any amount is welcome. It will encourage me to make this extension better and better!
![Alipay](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/alipay.png)
## Features
* Run code file of current active Text Editor
* Run code file through context menu of file explorer
* Run selected code snippet in Text Editor
* Run code per Shebang
* Run code per filename glob
* Run custom command
* Stop code running
* View output in Output Window
* Set default language to run
* Select language to run
* Support REPL by running code in Integrated Terminal
## Usages
* To run code:
* use shortcut `Ctrl+Alt+N`
* or press `F1` and then select/type `Run Code`,
* or right click the Text Editor and then click `Run Code` in editor context menu
* or click `Run Code` button in editor title menu
* or click `Run Code` button in context menu of file explorer
* To stop the running code:
* use shortcut `Ctrl+Alt+M`
* or press `F1` and then select/type `Stop Code Run`
* or click `Stop Code Run` button in editor title menu
* or right click the Output Channel and then click `Stop Code Run` in context menu
![Usage](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/usage.gif)
* To select language to run, use shortcut `Ctrl+Alt+J`, or press `F1` and then select/type `Run By Language`, then type or select the language to run: e.g `php, javascript, bat, shellscript...`
![Usage](https://github.com/formulahendry/vscode-code-runner/raw/HEAD/images/usageRunByLanguage.gif)
* To run custom command, then use shortcut `Ctrl+Alt+K`, or press `F1` and then select/type `Run Custom Command`
## Configuration
Make sure the executor PATH of each language is set in the environment variable.
You could also add entry into `code-runner.executorMap` to set the executor PATH.
e.g. To set the executor PATH for ruby, php and html:
```json
{
"code-runner.executorMap": {
"javascript": "node",
"php": "C:\\php\\php.exe",
"python": "python",
"perl": "perl",
"ruby": "C:\\Ruby23-x64\\bin\\ruby.exe",
"go": "go run",
"html": "\"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe\"",
"java": "cd $dir && javac $fileName && java $fileNameWithoutExt",
"c": "cd $dir && gcc $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt"
}
}
```
**Supported customized parameters**
* $workspaceRoot: The path of the folder opened in VS Code
* $dir: The directory of the code file being run
* $dirWithoutTrailingSlash: The directory of the code file being run without a trailing slash
* $fullFileName: The full name of the code file being run
* $fileName: The base name of the code file being run, that is the file without the directory
* $fileNameWithoutExt: The base name of the code file being run without its extension
* $driveLetter: The drive letter of the code file being run (Windows only)
* $pythonPath: The path of Python interpreter (set by `Python: Select Interpreter` command)
**Please take care of the back slash and the space in file path of the executor**
* Back slash: please use `\\`
* If there ares spaces in file path, please use `\"` to surround your file path
You could set the executor per filename [glob](https://en.wikipedia.org/wiki/Glob_(programming)):
```json
{
"code-runner.executorMapByGlob": {
"pom.xml": "cd $dir && mvn clean package",
"*.test.js": "tap",
"*.js": "node"
}
}
```
Besides, you could set the default language to run:
```json
{
"code-runner.defaultLanguage": "javascript"
}
```
**For the default language:** It should be set with language id defined in [VS Code](https://github.com/Microsoft/vscode/tree/master/extensions). The languages you could set are `java, c, cpp, javascript, php, python, perl, ruby, go, lua, groovy, powershell, bat, shellscript, fsharp, csharp, vbscript, typescript, coffeescript, swift, r, clojure, haxe, objective-c, rust, racket, ahk, autoit, kotlin, dart, pascal, haskell, nim, d, lisp`
Also, you could set the executor per file extension:
```json
{
"code-runner.executorMapByFileExtension": {
".vbs": "cscript //Nologo"
}
}
```
To set the custom command to run:
```json
{
"code-runner.customCommand": "echo Hello"
}
```
To set the the working directory:
```json
{
"code-runner.cwd": "path/to/working/directory"
}
```
To set whether to clear previous output before each run (default is false):
```json
{
"code-runner.clearPreviousOutput": false
}
```
To set whether to save all files before running (default is false):
```json
{
"code-runner.saveAllFilesBeforeRun": false
}
```
To set whether to save the current file before running (default is false):
```json
{
"code-runner.saveFileBeforeRun": false
}
```
To set whether to show extra execution message like [Running] ... and [Done] ... (default is true):
```json
{
"code-runner.showExecutionMessage": true
}
```
**[REPL support]** To set whether to run code in Integrated Terminal (only support to run whole file in Integrated Terminal, neither untitled file nor code snippet) (default is false):
```json
{
"code-runner.runInTerminal": false
}
```
To set whether to preserve focus on code editor after code run is triggered (default is true, the code editor will keep focus; when it is false, Terminal or Output Channel will take focus):
```json
{
"code-runner.preserveFocus": true
}
```
`code-runner.ignoreSelection`: Whether to ignore selection to always run entire file. (Default is **false**)
`code-runner.showRunIconInEditorTitleMenu`: Whether to show 'Run Code' icon in editor title menu. (Default is **true**)
`code-runner.showRunCommandInEditorContextMenu`: Whether to show 'Run Code' command in editor context menu. (Default is **true**)
`code-runner.showRunCommandInExplorerContextMenu`: Whether to show 'Run Code' command in explorer context menu. (Default is **true**)
`code-runner.showStopIconInEditorTitleMenu`: Whether to show 'Stop Code Run' icon in editor title menu when code is running. (Default is **true**)
`code-runner.terminalRoot`: For Windows system, replaces the Windows style drive letter in the command with a Unix style root when using a custom shell as the terminal, like Bash or Cgywin. Example: Setting this to `/mnt/` will replace `C:\path` with `/mnt/c/path` (Default is **""**)
`code-runner.temporaryFileName`: Temporary file name used in running selected code snippet. When it is set as empty, the file name will be random. (Default is **"tempCodeRunnerFile"**)
`code-runner.respectShebang`: Whether to respect Shebang to run code. (Default is **true**)
## About CWD Setting (current working directory)
1. By default, use the `code-runner.cwd` setting
2. If `code-runner.cwd` is not set and `code-runner.fileDirectoryAsCwd` is `true`, use the directory of the file to be executed
3. If `code-runner.cwd` is not set and `code-runner.fileDirectoryAsCwd` is `false`, use the path of root folder that is open in VS Code
4. If no folder is open, use the os temp folder
## Note
* For Objective-C, it is only supported on macOS
* To run C# script, you need to install [scriptcs](http://scriptcs.net/)
* To run TypeScript, you need to install [ts-node](https://github.com/TypeStrong/ts-node)
* To run Clojure, you need to install [Leiningen](https://leiningen.org/) and [lein-exec](https://github.com/kumarshantanu/lein-exec)
## Telemetry data
By default, telemetry data collection is turned on to understand user behavior to improve this extension. To disable it, update the settings.json as below:
```json
{
"code-runner.enableAppInsights": false
}
```
## Change Log
See Change Log [here](https://github.com/formulahendry/vscode-code-runner/blob/HEAD/CHANGELOG.md)
## Issues
Submit the [issues](https://github.com/formulahendry/vscode-code-runner/issues) if you find any bug or have any suggestion.
## Contribution
Fork the [repo](https://github.com/formulahendry/vscode-code-runner) and submit pull requests.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

View File

@@ -0,0 +1,21 @@
# Ignore built files
obj
*.tgz
# Ignore Visual Studio files
*.suo
*.sln
*.njsproj
.vscode/
# Ignore test files
Tests
# Don't publish source files that aren't needed in package
*.ts
*.js.map
*.d.ts
.travis.yml
.ntvs_analysis.dat
Declarations

View File

@@ -0,0 +1,116 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var url = require("url");
var ContractsModule = require("../Library/Contracts");
var Util = require("../Library/Util");
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
var RequestParser = require("./RequestParser");
/**
* Helper class to read data from the requst/response objects and convert them into the telemetry contract
*/
var ClientRequestParser = (function (_super) {
__extends(ClientRequestParser, _super);
function ClientRequestParser(requestOptions, request) {
_super.call(this);
if (request && request.method && requestOptions) {
// The ClientRequest.method property isn't documented, but is always there.
this.method = request.method;
this.url = ClientRequestParser._getUrlFromRequestOptions(requestOptions, request);
this.startTime = +new Date();
}
}
/**
* Called when the ClientRequest emits an error event.
*/
ClientRequestParser.prototype.onError = function (error, properties) {
this._setStatus(undefined, error, properties);
};
/**
* Called when the ClientRequest emits a response event.
*/
ClientRequestParser.prototype.onResponse = function (response, properties) {
this._setStatus(response.statusCode, undefined, properties);
this.targetIKeyHash =
response.headers && response.headers[RequestResponseHeaders.targetInstrumentationKeyHeader];
};
/**
* Gets a dependency data contract object for a completed ClientRequest.
*/
ClientRequestParser.prototype.getDependencyData = function () {
var urlObject = url.parse(this.url);
urlObject.search = undefined;
urlObject.hash = undefined;
var dependencyName = this.method.toUpperCase() + " " + urlObject.pathname;
var remoteDependency = new ContractsModule.Contracts.RemoteDependencyData();
remoteDependency.type = ContractsModule.Contracts.RemoteDependencyDataConstants.TYPE_HTTP;
if (this.targetIKeyHash) {
remoteDependency.type = "ApplicationInsights";
remoteDependency.target = urlObject.hostname + " | " + this.targetIKeyHash;
}
else {
remoteDependency.type = ContractsModule.Contracts.RemoteDependencyDataConstants.TYPE_HTTP;
remoteDependency.target = urlObject.hostname;
}
remoteDependency.name = dependencyName;
remoteDependency.data = this.url;
remoteDependency.duration = Util.msToTimeSpan(this.duration);
remoteDependency.success = this._isSuccess();
remoteDependency.resultCode = this.statusCode ? this.statusCode.toString() : null;
remoteDependency.properties = this.properties || {};
var data = new ContractsModule.Contracts.Data();
data.baseType = "Microsoft.ApplicationInsights.RemoteDependencyData";
data.baseData = remoteDependency;
return data;
};
/**
* Builds a URL from request options, using the same logic as http.request(). This is
* necessary because a ClientRequest object does not expose a url property.
*/
ClientRequestParser._getUrlFromRequestOptions = function (options, request) {
if (typeof options === 'string') {
options = url.parse(options);
}
else {
// Avoid modifying the original options object.
var originalOptions_1 = options;
options = {};
if (originalOptions_1) {
Object.keys(originalOptions_1).forEach(function (key) {
options[key] = originalOptions_1[key];
});
}
}
// Oddly, url.format ignores path and only uses pathname and search,
// so create them from the path, if path was specified
if (options.path) {
var parsedQuery = url.parse(options.path);
options.pathname = parsedQuery.pathname;
options.search = parsedQuery.search;
}
// Simiarly, url.format ignores hostname and port if host is specified,
// even if host doesn't have the port, but http.request does not work
// this way. It will use the port if one is not specified in host,
// effectively treating host as hostname, but will use the port specified
// in host if it exists.
if (options.host && options.port) {
// Force a protocol so it will parse the host as the host, not path.
// It is discarded and not used, so it doesn't matter if it doesn't match
var parsedHost = url.parse("http://" + options.host);
if (!parsedHost.port && options.port) {
options.hostname = options.host;
delete options.host;
}
}
// Mix in default values used by http.request and others
options.protocol = options.protocol || request.agent.protocol;
options.hostname = options.hostname || 'localhost';
return url.format(options);
};
return ClientRequestParser;
}(RequestParser));
module.exports = ClientRequestParser;

View File

@@ -0,0 +1,100 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var http = require("http");
var https = require("https");
var Logging = require("../Library/Logging");
var Util = require("../Library/Util");
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
var ClientRequestParser = require("./ClientRequestParser");
var AutoCollectClientRequests = (function () {
function AutoCollectClientRequests(client) {
if (!!AutoCollectClientRequests.INSTANCE) {
throw new Error("Client request tracking should be configured from the applicationInsights object");
}
AutoCollectClientRequests.INSTANCE = this;
this._client = client;
}
AutoCollectClientRequests.prototype.enable = function (isEnabled) {
this._isEnabled = isEnabled;
if (this._isEnabled && !this._isInitialized) {
this._initialize();
}
};
AutoCollectClientRequests.prototype.isInitialized = function () {
return this._isInitialized;
};
AutoCollectClientRequests.prototype._initialize = function () {
var _this = this;
this._isInitialized = true;
var originalRequest = http.request;
http.request = function (options) {
var requestArgs = [];
for (var _i = 1; _i < arguments.length; _i++) {
requestArgs[_i - 1] = arguments[_i];
}
var request = originalRequest.call.apply(originalRequest, [http, options].concat(requestArgs));
if (request && options && !options[AutoCollectClientRequests.disableCollectionRequestOption]) {
AutoCollectClientRequests.trackRequest(_this._client, options, request);
}
return request;
};
// On node >= v0.11.12, https.request just calls http.request (with additional options).
// But on older versions, https.request needs to be patched also.
// The regex matches versions < 0.11.12 (avoiding a semver package dependency).
if (/^0\.([0-9]\.)|(10\.)|(11\.([0-9]|10|11)$)/.test(process.versions.node)) {
var originalHttpsRequest_1 = https.request;
https.request = function (options) {
var requestArgs = [];
for (var _i = 1; _i < arguments.length; _i++) {
requestArgs[_i - 1] = arguments[_i];
}
var request = originalHttpsRequest_1.call.apply(originalHttpsRequest_1, [https, options].concat(requestArgs));
if (request && options && !options[AutoCollectClientRequests.disableCollectionRequestOption]) {
AutoCollectClientRequests.trackRequest(_this._client, options, request);
}
return request;
};
}
};
/**
* Tracks an outgoing request. Because it may set headers this method must be called before
* writing content to or ending the request.
*/
AutoCollectClientRequests.trackRequest = function (client, requestOptions, request, properties) {
if (!requestOptions || !request || !client) {
Logging.info("AutoCollectClientRequests.trackRequest was called with invalid parameters: ", !requestOptions, !request, !client);
return;
}
var requestParser = new ClientRequestParser(requestOptions, request);
// Add the source ikey hash to the request headers, if a value was not already provided.
// The getHeader/setHeader methods aren't available on very old Node versions, and
// are not included in the v0.10 type declarations currently used. So check if the
// methods exist before invoking them.
if (client.config && client.config.instrumentationKeyHash &&
Util.canIncludeCorrelationHeader(client, requestParser.getUrl()) &&
request['getHeader'] && request['setHeader'] &&
!request['getHeader'](RequestResponseHeaders.sourceInstrumentationKeyHeader)) {
request['setHeader'](RequestResponseHeaders.sourceInstrumentationKeyHeader, client.config.instrumentationKeyHash);
}
// Collect dependency telemetry about the request when it finishes.
if (request.on) {
request.on('response', function (response) {
requestParser.onResponse(response, properties);
var context = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "http.ClientResponse": response };
client.track(requestParser.getDependencyData(), null, context);
});
request.on('error', function (e) {
requestParser.onError(e, properties);
var context = { "http.RequestOptions": requestOptions, "http.ClientRequest": request, "Error": e };
client.track(requestParser.getDependencyData(), null, context);
});
}
};
AutoCollectClientRequests.prototype.dispose = function () {
AutoCollectClientRequests.INSTANCE = null;
this._isInitialized = false;
};
AutoCollectClientRequests.disableCollectionRequestOption = 'disableAppInsightsAutoCollection';
return AutoCollectClientRequests;
}());
module.exports = AutoCollectClientRequests;

View File

@@ -0,0 +1,22 @@
"use strict";
var AutoCollectConsole = (function () {
function AutoCollectConsole(client) {
if (!!AutoCollectConsole.INSTANCE) {
throw new Error("Console logging adapter tracking should be configured from the applicationInsights object");
}
this._client = client;
AutoCollectConsole.INSTANCE = this;
}
AutoCollectConsole.prototype.enable = function (isEnabled) {
// todo: investigate feasibility/utility of this; does it make sense to have a logging adapter in node?
};
AutoCollectConsole.prototype.isInitialized = function () {
return this._isInitialized;
};
AutoCollectConsole.prototype.dispose = function () {
AutoCollectConsole.INSTANCE = null;
};
AutoCollectConsole._methodNames = ["debug", "info", "log", "warn", "error"];
return AutoCollectConsole;
}());
module.exports = AutoCollectConsole;

View File

@@ -0,0 +1,191 @@
"use strict";
var Util = require("../Library/Util");
var CorrelationContextManager = (function () {
function CorrelationContextManager() {
}
/**
* Provides the current Context.
* The context is the most recent one entered into for the current
* logical chain of execution, including across asynchronous calls.
*/
CorrelationContextManager.getCurrentContext = function () {
if (!CorrelationContextManager.enabled) {
return null;
}
return Zone.current.get("context");
};
/**
* A helper to generate objects conforming to the CorrelationContext interface
*/
CorrelationContextManager.generateContextObject = function (parentId, operationName, operationId) {
operationId = operationId || Util.newGuid();
parentId = parentId || operationId;
if (this.enabled) {
return {
operation: {
name: operationName,
id: operationId,
parentId: parentId
},
customProperties: {}
};
}
return null;
};
/**
* Runs a function inside a given Context.
* All logical children of the execution path that entered this Context
* will receive this Context object on calls to GetCurrentContext.
*/
CorrelationContextManager.runWithContext = function (context, fn) {
if (CorrelationContextManager.enabled) {
var newZone = Zone.current.fork({
name: "AI-" + ((context && context.operation.parentId) || "Unknown"),
properties: { context: context }
});
newZone.run(fn);
}
else {
fn();
}
};
/**
* Patches a callback to restore the correct Context when getCurrentContext
* is run within it. This is necessary if automatic correlation fails to work
* with user-included libraries.
*
* The supplied callback will be given the same context that was present for
* the call to wrapCallback. */
CorrelationContextManager.wrapCallback = function (fn) {
if (CorrelationContextManager.enabled) {
return Zone.current.wrap(fn, "User-wrapped method");
}
return fn;
};
/**
* Enables the CorrelationContextManager.
*/
CorrelationContextManager.enable = function () {
if (!this.isNodeVersionCompatible()) {
this.enabled = false;
return;
}
// Load in Zone.js
require("zone.js");
// Run patches for Zone.js
if (!this.hasEverEnabled) {
this.hasEverEnabled = true;
this.patchError();
this.patchTimers(["setTimeout", "setInterval"]);
this.patchRedis();
}
this.enabled = true;
};
/**
* Disables the CorrelationContextManager.
*/
CorrelationContextManager.disable = function () {
this.enabled = false;
};
/**
* Reports if the CorrelationContextManager is able to run in this environment
*/
CorrelationContextManager.isNodeVersionCompatible = function () {
// Unit tests warn of errors < 3.3 from timer patching. All versions before 4 were 0.x
var nodeVer = process.versions.node.split(".");
return parseInt(nodeVer[0]) > 3 || (parseInt(nodeVer[0]) > 2 && parseInt(nodeVer[1]) > 2);
};
// Patch methods that manually go async that Zone doesn't catch
CorrelationContextManager.requireForPatch = function (module) {
var req = null;
try {
req = require(module);
}
catch (e) {
return null;
}
return req;
};
// A good example of patching a third party library to respect context.
// send_command is always used in this library to send data out.
// By overwriting the function to capture the callback provided to it,
// and wrapping that callback, we ensure that consumers of this library
// will have context persisted.
CorrelationContextManager.patchRedis = function () {
var redis = this.requireForPatch("redis");
if (redis && redis.RedisClient) {
var orig = redis.RedisClient.prototype.send_command;
redis.RedisClient.prototype.send_command = function () {
var args = Array.prototype.slice.call(arguments);
var lastArg = args[args.length - 1];
if (typeof lastArg === "function") {
args[args.length - 1] = Zone.current.wrap(lastArg, "AI.CCM.patchRedis");
}
else if (lastArg instanceof Array && typeof lastArg[lastArg.length - 1] === "function") {
// The last argument can be an array!
var lastIndexLastArg = lastArg[lastArg.length - 1];
lastArg[lastArg.length - 1] = Zone.current.wrap(lastIndexLastArg, "AI.CCM.patchRedis");
}
return orig.apply(this, args);
};
}
};
// Zone.js breaks concatenation of timer return values.
// This fixes that.
CorrelationContextManager.patchTimers = function (methodNames) {
methodNames.forEach(function (methodName) {
var orig = global[methodName];
global[methodName] = function () {
var ret = orig.apply(this, arguments);
ret.toString = function () {
if (this.data && typeof this.data.handleId !== 'undefined') {
return this.data.handleId.toString();
}
else {
return Object.prototype.toString.call(this);
}
};
return ret;
};
});
};
// Zone.js breaks deepEqual on error objects (by making internal properties enumerable).
// This fixes that by subclassing the error object and making all properties not enumerable
CorrelationContextManager.patchError = function () {
var orig = global.Error;
// New error handler
function AppInsightsAsyncCorrelatedErrorWrapper() {
if (!(this instanceof AppInsightsAsyncCorrelatedErrorWrapper)) {
return AppInsightsAsyncCorrelatedErrorWrapper.apply(Object.create(AppInsightsAsyncCorrelatedErrorWrapper.prototype), arguments);
}
orig.apply(this, arguments);
// getOwnPropertyNames should be a superset of Object.keys...
// This appears to not always be the case
var props = Object.getOwnPropertyNames(this).concat(Object.keys(this));
// Zone.js will automatically create some hidden properties at read time.
// We need to proactively make those not enumerable as well as the currently visible properties
for (var i = 0; i < props.length; i++) {
var propertyName = props[i];
var hiddenPropertyName = Zone['__symbol__'](propertyName);
Object.defineProperty(this, propertyName, { enumerable: false });
Object.defineProperty(this, hiddenPropertyName, { enumerable: false, writable: true });
}
return this;
}
// Inherit from the Zone.js error handler
AppInsightsAsyncCorrelatedErrorWrapper.prototype = orig.prototype;
// We need this loop to copy outer methods like Error.captureStackTrace
var props = Object.getOwnPropertyNames(orig);
for (var i = 0; i < props.length; i++) {
var propertyName = props[i];
if (!AppInsightsAsyncCorrelatedErrorWrapper[propertyName]) {
Object.defineProperty(AppInsightsAsyncCorrelatedErrorWrapper, propertyName, Object.getOwnPropertyDescriptor(orig, propertyName));
}
}
global.Error = AppInsightsAsyncCorrelatedErrorWrapper;
};
CorrelationContextManager.enabled = false;
CorrelationContextManager.hasEverEnabled = false;
return CorrelationContextManager;
}());
exports.CorrelationContextManager = CorrelationContextManager;

View File

@@ -0,0 +1,150 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var ContractsModule = require("../Library/Contracts");
var Util = require("../Library/Util");
var AutoCollectExceptions = (function () {
function AutoCollectExceptions(client) {
if (!!AutoCollectExceptions.INSTANCE) {
throw new Error("Exception tracking should be configured from the applicationInsights object");
}
AutoCollectExceptions.INSTANCE = this;
this._client = client;
}
AutoCollectExceptions.prototype.isInitialized = function () {
return this._isInitialized;
};
AutoCollectExceptions.prototype.enable = function (isEnabled) {
var _this = this;
if (isEnabled) {
this._isInitialized = true;
var self = this;
if (!this._exceptionListenerHandle) {
var handle = function (reThrow, error) {
var data = AutoCollectExceptions.getExceptionData(error, false);
var envelope = _this._client.getEnvelope(data);
_this._client.channel.handleCrash(envelope);
if (reThrow) {
throw error;
}
};
this._exceptionListenerHandle = handle.bind(this, true);
this._rejectionListenerHandle = handle.bind(this, false);
process.on("uncaughtException", this._exceptionListenerHandle);
process.on("unhandledRejection", this._rejectionListenerHandle);
}
}
else {
if (this._exceptionListenerHandle) {
process.removeListener("uncaughtException", this._exceptionListenerHandle);
process.removeListener("unhandledRejection", this._rejectionListenerHandle);
this._exceptionListenerHandle = undefined;
this._rejectionListenerHandle = undefined;
delete this._exceptionListenerHandle;
delete this._rejectionListenerHandle;
}
}
};
/**
* Track an exception
* @param error the exception to track
* @param handledAt where this exception was handled (leave null for unhandled)
* @param properties additional properties
* @param measurements metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
AutoCollectExceptions.getExceptionData = function (error, isHandled, properties, measurements) {
var exception = new ContractsModule.Contracts.ExceptionData();
exception.properties = properties;
exception.severityLevel = ContractsModule.Contracts.SeverityLevel.Error;
exception.measurements = measurements;
exception.exceptions = [];
var stack = error["stack"];
var exceptionDetails = new ContractsModule.Contracts.ExceptionDetails();
exceptionDetails.message = error.message;
exceptionDetails.typeName = error.name;
exceptionDetails.parsedStack = this.parseStack(stack);
exceptionDetails.hasFullStack = Util.isArray(exceptionDetails.parsedStack) && exceptionDetails.parsedStack.length > 0;
exception.exceptions.push(exceptionDetails);
var data = new ContractsModule.Contracts.Data();
data.baseType = "Microsoft.ApplicationInsights.ExceptionData";
data.baseData = exception;
return data;
};
AutoCollectExceptions.parseStack = function (stack) {
var parsedStack = undefined;
if (typeof stack === "string") {
var frames = stack.split("\n");
parsedStack = [];
var level = 0;
var totalSizeInBytes = 0;
for (var i = 0; i <= frames.length; i++) {
var frame = frames[i];
if (_StackFrame.regex.test(frame)) {
var parsedFrame = new _StackFrame(frames[i], level++);
totalSizeInBytes += parsedFrame.sizeInBytes;
parsedStack.push(parsedFrame);
}
}
// DP Constraint - exception parsed stack must be < 32KB
// remove frames from the middle to meet the threshold
var exceptionParsedStackThreshold = 32 * 1024;
if (totalSizeInBytes > exceptionParsedStackThreshold) {
var left = 0;
var right = parsedStack.length - 1;
var size = 0;
var acceptedLeft = left;
var acceptedRight = right;
while (left < right) {
// check size
var lSize = parsedStack[left].sizeInBytes;
var rSize = parsedStack[right].sizeInBytes;
size += lSize + rSize;
if (size > exceptionParsedStackThreshold) {
// remove extra frames from the middle
var howMany = acceptedRight - acceptedLeft + 1;
parsedStack.splice(acceptedLeft, howMany);
break;
}
// update pointers
acceptedLeft = left;
acceptedRight = right;
left++;
right--;
}
}
}
return parsedStack;
};
AutoCollectExceptions.prototype.dispose = function () {
AutoCollectExceptions.INSTANCE = null;
this._isInitialized = false;
};
AutoCollectExceptions.INSTANCE = null;
return AutoCollectExceptions;
}());
var _StackFrame = (function () {
function _StackFrame(frame, level) {
this.sizeInBytes = 0;
this.level = level;
this.method = "<no_method>";
this.assembly = Util.trim(frame);
var matches = frame.match(_StackFrame.regex);
if (matches && matches.length >= 5) {
this.method = Util.trim(matches[2]) || this.method;
this.fileName = Util.trim(matches[4]) || "<no_filename>";
this.line = parseInt(matches[5]) || 0;
}
this.sizeInBytes += this.method.length;
this.sizeInBytes += this.fileName.length;
this.sizeInBytes += this.assembly.length;
// todo: these might need to be removed depending on how the back-end settles on their size calculation
this.sizeInBytes += _StackFrame.baseSize;
this.sizeInBytes += this.level.toString().length;
this.sizeInBytes += this.line.toString().length;
}
// regex to match stack frames from ie/chrome/ff
// methodName=$2, fileName=$4, lineNo=$5, column=$6
_StackFrame.regex = /^([\s]+at)?(.*?)(\@|\s\(|\s)([^\(\@\n]+):([0-9]+):([0-9]+)(\)?)$/;
_StackFrame.baseSize = 58; //'{"method":"","level":,"assembly":"","fileName":"","line":}'.length
return _StackFrame;
}());
module.exports = AutoCollectExceptions;

View File

@@ -0,0 +1,231 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var http = require("http");
var os = require("os");
var ContractsModule = require("../Library/Contracts");
var Logging = require("../Library/Logging");
var PerfCounterType;
(function (PerfCounterType) {
PerfCounterType[PerfCounterType["ProcessorTime"] = 0] = "ProcessorTime";
PerfCounterType[PerfCounterType["AvailableMemory"] = 1] = "AvailableMemory";
PerfCounterType[PerfCounterType["RequestsPerSec"] = 2] = "RequestsPerSec";
PerfCounterType[PerfCounterType["PrivateBytes"] = 3] = "PrivateBytes";
PerfCounterType[PerfCounterType["RequestExecutionTime"] = 4] = "RequestExecutionTime";
PerfCounterType[PerfCounterType["PercentProcessorTime"] = 5] = "PercentProcessorTime";
})(PerfCounterType || (PerfCounterType = {}));
var AutoCollectPerformance = (function () {
function AutoCollectPerformance(client) {
if (!!AutoCollectPerformance.INSTANCE) {
throw new Error("Performance tracking should be configured from the applicationInsights object");
}
AutoCollectPerformance.INSTANCE = this;
this._isInitialized = false;
this._client = client;
}
AutoCollectPerformance.prototype.enable = function (isEnabled) {
var _this = this;
this._isEnabled = isEnabled;
if (this._isEnabled && !this._isInitialized) {
this._initialize();
}
if (isEnabled) {
if (!this._handle) {
this._lastCpus = os.cpus();
this._lastRequests = {
totalRequestCount: AutoCollectPerformance._totalRequestCount,
totalFailedRequestCount: AutoCollectPerformance._totalFailedRequestCount,
time: +new Date
};
this._handle = setInterval(function () { return _this.trackPerformance(); }, 10000);
}
}
else {
if (this._handle) {
clearInterval(this._handle);
this._handle = undefined;
}
}
};
AutoCollectPerformance.prototype.isInitialized = function () {
return this._isInitialized;
};
AutoCollectPerformance.prototype._initialize = function () {
var _this = this;
this._isInitialized = true;
var originalServer = http.createServer;
http.createServer = function (onRequest) {
return originalServer(function (request, response) {
if (_this._isEnabled) {
AutoCollectPerformance.countRequest(request, response);
}
if (typeof onRequest === "function") {
onRequest(request, response);
}
});
};
};
AutoCollectPerformance.countRequest = function (request, response) {
var _this = this;
var start = +new Date;
if (!request || !response) {
Logging.warn("AutoCollectPerformance.countRequest was called with invalid parameters: ", !!request, !!response);
return;
}
// response listeners
if (typeof response.once === "function") {
response.once("finish", function () {
var end = +new Date;
_this._lastRequestExecutionTime = end - start;
AutoCollectPerformance._totalRequestCount++;
if (response.statusCode >= 400) {
AutoCollectPerformance._totalFailedRequestCount++;
}
});
}
};
AutoCollectPerformance.prototype.trackPerformance = function () {
this._trackCpu();
this._trackMemory();
this._trackNetwork();
};
// this is necessary to accommodate some point-in-time UI quirks
AutoCollectPerformance.prototype._trackLegacyPerformance = function (counterType, value) {
var perfmetric = new ContractsModule.Contracts.PerformanceCounterData();
// semantic descriptions of these can be found here: https://support.microsoft.com/en-us/kb/815159/
switch (counterType) {
case PerfCounterType.ProcessorTime:
perfmetric.categoryName = "Process";
perfmetric.counterName = "% Processor Time";
break;
case PerfCounterType.AvailableMemory:
perfmetric.categoryName = "Memory";
perfmetric.counterName = "Available Bytes";
break;
case PerfCounterType.RequestsPerSec:
perfmetric.categoryName = "ASP.NET Applications";
perfmetric.counterName = "Requests/Sec";
break;
case PerfCounterType.PrivateBytes:
perfmetric.categoryName = "Process";
perfmetric.counterName = "Private Bytes";
break;
case PerfCounterType.RequestExecutionTime:
perfmetric.categoryName = "ASP.NET Applications";
perfmetric.counterName = "Request Execution Time";
break;
case PerfCounterType.PercentProcessorTime:
perfmetric.categoryName = "Processor";
perfmetric.counterName = "% Processor Time";
break;
}
perfmetric.count = 1;
perfmetric.kind = ContractsModule.Contracts.DataPointType.Aggregation;
perfmetric.max = value;
perfmetric.min = value;
perfmetric.stdDev = 0;
perfmetric.value = value;
var data = new ContractsModule.Contracts.Data();
data.baseType = "Microsoft.ApplicationInsights.PerformanceCounterData";
data.baseData = perfmetric;
this._client.track(data);
};
AutoCollectPerformance.prototype._trackCpu = function () {
// this reports total ms spent in each category since the OS was booted, to calculate percent it is necessary
// to find the delta since the last measurement
var cpus = os.cpus();
if (cpus && cpus.length && this._lastCpus && cpus.length === this._lastCpus.length) {
var totalUser = 0;
var totalSys = 0;
var totalNice = 0;
var totalIdle = 0;
var totalIrq = 0;
for (var i = 0; !!cpus && i < cpus.length; i++) {
var cpu = cpus[i];
var lastCpu = this._lastCpus[i];
var name = "% cpu(" + i + ") ";
var model = cpu.model;
var speed = cpu.speed;
var times = cpu.times;
var lastTimes = lastCpu.times;
// user cpu time (or) % CPU time spent in user space
var user = (times.user - lastTimes.user) || 0;
totalUser += user;
// system cpu time (or) % CPU time spent in kernel space
var sys = (times.sys - lastTimes.sys) || 0;
totalSys += sys;
// user nice cpu time (or) % CPU time spent on low priority processes
var nice = (times.nice - lastTimes.nice) || 0;
totalNice += nice;
// idle cpu time (or) % CPU time spent idle
var idle = (times.idle - lastTimes.idle) || 0;
totalIdle += idle;
// irq (or) % CPU time spent servicing/handling hardware interrupts
var irq = (times.irq - lastTimes.irq) || 0;
totalIrq += irq;
var total = (user + sys + nice + idle + irq) || 1; // don"t let this be 0 since it is a divisor
this._client.trackMetric(name + "user", user / total);
}
var combinedName = "% total cpu ";
var combinedTotal = (totalUser + totalSys + totalNice + totalIdle + totalIrq) || 1;
this._client.trackMetric(combinedName + "user", totalUser / combinedTotal);
this._client.trackMetric(combinedName + "sys", totalSys / combinedTotal);
this._client.trackMetric(combinedName + "nice", totalNice / combinedTotal);
this._client.trackMetric(combinedName + "idle", totalIdle / combinedTotal);
this._client.trackMetric(combinedName + "irq", totalIrq / combinedTotal);
// todo: remove this legacy counter once the UI updates (~june 2015)
this._trackLegacyPerformance(PerfCounterType.ProcessorTime, totalUser / combinedTotal);
this._trackLegacyPerformance(PerfCounterType.PercentProcessorTime, (combinedTotal - totalIdle) / combinedTotal);
}
this._lastCpus = cpus;
};
AutoCollectPerformance.prototype._trackMemory = function () {
var totalMem = os.totalmem();
var freeMem = os.freemem();
var usedMem = totalMem - freeMem;
var percentUsedMem = usedMem / (totalMem || 1);
var percentAvailableMem = freeMem / (totalMem || 1);
this._client.trackMetric("Memory Used", usedMem);
this._client.trackMetric("Memory Free", freeMem);
this._client.trackMetric("Memory Total", totalMem);
this._client.trackMetric("% Memory Used", percentUsedMem);
this._client.trackMetric("% Memory Free", percentAvailableMem);
// todo: remove this legacy counter once the UI updates (~june 2015)
this._trackLegacyPerformance(PerfCounterType.AvailableMemory, freeMem);
this._trackLegacyPerformance(PerfCounterType.PrivateBytes, usedMem);
};
AutoCollectPerformance.prototype._trackNetwork = function () {
// track total request counters
var lastRequests = this._lastRequests;
var requests = {
totalRequestCount: AutoCollectPerformance._totalRequestCount,
totalFailedRequestCount: AutoCollectPerformance._totalFailedRequestCount,
time: +new Date
};
var intervalRequests = (requests.totalRequestCount - lastRequests.totalRequestCount) || 0;
var intervalFailedRequests = (requests.totalFailedRequestCount - lastRequests.totalFailedRequestCount) || 0;
var elapsedMs = requests.time - lastRequests.time;
var elapsedSeconds = elapsedMs / 1000;
if (elapsedMs > 0) {
var requestsPerSec = intervalRequests / elapsedSeconds;
var failedRequestsPerSec = intervalFailedRequests / elapsedSeconds;
this._client.trackMetric("Total Requests", requests.totalRequestCount);
this._client.trackMetric("Total Failed Requests", requests.totalFailedRequestCount);
this._client.trackMetric("Requests per Second", requestsPerSec);
this._client.trackMetric("Failed Requests per Second", failedRequestsPerSec);
this._client.trackMetric("Last Request Execution Time", AutoCollectPerformance._lastRequestExecutionTime);
// todo: remove this legacy counter once the UI updates (~june 2015)
this._trackLegacyPerformance(PerfCounterType.RequestsPerSec, requestsPerSec);
this._trackLegacyPerformance(PerfCounterType.RequestExecutionTime, AutoCollectPerformance._lastRequestExecutionTime);
}
this._lastRequests = requests;
};
AutoCollectPerformance.prototype.dispose = function () {
AutoCollectPerformance.INSTANCE = null;
this._isInitialized = false;
};
AutoCollectPerformance._totalRequestCount = 0;
AutoCollectPerformance._totalFailedRequestCount = 0;
AutoCollectPerformance._lastRequestExecutionTime = 0;
return AutoCollectPerformance;
}());
module.exports = AutoCollectPerformance;

View File

@@ -0,0 +1,45 @@
"use strict";
/**
* Base class for helpers that read data from HTTP requst/response objects and convert them
* into the telemetry contract objects.
*/
var RequestParser = (function () {
function RequestParser() {
}
/**
* Gets a url parsed out from request options
*/
RequestParser.prototype.getUrl = function () {
return this.url;
};
RequestParser.prototype.RequestParser = function () {
this.startTime = +new Date();
};
RequestParser.prototype._setStatus = function (status, error, properties) {
var endTime = +new Date();
this.duration = endTime - this.startTime;
this.statusCode = status;
if (error) {
if (!properties) {
properties = {};
}
if (typeof error === "string") {
properties["error"] = error;
}
else if (error instanceof Error) {
properties["error"] = error.message;
}
else if (typeof error === "object") {
for (var key in error) {
properties[key] = error[key] && error[key].toString && error[key].toString();
}
}
}
this.properties = properties;
};
RequestParser.prototype._isSuccess = function () {
return (0 < this.statusCode) && (this.statusCode < 400);
};
return RequestParser;
}());
module.exports = RequestParser;

View File

@@ -0,0 +1,145 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var url = require("url");
var ContractsModule = require("../Library/Contracts");
var Util = require("../Library/Util");
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
var RequestParser = require("./RequestParser");
/**
* Helper class to read data from the requst/response objects and convert them into the telemetry contract
*/
var ServerRequestParser = (function (_super) {
__extends(ServerRequestParser, _super);
function ServerRequestParser(request, requestId) {
_super.call(this);
if (request) {
this.requestId = requestId || Util.newGuid();
this.method = request.method;
this.url = this._getAbsoluteUrl(request);
this.startTime = +new Date();
this.rawHeaders = request.headers || request.rawHeaders;
this.socketRemoteAddress = request.socket && request.socket.remoteAddress;
this.userAgent = request.headers && request.headers["user-agent"];
this.sourceIKeyHash =
request.headers && request.headers[RequestResponseHeaders.sourceInstrumentationKeyHeader];
this.parentId =
request.headers && request.headers[RequestResponseHeaders.parentIdHeader];
this.operationId =
request.headers && request.headers[RequestResponseHeaders.rootIdHeader];
if (request.connection) {
this.connectionRemoteAddress = request.connection.remoteAddress;
this.legacySocketRemoteAddress = request.connection["socket"] && request.connection["socket"].remoteAddress;
}
}
}
ServerRequestParser.prototype.onError = function (error, properties, ellapsedMilliseconds) {
this._setStatus(undefined, error, properties);
};
ServerRequestParser.prototype.onResponse = function (response, properties, ellapsedMilliseconds) {
this._setStatus(response.statusCode, undefined, properties);
if (ellapsedMilliseconds) {
this.duration = ellapsedMilliseconds;
}
};
ServerRequestParser.prototype.getRequestData = function () {
var requestData = new ContractsModule.Contracts.RequestData();
requestData.id = this.requestId;
requestData.name = this.method + " " + url.parse(this.url).pathname;
requestData.url = this.url;
requestData.source = this.sourceIKeyHash;
requestData.duration = Util.msToTimeSpan(this.duration);
requestData.responseCode = this.statusCode ? this.statusCode.toString() : null;
requestData.success = this._isSuccess();
requestData.properties = this.properties;
var data = new ContractsModule.Contracts.Data();
data.baseType = "Microsoft.ApplicationInsights.RequestData";
data.baseData = requestData;
return data;
};
ServerRequestParser.prototype.getRequestTags = function (tags) {
// create a copy of the context for requests since client info will be used here
var newTags = {};
for (var key in tags) {
newTags[key] = tags[key];
}
// don't override tags if they are already set
newTags[ServerRequestParser.keys.locationIp] = tags[ServerRequestParser.keys.locationIp] || this._getIp();
newTags[ServerRequestParser.keys.sessionId] = tags[ServerRequestParser.keys.sessionId] || this._getId("ai_session");
newTags[ServerRequestParser.keys.userId] = tags[ServerRequestParser.keys.userId] || this._getId("ai_user");
newTags[ServerRequestParser.keys.userAgent] = tags[ServerRequestParser.keys.userAgent] || this.userAgent;
newTags[ServerRequestParser.keys.operationName] = this.getOperationName(tags);
newTags[ServerRequestParser.keys.operationParentId] = this.getOperationParentId(tags);
newTags[ServerRequestParser.keys.operationId] = this.getOperationId(tags);
return newTags;
};
ServerRequestParser.prototype.getOperationId = function (tags) {
return tags[ServerRequestParser.keys.operationId] || this.operationId;
};
ServerRequestParser.prototype.getOperationParentId = function (tags) {
return tags[ServerRequestParser.keys.operationParentId] || this.parentId || this.getOperationId(tags);
};
ServerRequestParser.prototype.getOperationName = function (tags) {
return tags[ServerRequestParser.keys.operationName] || this.method + " " + url.parse(this.url).pathname;
};
ServerRequestParser.prototype.getRequestId = function () {
return this.requestId;
};
ServerRequestParser.prototype._getAbsoluteUrl = function (request) {
if (!request.headers) {
return request.url;
}
var encrypted = request.connection ? request.connection.encrypted : null;
var requestUrl = url.parse(request.url);
var pathName = requestUrl.pathname;
var search = requestUrl.search;
var absoluteUrl = url.format({
protocol: encrypted ? "https" : "http",
host: request.headers.host,
pathname: pathName,
search: search
});
return absoluteUrl;
};
ServerRequestParser.prototype._getIp = function () {
// regex to match ipv4 without port
// Note: including the port would cause the payload to be rejected by the data collector
var ipMatch = /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/;
var check = function (str) {
var results = ipMatch.exec(str);
if (results) {
return results[0];
}
};
var ip = check(this.rawHeaders["x-forwarded-for"])
|| check(this.rawHeaders["x-client-ip"])
|| check(this.rawHeaders["x-real-ip"])
|| check(this.connectionRemoteAddress)
|| check(this.socketRemoteAddress)
|| check(this.legacySocketRemoteAddress);
// node v12 returns this if the address is "localhost"
if (!ip
&& this.connectionRemoteAddress
&& this.connectionRemoteAddress.substr
&& this.connectionRemoteAddress.substr(0, 2) === "::") {
ip = "127.0.0.1";
}
return ip;
};
ServerRequestParser.prototype._getId = function (name) {
var cookie = (this.rawHeaders && this.rawHeaders["cookie"] &&
typeof this.rawHeaders["cookie"] === 'string' && this.rawHeaders["cookie"]) || "";
var value = ServerRequestParser.parseId(Util.getCookie(name, cookie));
return value;
};
ServerRequestParser.parseId = function (cookieValue) {
return cookieValue.substr(0, cookieValue.indexOf('|'));
};
ServerRequestParser.keys = new ContractsModule.Contracts.ContextTagKeys();
return ServerRequestParser;
}(RequestParser));
module.exports = ServerRequestParser;

View File

@@ -0,0 +1,169 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var http = require("http");
var https = require("https");
var Logging = require("../Library/Logging");
var Util = require("../Library/Util");
var RequestResponseHeaders = require("../Library/RequestResponseHeaders");
var ServerRequestParser = require("./ServerRequestParser");
var CorrelationContextManager_1 = require("./CorrelationContextManager");
var AutoCollectServerRequests = (function () {
function AutoCollectServerRequests(client) {
if (!!AutoCollectServerRequests.INSTANCE) {
throw new Error("Server request tracking should be configured from the applicationInsights object");
}
AutoCollectServerRequests.INSTANCE = this;
this._client = client;
}
AutoCollectServerRequests.prototype.enable = function (isEnabled) {
this._isEnabled = isEnabled;
// Autocorrelation requires automatic monitoring of incoming server requests
// Disabling autocollection but enabling autocorrelation will still enable
// request monitoring but will not produce request events
if ((this._isAutoCorrelating || this._isEnabled) && !this._isInitialized) {
this.useAutoCorrelation(this._isAutoCorrelating);
this._initialize();
}
};
AutoCollectServerRequests.prototype.useAutoCorrelation = function (isEnabled) {
if (isEnabled && !this._isAutoCorrelating) {
CorrelationContextManager_1.CorrelationContextManager.enable();
}
else if (!isEnabled && this._isAutoCorrelating) {
CorrelationContextManager_1.CorrelationContextManager.disable();
}
this._isAutoCorrelating = isEnabled;
};
AutoCollectServerRequests.prototype.isInitialized = function () {
return this._isInitialized;
};
AutoCollectServerRequests.prototype.isAutoCorrelating = function () {
return this._isAutoCorrelating;
};
AutoCollectServerRequests.prototype._generateCorrelationContext = function (requestParser) {
if (!this._isAutoCorrelating) {
return;
}
return CorrelationContextManager_1.CorrelationContextManager.generateContextObject(requestParser.getRequestId(), requestParser.getOperationName(this._client.context.tags), requestParser.getOperationId(this._client.context.tags));
};
AutoCollectServerRequests.prototype._initialize = function () {
var _this = this;
this._isInitialized = true;
var originalHttpServer = http.createServer;
http.createServer = function (onRequest) {
// todo: get a pointer to the server so the IP address can be read from server.address
return originalHttpServer(function (request, response) {
// Set up correlation context
var requestParser = new ServerRequestParser(request);
var correlationContext = _this._generateCorrelationContext(requestParser);
CorrelationContextManager_1.CorrelationContextManager.runWithContext(correlationContext, function () {
if (_this._isEnabled) {
// Auto collect request
AutoCollectServerRequests.trackRequest(_this._client, request, response, null, requestParser);
}
if (typeof onRequest === "function") {
onRequest(request, response);
}
});
});
};
var originalHttpsServer = https.createServer;
https.createServer = function (options, onRequest) {
return originalHttpsServer(options, function (request, response) {
// Set up correlation context
var requestParser = new ServerRequestParser(request);
var correlationContext = _this._generateCorrelationContext(requestParser);
CorrelationContextManager_1.CorrelationContextManager.runWithContext(correlationContext, function () {
if (_this._isEnabled) {
AutoCollectServerRequests.trackRequest(_this._client, request, response, null, requestParser);
}
if (typeof onRequest === "function") {
onRequest(request, response);
}
});
});
};
};
/**
* Tracks a request synchronously (doesn't wait for response 'finish' event)
*/
AutoCollectServerRequests.trackRequestSync = function (client, request, response, ellapsedMilliseconds, properties, error) {
if (!request || !response || !client) {
Logging.info("AutoCollectServerRequests.trackRequestSync was called with invalid parameters: ", !request, !response, !client);
return;
}
AutoCollectServerRequests.addResponseIKeyHeader(client, response);
// store data about the request
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
var requestParser = new ServerRequestParser(request, (correlationContext && correlationContext.operation.parentId) || Util.newGuid());
// Overwrite correlation context with request parser results
if (correlationContext) {
correlationContext.operation.id = requestParser.getOperationId(client.context.tags) || correlationContext.operation.id;
correlationContext.operation.name = requestParser.getOperationName(client.context.tags) || correlationContext.operation.name;
correlationContext.operation.parentId = requestParser.getRequestId() || correlationContext.operation.parentId;
}
AutoCollectServerRequests.endRequest(client, requestParser, request, response, ellapsedMilliseconds, properties, error);
};
/**
* Tracks a request by listening to the response 'finish' event
*/
AutoCollectServerRequests.trackRequest = function (client, request, response, properties, _requestParser) {
if (!request || !response || !client) {
Logging.info("AutoCollectServerRequests.trackRequest was called with invalid parameters: ", !request, !response, !client);
return;
}
// store data about the request
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
var requestParser = _requestParser || new ServerRequestParser(request, correlationContext && correlationContext.operation.parentId || Util.newGuid());
if (Util.canIncludeCorrelationHeader(client, requestParser.getUrl())) {
AutoCollectServerRequests.addResponseIKeyHeader(client, response);
}
// Overwrite correlation context with request parser results (if not an automatic track. we've already precalculated the correlation context in that case)
if (correlationContext && !_requestParser) {
correlationContext.operation.id = requestParser.getOperationId(client.context.tags) || correlationContext.operation.id;
correlationContext.operation.name = requestParser.getOperationName(client.context.tags) || correlationContext.operation.name;
correlationContext.operation.parentId = requestParser.getOperationParentId(client.context.tags) || correlationContext.operation.parentId;
}
// response listeners
if (response.once) {
response.once("finish", function () {
AutoCollectServerRequests.endRequest(client, requestParser, request, response, null, properties, null);
});
}
// track a failed request if an error is emitted
if (request.on) {
request.on("error", function (error) {
AutoCollectServerRequests.endRequest(client, requestParser, request, response, null, properties, error);
});
}
};
/**
* Add the target ikey hash to the response headers, if not already provided.
*/
AutoCollectServerRequests.addResponseIKeyHeader = function (client, response) {
if (client.config && client.config.instrumentationKeyHash &&
response.getHeader && response.setHeader &&
!response.getHeader(RequestResponseHeaders.targetInstrumentationKeyHeader) &&
!response.headersSent) {
response.setHeader(RequestResponseHeaders.targetInstrumentationKeyHeader, client.config.instrumentationKeyHash);
}
};
AutoCollectServerRequests.endRequest = function (client, requestParser, request, response, ellapsedMilliseconds, properties, error) {
if (error) {
requestParser.onError(error, properties, ellapsedMilliseconds);
}
else {
requestParser.onResponse(response, properties, ellapsedMilliseconds);
}
var context = { "http.ServerRequest": request, "http.ServerResponse": response };
var data = requestParser.getRequestData();
var tags = requestParser.getRequestTags(client.context.tags);
client.track(data, tags, context);
};
AutoCollectServerRequests.prototype.dispose = function () {
AutoCollectServerRequests.INSTANCE = null;
this._isInitialized = false;
};
return AutoCollectServerRequests;
}());
module.exports = AutoCollectServerRequests;

View File

@@ -0,0 +1,107 @@
"use strict";
var Logging = require("./Logging");
var Channel = (function () {
function Channel(isDisabled, getBatchSize, getBatchIntervalMs, sender) {
this._buffer = [];
this._lastSend = 0;
this._isDisabled = isDisabled;
this._getBatchSize = getBatchSize;
this._getBatchIntervalMs = getBatchIntervalMs;
this._sender = sender;
}
/**
* Enable or disable offline mode
*/
Channel.prototype.setOfflineMode = function (value, resendInterval) {
this._sender.setOfflineMode(value, resendInterval);
};
/**
* Add a telemetry item to the send buffer
*/
Channel.prototype.send = function (envelope) {
var _this = this;
// if master off switch is set, don't send any data
if (this._isDisabled()) {
// Do not send/save data
return;
}
// validate input
if (!envelope) {
Logging.warn("Cannot send null/undefined telemetry");
return;
}
// check if the incoming payload is too large, truncate if necessary
var payload = this._stringify(envelope);
if (typeof payload !== "string") {
return;
}
// enqueue the payload
this._buffer.push(payload);
// flush if we would exceed the max-size limit by adding this item
if (this._buffer.length >= this._getBatchSize()) {
this.triggerSend(false);
return;
}
// ensure an invocation timeout is set if anything is in the buffer
if (!this._timeoutHandle && this._buffer.length > 0) {
this._timeoutHandle = setTimeout(function () {
_this._timeoutHandle = null;
_this.triggerSend(false);
}, this._getBatchIntervalMs());
}
};
Channel.prototype.handleCrash = function (envelope) {
if (envelope) {
var payload = this._stringify(envelope);
if (typeof payload === "string") {
this._buffer.push(payload);
this.triggerSend(true);
}
else {
Logging.warn("Could not send crash", envelope);
}
}
else {
Logging.warn("handleCrash was called with empty payload", envelope);
}
};
/**
* Immediately send buffered data
*/
Channel.prototype.triggerSend = function (isNodeCrashing, callback) {
var bufferIsEmpty = this._buffer.length < 1;
if (!bufferIsEmpty) {
// compose an array of payloads
var batch = this._buffer.join("\n");
// invoke send
if (isNodeCrashing) {
this._sender.saveOnCrash(batch);
if (typeof callback === "function") {
callback("data saved on crash");
}
}
else {
this._sender.send(new Buffer(batch), callback);
}
}
// update lastSend time to enable throttling
this._lastSend = +new Date;
// clear buffer
this._buffer.length = 0;
clearTimeout(this._timeoutHandle);
this._timeoutHandle = null;
if (bufferIsEmpty && typeof callback === "function") {
callback("no data to send");
}
};
Channel.prototype._stringify = function (envelope) {
try {
return JSON.stringify(envelope);
}
catch (error) {
Logging.warn("Failed to serialize payload", error, envelope);
}
};
return Channel;
}());
module.exports = Channel;

View File

@@ -0,0 +1,253 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var url = require("url");
var Config = require("./Config");
var Context = require("./Context");
var ExceptionTracking = require("../AutoCollection/Exceptions");
var ContractsModule = require("../Library/Contracts");
var Channel = require("./Channel");
var ServerRequestTracking = require("../AutoCollection/ServerRequests");
var ClientRequestTracking = require("../AutoCollection/ClientRequests");
var CorrelationContextManager_1 = require("../AutoCollection/CorrelationContextManager");
var Sender = require("./Sender");
var Util = require("./Util");
var Logging = require("./Logging");
var Client = (function () {
/**
* Constructs a new client of the client
* @param iKey the instrumentation key to use (read from environment variable if not specified)
*/
function Client(iKey) {
this._telemetryProcessors = [];
var config = new Config(iKey);
this.config = config;
this.context = new Context();
this.commonProperties = {};
var sender = new Sender(function () { return config.endpointUrl; });
this.channel = new Channel(function () { return config.disableAppInsights; }, function () { return config.maxBatchSize; }, function () { return config.maxBatchIntervalMs; }, sender);
}
/**
* Log a user action or other occurrence.
* @param name A string to identify this event in the portal.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
Client.prototype.trackEvent = function (name, properties, measurements) {
var event = new ContractsModule.Contracts.EventData();
event.name = name;
event.properties = properties;
event.measurements = measurements;
var data = new ContractsModule.Contracts.Data();
data.baseType = "EventData";
data.baseData = event;
this.track(data);
};
/**
* Log a trace message
* @param message A string to identify this event in the portal.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
*/
Client.prototype.trackTrace = function (message, severityLevel, properties) {
var trace = new ContractsModule.Contracts.MessageData();
trace.message = message;
trace.properties = properties;
if (!isNaN(severityLevel)) {
trace.severityLevel = severityLevel;
}
else {
trace.severityLevel = ContractsModule.Contracts.SeverityLevel.Information;
}
var data = new ContractsModule.Contracts.Data();
data.baseType = "MessageData";
data.baseData = trace;
this.track(data);
};
/**
* Log an exception you have caught.
* @param exception An Error from a catch clause, or the string error message.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
Client.prototype.trackException = function (exception, properties, measurements) {
if (!Util.isError(exception)) {
exception = new Error(exception);
}
var data = ExceptionTracking.getExceptionData(exception, true, properties, measurements);
this.track(data);
};
/**
* * Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
* telemetry bandwidth by aggregating multiple measurements and sending the resulting average at intervals.
*
* @param name A string that identifies the metric.
* @param value The value of the metric
* @param count the number of samples used to get this value
* @param min the min sample for this set
* @param max the max sample for this set
* @param stdDev the standard deviation of the set
*/
Client.prototype.trackMetric = function (name, value, count, min, max, stdDev, properties) {
var metrics = new ContractsModule.Contracts.MetricData(); // todo: enable client-batching of these
metrics.metrics = [];
var metric = new ContractsModule.Contracts.DataPoint();
metric.count = !isNaN(count) ? count : 1;
metric.kind = ContractsModule.Contracts.DataPointType.Aggregation;
metric.max = !isNaN(max) ? max : value;
metric.min = !isNaN(min) ? min : value;
metric.name = name;
metric.stdDev = !isNaN(stdDev) ? stdDev : 0;
metric.value = value;
metrics.metrics.push(metric);
metrics.properties = properties;
var data = new ContractsModule.Contracts.Data();
data.baseType = "MetricData";
data.baseData = metrics;
this.track(data);
};
Client.prototype.trackRequestSync = function (request, response, ellapsedMilliseconds, properties, error) {
ServerRequestTracking.trackRequestSync(this, request, response, ellapsedMilliseconds, properties, error);
};
Client.prototype.trackRequest = function (request, response, properties) {
ServerRequestTracking.trackRequest(this, request, response, properties);
};
Client.prototype.trackDependencyRequest = function (requestOptions, request, properties) {
ClientRequestTracking.trackRequest(this, requestOptions, request, properties);
};
Client.prototype.trackDependency = function (name, commandName, elapsedTimeMs, success, dependencyTypeName, properties, async, target) {
if (properties === void 0) { properties = {}; }
if (async === void 0) { async = false; }
if (target === void 0) { target = null; }
if (!target && commandName) {
target = url.parse(commandName).host;
}
var remoteDependency = new ContractsModule.Contracts.RemoteDependencyData();
remoteDependency.name = name;
remoteDependency.data = commandName;
remoteDependency.target = target;
remoteDependency.duration = Util.msToTimeSpan(elapsedTimeMs);
remoteDependency.success = success;
remoteDependency.type = dependencyTypeName;
remoteDependency.properties = properties;
var data = new ContractsModule.Contracts.Data();
data.baseType = "RemoteDependencyData";
data.baseData = remoteDependency;
this.track(data);
};
/**
* Immediately send all queued telemetry.
*/
Client.prototype.sendPendingData = function (callback) {
this.channel.triggerSend(false, callback);
};
Client.prototype.getEnvelope = function (data, tagOverrides) {
if (data && data.baseData) {
data.baseData.ver = 2;
// if no properties are specified just add the common ones
if (!data.baseData.properties) {
data.baseData.properties = this.commonProperties;
}
else {
// otherwise, check each of the common ones
for (var name in this.commonProperties) {
// only override if the property `name` has not been set on this item
if (!data.baseData.properties[name]) {
data.baseData.properties[name] = this.commonProperties[name];
}
}
}
}
// sanitize properties
data.baseData.properties = Util.validateStringMap(data.baseData.properties);
var iKey = this.config.instrumentationKey;
var envelope = new ContractsModule.Contracts.Envelope();
envelope.data = data;
envelope.iKey = iKey;
// this is kind of a hack, but the envelope name is always the same as the data name sans the chars "data"
envelope.name =
"Microsoft.ApplicationInsights." +
iKey.replace(/-/g, "") +
"." +
data.baseType.substr(0, data.baseType.length - 4);
envelope.tags = this.getTags(tagOverrides);
envelope.time = (new Date()).toISOString();
envelope.ver = 1;
return envelope;
};
/**
* Generic track method for all telemetry types
* @param data the telemetry to send
* @param tagOverrides the context tags to use for this telemetry which overwrite default context values
*/
Client.prototype.track = function (data, tagOverrides, contextObjects) {
var envelope = this.getEnvelope(data, tagOverrides);
var accepted = this.runTelemetryProcessors(envelope, contextObjects);
if (accepted) {
this.channel.send(envelope);
}
};
/**
* Adds telemetry processor to the collection. Telemetry processors will be called one by one
* before telemetry item is pushed for sending and in the order they were added.
*
* @param telemetryProcessor function, takes Envelope, and optional context object and returns boolean
*/
Client.prototype.addTelemetryProcessor = function (telemetryProcessor) {
this._telemetryProcessors.push(telemetryProcessor);
};
/*
* Removes all telemetry processors
*/
Client.prototype.clearTelemetryProcessors = function () {
this._telemetryProcessors = [];
};
Client.prototype.runTelemetryProcessors = function (envelope, contextObjects) {
var accepted = true;
var telemetryProcessorsCount = this._telemetryProcessors.length;
if (telemetryProcessorsCount === 0) {
return accepted;
}
contextObjects = contextObjects || {};
contextObjects['correlationContext'] = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
for (var i = 0; i < telemetryProcessorsCount; ++i) {
try {
var processor = this._telemetryProcessors[i];
if (processor) {
if (processor.apply(null, [envelope, contextObjects]) === false) {
accepted = false;
break;
}
}
}
catch (error) {
accepted = false;
Logging.warn("One of telemetry processors failed, telemetry item will not be sent.", error, envelope);
}
}
return accepted;
};
Client.prototype.getTags = function (tagOverrides) {
var correlationContext = CorrelationContextManager_1.CorrelationContextManager.getCurrentContext();
// Make a copy of context tags so we don't alter the actual object
// Also perform tag overriding
var newTags = {};
for (var key in this.context.tags) {
newTags[key] = this.context.tags[key];
}
for (var key in tagOverrides) {
newTags[key] = tagOverrides[key];
}
if (!correlationContext) {
return newTags;
}
// Fill in internally-populated values if not already set
if (correlationContext) {
newTags[this.context.keys.operationId] = newTags[this.context.keys.operationId] || correlationContext.operation.id;
newTags[this.context.keys.operationName] = newTags[this.context.keys.operationName] || correlationContext.operation.name;
newTags[this.context.keys.operationParentId] = newTags[this.context.keys.operationParentId] || correlationContext.operation.parentId;
}
return newTags;
};
return Client;
}());
module.exports = Client;

View File

@@ -0,0 +1,44 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var crypto = require('crypto');
var Config = (function () {
function Config(instrumentationKey) {
this.instrumentationKey = instrumentationKey || Config._getInstrumentationKey();
this.instrumentationKeyHash = Config._getStringHashBase64(this.instrumentationKey);
this.endpointUrl = "https://dc.services.visualstudio.com/v2/track";
this.sessionRenewalMs = 30 * 60 * 1000;
this.sessionExpirationMs = 24 * 60 * 60 * 1000;
this.maxBatchSize = 250;
this.maxBatchIntervalMs = 15000;
this.disableAppInsights = false;
this.correlationHeaderExcludedDomains = [
"*.blob.core.windows.net",
"*.blob.core.chinacloudapi.cn",
"*.blob.core.cloudapi.de",
"*.blob.core.usgovcloudapi.net"];
}
Config._getInstrumentationKey = function () {
// check for both the documented env variable and the azure-prefixed variable
var iKey = process.env[Config.ENV_iKey]
|| process.env[Config.ENV_azurePrefix + Config.ENV_iKey]
|| process.env[Config.legacy_ENV_iKey]
|| process.env[Config.ENV_azurePrefix + Config.legacy_ENV_iKey];
if (!iKey || iKey == "") {
throw new Error("Instrumentation key not found, pass the key in the config to this method or set the key in the environment variable APPINSIGHTS_INSTRUMENTATIONKEY before starting the server");
}
return iKey;
};
Config._getStringHashBase64 = function (value) {
var hash = crypto.createHash('sha256');
hash.update(value);
var result = hash.digest('base64');
return result;
};
// Azure adds this prefix to all environment variables
Config.ENV_azurePrefix = "APPSETTING_";
// This key is provided in the readme
Config.ENV_iKey = "APPINSIGHTS_INSTRUMENTATIONKEY";
Config.legacy_ENV_iKey = "APPINSIGHTS_INSTRUMENTATION_KEY";
return Config;
}());
module.exports = Config;

View File

@@ -0,0 +1,62 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var os = require("os");
var ContractsModule = require("../Library/Contracts");
var Logging = require("./Logging");
var Context = (function () {
function Context(packageJsonPath) {
this.keys = new ContractsModule.Contracts.ContextTagKeys();
this.tags = {};
this._loadApplicationContext();
this._loadDeviceContext();
this._loadInternalContext();
}
Context.prototype._loadApplicationContext = function (packageJsonPath) {
var version = "unknown";
var description = undefined;
try {
// note: this should return the host package.json
var packageJson = require(packageJsonPath || "../../../package.json");
if (packageJson) {
if (typeof packageJson.version === "string") {
version = packageJson.version;
}
if (typeof packageJson.description === "string") {
description = packageJson.description;
}
}
}
catch (exception) {
Logging.info("unable to read app version: ", exception);
}
this.tags[this.keys.applicationVersion] = version;
// TODO: consider sending it as a custom property
//if(description) {
// this.tags[this.keys.applicationBuild] = description;
//}
};
Context.prototype._loadDeviceContext = function () {
this.tags[this.keys.deviceId] = "";
this.tags[this.keys.cloudRoleInstance] = os && os.hostname();
this.tags[this.keys.deviceOSVersion] = os && os.type() + " " + os && os.release();
// not yet supported tags
this.tags["ai.device.osArchitecture"] = os && os.arch();
this.tags["ai.device.osPlatform"] = os && os.platform();
};
Context.prototype._loadInternalContext = function () {
var version = "unknown";
try {
// note: this should return the appInsights package.json
var packageJson = require("../package.json");
if (packageJson && typeof packageJson.version === "string") {
version = packageJson.version;
}
}
catch (exception) {
Logging.info("unable to read SDK version: " + exception);
}
this.tags[this.keys.internalSdkVersion] = "node:" + version || "unknown";
};
return Context;
}());
module.exports = Context;

View File

@@ -0,0 +1,225 @@
// this file is manually constructed and many types and fields here are deprecated.
// Need to switch to use Declarations\Constracts\Generated instead
// This will be consistent with JavaScript SDK
"use strict";
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Contracts;
(function (Contracts) {
(function (DataPointType) {
DataPointType[DataPointType["Measurement"] = 0] = "Measurement";
DataPointType[DataPointType["Aggregation"] = 1] = "Aggregation";
})(Contracts.DataPointType || (Contracts.DataPointType = {}));
var DataPointType = Contracts.DataPointType;
(function (SeverityLevel) {
SeverityLevel[SeverityLevel["Verbose"] = 0] = "Verbose";
SeverityLevel[SeverityLevel["Information"] = 1] = "Information";
SeverityLevel[SeverityLevel["Warning"] = 2] = "Warning";
SeverityLevel[SeverityLevel["Error"] = 3] = "Error";
SeverityLevel[SeverityLevel["Critical"] = 4] = "Critical";
})(Contracts.SeverityLevel || (Contracts.SeverityLevel = {}));
var SeverityLevel = Contracts.SeverityLevel;
var ContextTagKeys = (function () {
function ContextTagKeys() {
this.applicationVersion = "ai.application.ver";
this.deviceId = "ai.device.id";
this.deviceLocale = "ai.device.locale";
this.deviceModel = "ai.device.model";
this.deviceOEMName = "ai.device.oemName";
this.deviceOSVersion = "ai.device.osVersion";
this.deviceType = "ai.device.type";
this.locationIp = "ai.location.ip";
this.operationId = "ai.operation.id";
this.operationName = "ai.operation.name";
this.operationParentId = "ai.operation.parentId";
this.operationSyntheticSource = "ai.operation.syntheticSource";
this.operationCorrelationVector = "ai.operation.correlationVector";
this.sessionId = "ai.session.id";
this.sessionIsFirst = "ai.session.isFirst";
this.userAccountId = "ai.user.accountId";
this.userAgent = "ai.user.userAgent";
this.userId = "ai.user.id";
this.userAuthUserId = "ai.user.authUserId";
this.cloudRole = "ai.cloud.role";
this.cloudRoleInstance = "ai.cloud.roleInstance";
this.internalSdkVersion = "ai.internal.sdkVersion";
this.internalAgentVersion = "ai.internal.agentVersion";
this.internalNodeName = "ai.internal.nodeName";
}
return ContextTagKeys;
}());
Contracts.ContextTagKeys = ContextTagKeys;
var Domain = (function () {
function Domain() {
}
return Domain;
}());
Contracts.Domain = Domain;
var Data = (function () {
function Data() {
}
return Data;
}());
Contracts.Data = Data;
var Envelope = (function () {
function Envelope() {
this.ver = 1;
// the 'name' property must be initialized before 'tags' and/or 'data'.
this.name = "";
// the 'time' property must be initialized before 'tags' and/or 'data'.
this.time = "";
this.sampleRate = 100.0;
this.tags = {};
}
return Envelope;
}());
Contracts.Envelope = Envelope;
var EventData = (function (_super) {
__extends(EventData, _super);
function EventData() {
_super.call(this);
this.ver = 2;
this.properties = {};
this.measurements = {};
_super.call(this);
}
return EventData;
}(Contracts.Domain));
Contracts.EventData = EventData;
var MessageData = (function (_super) {
__extends(MessageData, _super);
function MessageData() {
_super.call(this);
this.ver = 2;
this.properties = {};
_super.call(this);
}
return MessageData;
}(Contracts.Domain));
Contracts.MessageData = MessageData;
var ExceptionData = (function (_super) {
__extends(ExceptionData, _super);
function ExceptionData() {
_super.call(this);
this.ver = 2;
this.exceptions = [];
this.properties = {};
this.measurements = {};
}
return ExceptionData;
}(Contracts.Domain));
Contracts.ExceptionData = ExceptionData;
var StackFrame = (function () {
function StackFrame() {
}
return StackFrame;
}());
Contracts.StackFrame = StackFrame;
var ExceptionDetails = (function () {
function ExceptionDetails() {
this.hasFullStack = true;
this.parsedStack = [];
}
return ExceptionDetails;
}());
Contracts.ExceptionDetails = ExceptionDetails;
var DataPoint = (function () {
function DataPoint() {
this.kind = Contracts.DataPointType.Measurement;
}
return DataPoint;
}());
Contracts.DataPoint = DataPoint;
var MetricData = (function (_super) {
__extends(MetricData, _super);
function MetricData() {
_super.call(this);
this.ver = 2;
this.metrics = [];
this.properties = {};
_super.call(this);
}
return MetricData;
}(Contracts.Domain));
Contracts.MetricData = MetricData;
var PageViewData = (function (_super) {
__extends(PageViewData, _super);
function PageViewData() {
_super.call(this);
this.ver = 2;
this.properties = {};
this.measurements = {};
_super.call(this);
}
return PageViewData;
}(Contracts.EventData));
Contracts.PageViewData = PageViewData;
var PageViewPerfData = (function (_super) {
__extends(PageViewPerfData, _super);
function PageViewPerfData() {
_super.call(this);
this.ver = 2;
this.properties = {};
this.measurements = {};
}
return PageViewPerfData;
}(Contracts.PageViewData));
Contracts.PageViewPerfData = PageViewPerfData;
var RemoteDependencyDataConstants = (function () {
function RemoteDependencyDataConstants() {
}
RemoteDependencyDataConstants.TYPE_HTTP = "Http";
return RemoteDependencyDataConstants;
}());
Contracts.RemoteDependencyDataConstants = RemoteDependencyDataConstants;
var RemoteDependencyData = (function (_super) {
__extends(RemoteDependencyData, _super);
function RemoteDependencyData() {
_super.call(this);
this.ver = 2;
this.success = true;
this.properties = {};
this.measurements = {};
}
return RemoteDependencyData;
}(Contracts.Domain));
Contracts.RemoteDependencyData = RemoteDependencyData;
var AjaxCallData = (function (_super) {
__extends(AjaxCallData, _super);
function AjaxCallData() {
_super.call(this);
this.ver = 2;
this.properties = {};
this.measurements = {};
_super.call(this);
}
return AjaxCallData;
}(Contracts.PageViewData));
Contracts.AjaxCallData = AjaxCallData;
var RequestData = (function (_super) {
__extends(RequestData, _super);
function RequestData() {
_super.call(this);
this.ver = 2;
this.properties = {};
this.measurements = {};
}
return RequestData;
}(Contracts.Domain));
Contracts.RequestData = RequestData;
var PerformanceCounterData = (function (_super) {
__extends(PerformanceCounterData, _super);
function PerformanceCounterData() {
_super.call(this);
this.ver = 2;
this.kind = DataPointType.Aggregation;
this.properties = {};
_super.call(this);
}
return PerformanceCounterData;
}(Contracts.Domain));
Contracts.PerformanceCounterData = PerformanceCounterData;
})(Contracts = exports.Contracts || (exports.Contracts = {}));

View File

@@ -0,0 +1,28 @@
"use strict";
var Logging = (function () {
function Logging() {
}
Logging.info = function (message) {
var optionalParams = [];
for (var _i = 1; _i < arguments.length; _i++) {
optionalParams[_i - 1] = arguments[_i];
}
if (Logging.enableDebug) {
console.info(Logging.TAG + message, optionalParams);
}
};
Logging.warn = function (message) {
var optionalParams = [];
for (var _i = 1; _i < arguments.length; _i++) {
optionalParams[_i - 1] = arguments[_i];
}
if (!Logging.disableWarnings) {
console.warn(Logging.TAG + message, optionalParams);
}
};
Logging.enableDebug = false;
Logging.disableWarnings = false;
Logging.TAG = "ApplicationInsights:";
return Logging;
}());
module.exports = Logging;

View File

@@ -0,0 +1,22 @@
"use strict";
module.exports = {
/**
* Source instrumentation header that is added by an application while making http
* requests and retrieved by the other application when processing incoming requests.
*/
sourceInstrumentationKeyHeader: "x-ms-request-source-ikey",
/**
* Target instrumentation header that is added to the response and retrieved by the
* calling application when processing incoming responses.
*/
targetInstrumentationKeyHeader: "x-ms-request-target-ikey",
/**
* Header containing the id of the immidiate caller
*/
parentIdHeader: "x-ms-request-id",
/**
* Header containing the correlation id that kept the same for every telemetry item
* accross transactions
*/
rootIdHeader: "x-ms-request-root-id"
};

View File

@@ -0,0 +1,218 @@
///<reference path="..\typings\globals\node\index.d.ts" />
"use strict";
var fs = require("fs");
var http = require("http");
var https = require("https");
var os = require("os");
var path = require("path");
var url = require("url");
var zlib = require("zlib");
var Logging = require("./Logging");
var AutoCollectClientRequests = require("../AutoCollection/ClientRequests");
var Sender = (function () {
function Sender(getUrl, onSuccess, onError) {
this._getUrl = getUrl;
this._onSuccess = onSuccess;
this._onError = onError;
this._enableOfflineMode = false;
this._resendInterval = Sender.WAIT_BETWEEN_RESEND;
}
/**
* Enable or disable offline mode
*/
Sender.prototype.setOfflineMode = function (value, resendInterval) {
this._enableOfflineMode = value;
if (typeof resendInterval === 'number' && resendInterval >= 0) {
this._resendInterval = Math.floor(resendInterval);
}
};
Sender.prototype.send = function (payload, callback) {
var _this = this;
var endpointUrl = this._getUrl();
if (endpointUrl && endpointUrl.indexOf("//") === 0) {
// use https if the config did not specify a protocol
endpointUrl = "https:" + endpointUrl;
}
// todo: investigate specifying an agent here: https://nodejs.org/api/http.html#http_class_http_agent
var parsedUrl = url.parse(endpointUrl);
var options = {
host: parsedUrl.hostname,
port: parsedUrl.port,
path: parsedUrl.pathname,
method: "POST",
headers: {
"Content-Type": "application/x-json-stream"
}
};
zlib.gzip(payload, function (err, buffer) {
var dataToSend = buffer;
if (err) {
Logging.warn(err);
dataToSend = payload; // something went wrong so send without gzip
options.headers["Content-Length"] = payload.length;
}
else {
options.headers["Content-Encoding"] = "gzip";
options.headers["Content-Length"] = buffer.length;
}
Logging.info(Sender.TAG, options);
// Ensure this request is not captured by auto-collection.
options[AutoCollectClientRequests.disableCollectionRequestOption] = true;
var requestCallback = function (res) {
res.setEncoding("utf-8");
//returns empty if the data is accepted
var responseString = "";
res.on("data", function (data) {
responseString += data;
});
res.on("end", function () {
Logging.info(Sender.TAG, responseString);
if (typeof _this._onSuccess === "function") {
_this._onSuccess(responseString);
}
if (typeof callback === "function") {
callback(responseString);
}
if (_this._enableOfflineMode) {
// try to send any cached events if the user is back online
if (res.statusCode === 200) {
setTimeout(function () { return _this._sendFirstFileOnDisk(); }, _this._resendInterval);
}
else if (res.statusCode === 206 ||
res.statusCode === 429 ||
res.statusCode === 439) {
_this._storeToDisk(payload);
}
}
});
};
var req = (parsedUrl.protocol == "https:") ?
https.request(options, requestCallback) :
http.request(options, requestCallback);
req.on("error", function (error) {
// todo: handle error codes better (group to recoverable/non-recoverable and persist)
Logging.warn(Sender.TAG, error);
_this._onErrorHelper(error);
if (typeof callback === "function") {
var errorMessage = "error sending telemetry";
if (error && (typeof error.toString === "function")) {
errorMessage = error.toString();
}
callback(errorMessage);
}
if (_this._enableOfflineMode) {
_this._storeToDisk(payload);
}
});
req.write(dataToSend);
req.end();
});
};
Sender.prototype.saveOnCrash = function (payload) {
this._storeToDiskSync(payload);
};
Sender.prototype._confirmDirExists = function (direcotry, callback) {
fs.exists(direcotry, function (exists) {
if (!exists) {
fs.mkdir(direcotry, function (err) {
callback(err);
});
}
else {
callback(null);
}
});
};
/**
* Stores the payload as a json file on disk in the temp direcotry
*/
Sender.prototype._storeToDisk = function (payload) {
var _this = this;
//ensure directory is created
var direcotry = path.join(os.tmpdir(), Sender.TEMPDIR);
this._confirmDirExists(direcotry, function (error) {
if (error) {
_this._onErrorHelper(error);
return;
}
//create file - file name for now is the timestamp, a better approach would be a UUID but that
//would require an external dependency
var fileName = new Date().getTime() + ".ai.json";
var fileFullPath = path.join(direcotry, fileName);
Logging.info(Sender.TAG, "saving data to disk at: " + fileFullPath);
fs.writeFile(fileFullPath, payload, function (error) { return _this._onErrorHelper(error); });
});
};
/**
* Stores the payload as a json file on disk using sync file operations
* this is used when storing data before crashes
*/
Sender.prototype._storeToDiskSync = function (payload) {
var direcotry = path.join(os.tmpdir(), Sender.TEMPDIR);
try {
if (!fs.existsSync(direcotry)) {
fs.mkdirSync(direcotry);
}
//create file - file name for now is the timestamp, a better approach would be a UUID but that
//would require an external dependency
var fileName = new Date().getTime() + ".ai.json";
var fileFullPath = path.join(direcotry, fileName);
Logging.info(Sender.TAG, "saving data before crash to disk at: " + fileFullPath);
fs.writeFileSync(fileFullPath, payload);
}
catch (error) {
this._onErrorHelper(error);
}
};
/**
* Check for temp telemetry files
* reads the first file if exist, deletes it and tries to send its load
*/
Sender.prototype._sendFirstFileOnDisk = function () {
var _this = this;
var tempDir = path.join(os.tmpdir(), Sender.TEMPDIR);
fs.exists(tempDir, function (exists) {
if (exists) {
fs.readdir(tempDir, function (error, files) {
if (!error) {
files = files.filter(function (f) { return path.basename(f).indexOf(".ai.json") > -1; });
if (files.length > 0) {
var firstFile = files[0];
var filePath = path.join(tempDir, firstFile);
fs.readFile(filePath, function (error, payload) {
if (!error) {
// delete the file first to prevent double sending
fs.unlink(filePath, function (error) {
if (!error) {
_this.send(payload);
}
else {
_this._onErrorHelper(error);
}
});
}
else {
_this._onErrorHelper(error);
}
});
}
}
else {
_this._onErrorHelper(error);
}
});
}
});
};
Sender.prototype._onErrorHelper = function (error) {
if (typeof this._onError === "function") {
this._onError(error);
}
};
Sender.TAG = "Sender";
// the amount of time the SDK will wait between resending cached data, this buffer is to avoid any throtelling from the service side
Sender.WAIT_BETWEEN_RESEND = 60 * 1000;
Sender.TEMPDIR = "appInsights-node";
return Sender;
}());
module.exports = Sender;

View File

@@ -0,0 +1,166 @@
"use strict";
var url = require("url");
var Logging = require("./Logging");
var Util = (function () {
function Util() {
}
/**
* helper method to access userId and sessionId cookie
*/
Util.getCookie = function (name, cookie) {
var value = "";
if (name && name.length && typeof cookie === "string") {
var cookieName = name + "=";
var cookies = cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
cookie = Util.trim(cookie);
if (cookie && cookie.indexOf(cookieName) === 0) {
value = cookie.substring(cookieName.length, cookies[i].length);
break;
}
}
}
return value;
};
/**
* helper method to trim strings (IE8 does not implement String.prototype.trim)
*/
Util.trim = function (str) {
if (typeof str === "string") {
return str.replace(/^\s+|\s+$/g, "");
}
else {
return "";
}
};
/**
* Convert an array of int32 to Base64 (no '==' at the end).
* MSB first.
*/
Util.int32ArrayToBase64 = function (array) {
var toChar = function (v, i) {
return String.fromCharCode((v >> i) & 0xFF);
};
var int32AsString = function (v) {
return toChar(v, 24) + toChar(v, 16) + toChar(v, 8) + toChar(v, 0);
};
var x = array.map(int32AsString).join("");
var s = new Buffer(x, "binary").toString("base64");
return s.substr(0, s.indexOf("="));
};
/**
* generate a random 32bit number (-0x80000000..0x7FFFFFFF).
*/
Util.random32 = function () {
return (0x100000000 * Math.random()) | 0;
};
/**
* generate GUID
*/
Util.newGuid = function () {
var hexValues = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
var oct = "", tmp;
for (var a = 0; a < 4; a++) {
tmp = Util.random32();
oct +=
hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
// "Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively"
var clockSequenceHi = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + "-" + oct.substr(9, 4) + "-4" + oct.substr(13, 3) + "-" + clockSequenceHi + oct.substr(16, 3) + "-" + oct.substr(19, 12);
};
/**
* Check if an object is of type Array
*/
Util.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/**
* Check if an object is of type Error
*/
Util.isError = function (obj) {
return obj instanceof Error;
};
/**
* Check if an object is of type Date
*/
Util.isDate = function (obj) {
return Object.prototype.toString.call(obj) === "[object Date]";
};
/**
* Convert ms to c# time span format
*/
Util.msToTimeSpan = function (totalms) {
if (isNaN(totalms) || totalms < 0) {
totalms = 0;
}
var ms = "" + totalms % 1000;
var sec = "" + Math.floor(totalms / 1000) % 60;
var min = "" + Math.floor(totalms / (1000 * 60)) % 60;
var hour = "" + Math.floor(totalms / (1000 * 60 * 60)) % 24;
var days = Math.floor(totalms / (1000 * 60 * 60 * 24));
ms = ms.length === 1 ? "00" + ms : ms.length === 2 ? "0" + ms : ms;
sec = sec.length < 2 ? "0" + sec : sec;
min = min.length < 2 ? "0" + min : min;
hour = hour.length < 2 ? "0" + hour : hour;
var daysText = days > 0 ? days + "." : "";
return daysText + hour + ":" + min + ":" + sec + "." + ms;
};
/**
* Validate that an object is of type { [key: string]: string }
*/
Util.validateStringMap = function (obj) {
var map;
if (typeof obj === "object") {
map = {};
for (var field in obj) {
var property = obj[field];
var propertyType = typeof property;
if (propertyType !== "string") {
if (property != null && typeof property.toString === "function") {
property = property.toString();
}
else {
Logging.info("key: " + field + ", invalid property type: " + propertyType);
continue;
}
}
map[field] = property.trim(0, Util.MAX_PROPERTY_LENGTH);
}
}
else {
Logging.info("Invalid properties dropped from payload");
}
return map;
};
/**
* Checks if a request url is not on a excluded domain list
* and if it is safe to add correlation headers (x-ms-request-source-ikey, x-ms-request-target-ikey)
*/
Util.canIncludeCorrelationHeader = function (client, requestUrl) {
var excludedDomains = client && client.config && client.config.correlationHeaderExcludedDomains;
if (!excludedDomains || excludedDomains.length == 0 || !requestUrl) {
return true;
}
for (var i = 0; i < excludedDomains.length; i++) {
var regex = new RegExp(excludedDomains[i].replace(/\./g, "\.").replace(/\*/g, ".*"));
if (regex.test(url.parse(requestUrl).hostname)) {
return false;
}
}
return true;
};
Util.MAX_PROPERTY_LENGTH = 1024;
Util.document = typeof document !== "undefined" ? document : {};
return Util;
}());
module.exports = Util;

View File

@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright © Microsoft Corporation
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.

View File

@@ -0,0 +1,214 @@
# Application Insights for Node.js
[![NPM version](https://badge.fury.io/js/applicationinsights.svg)](http://badge.fury.io/js/applicationinsights)
[![Build Status](https://travis-ci.org/Microsoft/ApplicationInsights-node.js.svg?branch=master)](https://travis-ci.org/Microsoft/ApplicationInsights-node.js)
This project provides a [Visual Studio Application Insights](https://azure.microsoft.com/documentation/articles/app-insights-overview/) SDK for [Node.js](https://nodejs.org/). The SDK sends telemetry about the performance and usage of your live Node.js application to the Application Insights service. There you can analyze charts of request rates, response times, failures and dependencies, and diagnose issues using powerful search and aggregation tools.
The SDK provides automatic collection of incoming HTTP request rates and responses, performance counters (CPU, memory, RPS), and unhandled exceptions. In addition, you can add custom calls to track dependencies, metrics, or other events.
In versions of Node.js > 4.0 (and io.js > 3.3) the SDK provides automatic correlation of dependencies to requests (off by default, see Customized Usage below to enable).
## Requirements ##
**Install**
```
npm install applicationinsights
```
### Get an instrumentation key
[Create an Application Insights resource](https://azure.microsoft.com/documentation/articles/app-insights-create-new-resource/) where your telemetry will be displayed. This provides you with an instrumentation key that identifies the resource. (You can try the SDK without sending telemetry: set the instrumentation key to a non-empty string.)
## Usage ##
This will enable request monitoring, unhandled exception tracking, and system performance monitoring (CPU/Memory/RPS).
```javascript
import appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>").start();
```
>The instrumentation key can also be set in the environment variable APPINSIGHTS_INSTRUMENTATIONKEY. If this is done, no argument is required when calling `appInsights.setup()` or `appInsights.getClient()`.
## Customized Usage ##
### Enabling automatic correlation
```javascript
import appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>")
.setAutoDependencyCorrelation(true)
// no telemetry will be sent until .start() is called
.start();
```
> Be sure to call `require("applicationinsights")` before your other imports. This allows the SDK to do patching necessary for tracking correlation state before other libraries use patched methods. If you encounter conflicts with other libraries doing similar patching, place this import below those libraries.
### Disabling automatic collection
```javascript
import appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>")
.setAutoCollectRequests(false)
.setAutoCollectPerformance(false)
.setAutoCollectExceptions(false)
.setAutoCollectDependencies(false)
// no telemetry will be sent until .start() is called
.start();
```
### Custom monitoring
```javascript
import appInsights = require("applicationinsights");
var client = appInsights.getClient();
client.trackEvent("custom event", {customProperty: "custom property value"});
client.trackException(new Error("handled exceptions can be logged with this method"));
client.trackMetric("custom metric", 3);
client.trackTrace("trace message");
```
### Telemetry Processor
```javascript
public addTelemetryProcessor(telemetryProcessor: (envelope: ContractsModule.Contracts.Envelope, context: { http.RequestOptions, http.ClientRequest, http.ClientResponse, correlationContext }) => boolean)
```
Adds a telemetry processor to the collection. Telemetry processors will be called one by one, in the order they were added, before the telemetry item is pushed for sending.
If one of the telemetry processors returns false then the telemetry item will not be sent.
If one of the telemetry processors throws an error then the telemetry item will not be sent.
All telemetry processors receive the envelope to modify before sending. They also receive a context object with relevant request information (if available)
as well as the request storage object returned by `appInsights.getCorrelationContext()` (if automatic dependency correlation is enabled).
**Example**
Add the below code before you send any telemetry, it will remove stack trace information from any Exception reported by the SDK.
```javascript
appInsights.client.addTelemetryProcessor((envelope) => {
if (envelope.data.baseType === "Microsoft.ApplicationInsights.ExceptionData") {
var data = envelope.data.baseData;
if (data.exceptions && data.exceptions.length > 0) {
for(var i = 0; i < data.exceptions.length; i++) {
var exception = data.exceptions[i];
exception.parsedStack = null;
exception.hasFullStack = false;
}
}
}
return true;
});
```
[Learn more about the telemetry API](https://azure.microsoft.com/documentation/articles/app-insights-api-custom-events-metrics/).
### Using multiple instrumentation keys
```javascript
import appInsights = require("applicationinsights");
// configure auto-collection with one instrumentation key
appInsights.setup("<instrumentation_key>").start();
// get a client for another instrumentation key
var otherClient = appInsights.getClient("<other_instrumentation_key>");
otherClient.trackEvent("custom event");
```
## Examples
### Tracking dependency
```javascript
import appInsights = require("applicationinsights");
var client = appInsights.getClient();
var startTime = Date.now();
// execute dependency call
var endTime = Date.now();
var elapsedTime = endTime - startTime;
var success = true;
client.trackDependency("dependency name", "command name", elapsedTime, success);
```
### Manual request tracking of all "GET" requests
```javascript
var http = require("http");
var appInsights = require("applicationinsights");
appInsights.setup("<instrumentation_key>")
.setAutoCollectRequests(false) // disable auto-collection of requests for this example
.start();
// assign common properties to all telemetry sent from the default client
appInsights.client.commonProperties = {
environment: process.env.SOME_ENV_VARIABLE
};
// track a system startup event
appInsights.client.trackEvent("server start");
// create server
var port = process.env.port || 1337
var server = http.createServer(function (req, res) {
// track all "GET" requests
if(req.method === "GET") {
appInsights.client.trackRequest(req, res);
}
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World\n");
}).listen(port);
// track startup time of the server as a custom metric
var start = +new Date;
server.on("listening", () => {
var end = +new Date;
var duration = end - start;
appInsights.client.trackMetric("StartupTime", duration);
});
```
## Branches
- [master](https://github.com/Microsoft/ApplicationInsights-node.js/tree/master) contains the *latest* published release located on [NPM](https://www.npmjs.com/package/applicationinsights).
- [develop](https://github.com/Microsoft/ApplicationInsights-node.js/tree/develop) contains the code for the *next* release. Please send all pull requests to this branch.
## Links
* Follow the latest Application Insights changes and announcements on [ApplicationInsights Announcements](https://github.com/Microsoft/ApplicationInsights-Announcements)
* [Application Insights Home](https://github.com/Microsoft/ApplicationInsights-Home). The main repository for documentation of overall SDK offerings for all platforms.
* [SDK Release Schedule](https://github.com/Microsoft/ApplicationInsights-Home/wiki/SDK-Release-Schedule)
## Contributing
**Development environment**
* Install dev dependencies
```
npm install
```
* (optional) Set an environment variable to your instrumentation key
```
set APPINSIGHTS_INSTRUMENTATIONKEY=<insert_your_instrumentation_key_here>
```
* Run tests
```
npm test
```
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@@ -0,0 +1,47 @@
import "Domain.bond"
namespace AI
[Description("Instances of AvailabilityData represent the result of executing an availability test.")]
struct AvailabilityData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[MaxStringLength("64")]
[Description("Identifier of a test run. Use it to correlate steps of test run and telemetry generated by the service.")]
[ActAsRequired("Renaming testRunId to id.")]
21: required string id;
[MaxStringLength("1024")]
[Description("Name of the test that these availability results represents.")]
[ActAsRequired("Renaming testName to name.")]
41: required string name;
[Description("Duration in TimeSpan 'G' (general long) format: d:hh:mm:ss.fffffff")]
[CSType("TimeSpan")]
50: required string duration;
[ActAsRequired("Renaming result to success.")]
[Description("Success flag.")]
61: required bool success;
[MaxStringLength("1024")]
[Description("Name of the location where the test was run from.")]
70: string runLocation;
[MaxStringLength("8192")]
[Description("Diagnostic message for the result.")]
80: string message;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
[Description("Collection of custom measurements.")]
[MaxKeyLength("150")]
200: map<string, double> measurements;
}

View File

@@ -0,0 +1,11 @@
namespace AI
[Description("Data struct to contain only C section with custom fields.")]
struct Base
{
[Name("ItemTypeName")]
[Description("Name of item (B section) if any. If telemetry data is derived straight from this, this should be null.")]
10: string baseType;
}

View File

@@ -0,0 +1,105 @@
namespace AI
[ContextContract("Emit")]
[PseudoType("JSMap")]
struct ContextTagKeys
{
[Description("Application version. Information in the application context fields is always about the application that is sending the telemetry.")]
[MaxStringLength("1024")]
10: string ApplicationVersion = "ai.application.ver";
[Description("Unique client device id. Computer name in most cases.")]
[MaxStringLength("1024")]
100: string DeviceId = "ai.device.id";
[Description("Device locale using <language>-<REGION> pattern, following RFC 5646. Example 'en-US'.")]
[MaxStringLength("64")]
115: string DeviceLocale = "ai.device.locale";
[Description("Model of the device the end user of the application is using. Used for client scenarios. If this field is empty then it is derived from the user agent.")]
[MaxStringLength("256")]
120: string DeviceModel = "ai.device.model";
[Description("Client device OEM name taken from the browser.")]
[MaxStringLength("256")]
130: string DeviceOEMName = "ai.device.oemName";
[Description("Operating system name and version of the device the end user of the application is using. If this field is empty then it is derived from the user agent. Example 'Windows 10 Pro 10.0.10586.0'")]
[MaxStringLength("256")]
140: string DeviceOSVersion = "ai.device.osVersion";
[Description("The type of the device the end user of the application is using. Used primarily to distinguish JavaScript telemetry from server side telemetry. Examples: 'PC', 'Phone', 'Browser'. 'PC' is the default value.")]
[MaxStringLength("64")]
160: string DeviceType = "ai.device.type";
[Description("The IP address of the client device. IPv4 and IPv6 is supported. Information in the location context fields is always about the end user. When telemetry is sent from a service, the location context is about the user that initiated the operation in the service.")]
[MaxStringLength("45")]
200: string LocationIp = "ai.location.ip";
[Description("A unique identifier for the operation instance. The operation.id is created by either a request or a page view. All other telemetry sets this to the value for the containing request or page view. Operation.id is used for finding all the telemetry items for a specific operation instance.")]
[MaxStringLength("128")]
300: string OperationId = "ai.operation.id";
[Description("The name (group) of the operation. The operation.name is created by either a request or a page view. All other telemetry items set this to the value for the containing request or page view. Operation.name is used for finding all the telemetry items for a group of operations (i.e. 'GET Home/Index').")]
[MaxStringLength("1024")]
305: string OperationName = "ai.operation.name";
[Description("The unique identifier of the telemetry item's immediate parent.")]
[MaxStringLength("128")]
310: string OperationParentId = "ai.operation.parentId";
[Description("Name of synthetic source. Some telemetry from the application may represent a synthetic traffic. It may be web crawler indexing the web site, site availability tests or traces from diagnostic libraries like Application Insights SDK itself.")]
[MaxStringLength("1024")]
320: string OperationSyntheticSource = "ai.operation.syntheticSource";
[Description("The correlation vector is a light weight vector clock which can be used to identify and order related events across clients and services.")]
[MaxStringLength("64")]
330: string OperationCorrelationVector = "ai.operation.correlationVector";
[Description("Session ID - the instance of the user's interaction with the app. Information in the session context fields is always about the end user. When telemetry is sent from a service, the session context is about the user that initiated the operation in the service.")]
[MaxStringLength("64")]
400: string SessionId = "ai.session.id";
[Description("Boolean value indicating whether the session identified by ai.session.id is first for the user or not.")]
[MaxStringLength("5")]
[Question("Should it be marked as JSType-bool for breeze?")]
405: string SessionIsFirst = "ai.session.isFirst";
[Description("In multi-tenant applications this is the account ID or name which the user is acting with. Examples may be subscription ID for Azure portal or blog name blogging platform.")]
[MaxStringLength("1024")]
505: string UserAccountId = "ai.user.accountId";
[Description("The browser's user agent string as reported by the browser. This property will be used to extract informaiton regarding the customer's browser but will not be stored. Use custom properties to store the original user agent.")]
[MaxStringLength("2048")]
510: string UserAgent = "ai.user.userAgent";
[Description("Anonymous user id. Represents the end user of the application. When telemetry is sent from a service, the user context is about the user that initiated the operation in the service.")]
[MaxStringLength("128")]
515: string UserId = "ai.user.id";
[Description("Authenticated user id. The opposite of ai.user.id, this represents the user with a friendly name. Since it's PII information it is not collected by default by most SDKs.")]
[MaxStringLength("1024")]
525: string UserAuthUserId = "ai.user.authUserId";
[Description("Name of the role the application is a part of. Maps directly to the role name in azure.")]
[MaxStringLength("256")]
705: string CloudRole = "ai.cloud.role";
[Description("Name of the instance where the application is running. Computer name for on-premisis, instance name for Azure.")]
[MaxStringLength("256")]
715: string CloudRoleInstance = "ai.cloud.roleInstance";
[Description("SDK version. See https://github.com/Microsoft/ApplicationInsights-Home/blob/master/SDK-AUTHORING.md#sdk-version-specification for information.")]
[MaxStringLength("64")]
1000: string InternalSdkVersion = "ai.internal.sdkVersion";
[Description("Agent version. Used to indicate the version of StatusMonitor installed on the computer if it is used for data collection.")]
[MaxStringLength("64")]
1001: string InternalAgentVersion = "ai.internal.agentVersion";
[Description("This is the node name used for billing purposes. Use it to override the standard detection of nodes.")]
[MaxStringLength("256")]
1002: string InternalNodeName = "ai.internal.nodeName";
}

View File

@@ -0,0 +1,13 @@
import "Base.bond"
namespace AI
[Description("Data struct to contain both B and C sections.")]
struct Data<TDomain>
: Base
{
[Name("Item")]
[Description("Container for data item (B section).")]
20: required TDomain baseData;
}

View File

@@ -0,0 +1,30 @@
import "DataPointType.bond"
namespace AI
[Description("Metric data single measurement.")]
struct DataPoint
{
[Description("Name of the metric.")]
[MaxStringLength("1024")]
10: required string name;
[Description("Metric type.")]
20: AI.DataPointType kind = Measurement;
[Description("Metric calculated value.")]
30: required double value;
[Description("Metric weight of the aggregated metric. Should not be set for a measurement.")]
40: nullable<int32> count;
[Description("Minimum value of the aggregated metric. Should not be set for a measurement.")]
50: nullable<double> min;
[Description("Maximum value of the aggregated metric. Should not be set for a measurement.")]
60: nullable<double> max;
[Description("Standard deviation of the aggregated metric. Should not be set for a measurement.")]
70: nullable<double> stdDev;
}

View File

@@ -0,0 +1,8 @@
namespace AI
[Description("Type of the metric data measurement.")]
enum DataPointType
{
Measurement,
Aggregation,
}

View File

@@ -0,0 +1,7 @@
namespace AI
[Description("The abstract common base of all domains.")]
struct Domain
{
}

View File

@@ -0,0 +1,49 @@
import "Base.bond"
namespace AI
[Description("System variables for a telemetry item.")]
struct Envelope
{
[Description("Envelope version. For internal use only. By assigning this the default, it will not be serialized within the payload unless changed to a value other than #1.")]
[Name("SchemaVersion")]
10: int32 ver = 1;
[Description("Type name of telemetry data item.")]
[Name("DataTypeName")]
[MaxStringLength("1024")]
20: required string name;
[Description("Event date time when telemetry item was created. This is the wall clock time on the client when the event was generated. There is no guarantee that the client's time is accurate. This field must be formatted in UTC ISO 8601 format, with a trailing 'Z' character, as described publicly on https://en.wikipedia.org/wiki/ISO_8601#UTC. Note: the number of decimal seconds digits provided are variable (and unspecified). Consumers should handle this, i.e. managed code consumers should not use format 'O' for parsing as it specifies a fixed length. Example: 2009-06-15T13:45:30.0000000Z.")]
[Name("DateTime")]
[CSType("DateTimeOffset")]
[JSType("Date")]
[HockeyAppMinDateOffsetFromNow("2592000000")]
[MinDateOffsetFromNow("172800000")]
[MaxDateOffsetFromNow("7200000")]
30: required string time;
[Name("SamplingRate")]
[Description("Sampling rate used in application. This telemetry item represents 1 / sampleRate actual telemetry items.")]
40: double sampleRate = 100.0;
[Description("Sequence field used to track absolute order of uploaded events.")]
[Name("SequenceNumber")]
[MaxStringLength("64")]
50: string seq;
[Description("The application's instrumentation key.")]
[Name("InstrumentationKey")]
[MaxStringLength("40")]
60: string iKey;
[Name("Tags")]
[TypeAlias("ContextTagKeys")]
[Description("Key/value collection of context properties. See ContextTagKeys for information on available properties.")]
500: map<string, string> tags;
[Name("TelemetryData")]
[Description("Telemetry data item.")]
999: Base data;
}

View File

@@ -0,0 +1,26 @@
import "Domain.bond"
namespace AI
[Description("Instances of Event represent structured event records that can be grouped and searched by their properties. Event data item also creates a metric of event count by name.")]
struct EventData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[MaxStringLength("512")]
[Description("Event name. Keep it low cardinality to allow proper grouping and useful metrics.")]
[Question("Why Custom Event name is shorter than Request name or dependency name?")]
20: required string name;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
[Description("Collection of custom measurements.")]
[MaxKeyLength("150")]
200: map<string, double> measurements;
}

View File

@@ -0,0 +1,33 @@
import "Domain.bond"
import "ExceptionDetails.bond"
import "SeverityLevel.bond"
namespace AI
[Description("An instance of Exception represents a handled or unhandled exception that occurred during execution of the monitored application.")]
struct ExceptionData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[Description("Exception chain - list of inner exceptions.")]
50: required vector<ExceptionDetails> exceptions;
[Description("Severity level. Mostly used to indicate exception severity level when it is reported by logging library.")]
60: nullable<AI.SeverityLevel> severityLevel;
[Description("Identifier of where the exception was thrown in code. Used for exceptions grouping. Typically a combination of exception type and a function from the call stack.")]
[MaxStringLength("1024")]
80: string problemId;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
[Description("Collection of custom measurements.")]
[MaxKeyLength("150")]
200: map<string, double> measurements;
}

View File

@@ -0,0 +1,32 @@
import "StackFrame.bond"
namespace AI
[Description("Exception details of the exception in a chain.")]
struct ExceptionDetails
{
[Description("In case exception is nested (outer exception contains inner one), the id and outerId properties are used to represent the nesting.")]
10: int32 id;
[Description("The value of outerId is a reference to an element in ExceptionDetails that represents the outer exception")]
20: int32 outerId;
[Description("Exception type name.")]
[MaxStringLength("1024")]
30: required string typeName;
[Description("Exception message.")]
[MaxStringLength("32768")]
40: required string message;
[Description("Indicates if full exception stack is provided in the exception. The stack may be trimmed, such as in the case of a StackOverflow exception.")]
50: bool hasFullStack = true;
[Description("Text describing the stack. Either stack or parsedStack should have a value.")]
[MaxStringLength("32768")]
60: string stack;
[Description("List of stack frames. Either stack or parsedStack should have a value.")]
70: vector<StackFrame> parsedStack;
}

View File

@@ -0,0 +1,25 @@
import "Domain.bond"
import "SeverityLevel.bond"
namespace AI
[Description("Instances of Message represent printf-like trace statements that are text-searched. Log4Net, NLog and other text-based log file entries are translated into intances of this type. The message does not have measurements.")]
struct MessageData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[MaxStringLength("32768")]
[Description("Trace message")]
20: required string message;
[Description("Trace severity level.")]
30: nullable<AI.SeverityLevel> severityLevel;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
}

View File

@@ -0,0 +1,21 @@
import "Domain.bond"
import "DataPoint.bond"
namespace AI
[Description("An instance of the Metric item is a list of measurements (single data points) and/or aggregations.")]
struct MetricData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[Description("List of metrics.")]
20: required vector<DataPoint> metrics;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
}

View File

@@ -0,0 +1,18 @@
import "EventData.bond"
namespace AI
[Description("An instance of PageView represents a generic action on a page like a button click. It is also the base type for PageView.")]
[Alias("PageviewData;PageEventData")]
struct PageViewData
: EventData
{
[MaxStringLength("2048")]
[Description("Request URL with all query string parameters")]
10: string url;
[CSType("TimeSpan")]
[Description("Request duration in TimeSpan 'G' (general long) format: d:hh:mm:ss.fffffff. For a page view (PageViewData), this is the duration. For a page view with performance information (PageViewPerfData), this is the page load time.")]
20: string duration;
}

View File

@@ -0,0 +1,53 @@
import "Domain.bond"
namespace AI
[Description("An instance of Remote Dependency represents an interaction of the monitored component with a remote component/service like SQL or an HTTP endpoint.")]
struct RemoteDependencyData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[MaxStringLength("1024")]
[Description("Name of the command initiated with this dependency call. Low cardinality value. Examples are stored procedure name and URL path template.")]
20: required string name;
[MaxStringLength("128")]
[Description("Identifier of a dependency call instance. Used for correlation with the request telemetry item corresponding to this dependency call.")]
30: string id;
[MaxStringLength("1024")]
[Description("Result code of a dependency call. Examples are SQL error code and HTTP status code.")]
40: string resultCode;
[CSType("TimeSpan")]
[Description("Request duration in TimeSpan 'G' (general long) format: d:hh:mm:ss.fffffff.")]
[ActAsRequired("Renaming value to duration.")]
61: required string duration;
[Description("Indication of successfull or unsuccessfull call.")]
120: nullable<bool> success = true;
[MaxStringLength("8192")]
[Description("Command initiated by this dependency call. Examples are SQL statement and HTTP URL's with all query parameters.")]
151: string data;
[MaxStringLength("1024")]
[Description("Dependency type name. Very low cardinality value for logical grouping of dependencies and interpretation of other fields like commandName and resultCode. Examples are SQL, Azure table, and HTTP.")]
162: string type;
[MaxStringLength("1024")]
[Description("Target site of a dependency call. Examples are server name, host address.")]
161: string target;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
200: map<string, string> properties;
[Description("Collection of custom measurements.")]
[MaxKeyLength("150")]
300: map<string, double> measurements;
}

View File

@@ -0,0 +1,48 @@
import "Domain.bond"
namespace AI
[Description("An instance of Request represents completion of an external request to the application to do work and contains a summary of that request execution and the results.")]
struct RequestData
: Domain
{
[Description("Schema version")]
10: required int32 ver = 2;
[MaxStringLength("128")]
[Description("Identifier of a request call instance. Used for correlation between request and other telemetry items.")]
20: required string id;
[CSType("TimeSpan")]
[Description("Request duration in TimeSpan 'G' (general long) format: d:hh:mm:ss.fffffff.")]
50: required string duration;
[MaxStringLength("1024")]
[Description("Result of a request execution. HTTP status code for HTTP requests.")]
60: required string responseCode;
[Description("Indication of successfull or unsuccessfull call.")]
70: required bool success;
[MaxStringLength("1024")]
[Description("Source of the request. Examples are the instrumentation key of the caller or the ip address of the caller.")]
29: string source;
[MaxStringLength("1024")]
[Description("Name of the request. Represents code path taken to process request. Low cardinality value to allow better grouping of requests. For HTTP requests it represents the HTTP method and URL path template like 'GET /values/{id}'.")]
30: string name;
[MaxStringLength("2048")]
[Description("Request URL with all query string parameters.")]
90: string url;
[Description("Collection of custom properties.")]
[MaxKeyLength("150")]
[MaxValueLength("8192")]
100: map<string, string> properties;
[Description("Collection of custom measurements.")]
[MaxKeyLength("150")]
200: map<string, double> measurements;
}

View File

@@ -0,0 +1,11 @@
namespace AI
[Description("Defines the level of severity for the event.")]
enum SeverityLevel
{
Verbose,
Information,
Warning,
Error,
Critical,
}

View File

@@ -0,0 +1,25 @@
namespace AI
[Description("Stack frame information.")]
struct StackFrame
{
[Description("Level in the call stack. For the long stacks SDK may not report every function in a call stack.")]
10: required int32 level;
[Description("Method name.")]
[MaxStringLength("1024")]
20: required string method;
[Description("Name of the assembly (dll, jar, etc.) containing this function.")]
[MaxStringLength("1024")]
30: string assembly;
[Description("File name or URL of the method implementation.")]
[MaxStringLength("1024")]
50: string fileName;
[Description("Line number of the code implementation.")]
60: int32 line;
}

View File

@@ -0,0 +1,60 @@
$generatorPath = "C:\src\mseng\AppInsights-Common"
$publicSchemaLocation = "https://raw.githubusercontent.com/Microsoft/ApplicationInsights-Home/master/EndpointSpecs/Schemas/Bond"
$currentDir = $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
#fix path
$generatorPath = "$generatorPath\..\bin\Debug\BondSchemaGenerator\BondSchemaGenerator"
#####################################################################
## PUBLIC SCHEMA
#####################################################################
mkdir -Force $currentDir\PublicSchema
del "$currentDir\PublicSchema\*.bond"
$argumentList = ""
# Download public schema from the github
@(
"AvailabilityData.bond",
"Base.bond",
"ContextTagKeys.bond",
"Data.bond",
"DataPoint.bond",
"DataPointType.bond",
"Domain.bond",
"Envelope.bond",
"EventData.bond",
"ExceptionData.bond",
"ExceptionDetails.bond",
"MessageData.bond",
"MetricData.bond",
"PageViewData.bond",
"RemoteDependencyData.bond",
"RequestData.bond",
"SeverityLevel.bond",
"StackFrame.bond"
) | ForEach-Object {
$fileName = $_
$argumentList = "$argumentList -i $currentDir\PublicSchema\$fileName"
& Invoke-WebRequest -o "$currentDir\PublicSchema\$fileName" "$publicSchemaLocation/$fileName"
}
$argumentList = "-v $argumentList -o $currentDir\PublicSchema\ -e TypeScriptLanguage -t TypeScriptLayout -n AI --flatten true"
# Generate public schema using bond generator
$p1 = Start-Process "$generatorPath\BondSchemaGenerator.exe" -ArgumentList $argumentList -wait -NoNewWindow -PassThru
$p1.HasExited
$p1.ExitCode
del "$currentDir\..\Declarations\Contracts\Generated\*.ts"
dir "$currentDir\PublicSchema\Contracts\Generated\*.ts" | ForEach-Object {
$fileName = $_
copy $fileName "$currentDir\..\Declarations\Contracts\Generated\"
}
del "$currentDir\PublicSchema\Contracts\Generated\*.ts"

View File

@@ -0,0 +1,226 @@
"use strict";
var CorrelationContextManager = require("./AutoCollection/CorrelationContextManager"); // Keep this first
var AutoCollectConsole = require("./AutoCollection/Console");
var AutoCollectExceptions = require("./AutoCollection/Exceptions");
var AutoCollectPerformance = require("./AutoCollection/Performance");
var AutoCollectClientRequests = require("./AutoCollection/ClientRequests");
var AutoCollectServerRequests = require("./AutoCollection/ServerRequests");
var Client = require("./Library/Client");
var Logging = require("./Library/Logging");
/**
* The singleton meta class for the default client of the client. This class is used to setup/start and configure
* the auto-collection behavior of the application insights module.
*/
var ApplicationInsights = (function () {
function ApplicationInsights() {
}
/**
* Initializes a client with the given instrumentation key, if this is not specified, the value will be
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
* @returns {ApplicationInsights/Client} a new client
*/
ApplicationInsights.getClient = function (instrumentationKey) {
return new Client(instrumentationKey);
};
/**
* Initializes the default client of the client and sets the default configuration
* @param instrumentationKey the instrumentation key to use. Optional, if this is not specified, the value will be
* read from the environment variable APPINSIGHTS_INSTRUMENTATIONKEY
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setup = function (instrumentationKey) {
if (!ApplicationInsights.client) {
ApplicationInsights.client = ApplicationInsights.getClient(instrumentationKey);
ApplicationInsights._console = new AutoCollectConsole(ApplicationInsights.client);
ApplicationInsights._exceptions = new AutoCollectExceptions(ApplicationInsights.client);
ApplicationInsights._performance = new AutoCollectPerformance(ApplicationInsights.client);
ApplicationInsights._serverRequests = new AutoCollectServerRequests(ApplicationInsights.client);
ApplicationInsights._clientRequests = new AutoCollectClientRequests(ApplicationInsights.client);
}
else {
Logging.info("The default client is already setup");
}
if (ApplicationInsights.client && ApplicationInsights.client.channel) {
ApplicationInsights.client.channel.setOfflineMode(ApplicationInsights._isOfflineMode);
}
return ApplicationInsights;
};
/**
* Starts automatic collection of telemetry. Prior to calling start no telemetry will be collected
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.start = function () {
if (!!this.client) {
ApplicationInsights._isStarted = true;
ApplicationInsights._console.enable(ApplicationInsights._isConsole);
ApplicationInsights._exceptions.enable(ApplicationInsights._isExceptions);
ApplicationInsights._performance.enable(ApplicationInsights._isPerformance);
ApplicationInsights._serverRequests.useAutoCorrelation(ApplicationInsights._isCorrelating);
ApplicationInsights._serverRequests.enable(ApplicationInsights._isRequests);
ApplicationInsights._clientRequests.enable(ApplicationInsights._isDependencies);
}
else {
Logging.warn("Start cannot be called before setup");
}
return ApplicationInsights;
};
/**
* Returns an object that is shared across all code handling a given request. This can be used similarly to thread-local storage in other languages.
* Properties set on this object will be available to telemetry processors.
*
* Do not store sensitive information here.
* Custom properties set on this object can be exposed in a future SDK release via outgoing HTTP headers.
* This is to allow for correlating data cross-component.
*
* This method will return null if automatic dependency correlation is disabled.
* @returns A plain object for request storage or null if automatic dependency correlation is disabled.
*/
ApplicationInsights.getCorrelationContext = function () {
if (this._isCorrelating) {
return CorrelationContextManager.CorrelationContextManager.getCurrentContext();
}
return null;
};
/**
* Returns a function that will get the same correlation context within its function body as the code executing this function.
* Use this method if automatic dependency correlation is not propagating correctly to an asynchronous callback.
*/
ApplicationInsights.wrapWithCorrelationContext = function (fn) {
return CorrelationContextManager.CorrelationContextManager.wrapCallback(fn);
};
/**
* Sets the state of console tracking (enabled by default)
* @param value if true console activity will be sent to Application Insights
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoCollectConsole = function (value) {
ApplicationInsights._isConsole = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._console.enable(value);
}
return ApplicationInsights;
};
/**
* Sets the state of exception tracking (enabled by default)
* @param value if true uncaught exceptions will be sent to Application Insights
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoCollectExceptions = function (value) {
ApplicationInsights._isExceptions = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._exceptions.enable(value);
}
return ApplicationInsights;
};
/**
* Sets the state of performance tracking (enabled by default)
* @param value if true performance counters will be collected every second and sent to Application Insights
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoCollectPerformance = function (value) {
ApplicationInsights._isPerformance = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._performance.enable(value);
}
return ApplicationInsights;
};
/**
* Sets the state of request tracking (enabled by default)
* @param value if true requests will be sent to Application Insights
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoCollectRequests = function (value) {
ApplicationInsights._isRequests = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._serverRequests.enable(value);
}
return ApplicationInsights;
};
/**
* Sets the state of dependency tracking (enabled by default)
* @param value if true dependencies will be sent to Application Insights
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoCollectDependencies = function (value) {
ApplicationInsights._isDependencies = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._clientRequests.enable(value);
}
return ApplicationInsights;
};
/**
* Sets the state of automatic dependency correlation (enabled by default)
* @param value if true dependencies will be correlated with requests
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setAutoDependencyCorrelation = function (value) {
ApplicationInsights._isCorrelating = value;
if (ApplicationInsights._isStarted) {
ApplicationInsights._serverRequests.useAutoCorrelation(value);
}
return ApplicationInsights;
};
/**
* Enable or disable offline mode to cache events when client is offline (disabled by default)
* @param value if true events that occured while client is offline will be cached on disk
* @param resendInterval. The wait interval for resending cached events.
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.setOfflineMode = function (value, resendInterval) {
ApplicationInsights._isOfflineMode = value;
if (ApplicationInsights.client && ApplicationInsights.client.channel) {
ApplicationInsights.client.channel.setOfflineMode(value, resendInterval);
}
return ApplicationInsights;
};
/**
* Enables verbose debug logging
* @returns {ApplicationInsights} this class
*/
ApplicationInsights.enableVerboseLogging = function (enableWarningLogging) {
if (enableWarningLogging === void 0) { enableWarningLogging = true; }
Logging.enableDebug = true;
Logging.disableWarnings = !enableWarningLogging;
return ApplicationInsights;
};
/**
* Disables verbose debug and warning logging
*/
ApplicationInsights.disableConsoleLogging = function () {
Logging.enableDebug = false;
Logging.disableWarnings = true;
return ApplicationInsights;
};
/**
* Disposes the default client and all the auto collectors so they can be reinitialized with different configuration
*/
ApplicationInsights.dispose = function () {
ApplicationInsights.client = null;
ApplicationInsights._isStarted = false;
if (ApplicationInsights._console) {
ApplicationInsights._console.dispose();
}
if (ApplicationInsights._exceptions) {
ApplicationInsights._exceptions.dispose();
}
if (ApplicationInsights._performance) {
ApplicationInsights._performance.dispose();
}
if (ApplicationInsights._serverRequests) {
ApplicationInsights._serverRequests.dispose();
}
if (ApplicationInsights._clientRequests) {
ApplicationInsights._clientRequests.dispose();
}
};
ApplicationInsights._isConsole = true;
ApplicationInsights._isExceptions = true;
ApplicationInsights._isPerformance = true;
ApplicationInsights._isRequests = true;
ApplicationInsights._isDependencies = true;
ApplicationInsights._isOfflineMode = false;
ApplicationInsights._isCorrelating = false;
ApplicationInsights._isStarted = false;
return ApplicationInsights;
}());
module.exports = ApplicationInsights;

View File

@@ -0,0 +1,57 @@
{
"name": "applicationinsights",
"license": "MIT",
"bugs": "https://github.com/Microsoft/ApplicationInsights-node.js/issues",
"version": "0.19.0",
"description": "Microsoft Application Insights module for Node.JS",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/ApplicationInsights-node.js"
},
"main": "applicationinsights",
"keywords": [
"exception monitoring",
"request monitoring",
"performance monitoring",
"application insights",
"microsoft",
"azure"
],
"contributors": [
{
"name": "Application Insights Developer Support",
"email": "aidevsupport@microsoft.com"
},
{
"name": "kszostak",
"email": "kszostak@microsoft.com"
},
{
"name": "southwood",
"url": "https://github.com/southwood"
},
{
"name": "bogdanbe",
"email": "bogdanbe@microsoft.com"
},
{
"name": "lukim",
"email": "lukim@microsoft.com"
}
],
"scripts": {
"prepublish": "tsc --module commonjs --declaration applicationinsights.ts",
"pretest": "find Tests -type f -name \"*.ts\" | xargs tsc --module commonjs",
"test": "./node_modules/mocha/bin/mocha ./Tests --recursive"
},
"devDependencies": {
"mocha": "3.1.2",
"node-mocks-http": "1.2.3",
"sinon": "1.17.6",
"typescript": "2.0.10",
"typings": "2.0.0"
},
"dependencies": {
"zone.js": "0.7.6"
}
}

View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"sourceMap": true
}
}

View File

@@ -0,0 +1,7 @@
{
"globalDependencies": {
"mocha": "registry:dt/mocha#2.2.5+20161028141524",
"node": "registry:dt/node#0.10.1+20161019125345",
"sinon": "registry:dt/sinon#1.16.0+20160924120326"
}
}

View File

@@ -0,0 +1,8 @@
{
"resolution": "main",
"tree": {
"src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7155ef2d2dced331b5e389363a4180957e76f95e/mocha/mocha.d.ts",
"raw": "registry:dt/mocha#2.2.5+20161028141524",
"typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/7155ef2d2dced331b5e389363a4180957e76f95e/mocha/mocha.d.ts"
}
}

View File

@@ -0,0 +1,8 @@
{
"resolution": "main",
"tree": {
"src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/988a48ab2cfff3243868d70d836332a118d9f060/node/node-0.10.d.ts",
"raw": "registry:dt/node#0.10.1+20161019125345",
"typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/988a48ab2cfff3243868d70d836332a118d9f060/node/node-0.10.d.ts"
}
}

View File

@@ -0,0 +1,8 @@
{
"resolution": "main",
"tree": {
"src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/4bb4c29cad26ee049899b35da7d9502c3c73fde6/sinon/sinon.d.ts",
"raw": "registry:dt/sinon#1.16.0+20160924120326",
"typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/4bb4c29cad26ee049899b35da7d9502c3c73fde6/sinon/sinon.d.ts"
}
}

View File

@@ -0,0 +1,184 @@
# Release history
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
<details>
<summary><strong>Guiding Principles</strong></summary>
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- Versions and sections should be linkable.
- The latest version comes first.
- The release date of each versions is displayed.
- Mention whether you follow Semantic Versioning.
</details>
<details>
<summary><strong>Types of changes</strong></summary>
Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_):
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
</details>
## [3.0.0] - 2018-04-08
v3.0 is a complete refactor, resulting in a faster, smaller codebase, with fewer deps, and a more accurate parser and compiler.
**Breaking Changes**
- The undocumented `.makeRe` method was removed
**Non-breaking changes**
- Caching was removed
## [2.3.2] - 2018-04-08
- start refactoring
- cover sets
- better range handling
## [2.3.1] - 2018-02-17
- Remove unnecessary escape in Regex. (#14)
## [2.3.0] - 2017-10-19
- minor code reorganization
- optimize regex
- expose `maxLength` option
## [2.2.1] - 2017-05-30
- don't condense when braces contain extglobs
## [2.2.0] - 2017-05-28
- ensure word boundaries are preserved
- fixes edge case where extglob characters precede a brace pattern
## [2.1.1] - 2017-04-27
- use snapdragon-node
- handle edge case
- optimizations, lint
## [2.0.4] - 2017-04-11
- pass opts to compiler
- minor optimization in create method
- re-write parser handlers to remove negation regex
## [2.0.3] - 2016-12-10
- use split-string
- clear queue at the end
- adds sequences example
- add unit tests
## [2.0.2] - 2016-10-21
- fix comma handling in nested extglobs
## [2.0.1] - 2016-10-20
- add comments
- more tests, ensure quotes are stripped
## [2.0.0] - 2016-10-19
- don't expand braces inside character classes
- add quantifier pattern
## [1.8.5] - 2016-05-21
- Refactor (#10)
## [1.8.4] - 2016-04-20
- fixes https://github.com/jonschlinkert/micromatch/issues/66
## [1.8.0] - 2015-03-18
- adds exponent examples, tests
- fixes the first example in https://github.com/jonschlinkert/micromatch/issues/38
## [1.6.0] - 2015-01-30
- optimizations, `bash` mode:
- improve path escaping
## [1.5.0] - 2015-01-28
- Merge pull request #5 from eush77/lib-files
## [1.4.0] - 2015-01-24
- add extglob tests
- externalize exponent function
- better whitespace handling
## [1.3.0] - 2015-01-24
- make regex patterns explicity
## [1.1.0] - 2015-01-11
- don't create a match group with `makeRe`
## [1.0.0] - 2014-12-23
- Merge commit '97b05f5544f8348736a8efaecf5c32bbe3e2ad6e'
- support empty brace syntax
- better bash coverage
- better support for regex strings
## [0.1.4] - 2014-11-14
- improve recognition of bad args, recognize mismatched argument types
- support escaping
- remove pathname-expansion
- support whitespace in patterns
## [0.1.0]
- first commit
[2.3.2]: https://github.com/micromatch/braces/compare/2.3.1...2.3.2
[2.3.1]: https://github.com/micromatch/braces/compare/2.3.0...2.3.1
[2.3.0]: https://github.com/micromatch/braces/compare/2.2.1...2.3.0
[2.2.1]: https://github.com/micromatch/braces/compare/2.2.0...2.2.1
[2.2.0]: https://github.com/micromatch/braces/compare/2.1.1...2.2.0
[2.1.1]: https://github.com/micromatch/braces/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/micromatch/braces/compare/2.0.4...2.1.0
[2.0.4]: https://github.com/micromatch/braces/compare/2.0.3...2.0.4
[2.0.3]: https://github.com/micromatch/braces/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/micromatch/braces/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/micromatch/braces/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/micromatch/braces/compare/1.8.5...2.0.0
[1.8.5]: https://github.com/micromatch/braces/compare/1.8.4...1.8.5
[1.8.4]: https://github.com/micromatch/braces/compare/1.8.0...1.8.4
[1.8.0]: https://github.com/micromatch/braces/compare/1.6.0...1.8.0
[1.6.0]: https://github.com/micromatch/braces/compare/1.5.0...1.6.0
[1.5.0]: https://github.com/micromatch/braces/compare/1.4.0...1.5.0
[1.4.0]: https://github.com/micromatch/braces/compare/1.3.0...1.4.0
[1.3.0]: https://github.com/micromatch/braces/compare/1.2.0...1.3.0
[1.2.0]: https://github.com/micromatch/braces/compare/1.1.0...1.2.0
[1.1.0]: https://github.com/micromatch/braces/compare/1.0.0...1.1.0
[1.0.0]: https://github.com/micromatch/braces/compare/0.1.4...1.0.0
[0.1.4]: https://github.com/micromatch/braces/compare/0.1.0...0.1.4
[Unreleased]: https://github.com/micromatch/braces/compare/0.1.0...HEAD
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2018, Jon Schlinkert.
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.

View File

@@ -0,0 +1,593 @@
# braces [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces)
> Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save braces
```
## v3.0.0 Released!!
See the [changelog](CHANGELOG.md) for details.
## Why use braces?
Brace patterns make globs more powerful by adding the ability to match specific ranges and sequences of characters.
* **Accurate** - complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests)
* **[fast and performant](#benchmarks)** - Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity.
* **Organized code base** - The parser and compiler are easy to maintain and update when edge cases crop up.
* **Well-tested** - Thousands of test assertions, and passes all of the Bash, minimatch, and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests (as of the date this was written).
* **Safer** - You shouldn't have to worry about users defining aggressive or malicious brace patterns that can break your application. Braces takes measures to prevent malicious regex that can be used for DDoS attacks (see [catastrophic backtracking](https://www.regular-expressions.info/catastrophic.html)).
* [Supports lists](#lists) - (aka "sets") `a/{b,c}/d` => `['a/b/d', 'a/c/d']`
* [Supports sequences](#sequences) - (aka "ranges") `{01..03}` => `['01', '02', '03']`
* [Supports steps](#steps) - (aka "increments") `{2..10..2}` => `['2', '4', '6', '8', '10']`
* [Supports escaping](#escaping) - To prevent evaluation of special characters.
## Usage
The main export is a function that takes one or more brace `patterns` and `options`.
```js
const braces = require('braces');
// braces(patterns[, options]);
console.log(braces(['{01..05}', '{a..e}']));
//=> ['(0[1-5])', '([a-e])']
console.log(braces(['{01..05}', '{a..e}'], { expand: true }));
//=> ['01', '02', '03', '04', '05', 'a', 'b', 'c', 'd', 'e']
```
### Brace Expansion vs. Compilation
By default, brace patterns are compiled into strings that are optimized for creating regular expressions and matching.
**Compiled**
```js
console.log(braces('a/{x,y,z}/b'));
//=> ['a/(x|y|z)/b']
console.log(braces(['a/{01..20}/b', 'a/{1..5}/b']));
//=> [ 'a/(0[1-9]|1[0-9]|20)/b', 'a/([1-5])/b' ]
```
**Expanded**
Enable brace expansion by setting the `expand` option to true, or by using [braces.expand()](#expand) (returns an array similar to what you'd expect from Bash, or `echo {1..5}`, or [minimatch](https://github.com/isaacs/minimatch)):
```js
console.log(braces('a/{x,y,z}/b', { expand: true }));
//=> ['a/x/b', 'a/y/b', 'a/z/b']
console.log(braces.expand('{01..10}'));
//=> ['01','02','03','04','05','06','07','08','09','10']
```
### Lists
Expand lists (like Bash "sets"):
```js
console.log(braces('a/{foo,bar,baz}/*.js'));
//=> ['a/(foo|bar|baz)/*.js']
console.log(braces.expand('a/{foo,bar,baz}/*.js'));
//=> ['a/foo/*.js', 'a/bar/*.js', 'a/baz/*.js']
```
### Sequences
Expand ranges of characters (like Bash "sequences"):
```js
console.log(braces.expand('{1..3}')); // ['1', '2', '3']
console.log(braces.expand('a/{1..3}/b')); // ['a/1/b', 'a/2/b', 'a/3/b']
console.log(braces('{a..c}', { expand: true })); // ['a', 'b', 'c']
console.log(braces('foo/{a..c}', { expand: true })); // ['foo/a', 'foo/b', 'foo/c']
// supports zero-padded ranges
console.log(braces('a/{01..03}/b')); //=> ['a/(0[1-3])/b']
console.log(braces('a/{001..300}/b')); //=> ['a/(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)/b']
```
See [fill-range](https://github.com/jonschlinkert/fill-range) for all available range-expansion options.
### Steppped ranges
Steps, or increments, may be used with ranges:
```js
console.log(braces.expand('{2..10..2}'));
//=> ['2', '4', '6', '8', '10']
console.log(braces('{2..10..2}'));
//=> ['(2|4|6|8|10)']
```
When the [.optimize](#optimize) method is used, or [options.optimize](#optionsoptimize) is set to true, sequences are passed to [to-regex-range](https://github.com/jonschlinkert/to-regex-range) for expansion.
### Nesting
Brace patterns may be nested. The results of each expanded string are not sorted, and left to right order is preserved.
**"Expanded" braces**
```js
console.log(braces.expand('a{b,c,/{x,y}}/e'));
//=> ['ab/e', 'ac/e', 'a/x/e', 'a/y/e']
console.log(braces.expand('a/{x,{1..5},y}/c'));
//=> ['a/x/c', 'a/1/c', 'a/2/c', 'a/3/c', 'a/4/c', 'a/5/c', 'a/y/c']
```
**"Optimized" braces**
```js
console.log(braces('a{b,c,/{x,y}}/e'));
//=> ['a(b|c|/(x|y))/e']
console.log(braces('a/{x,{1..5},y}/c'));
//=> ['a/(x|([1-5])|y)/c']
```
### Escaping
**Escaping braces**
A brace pattern will not be expanded or evaluted if _either the opening or closing brace is escaped_:
```js
console.log(braces.expand('a\\{d,c,b}e'));
//=> ['a{d,c,b}e']
console.log(braces.expand('a{d,c,b\\}e'));
//=> ['a{d,c,b}e']
```
**Escaping commas**
Commas inside braces may also be escaped:
```js
console.log(braces.expand('a{b\\,c}d'));
//=> ['a{b,c}d']
console.log(braces.expand('a{d\\,c,b}e'));
//=> ['ad,ce', 'abe']
```
**Single items**
Following bash conventions, a brace pattern is also not expanded when it contains a single character:
```js
console.log(braces.expand('a{b}c'));
//=> ['a{b}c']
```
## Options
### options.maxLength
**Type**: `Number`
**Default**: `65,536`
**Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
```js
console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error
```
### options.expand
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Generate an "expanded" brace pattern (alternatively you can use the `braces.expand()` method, which does the same thing).
```js
console.log(braces('a/{b,c}/d', { expand: true }));
//=> [ 'a/b/d', 'a/c/d' ]
```
### options.nodupes
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Remove duplicates from the returned array.
### options.rangeLimit
**Type**: `Number`
**Default**: `1000`
**Description**: To prevent malicious patterns from being passed by users, an error is thrown when `braces.expand()` is used or `options.expand` is true and the generated range will exceed the `rangeLimit`.
You can customize `options.rangeLimit` or set it to `Inifinity` to disable this altogether.
**Examples**
```js
// pattern exceeds the "rangeLimit", so it's optimized automatically
console.log(braces.expand('{1..1000}'));
//=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
// pattern does not exceed "rangeLimit", so it's NOT optimized
console.log(braces.expand('{1..100}'));
//=> ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100']
```
### options.transform
**Type**: `Function`
**Default**: `undefined`
**Description**: Customize range expansion.
**Example: Transforming non-numeric values**
```js
const alpha = braces.expand('x/{a..e}/y', {
transform(value, index) {
// When non-numeric values are passed, "value" is a character code.
return 'foo/' + String.fromCharCode(value) + '-' + index;
}
});
console.log(alpha);
//=> [ 'x/foo/a-0/y', 'x/foo/b-1/y', 'x/foo/c-2/y', 'x/foo/d-3/y', 'x/foo/e-4/y' ]
```
**Example: Transforming numeric values**
```js
const numeric = braces.expand('{1..5}', {
transform(value) {
// when numeric values are passed, "value" is a number
return 'foo/' + value * 2;
}
});
console.log(numeric);
//=> [ 'foo/2', 'foo/4', 'foo/6', 'foo/8', 'foo/10' ]
```
### options.quantifiers
**Type**: `Boolean`
**Default**: `undefined`
**Description**: In regular expressions, quanitifiers can be used to specify how many times a token can be repeated. For example, `a{1,3}` will match the letter `a` one to three times.
Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers) are defined in the given pattern, and not to try to expand them as lists.
**Examples**
```js
const braces = require('braces');
console.log(braces('a/b{1,3}/{x,y,z}'));
//=> [ 'a/b(1|3)/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true}));
//=> [ 'a/b{1,3}/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true}));
//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
```
### options.unescape
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Strip backslashes that were used for escaping from the result.
## What is "brace expansion"?
Brace expansion is a type of parameter expansion that was made popular by unix shells for generating lists of strings, as well as regex-like matching when used alongside wildcards (globs).
In addition to "expansion", braces are also used for matching. In other words:
* [brace expansion](#brace-expansion) is for generating new lists
* [brace matching](#brace-matching) is for filtering existing lists
<details>
<summary><strong>More about brace expansion</strong> (click to expand)</summary>
There are two main types of brace expansion:
1. **lists**: which are defined using comma-separated values inside curly braces: `{a,b,c}`
2. **sequences**: which are defined using a starting value and an ending value, separated by two dots: `a{1..3}b`. Optionally, a third argument may be passed to define a "step" or increment to use: `a{1..100..10}b`. These are also sometimes referred to as "ranges".
Here are some example brace patterns to illustrate how they work:
**Sets**
```
{a,b,c} => a b c
{a,b,c}{1,2} => a1 a2 b1 b2 c1 c2
```
**Sequences**
```
{1..9} => 1 2 3 4 5 6 7 8 9
{4..-4} => 4 3 2 1 0 -1 -2 -3 -4
{1..20..3} => 1 4 7 10 13 16 19
{a..j} => a b c d e f g h i j
{j..a} => j i h g f e d c b a
{a..z..3} => a d g j m p s v y
```
**Combination**
Sets and sequences can be mixed together or used along with any other strings.
```
{a,b,c}{1..3} => a1 a2 a3 b1 b2 b3 c1 c2 c3
foo/{a,b,c}/bar => foo/a/bar foo/b/bar foo/c/bar
```
The fact that braces can be "expanded" from relatively simple patterns makes them ideal for quickly generating test fixtures, file paths, and similar use cases.
## Brace matching
In addition to _expansion_, brace patterns are also useful for performing regular-expression-like matching.
For example, the pattern `foo/{1..3}/bar` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
```
But not:
```
baz/1/qux
baz/2/qux
baz/3/qux
```
Braces can also be combined with [glob patterns](https://github.com/jonschlinkert/micromatch) to perform more advanced wildcard matching. For example, the pattern `*/{1..3}/*` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
baz/1/qux
baz/2/qux
baz/3/qux
```
## Brace matching pitfalls
Although brace patterns offer a user-friendly way of matching ranges or sets of strings, there are also some major disadvantages and potential risks you should be aware of.
### tldr
**"brace bombs"**
* brace expansion can eat up a huge amount of processing resources
* as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially
* users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!)
For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section.
### The solution
Jump to the [performance section](#performance) to see how Braces solves this problem in comparison to other libraries.
### Geometric complexity
At minimum, brace patterns with sets limited to two elements have quadradic or `O(n^2)` complexity. But the complexity of the algorithm increases exponentially as the number of sets, _and elements per set_, increases, which is `O(n^c)`.
For example, the following sets demonstrate quadratic (`O(n^2)`) complexity:
```
{1,2}{3,4} => (2X2) => 13 14 23 24
{1,2}{3,4}{5,6} => (2X2X2) => 135 136 145 146 235 236 245 246
```
But add an element to a set, and we get a n-fold Cartesian product with `O(n^c)` complexity:
```
{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248
249 257 258 259 267 268 269 347 348 349 357
358 359 367 368 369
```
Now, imagine how this complexity grows given that each element is a n-tuple:
```
{1..100}{1..100} => (100X100) => 10,000 elements (38.4 kB)
{1..100}{1..100}{1..100} => (100X100X100) => 1,000,000 elements (5.76 MB)
```
Although these examples are clearly contrived, they demonstrate how brace patterns can quickly grow out of control.
**More information**
Interested in learning more about brace expansion?
* [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion)
* [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion)
* [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
</details>
## Performance
Braces is not only screaming fast, it's also more accurate the other brace expansion libraries.
### Better algorithms
Fortunately there is a solution to the ["brace bomb" problem](#brace-matching-pitfalls): _don't expand brace patterns into an array when they're used for matching_.
Instead, convert the pattern into an optimized regular expression. This is easier said than done, and braces is the only library that does this currently.
**The proof is in the numbers**
Minimatch gets exponentially slower as patterns increase in complexity, braces does not. The following results were generated using `braces()` and `minimatch.braceExpand()`, respectively.
| **Pattern** | **braces** | **[minimatch][]** |
| --- | --- | --- |
| `{1..9007199254740991}`[^1] | `298 B` (5ms 459μs)| N/A (freezes) |
| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) |
| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) |
| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) |
| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) |
| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) |
| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) |
| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) |
| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) |
| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) |
| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) |
| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) |
| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) |
| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) |
| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) |
| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) |
| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) |
### Faster algorithms
When you need expansion, braces is still much faster.
_(the following results were generated using `braces.expand()` and `minimatch.braceExpand()`, respectively)_
| **Pattern** | **braces** | **[minimatch][]** |
| --- | --- | --- |
| `{1..10000000}` | `78.89 MB` (2s 698ms 642μs) | `78.89 MB` (18s 601ms 974μs) |
| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) |
| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) |
| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) |
| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) |
| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) |
| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) |
| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) |
If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js).
## Benchmarks
### Running benchmarks
Install dev dependencies:
```bash
npm i -d && npm benchmark
```
### Latest results
Braces is more accurate, without sacrificing performance.
```bash
# range (expanded)
braces x 29,040 ops/sec ±3.69% (91 runs sampled))
minimatch x 4,735 ops/sec ±1.28% (90 runs sampled)
# range (optimized for regex)
braces x 382,878 ops/sec ±0.56% (94 runs sampled)
minimatch x 1,040 ops/sec ±0.44% (93 runs sampled)
# nested ranges (expanded)
braces x 19,744 ops/sec ±2.27% (92 runs sampled))
minimatch x 4,579 ops/sec ±0.50% (93 runs sampled)
# nested ranges (optimized for regex)
braces x 246,019 ops/sec ±2.02% (93 runs sampled)
minimatch x 1,028 ops/sec ±0.39% (94 runs sampled)
# set (expanded)
braces x 138,641 ops/sec ±0.53% (95 runs sampled)
minimatch x 219,582 ops/sec ±0.98% (94 runs sampled)
# set (optimized for regex)
braces x 388,408 ops/sec ±0.41% (95 runs sampled)
minimatch x 44,724 ops/sec ±0.91% (89 runs sampled)
# nested sets (expanded)
braces x 84,966 ops/sec ±0.48% (94 runs sampled)
minimatch x 140,720 ops/sec ±0.37% (95 runs sampled)
# nested sets (optimized for regex)
braces x 263,340 ops/sec ±2.06% (92 runs sampled)
minimatch x 28,714 ops/sec ±0.40% (90 runs sampled)
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 197 | [jonschlinkert](https://github.com/jonschlinkert) |
| 4 | [doowb](https://github.com/doowb) |
| 1 | [es128](https://github.com/es128) |
| 1 | [eush77](https://github.com/eush77) |
| 1 | [hemanth](https://github.com/hemanth) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Author
**Jon Schlinkert**
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
### License
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._

View File

@@ -0,0 +1,170 @@
'use strict';
const stringify = require('./lib/stringify');
const compile = require('./lib/compile');
const expand = require('./lib/expand');
const parse = require('./lib/parse');
/**
* Expand the given pattern or create a regex-compatible string.
*
* ```js
* const braces = require('braces');
* console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)']
* console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c']
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {String}
* @api public
*/
const braces = (input, options = {}) => {
let output = [];
if (Array.isArray(input)) {
for (let pattern of input) {
let result = braces.create(pattern, options);
if (Array.isArray(result)) {
output.push(...result);
} else {
output.push(result);
}
}
} else {
output = [].concat(braces.create(input, options));
}
if (options && options.expand === true && options.nodupes === true) {
output = [...new Set(output)];
}
return output;
};
/**
* Parse the given `str` with the given `options`.
*
* ```js
* // braces.parse(pattern, [, options]);
* const ast = braces.parse('a/{b,c}/d');
* console.log(ast);
* ```
* @param {String} pattern Brace pattern to parse
* @param {Object} options
* @return {Object} Returns an AST
* @api public
*/
braces.parse = (input, options = {}) => parse(input, options);
/**
* Creates a braces string from an AST, or an AST node.
*
* ```js
* const braces = require('braces');
* let ast = braces.parse('foo/{a,b}/bar');
* console.log(stringify(ast.nodes[2])); //=> '{a,b}'
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.stringify = (input, options = {}) => {
if (typeof input === 'string') {
return stringify(braces.parse(input, options), options);
}
return stringify(input, options);
};
/**
* Compiles a brace pattern into a regex-compatible, optimized string.
* This method is called by the main [braces](#braces) function by default.
*
* ```js
* const braces = require('braces');
* console.log(braces.compile('a/{b,c}/d'));
* //=> ['a/(b|c)/d']
* ```
* @param {String} `input` Brace pattern or AST.
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.compile = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
return compile(input, options);
};
/**
* Expands a brace pattern into an array. This method is called by the
* main [braces](#braces) function when `options.expand` is true. Before
* using this method it's recommended that you read the [performance notes](#performance))
* and advantages of using [.compile](#compile) instead.
*
* ```js
* const braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/b/d', 'a/c/d'];
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.expand = (input, options = {}) => {
if (typeof input === 'string') {
input = braces.parse(input, options);
}
let result = expand(input, options);
// filter out empty strings if specified
if (options.noempty === true) {
result = result.filter(Boolean);
}
// filter out duplicates if specified
if (options.nodupes === true) {
result = [...new Set(result)];
}
return result;
};
/**
* Processes a brace pattern and returns either an expanded array
* (if `options.expand` is true), a highly optimized regex-compatible string.
* This method is called by the main [braces](#braces) function.
*
* ```js
* const braces = require('braces');
* console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}'))
* //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)'
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.create = (input, options = {}) => {
if (input === '' || input.length < 3) {
return [input];
}
return options.expand !== true
? braces.compile(input, options)
: braces.expand(input, options);
};
/**
* Expose "braces"
*/
module.exports = braces;

View File

@@ -0,0 +1,57 @@
'use strict';
const fill = require('fill-range');
const utils = require('./utils');
const compile = (ast, options = {}) => {
let walk = (node, parent = {}) => {
let invalidBlock = utils.isInvalidBrace(parent);
let invalidNode = node.invalid === true && options.escapeInvalid === true;
let invalid = invalidBlock === true || invalidNode === true;
let prefix = options.escapeInvalid === true ? '\\' : '';
let output = '';
if (node.isOpen === true) {
return prefix + node.value;
}
if (node.isClose === true) {
return prefix + node.value;
}
if (node.type === 'open') {
return invalid ? (prefix + node.value) : '(';
}
if (node.type === 'close') {
return invalid ? (prefix + node.value) : ')';
}
if (node.type === 'comma') {
return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|');
}
if (node.value) {
return node.value;
}
if (node.nodes && node.ranges > 0) {
let args = utils.reduce(node.nodes);
let range = fill(...args, { ...options, wrap: false, toRegex: true });
if (range.length !== 0) {
return args.length > 1 && range.length > 1 ? `(${range})` : range;
}
}
if (node.nodes) {
for (let child of node.nodes) {
output += walk(child, node);
}
}
return output;
};
return walk(ast);
};
module.exports = compile;

View File

@@ -0,0 +1,57 @@
'use strict';
module.exports = {
MAX_LENGTH: 1024 * 64,
// Digits
CHAR_0: '0', /* 0 */
CHAR_9: '9', /* 9 */
// Alphabet chars.
CHAR_UPPERCASE_A: 'A', /* A */
CHAR_LOWERCASE_A: 'a', /* a */
CHAR_UPPERCASE_Z: 'Z', /* Z */
CHAR_LOWERCASE_Z: 'z', /* z */
CHAR_LEFT_PARENTHESES: '(', /* ( */
CHAR_RIGHT_PARENTHESES: ')', /* ) */
CHAR_ASTERISK: '*', /* * */
// Non-alphabetic chars.
CHAR_AMPERSAND: '&', /* & */
CHAR_AT: '@', /* @ */
CHAR_BACKSLASH: '\\', /* \ */
CHAR_BACKTICK: '`', /* ` */
CHAR_CARRIAGE_RETURN: '\r', /* \r */
CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */
CHAR_COLON: ':', /* : */
CHAR_COMMA: ',', /* , */
CHAR_DOLLAR: '$', /* . */
CHAR_DOT: '.', /* . */
CHAR_DOUBLE_QUOTE: '"', /* " */
CHAR_EQUAL: '=', /* = */
CHAR_EXCLAMATION_MARK: '!', /* ! */
CHAR_FORM_FEED: '\f', /* \f */
CHAR_FORWARD_SLASH: '/', /* / */
CHAR_HASH: '#', /* # */
CHAR_HYPHEN_MINUS: '-', /* - */
CHAR_LEFT_ANGLE_BRACKET: '<', /* < */
CHAR_LEFT_CURLY_BRACE: '{', /* { */
CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */
CHAR_LINE_FEED: '\n', /* \n */
CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */
CHAR_PERCENT: '%', /* % */
CHAR_PLUS: '+', /* + */
CHAR_QUESTION_MARK: '?', /* ? */
CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */
CHAR_RIGHT_CURLY_BRACE: '}', /* } */
CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */
CHAR_SEMICOLON: ';', /* ; */
CHAR_SINGLE_QUOTE: '\'', /* ' */
CHAR_SPACE: ' ', /* */
CHAR_TAB: '\t', /* \t */
CHAR_UNDERSCORE: '_', /* _ */
CHAR_VERTICAL_LINE: '|', /* | */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */
};

View File

@@ -0,0 +1,113 @@
'use strict';
const fill = require('fill-range');
const stringify = require('./stringify');
const utils = require('./utils');
const append = (queue = '', stash = '', enclose = false) => {
let result = [];
queue = [].concat(queue);
stash = [].concat(stash);
if (!stash.length) return queue;
if (!queue.length) {
return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash;
}
for (let item of queue) {
if (Array.isArray(item)) {
for (let value of item) {
result.push(append(value, stash, enclose));
}
} else {
for (let ele of stash) {
if (enclose === true && typeof ele === 'string') ele = `{${ele}}`;
result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele));
}
}
}
return utils.flatten(result);
};
const expand = (ast, options = {}) => {
let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit;
let walk = (node, parent = {}) => {
node.queue = [];
let p = parent;
let q = parent.queue;
while (p.type !== 'brace' && p.type !== 'root' && p.parent) {
p = p.parent;
q = p.queue;
}
if (node.invalid || node.dollar) {
q.push(append(q.pop(), stringify(node, options)));
return;
}
if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) {
q.push(append(q.pop(), ['{}']));
return;
}
if (node.nodes && node.ranges > 0) {
let args = utils.reduce(node.nodes);
if (utils.exceedsLimit(...args, options.step, rangeLimit)) {
throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
}
let range = fill(...args, options);
if (range.length === 0) {
range = stringify(node, options);
}
q.push(append(q.pop(), range));
node.nodes = [];
return;
}
let enclose = utils.encloseBrace(node);
let queue = node.queue;
let block = node;
while (block.type !== 'brace' && block.type !== 'root' && block.parent) {
block = block.parent;
queue = block.queue;
}
for (let i = 0; i < node.nodes.length; i++) {
let child = node.nodes[i];
if (child.type === 'comma' && node.type === 'brace') {
if (i === 1) queue.push('');
queue.push('');
continue;
}
if (child.type === 'close') {
q.push(append(q.pop(), queue, enclose));
continue;
}
if (child.value && child.type !== 'open') {
queue.push(append(queue.pop(), child.value));
continue;
}
if (child.nodes) {
walk(child, node);
}
}
return queue;
};
return utils.flatten(walk(ast));
};
module.exports = expand;

View File

@@ -0,0 +1,333 @@
'use strict';
const stringify = require('./stringify');
/**
* Constants
*/
const {
MAX_LENGTH,
CHAR_BACKSLASH, /* \ */
CHAR_BACKTICK, /* ` */
CHAR_COMMA, /* , */
CHAR_DOT, /* . */
CHAR_LEFT_PARENTHESES, /* ( */
CHAR_RIGHT_PARENTHESES, /* ) */
CHAR_LEFT_CURLY_BRACE, /* { */
CHAR_RIGHT_CURLY_BRACE, /* } */
CHAR_LEFT_SQUARE_BRACKET, /* [ */
CHAR_RIGHT_SQUARE_BRACKET, /* ] */
CHAR_DOUBLE_QUOTE, /* " */
CHAR_SINGLE_QUOTE, /* ' */
CHAR_NO_BREAK_SPACE,
CHAR_ZERO_WIDTH_NOBREAK_SPACE
} = require('./constants');
/**
* parse
*/
const parse = (input, options = {}) => {
if (typeof input !== 'string') {
throw new TypeError('Expected a string');
}
let opts = options || {};
let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
if (input.length > max) {
throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`);
}
let ast = { type: 'root', input, nodes: [] };
let stack = [ast];
let block = ast;
let prev = ast;
let brackets = 0;
let length = input.length;
let index = 0;
let depth = 0;
let value;
let memo = {};
/**
* Helpers
*/
const advance = () => input[index++];
const push = node => {
if (node.type === 'text' && prev.type === 'dot') {
prev.type = 'text';
}
if (prev && prev.type === 'text' && node.type === 'text') {
prev.value += node.value;
return;
}
block.nodes.push(node);
node.parent = block;
node.prev = prev;
prev = node;
return node;
};
push({ type: 'bos' });
while (index < length) {
block = stack[stack.length - 1];
value = advance();
/**
* Invalid chars
*/
if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) {
continue;
}
/**
* Escaped chars
*/
if (value === CHAR_BACKSLASH) {
push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() });
continue;
}
/**
* Right square bracket (literal): ']'
*/
if (value === CHAR_RIGHT_SQUARE_BRACKET) {
push({ type: 'text', value: '\\' + value });
continue;
}
/**
* Left square bracket: '['
*/
if (value === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
let closed = true;
let next;
while (index < length && (next = advance())) {
value += next;
if (next === CHAR_LEFT_SQUARE_BRACKET) {
brackets++;
continue;
}
if (next === CHAR_BACKSLASH) {
value += advance();
continue;
}
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
brackets--;
if (brackets === 0) {
break;
}
}
}
push({ type: 'text', value });
continue;
}
/**
* Parentheses
*/
if (value === CHAR_LEFT_PARENTHESES) {
block = push({ type: 'paren', nodes: [] });
stack.push(block);
push({ type: 'text', value });
continue;
}
if (value === CHAR_RIGHT_PARENTHESES) {
if (block.type !== 'paren') {
push({ type: 'text', value });
continue;
}
block = stack.pop();
push({ type: 'text', value });
block = stack[stack.length - 1];
continue;
}
/**
* Quotes: '|"|`
*/
if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {
let open = value;
let next;
if (options.keepQuotes !== true) {
value = '';
}
while (index < length && (next = advance())) {
if (next === CHAR_BACKSLASH) {
value += next + advance();
continue;
}
if (next === open) {
if (options.keepQuotes === true) value += next;
break;
}
value += next;
}
push({ type: 'text', value });
continue;
}
/**
* Left curly brace: '{'
*/
if (value === CHAR_LEFT_CURLY_BRACE) {
depth++;
let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true;
let brace = {
type: 'brace',
open: true,
close: false,
dollar,
depth,
commas: 0,
ranges: 0,
nodes: []
};
block = push(brace);
stack.push(block);
push({ type: 'open', value });
continue;
}
/**
* Right curly brace: '}'
*/
if (value === CHAR_RIGHT_CURLY_BRACE) {
if (block.type !== 'brace') {
push({ type: 'text', value });
continue;
}
let type = 'close';
block = stack.pop();
block.close = true;
push({ type, value });
depth--;
block = stack[stack.length - 1];
continue;
}
/**
* Comma: ','
*/
if (value === CHAR_COMMA && depth > 0) {
if (block.ranges > 0) {
block.ranges = 0;
let open = block.nodes.shift();
block.nodes = [open, { type: 'text', value: stringify(block) }];
}
push({ type: 'comma', value });
block.commas++;
continue;
}
/**
* Dot: '.'
*/
if (value === CHAR_DOT && depth > 0 && block.commas === 0) {
let siblings = block.nodes;
if (depth === 0 || siblings.length === 0) {
push({ type: 'text', value });
continue;
}
if (prev.type === 'dot') {
block.range = [];
prev.value += value;
prev.type = 'range';
if (block.nodes.length !== 3 && block.nodes.length !== 5) {
block.invalid = true;
block.ranges = 0;
prev.type = 'text';
continue;
}
block.ranges++;
block.args = [];
continue;
}
if (prev.type === 'range') {
siblings.pop();
let before = siblings[siblings.length - 1];
before.value += prev.value + value;
prev = before;
block.ranges--;
continue;
}
push({ type: 'dot', value });
continue;
}
/**
* Text
*/
push({ type: 'text', value });
}
// Mark imbalanced braces and brackets as invalid
do {
block = stack.pop();
if (block.type !== 'root') {
block.nodes.forEach(node => {
if (!node.nodes) {
if (node.type === 'open') node.isOpen = true;
if (node.type === 'close') node.isClose = true;
if (!node.nodes) node.type = 'text';
node.invalid = true;
}
});
// get the location of the block on parent.nodes (block's siblings)
let parent = stack[stack.length - 1];
let index = parent.nodes.indexOf(block);
// replace the (invalid) block with it's nodes
parent.nodes.splice(index, 1, ...block.nodes);
}
} while (stack.length > 0);
push({ type: 'eos' });
return ast;
};
module.exports = parse;

View File

@@ -0,0 +1,32 @@
'use strict';
const utils = require('./utils');
module.exports = (ast, options = {}) => {
let stringify = (node, parent = {}) => {
let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent);
let invalidNode = node.invalid === true && options.escapeInvalid === true;
let output = '';
if (node.value) {
if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) {
return '\\' + node.value;
}
return node.value;
}
if (node.value) {
return node.value;
}
if (node.nodes) {
for (let child of node.nodes) {
output += stringify(child);
}
}
return output;
};
return stringify(ast);
};

View File

@@ -0,0 +1,112 @@
'use strict';
exports.isInteger = num => {
if (typeof num === 'number') {
return Number.isInteger(num);
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isInteger(Number(num));
}
return false;
};
/**
* Find a node of the given type
*/
exports.find = (node, type) => node.nodes.find(node => node.type === type);
/**
* Find a node of the given type
*/
exports.exceedsLimit = (min, max, step = 1, limit) => {
if (limit === false) return false;
if (!exports.isInteger(min) || !exports.isInteger(max)) return false;
return ((Number(max) - Number(min)) / Number(step)) >= limit;
};
/**
* Escape the given node with '\\' before node.value
*/
exports.escapeNode = (block, n = 0, type) => {
let node = block.nodes[n];
if (!node) return;
if ((type && node.type === type) || node.type === 'open' || node.type === 'close') {
if (node.escaped !== true) {
node.value = '\\' + node.value;
node.escaped = true;
}
}
};
/**
* Returns true if the given brace node should be enclosed in literal braces
*/
exports.encloseBrace = node => {
if (node.type !== 'brace') return false;
if ((node.commas >> 0 + node.ranges >> 0) === 0) {
node.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a brace node is invalid.
*/
exports.isInvalidBrace = block => {
if (block.type !== 'brace') return false;
if (block.invalid === true || block.dollar) return true;
if ((block.commas >> 0 + block.ranges >> 0) === 0) {
block.invalid = true;
return true;
}
if (block.open !== true || block.close !== true) {
block.invalid = true;
return true;
}
return false;
};
/**
* Returns true if a node is an open or close node
*/
exports.isOpenOrClose = node => {
if (node.type === 'open' || node.type === 'close') {
return true;
}
return node.open === true || node.close === true;
};
/**
* Reduce an array of text nodes.
*/
exports.reduce = nodes => nodes.reduce((acc, node) => {
if (node.type === 'text') acc.push(node.value);
if (node.type === 'range') node.type = 'text';
return acc;
}, []);
/**
* Flatten an array
*/
exports.flatten = (...args) => {
const result = [];
const flat = arr => {
for (let i = 0; i < arr.length; i++) {
let ele = arr[i];
Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele);
}
return result;
};
flat(args);
return result;
};

View File

@@ -0,0 +1,77 @@
{
"name": "braces",
"description": "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.",
"version": "3.0.2",
"homepage": "https://github.com/micromatch/braces",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Brian Woodward (https://twitter.com/doowb)",
"Elan Shanker (https://github.com/es128)",
"Eugene Sharygin (https://github.com/eush77)",
"hemanth.hm (http://h3manth.com)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)"
],
"repository": "micromatch/braces",
"bugs": {
"url": "https://github.com/micromatch/braces/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha",
"benchmark": "node benchmark"
},
"dependencies": {
"fill-range": "^7.0.1"
},
"devDependencies": {
"ansi-colors": "^3.2.4",
"bash-path": "^2.0.1",
"gulp-format-md": "^2.0.0",
"mocha": "^6.1.1"
},
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"braces",
"expand",
"expansion",
"filepath",
"fill",
"fs",
"glob",
"globbing",
"letter",
"match",
"matches",
"matching",
"number",
"numerical",
"path",
"range",
"ranges",
"sh"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"lint": {
"reflinks": true
},
"plugins": [
"gulp-format-md"
]
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
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.

View File

@@ -0,0 +1,237 @@
# fill-range [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/fill-range.svg?style=flat)](https://www.npmjs.com/package/fill-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![NPM total downloads](https://img.shields.io/npm/dt/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/fill-range.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/fill-range)
> Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save fill-range
```
## Usage
Expands numbers and letters, optionally using a `step` as the last argument. _(Numbers may be defined as JavaScript numbers or strings)_.
```js
const fill = require('fill-range');
// fill(from, to[, step, options]);
console.log(fill('1', '10')); //=> ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
console.log(fill('1', '10', { toRegex: true })); //=> [1-9]|10
```
**Params**
* `from`: **{String|Number}** the number or letter to start with
* `to`: **{String|Number}** the number or letter to end with
* `step`: **{String|Number|Object|Function}** Optionally pass a [step](#optionsstep) to use.
* `options`: **{Object|Function}**: See all available [options](#options)
## Examples
By default, an array of values is returned.
**Alphabetical ranges**
```js
console.log(fill('a', 'e')); //=> ['a', 'b', 'c', 'd', 'e']
console.log(fill('A', 'E')); //=> [ 'A', 'B', 'C', 'D', 'E' ]
```
**Numerical ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill('1', '5')); //=> [ 1, 2, 3, 4, 5 ]
```
**Negative ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill('-5', '-1')); //=> [ '-5', '-4', '-3', '-2', '-1' ]
console.log(fill('-5', '5')); //=> [ '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5' ]
```
**Steps (increments)**
```js
// numerical ranges with increments
console.log(fill('0', '25', 4)); //=> [ '0', '4', '8', '12', '16', '20', '24' ]
console.log(fill('0', '25', 5)); //=> [ '0', '5', '10', '15', '20', '25' ]
console.log(fill('0', '25', 6)); //=> [ '0', '6', '12', '18', '24' ]
// alphabetical ranges with increments
console.log(fill('a', 'z', 4)); //=> [ 'a', 'e', 'i', 'm', 'q', 'u', 'y' ]
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 6)); //=> [ 'a', 'g', 'm', 's', 'y' ]
```
## Options
### options.step
**Type**: `number` (formatted as a string or number)
**Default**: `undefined`
**Description**: The increment to use for the range. Can be used with letters or numbers.
**Example(s)**
```js
// numbers
console.log(fill('1', '10', 2)); //=> [ '1', '3', '5', '7', '9' ]
console.log(fill('1', '10', 3)); //=> [ '1', '4', '7', '10' ]
console.log(fill('1', '10', 4)); //=> [ '1', '5', '9' ]
// letters
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 7)); //=> [ 'a', 'h', 'o', 'v' ]
console.log(fill('a', 'z', 9)); //=> [ 'a', 'j', 's' ]
```
### options.strictRanges
**Type**: `boolean`
**Default**: `false`
**Description**: By default, `null` is returned when an invalid range is passed. Enable this option to throw a `RangeError` on invalid ranges.
**Example(s)**
The following are all invalid:
```js
fill('1.1', '2'); // decimals not supported in ranges
fill('a', '2'); // incompatible range values
fill(1, 10, 'foo'); // invalid "step" argument
```
### options.stringify
**Type**: `boolean`
**Default**: `undefined`
**Description**: Cast all returned values to strings. By default, integers are returned as numbers.
**Example(s)**
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill(1, 5, { stringify: true })); //=> [ '1', '2', '3', '4', '5' ]
```
### options.toRegex
**Type**: `boolean`
**Default**: `undefined`
**Description**: Create a regex-compatible source string, instead of expanding values to an array.
**Example(s)**
```js
// alphabetical range
console.log(fill('a', 'e', { toRegex: true })); //=> '[a-e]'
// alphabetical with step
console.log(fill('a', 'z', 3, { toRegex: true })); //=> 'a|d|g|j|m|p|s|v|y'
// numerical range
console.log(fill('1', '100', { toRegex: true })); //=> '[1-9]|[1-9][0-9]|100'
// numerical range with zero padding
console.log(fill('000001', '100000', { toRegex: true }));
//=> '0{5}[1-9]|0{4}[1-9][0-9]|0{3}[1-9][0-9]{2}|0{2}[1-9][0-9]{3}|0[1-9][0-9]{4}|100000'
```
### options.transform
**Type**: `function`
**Default**: `undefined`
**Description**: Customize each value in the returned array (or [string](#optionstoRegex)). _(you can also pass this function as the last argument to `fill()`)_.
**Example(s)**
```js
// add zero padding
console.log(fill(1, 5, value => String(value).padStart(4, '0')));
//=> ['0001', '0002', '0003', '0004', '0005']
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 116 | [jonschlinkert](https://github.com/jonschlinkert) |
| 4 | [paulmillr](https://github.com/paulmillr) |
| 2 | [realityking](https://github.com/realityking) |
| 2 | [bluelovers](https://github.com/bluelovers) |
| 1 | [edorivai](https://github.com/edorivai) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Author
**Jon Schlinkert**
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
Please consider supporting me on Patreon, or [start your own Patreon page](https://patreon.com/invite/bxpbvm)!
<a href="https://www.patreon.com/jonschlinkert">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" height="50">
</a>
### License
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 08, 2019._

View File

@@ -0,0 +1,249 @@
/*!
* fill-range <https://github.com/jonschlinkert/fill-range>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Licensed under the MIT License.
*/
'use strict';
const util = require('util');
const toRegexRange = require('to-regex-range');
const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
const transform = toNumber => {
return value => toNumber === true ? Number(value) : String(value);
};
const isValidValue = value => {
return typeof value === 'number' || (typeof value === 'string' && value !== '');
};
const isNumber = num => Number.isInteger(+num);
const zeros = input => {
let value = `${input}`;
let index = -1;
if (value[0] === '-') value = value.slice(1);
if (value === '0') return false;
while (value[++index] === '0');
return index > 0;
};
const stringify = (start, end, options) => {
if (typeof start === 'string' || typeof end === 'string') {
return true;
}
return options.stringify === true;
};
const pad = (input, maxLength, toNumber) => {
if (maxLength > 0) {
let dash = input[0] === '-' ? '-' : '';
if (dash) input = input.slice(1);
input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0'));
}
if (toNumber === false) {
return String(input);
}
return input;
};
const toMaxLen = (input, maxLength) => {
let negative = input[0] === '-' ? '-' : '';
if (negative) {
input = input.slice(1);
maxLength--;
}
while (input.length < maxLength) input = '0' + input;
return negative ? ('-' + input) : input;
};
const toSequence = (parts, options) => {
parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
let prefix = options.capture ? '' : '?:';
let positives = '';
let negatives = '';
let result;
if (parts.positives.length) {
positives = parts.positives.join('|');
}
if (parts.negatives.length) {
negatives = `-(${prefix}${parts.negatives.join('|')})`;
}
if (positives && negatives) {
result = `${positives}|${negatives}`;
} else {
result = positives || negatives;
}
if (options.wrap) {
return `(${prefix}${result})`;
}
return result;
};
const toRange = (a, b, isNumbers, options) => {
if (isNumbers) {
return toRegexRange(a, b, { wrap: false, ...options });
}
let start = String.fromCharCode(a);
if (a === b) return start;
let stop = String.fromCharCode(b);
return `[${start}-${stop}]`;
};
const toRegex = (start, end, options) => {
if (Array.isArray(start)) {
let wrap = options.wrap === true;
let prefix = options.capture ? '' : '?:';
return wrap ? `(${prefix}${start.join('|')})` : start.join('|');
}
return toRegexRange(start, end, options);
};
const rangeError = (...args) => {
return new RangeError('Invalid range arguments: ' + util.inspect(...args));
};
const invalidRange = (start, end, options) => {
if (options.strictRanges === true) throw rangeError([start, end]);
return [];
};
const invalidStep = (step, options) => {
if (options.strictRanges === true) {
throw new TypeError(`Expected step "${step}" to be a number`);
}
return [];
};
const fillNumbers = (start, end, step = 1, options = {}) => {
let a = Number(start);
let b = Number(end);
if (!Number.isInteger(a) || !Number.isInteger(b)) {
if (options.strictRanges === true) throw rangeError([start, end]);
return [];
}
// fix negative zero
if (a === 0) a = 0;
if (b === 0) b = 0;
let descending = a > b;
let startString = String(start);
let endString = String(end);
let stepString = String(step);
step = Math.max(Math.abs(step), 1);
let padded = zeros(startString) || zeros(endString) || zeros(stepString);
let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0;
let toNumber = padded === false && stringify(start, end, options) === false;
let format = options.transform || transform(toNumber);
if (options.toRegex && step === 1) {
return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options);
}
let parts = { negatives: [], positives: [] };
let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num));
let range = [];
let index = 0;
while (descending ? a >= b : a <= b) {
if (options.toRegex === true && step > 1) {
push(a);
} else {
range.push(pad(format(a, index), maxLen, toNumber));
}
a = descending ? a - step : a + step;
index++;
}
if (options.toRegex === true) {
return step > 1
? toSequence(parts, options)
: toRegex(range, null, { wrap: false, ...options });
}
return range;
};
const fillLetters = (start, end, step = 1, options = {}) => {
if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) {
return invalidRange(start, end, options);
}
let format = options.transform || (val => String.fromCharCode(val));
let a = `${start}`.charCodeAt(0);
let b = `${end}`.charCodeAt(0);
let descending = a > b;
let min = Math.min(a, b);
let max = Math.max(a, b);
if (options.toRegex && step === 1) {
return toRange(min, max, false, options);
}
let range = [];
let index = 0;
while (descending ? a >= b : a <= b) {
range.push(format(a, index));
a = descending ? a - step : a + step;
index++;
}
if (options.toRegex === true) {
return toRegex(range, null, { wrap: false, options });
}
return range;
};
const fill = (start, end, step, options = {}) => {
if (end == null && isValidValue(start)) {
return [start];
}
if (!isValidValue(start) || !isValidValue(end)) {
return invalidRange(start, end, options);
}
if (typeof step === 'function') {
return fill(start, end, 1, { transform: step });
}
if (isObject(step)) {
return fill(start, end, 0, step);
}
let opts = { ...options };
if (opts.capture === true) opts.wrap = true;
step = step || opts.step || 1;
if (!isNumber(step)) {
if (step != null && !isObject(step)) return invalidStep(step, opts);
return fill(start, end, 1, step);
}
if (isNumber(start) && isNumber(end)) {
return fillNumbers(start, end, step, opts);
}
return fillLetters(start, end, Math.max(Math.abs(step), 1), opts);
};
module.exports = fill;

View File

@@ -0,0 +1,69 @@
{
"name": "fill-range",
"description": "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`",
"version": "7.0.1",
"homepage": "https://github.com/jonschlinkert/fill-range",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Edo Rivai (edo.rivai.nl)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)",
"Paul Miller (paulmillr.com)",
"Rouven Weßling (www.rouvenwessling.de)",
"(https://github.com/wtgtybhertgeghgtwtg)"
],
"repository": "jonschlinkert/fill-range",
"bugs": {
"url": "https://github.com/jonschlinkert/fill-range/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"to-regex-range": "^5.0.1"
},
"devDependencies": {
"gulp-format-md": "^2.0.0",
"mocha": "^6.1.1"
},
"keywords": [
"alpha",
"alphabetical",
"array",
"bash",
"brace",
"expand",
"expansion",
"fill",
"glob",
"match",
"matches",
"matching",
"number",
"numerical",
"range",
"ranges",
"regex",
"sh"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
}
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
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.

View File

@@ -0,0 +1,187 @@
# is-number [![NPM version](https://img.shields.io/npm/v/is-number.svg?style=flat)](https://www.npmjs.com/package/is-number) [![NPM monthly downloads](https://img.shields.io/npm/dm/is-number.svg?style=flat)](https://npmjs.org/package/is-number) [![NPM total downloads](https://img.shields.io/npm/dt/is-number.svg?style=flat)](https://npmjs.org/package/is-number) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/is-number.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/is-number)
> Returns true if the value is a finite number.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save is-number
```
## Why is this needed?
In JavaScript, it's not always as straightforward as it should be to reliably check if a value is a number. It's common for devs to use `+`, `-`, or `Number()` to cast a string value to a number (for example, when values are returned from user input, regex matches, parsers, etc). But there are many non-intuitive edge cases that yield unexpected results:
```js
console.log(+[]); //=> 0
console.log(+''); //=> 0
console.log(+' '); //=> 0
console.log(typeof NaN); //=> 'number'
```
This library offers a performant way to smooth out edge cases like these.
## Usage
```js
const isNumber = require('is-number');
```
See the [tests](./test.js) for more examples.
### true
```js
isNumber(5e3); // true
isNumber(0xff); // true
isNumber(-1.1); // true
isNumber(0); // true
isNumber(1); // true
isNumber(1.1); // true
isNumber(10); // true
isNumber(10.10); // true
isNumber(100); // true
isNumber('-1.1'); // true
isNumber('0'); // true
isNumber('012'); // true
isNumber('0xff'); // true
isNumber('1'); // true
isNumber('1.1'); // true
isNumber('10'); // true
isNumber('10.10'); // true
isNumber('100'); // true
isNumber('5e3'); // true
isNumber(parseInt('012')); // true
isNumber(parseFloat('012')); // true
```
### False
Everything else is false, as you would expect:
```js
isNumber(Infinity); // false
isNumber(NaN); // false
isNumber(null); // false
isNumber(undefined); // false
isNumber(''); // false
isNumber(' '); // false
isNumber('foo'); // false
isNumber([1]); // false
isNumber([]); // false
isNumber(function () {}); // false
isNumber({}); // false
```
## Release history
### 7.0.0
* Refactor. Now uses `.isFinite` if it exists.
* Performance is about the same as v6.0 when the value is a string or number. But it's now 3x-4x faster when the value is not a string or number.
### 6.0.0
* Optimizations, thanks to @benaadams.
### 5.0.0
**Breaking changes**
* removed support for `instanceof Number` and `instanceof String`
## Benchmarks
As with all benchmarks, take these with a grain of salt. See the [benchmarks](./benchmark/index.js) for more detail.
```
# all
v7.0 x 413,222 ops/sec ±2.02% (86 runs sampled)
v6.0 x 111,061 ops/sec ±1.29% (85 runs sampled)
parseFloat x 317,596 ops/sec ±1.36% (86 runs sampled)
fastest is 'v7.0'
# string
v7.0 x 3,054,496 ops/sec ±1.05% (89 runs sampled)
v6.0 x 2,957,781 ops/sec ±0.98% (88 runs sampled)
parseFloat x 3,071,060 ops/sec ±1.13% (88 runs sampled)
fastest is 'parseFloat,v7.0'
# number
v7.0 x 3,146,895 ops/sec ±0.89% (89 runs sampled)
v6.0 x 3,214,038 ops/sec ±1.07% (89 runs sampled)
parseFloat x 3,077,588 ops/sec ±1.07% (87 runs sampled)
fastest is 'v6.0'
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Related projects
You might also be interested in these projects:
* [is-plain-object](https://www.npmjs.com/package/is-plain-object): Returns true if an object was created by the `Object` constructor. | [homepage](https://github.com/jonschlinkert/is-plain-object "Returns true if an object was created by the `Object` constructor.")
* [is-primitive](https://www.npmjs.com/package/is-primitive): Returns `true` if the value is a primitive. | [homepage](https://github.com/jonschlinkert/is-primitive "Returns `true` if the value is a primitive. ")
* [isobject](https://www.npmjs.com/package/isobject): Returns true if the value is an object and not an array or null. | [homepage](https://github.com/jonschlinkert/isobject "Returns true if the value is an object and not an array or null.")
* [kind-of](https://www.npmjs.com/package/kind-of): Get the native type of a value. | [homepage](https://github.com/jonschlinkert/kind-of "Get the native type of a value.")
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 49 | [jonschlinkert](https://github.com/jonschlinkert) |
| 5 | [charlike-old](https://github.com/charlike-old) |
| 1 | [benaadams](https://github.com/benaadams) |
| 1 | [realityking](https://github.com/realityking) |
### Author
**Jon Schlinkert**
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
### License
Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on June 15, 2018._

View File

@@ -0,0 +1,18 @@
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
module.exports = function(num) {
if (typeof num === 'number') {
return num - num === 0;
}
if (typeof num === 'string' && num.trim() !== '') {
return Number.isFinite ? Number.isFinite(+num) : isFinite(+num);
}
return false;
};

View File

@@ -0,0 +1,82 @@
{
"name": "is-number",
"description": "Returns true if a number or string value is a finite number. Useful for regex matches, parsing, user input, etc.",
"version": "7.0.0",
"homepage": "https://github.com/jonschlinkert/is-number",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"Jon Schlinkert (http://twitter.com/jonschlinkert)",
"Olsten Larck (https://i.am.charlike.online)",
"Rouven Weßling (www.rouvenwessling.de)"
],
"repository": "jonschlinkert/is-number",
"bugs": {
"url": "https://github.com/jonschlinkert/is-number/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=0.12.0"
},
"scripts": {
"test": "mocha"
},
"devDependencies": {
"ansi": "^0.3.1",
"benchmark": "^2.1.4",
"gulp-format-md": "^1.0.0",
"mocha": "^3.5.3"
},
"keywords": [
"cast",
"check",
"coerce",
"coercion",
"finite",
"integer",
"is",
"isnan",
"is-nan",
"is-num",
"is-number",
"isnumber",
"isfinite",
"istype",
"kind",
"math",
"nan",
"num",
"number",
"numeric",
"parseFloat",
"parseInt",
"test",
"type",
"typeof",
"value"
],
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"related": {
"list": [
"is-plain-object",
"is-primitive",
"isobject",
"kind-of"
]
},
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
}
}
}

View File

@@ -0,0 +1,108 @@
# Release history
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
<details>
<summary><strong>Guiding Principles</strong></summary>
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- Versions and sections should be linkable.
- The latest version comes first.
- The release date of each versions is displayed.
- Mention whether you follow Semantic Versioning.
</details>
<details>
<summary><strong>Types of changes</strong></summary>
Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_):
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
</details>
## [4.0.0] - 2019-03-20
### Added
- Adds support for `options.onMatch`. See the readme for details
- Adds support for `options.onIgnore`. See the readme for details
- Adds support for `options.onResult`. See the readme for details
### Breaking changes
- Removed support for passing an array of brace patterns to `micromatch.braces()`.
- To strictly enforce closing brackets (for `{`, `[`, and `(`), you must now use `strictBrackets=true` instead of `strictErrors`.
- `cache` - caching and all related options and methods have been removed
- `options.unixify` was renamed to `options.windows`
- `options.nodupes` Was removed. Duplicates are always removed by default. You can override this with custom behavior by using the `onMatch`, `onResult` and `onIgnore` functions.
- `options.snapdragon` was removed, as snapdragon is no longer used.
- `options.sourcemap` was removed, as snapdragon is no longer used, which provided sourcemap support.
## [3.0.0] - 2017-04-11
Complete overhaul, with 36,000+ new unit tests validated against actual output generated by Bash and minimatch. More specifically, 35,000+ of the tests:
- micromatch results are directly compared to bash results
- in rare cases, when micromatch and bash disagree, micromatch's results are compared to minimatch's results
- micromatch is much more accurate than minimatch, so there were cases where I had to make assumptions. I'll try to document these.
This refactor introduces a parser and compiler that are supersets of more granular parsers and compilers from other sub-modules. Each of these sub-modules has a singular responsibility and focuses on a certain type of matching that aligns with a specific part of the Bash "expansion" API.
These sub-modules work like plugins to seamlessly create the micromatch parser/compiler, so that strings are parsed in one pass, an [AST is created](https://gist.github.com/jonschlinkert/099c8914f56529f75bc757cc9e5e8e2a), then a new string is generated by the compiler.
Here are those sub-modules with links to related prs on those modules if you want to see how they contribute to this code:
[nanomatch](https://github.com/jonschlinkert/nanomatch) (new library) - glob expansion (`*`, `**`, `?` and `[...]`))
[braces](https://github.com/jonschlinkert/braces/pull/10) - brace expansion (`{1..10}`, `{a,b,c}`, etc)
[extglob](https://github.com/jonschlinkert/extglob/pull/5) - extended globs (`!(a|b)`, `@(!(foo|bar))`, etc)
[expand-brackets](https://github.com/jonschlinkert/expand-brackets/pull/5) - POSIX character classes `[[:alpha:][:digit:]]`
**Added**
- source map support (optionally created when using parse or compile - I have no idea what the use case is yet, but they come for free) (note that source maps are not generated for brace expansion at present, since the braces compiler uses a different strategy. I'll update if/when this changes).
- parser is exposed, so that implementors can customize or override specific micromatch parsers if necessary
- compiler is exposed, so that implementors can customize or override specific micromatch compilers if necessary
**Fixed**
- more accurate matching (passes 100% of Bash 4.3 of the brace expansion and extglob unit tests, as well as all Bash glob tests that are relevant to node.js usage, all minimatch tests, all brace-expansion tests, and also passes a couple of tests that bash fails)
- even safer - micromatch has always generated optimized patterns so it's not subject to DoS exploits like minimatch (completely different than the regex DoS issue, minimatch and multimatch are still openly exposed to being used for DoS attacks), but more safeguards were built into this refactor
**Changed**
- the public API of this library did not change in this version and should be safe to upgrade without changing implentor code. However, we have released this as a major version for the following reasons:
- out of an abundance of caution due to the large amount of code changed in this release
- we have improved parser accuracy to such a degree that some implementors using invalid globs have noted change in behavior. If this is the case for you, please check that you are using a valid glob expression before logging a bug with this library
## [1.0.1] - 2016-12-12
**Added**
- Support for windows path edge cases where backslashes are used in brackets or other unusual combinations.
## [1.0.0] - 2016-12-12
Stable release.
## [0.1.0] - 2016-10-08
First release.
[Unreleased]: https://github.com/jonschlinkert/micromatch/compare/0.1.0...HEAD
[0.2.0]: https://github.com/jonschlinkert/micromatch/compare/0.1.0...0.2.0
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-present, Jon Schlinkert.
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.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,467 @@
'use strict';
const util = require('util');
const braces = require('braces');
const picomatch = require('picomatch');
const utils = require('picomatch/lib/utils');
const isEmptyString = val => typeof val === 'string' && (val === '' || val === './');
/**
* Returns an array of strings that match one or more glob patterns.
*
* ```js
* const mm = require('micromatch');
* // mm(list, patterns[, options]);
*
* console.log(mm(['a.js', 'a.txt'], ['*.js']));
* //=> [ 'a.js' ]
* ```
* @param {String|Array<string>} list List of strings to match.
* @param {String|Array<string>} patterns One or more glob patterns to use for matching.
* @param {Object} options See available [options](#options)
* @return {Array} Returns an array of matches
* @summary false
* @api public
*/
const micromatch = (list, patterns, options) => {
patterns = [].concat(patterns);
list = [].concat(list);
let omit = new Set();
let keep = new Set();
let items = new Set();
let negatives = 0;
let onResult = state => {
items.add(state.output);
if (options && options.onResult) {
options.onResult(state);
}
};
for (let i = 0; i < patterns.length; i++) {
let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true);
let negated = isMatch.state.negated || isMatch.state.negatedExtglob;
if (negated) negatives++;
for (let item of list) {
let matched = isMatch(item, true);
let match = negated ? !matched.isMatch : matched.isMatch;
if (!match) continue;
if (negated) {
omit.add(matched.output);
} else {
omit.delete(matched.output);
keep.add(matched.output);
}
}
}
let result = negatives === patterns.length ? [...items] : [...keep];
let matches = result.filter(item => !omit.has(item));
if (options && matches.length === 0) {
if (options.failglob === true) {
throw new Error(`No matches found for "${patterns.join(', ')}"`);
}
if (options.nonull === true || options.nullglob === true) {
return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns;
}
}
return matches;
};
/**
* Backwards compatibility
*/
micromatch.match = micromatch;
/**
* Returns a matcher function from the given glob `pattern` and `options`.
* The returned function takes a string to match as its only argument and returns
* true if the string is a match.
*
* ```js
* const mm = require('micromatch');
* // mm.matcher(pattern[, options]);
*
* const isMatch = mm.matcher('*.!(*a)');
* console.log(isMatch('a.a')); //=> false
* console.log(isMatch('a.b')); //=> true
* ```
* @param {String} `pattern` Glob pattern
* @param {Object} `options`
* @return {Function} Returns a matcher function.
* @api public
*/
micromatch.matcher = (pattern, options) => picomatch(pattern, options);
/**
* Returns true if **any** of the given glob `patterns` match the specified `string`.
*
* ```js
* const mm = require('micromatch');
* // mm.isMatch(string, patterns[, options]);
*
* console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true
* console.log(mm.isMatch('a.a', 'b.*')); //=> false
* ```
* @param {String} str The string to test.
* @param {String|Array} patterns One or more glob patterns to use for matching.
* @param {Object} [options] See available [options](#options).
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
/**
* Backwards compatibility
*/
micromatch.any = micromatch.isMatch;
/**
* Returns a list of strings that _**do not match any**_ of the given `patterns`.
*
* ```js
* const mm = require('micromatch');
* // mm.not(list, patterns[, options]);
*
* console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a'));
* //=> ['b.b', 'c.c']
* ```
* @param {Array} `list` Array of strings to match.
* @param {String|Array} `patterns` One or more glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Array} Returns an array of strings that **do not match** the given patterns.
* @api public
*/
micromatch.not = (list, patterns, options = {}) => {
patterns = [].concat(patterns).map(String);
let result = new Set();
let items = [];
let onResult = state => {
if (options.onResult) options.onResult(state);
items.push(state.output);
};
let matches = micromatch(list, patterns, { ...options, onResult });
for (let item of items) {
if (!matches.includes(item)) {
result.add(item);
}
}
return [...result];
};
/**
* Returns true if the given `string` contains the given pattern. Similar
* to [.isMatch](#isMatch) but the pattern can match any part of the string.
*
* ```js
* var mm = require('micromatch');
* // mm.contains(string, pattern[, options]);
*
* console.log(mm.contains('aa/bb/cc', '*b'));
* //=> true
* console.log(mm.contains('aa/bb/cc', '*d'));
* //=> false
* ```
* @param {String} `str` The string to match.
* @param {String|Array} `patterns` Glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if the patter matches any part of `str`.
* @api public
*/
micromatch.contains = (str, pattern, options) => {
if (typeof str !== 'string') {
throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
}
if (Array.isArray(pattern)) {
return pattern.some(p => micromatch.contains(str, p, options));
}
if (typeof pattern === 'string') {
if (isEmptyString(str) || isEmptyString(pattern)) {
return false;
}
if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) {
return true;
}
}
return micromatch.isMatch(str, pattern, { ...options, contains: true });
};
/**
* Filter the keys of the given object with the given `glob` pattern
* and `options`. Does not attempt to match nested keys. If you need this feature,
* use [glob-object][] instead.
*
* ```js
* const mm = require('micromatch');
* // mm.matchKeys(object, patterns[, options]);
*
* const obj = { aa: 'a', ab: 'b', ac: 'c' };
* console.log(mm.matchKeys(obj, '*b'));
* //=> { ab: 'b' }
* ```
* @param {Object} `object` The object with keys to filter.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Object} Returns an object with only keys that match the given patterns.
* @api public
*/
micromatch.matchKeys = (obj, patterns, options) => {
if (!utils.isObject(obj)) {
throw new TypeError('Expected the first argument to be an object');
}
let keys = micromatch(Object.keys(obj), patterns, options);
let res = {};
for (let key of keys) res[key] = obj[key];
return res;
};
/**
* Returns true if some of the strings in the given `list` match any of the given glob `patterns`.
*
* ```js
* const mm = require('micromatch');
* // mm.some(list, patterns[, options]);
*
* console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
* // true
* console.log(mm.some(['foo.js'], ['*.js', '!foo.js']));
* // false
* ```
* @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.some = (list, patterns, options) => {
let items = [].concat(list);
for (let pattern of [].concat(patterns)) {
let isMatch = picomatch(String(pattern), options);
if (items.some(item => isMatch(item))) {
return true;
}
}
return false;
};
/**
* Returns true if every string in the given `list` matches
* any of the given glob `patterns`.
*
* ```js
* const mm = require('micromatch');
* // mm.every(list, patterns[, options]);
*
* console.log(mm.every('foo.js', ['foo.js']));
* // true
* console.log(mm.every(['foo.js', 'bar.js'], ['*.js']));
* // true
* console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
* // false
* console.log(mm.every(['foo.js'], ['*.js', '!foo.js']));
* // false
* ```
* @param {String|Array} `list` The string or array of strings to test.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.every = (list, patterns, options) => {
let items = [].concat(list);
for (let pattern of [].concat(patterns)) {
let isMatch = picomatch(String(pattern), options);
if (!items.every(item => isMatch(item))) {
return false;
}
}
return true;
};
/**
* Returns true if **all** of the given `patterns` match
* the specified string.
*
* ```js
* const mm = require('micromatch');
* // mm.all(string, patterns[, options]);
*
* console.log(mm.all('foo.js', ['foo.js']));
* // true
*
* console.log(mm.all('foo.js', ['*.js', '!foo.js']));
* // false
*
* console.log(mm.all('foo.js', ['*.js', 'foo.js']));
* // true
*
* console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js']));
* // true
* ```
* @param {String|Array} `str` The string to test.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.all = (str, patterns, options) => {
if (typeof str !== 'string') {
throw new TypeError(`Expected a string: "${util.inspect(str)}"`);
}
return [].concat(patterns).every(p => picomatch(p, options)(str));
};
/**
* Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match.
*
* ```js
* const mm = require('micromatch');
* // mm.capture(pattern, string[, options]);
*
* console.log(mm.capture('test/*.js', 'test/foo.js'));
* //=> ['foo']
* console.log(mm.capture('test/*.js', 'foo/bar.css'));
* //=> null
* ```
* @param {String} `glob` Glob pattern to use for matching.
* @param {String} `input` String to match
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`.
* @api public
*/
micromatch.capture = (glob, input, options) => {
let posix = utils.isWindows(options);
let regex = picomatch.makeRe(String(glob), { ...options, capture: true });
let match = regex.exec(posix ? utils.toPosixSlashes(input) : input);
if (match) {
return match.slice(1).map(v => v === void 0 ? '' : v);
}
};
/**
* Create a regular expression from the given glob `pattern`.
*
* ```js
* const mm = require('micromatch');
* // mm.makeRe(pattern[, options]);
*
* console.log(mm.makeRe('*.js'));
* //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/
* ```
* @param {String} `pattern` A glob pattern to convert to regex.
* @param {Object} `options`
* @return {RegExp} Returns a regex created from the given pattern.
* @api public
*/
micromatch.makeRe = (...args) => picomatch.makeRe(...args);
/**
* Scan a glob pattern to separate the pattern into segments. Used
* by the [split](#split) method.
*
* ```js
* const mm = require('micromatch');
* const state = mm.scan(pattern[, options]);
* ```
* @param {String} `pattern`
* @param {Object} `options`
* @return {Object} Returns an object with
* @api public
*/
micromatch.scan = (...args) => picomatch.scan(...args);
/**
* Parse a glob pattern to create the source string for a regular
* expression.
*
* ```js
* const mm = require('micromatch');
* const state = mm(pattern[, options]);
* ```
* @param {String} `glob`
* @param {Object} `options`
* @return {Object} Returns an object with useful properties and output to be used as regex source string.
* @api public
*/
micromatch.parse = (patterns, options) => {
let res = [];
for (let pattern of [].concat(patterns || [])) {
for (let str of braces(String(pattern), options)) {
res.push(picomatch.parse(str, options));
}
}
return res;
};
/**
* Process the given brace `pattern`.
*
* ```js
* const { braces } = require('micromatch');
* console.log(braces('foo/{a,b,c}/bar'));
* //=> [ 'foo/(a|b|c)/bar' ]
*
* console.log(braces('foo/{a,b,c}/bar', { expand: true }));
* //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ]
* ```
* @param {String} `pattern` String with brace pattern to process.
* @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options.
* @return {Array}
* @api public
*/
micromatch.braces = (pattern, options) => {
if (typeof pattern !== 'string') throw new TypeError('Expected a string');
if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) {
return [pattern];
}
return braces(pattern, options);
};
/**
* Expand braces
*/
micromatch.braceExpand = (pattern, options) => {
if (typeof pattern !== 'string') throw new TypeError('Expected a string');
return micromatch.braces(pattern, { ...options, expand: true });
};
/**
* Expose micromatch
*/
module.exports = micromatch;

View File

@@ -0,0 +1,118 @@
{
"name": "micromatch",
"description": "Glob matching for javascript/node.js. A replacement and faster alternative to minimatch and multimatch.",
"version": "4.0.2",
"homepage": "https://github.com/micromatch/micromatch",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"contributors": [
"(https://github.com/DianeLooney)",
"Amila Welihinda (amilajack.com)",
"Bogdan Chadkin (https://github.com/TrySound)",
"Brian Woodward (https://twitter.com/doowb)",
"Devon Govett (http://badassjs.com)",
"Elan Shanker (https://github.com/es128)",
"Fabrício Matté (https://ultcombo.js.org)",
"Jon Schlinkert (http://twitter.com/jonschlinkert)",
"Martin Kolárik (https://kolarik.sk)",
"Olsten Larck (https://i.am.charlike.online)",
"Paul Miller (paulmillr.com)",
"Tom Byrer (https://github.com/tomByrer)",
"Tyler Akins (http://rumkin.com)",
"Peter Bright <drpizza@quiscalusmexicanus.org> (https://github.com/drpizza)"
],
"repository": "micromatch/micromatch",
"bugs": {
"url": "https://github.com/micromatch/micromatch/issues"
},
"license": "MIT",
"files": [
"index.js"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha"
},
"dependencies": {
"braces": "^3.0.1",
"picomatch": "^2.0.5"
},
"devDependencies": {
"fill-range": "^7.0.1",
"gulp-format-md": "^2.0.0",
"minimatch": "^3.0.4",
"mocha": "^5.2.0",
"time-require": "github:jonschlinkert/time-require"
},
"keywords": [
"bash",
"bracket",
"character-class",
"expand",
"expansion",
"expression",
"extglob",
"extglobs",
"file",
"files",
"filter",
"find",
"glob",
"globbing",
"globs",
"globstar",
"lookahead",
"lookaround",
"lookbehind",
"match",
"matcher",
"matches",
"matching",
"micromatch",
"minimatch",
"multimatch",
"negate",
"negation",
"path",
"pattern",
"patterns",
"posix",
"regex",
"regexp",
"regular",
"shell",
"star",
"wildcard"
],
"verb": {
"toc": "collapsible",
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"related": {
"list": [
"braces",
"expand-brackets",
"extglob",
"fill-range",
"nanomatch"
]
},
"reflinks": [
"extglob",
"fill-range",
"glob-object",
"minimatch",
"multimatch"
]
}
}

View File

@@ -0,0 +1,69 @@
# Release history
**All notable changes to this project will be documented in this file.**
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
<details>
<summary><strong>Guiding Principles</strong></summary>
- Changelogs are for humans, not machines.
- There should be an entry for every single version.
- The same types of changes should be grouped.
- Versions and sections should be linkable.
- The latest version comes first.
- The release date of each versions is displayed.
- Mention whether you follow Semantic Versioning.
</details>
<details>
<summary><strong>Types of changes</strong></summary>
Changelog entries are classified using the following labels _(from [keep-a-changelog](http://keepachangelog.com/)_):
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
</details>
## [2.0.4] - 2019-04-10
### Fixed
- Readme link [fixed](https://github.com/micromatch/picomatch/pull/13/commits/a96ab3aa2b11b6861c23289964613d85563b05df) by @danez.
- `options.capture` now works as expected when fastpaths are enabled. See https://github.com/micromatch/picomatch/pull/12/commits/26aefd71f1cfaf95c37f1c1fcab68a693b037304. Thanks to @DrPizza.
## [2.0.0] - 2019-04-10
### Added
- Adds support for `options.onIgnore`. See the readme for details
- Adds support for `options.onResult`. See the readme for details
### Breaking changes
- The unixify option was renamed to `windows`
- caching and all related options and methods have been removed
## [1.0.0] - 2018-11-05
- adds `.onMatch` option
- improvements to `.scan` method
- numerous improvements and optimizations for matching and parsing
- better windows path handling
## 0.1.0 - 2017-04-13
First release.
[2.0.4]: https://github.com/jonschlinkert/micromatch/compare/2.0.0...2.0.4
[2.0.0]: https://github.com/jonschlinkert/micromatch/compare/1.0.0...2.0.0
[1.0.0]: https://github.com/jonschlinkert/micromatch/compare/0.1.0...1.0.0
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017-present, Jon Schlinkert.
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.

View File

@@ -0,0 +1,605 @@
<h1 align="center">Picomatch</h1>
<p align="center">
<a href="https://npmjs.org/package/picomatch">
<img src="https://img.shields.io/npm/v/picomatch.svg" alt="version" />
</a>
<a href="https://travis-ci.org/micromatch/picomatch">
<img src="https://img.shields.io/travis/micromatch/picomatch.svg" alt="travis" />
</a>
<a href="https://npmjs.org/package/picomatch">
<img src="https://img.shields.io/npm/dm/picomatch.svg" alt="downloads" />
</a>
</p>
<br>
<br>
<p align="center">
<strong>Blazing fast and accurate glob matcher written in JavaScript.</strong></br>
<em>No dependencies and full support for standard and extended Bash glob features, including braces, extglobs, POSIX brackets, and regular expressions.</em>
</p>
<br>
<br>
## Why picomatch?
* **Lightweight** - No dependencies
* **Minimal** - Tiny API surface. Main export is a function that takes a glob pattern and returns a matcher function.
* **Fast** - Loads in about 2ms (that's several times faster than a [single frame of a HD movie](http://www.endmemo.com/sconvert/framespersecondframespermillisecond.php) at 60fps)
* **Performant** - Use the returned matcher function to speed up repeat matching (like when watching files)
* **Accurate matching** - Using wildcards (`*` and `?`), globstars (`**`) for nested directories, [advanced globbing](#advanced-globbing) with extglobs, braces, and POSIX brackets, and support for escaping special characters with `\` or quotes.
* **Well tested** - Thousands of unit tests
See the [library comparison](#library-comparisons) to other libraries.
<br>
<br>
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save picomatch
```
<br>
## Usage
The main export is a function that takes a glob pattern and an options object and returns a function for matching strings.
```js
const pm = require('picomatch');
const isMatch = pm('*.js');
console.log(isMatch('abcd')); //=> false
console.log(isMatch('a.js')); //=> true
console.log(isMatch('a.md')); //=> false
console.log(isMatch('a/b.js')); //=> false
```
<br>
## API
### [picomatch](lib/picomatch.js#L30)
Creates a matcher function from one or more glob patterns. The returned function takes a string to match as its first argument, and returns true if the string is a match. The returned matcher function also takes a boolean as the second argument that, when true, returns an object with additional information.
**Params**
* `globs` **{String|Array}**: One or more glob patterns.
* `options` **{Object=}**
* `returns` **{Function=}**: Returns a matcher function.
**Example**
```js
const picomatch = require('picomatch');
// picomatch(glob[, options]);
const isMatch = picomatch('*.!(*a)');
console.log(isMatch('a.a')); //=> false
console.log(isMatch('a.b')); //=> true
```
### [.test](lib/picomatch.js#L109)
Test `input` with the given `regex`. This is used by the main `picomatch()` function to test the input string.
**Params**
* `input` **{String}**: String to test.
* `regex` **{RegExp}**
* `returns` **{Object}**: Returns an object with matching info.
**Example**
```js
const picomatch = require('picomatch');
// picomatch.test(input, regex[, options]);
console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/));
// { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
```
### [.matchBase](lib/picomatch.js#L153)
Match the basename of a filepath.
**Params**
* `input` **{String}**: String to test.
* `glob` **{RegExp|String}**: Glob pattern or regex created by [.makeRe](#makeRe).
* `returns` **{Boolean}**
**Example**
```js
const picomatch = require('picomatch');
// picomatch.matchBase(input, glob[, options]);
console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
```
### [.isMatch](lib/picomatch.js#L175)
Returns true if **any** of the given glob `patterns` match the specified `string`.
**Params**
* **{String|Array}**: str The string to test.
* **{String|Array}**: patterns One or more glob patterns to use for matching.
* **{Object}**: See available [options](#options).
* `returns` **{Boolean}**: Returns true if any patterns match `str`
**Example**
```js
const picomatch = require('picomatch');
// picomatch.isMatch(string, patterns[, options]);
console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
```
### [.parse](lib/picomatch.js#L191)
Parse a glob pattern to create the source string for a regular expression.
**Params**
* `glob` **{String}**
* `options` **{Object}**
* `returns` **{Object}**: Returns an object with useful properties and output to be used as a regex source string.
**Example**
```js
const picomatch = require('picomatch');
const result = picomatch.parse(glob[, options]);
```
### [.scan](lib/picomatch.js#L215)
Scan a glob pattern to separate the pattern into segments.
**Params**
* `input` **{String}**: Glob pattern to scan.
* `options` **{Object}**
* `returns` **{Object}**: Returns an object with
**Example**
```js
const picomatch = require('picomatch');
// picomatch.scan(input[, options]);
const result = picomatch.scan('!./foo/*.js');
console.log(result);
// { prefix: '!./',
// input: '!./foo/*.js',
// base: 'foo',
// glob: '*.js',
// negated: true,
// isGlob: true }
```
### [.makeRe](lib/picomatch.js#L233)
Create a regular expression from a glob pattern.
**Params**
* `input` **{String}**: A glob pattern to convert to regex.
* `options` **{Object}**
* `returns` **{RegExp}**: Returns a regex created from the given pattern.
**Example**
```js
const picomatch = require('picomatch');
// picomatch.makeRe(input[, options]);
console.log(picomatch.makeRe('*.js'));
//=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
```
### [.toRegex](lib/picomatch.js#L294)
Create a regular expression from the given regex source string.
**Params**
* `source` **{String}**: Regular expression source string.
* `options` **{Object}**
* `returns` **{RegExp}**
**Example**
```js
const picomatch = require('picomatch');
// picomatch.toRegex(source[, options]);
const { output } = picomatch.parse('*.js');
console.log(picomatch.toRegex(output));
//=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
```
<br>
## Options
| **Option** | **Type** | **Default value** | **Description** |
| --- | --- | --- | --- |
| `basename` | `boolean` | `false` | If set, then patterns without slashes will be matched against the basename of the path if it contains slashes. For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`. |
| `bash` | `boolean` | `false` | Follow bash matching rules more strictly - disallows backslashes as escape characters, and treats single stars as globstars (`**`). |
| `capture` | `boolean` | `undefined` | Return regex matches in supporting methods. |
| `contains` | `boolean` | `undefined` | Allows glob to match any part of the given string(s). |
| `cwd` | `string` | `process.cwd()` | Current working directory. Used by `picomatch.split()` |
| `debug` | `boolean` | `undefined` | Debug regular expressions when an error is thrown. |
| `dot` | `boolean` | `false` | Enable dotfile matching. By default, dotfiles are ignored unless a `.` is explicitly defined in the pattern, or `options.dot` is true |
| `expandRange` | `function` | `undefined` | Custom function for expanding ranges in brace patterns, such as `{a..z}`. The function receives the range values as two arguments, and it must return a string to be used in the generated regex. It's recommended that returned strings be wrapped in parentheses. |
| `failglob` | `boolean` | `false` | Throws an error if no matches are found. Based on the bash option of the same name. |
| `fastpaths` | `boolean` | `true` | To speed up processing, full parsing is skipped for a handful common glob patterns. Disable this behavior by setting this option to `false`. |
| `flags` | `boolean` | `undefined` | Regex flags to use in the generated regex. If defined, the `nocase` option will be overridden. |
| [format](#optionsformat) | `function` | `undefined` | Custom function for formatting the returned string. This is useful for removing leading slashes, converting Windows paths to Posix paths, etc. |
| `ignore` | `array\|string` | `undefined` | One or more glob patterns for excluding strings that should not be matched from the result. |
| `keepQuotes` | `boolean` | `false` | Retain quotes in the generated regex, since quotes may also be used as an alternative to backslashes. |
| `literalBrackets` | `boolean` | `undefined` | When `true`, brackets in the glob pattern will be escaped so that only literal brackets will be matched. |
| `lookbehinds` | `boolean` | `true` | Support regex positive and negative lookbehinds. Note that you must be using Node 8.1.10 or higher to enable regex lookbehinds. |
| `matchBase` | `boolean` | `false` | Alias for `basename` |
| `maxLength` | `boolean` | `65536` | Limit the max length of the input string. An error is thrown if the input string is longer than this value. |
| `nobrace` | `boolean` | `false` | Disable brace matching, so that `{a,b}` and `{1..3}` would be treated as literal characters. |
| `nobracket` | `boolean` | `undefined` | Disable matching with regex brackets. |
| `nocase` | `boolean` | `false` | Make matching case-insensitive. Equivalent to the regex `i` flag. Note that this option is overridden by the `flags` option. |
| `nodupes` | `boolean` | `true` | Deprecated, use `nounique` instead. This option will be removed in a future major release. By default duplicates are removed. Disable uniquification by setting this option to false. |
| `noext` | `boolean` | `false` | Alias for `noextglob` |
| `noextglob` | `boolean` | `false` | Disable support for matching with extglobs (like `+(a\|b)`) |
| `noglobstar` | `boolean` | `false` | Disable support for matching nested directories with globstars (`**`) |
| `nonegate` | `boolean` | `false` | Disable support for negating with leading `!` |
| `noquantifiers` | `boolean` | `false` | Disable support for regex quantifiers (like `a{1,2}`) and treat them as brace patterns to be expanded. |
| [onIgnore](#optionsonIgnore) | `function` | `undefined` | Function to be called on ignored items. |
| [onMatch](#optionsonMatch) | `function` | `undefined` | Function to be called on matched items. |
| [onResult](#optionsonResult) | `function` | `undefined` | Function to be called on all items, regardless of whether or not they are matched or ignored. |
| `posix` | `boolean` | `false` | Support POSX character classes ("posix brackets"). |
| `posixSlashes` | `boolean` | `undefined` | Convert all slashes in file paths to forward slashes. This does not convert slashes in the glob pattern itself |
| `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. |
| `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). |
| `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. |
| `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. |
| `unescape` | `boolean` | `undefined` | Remove backslashes preceding escaped characters in the glob pattern. By default, backslashes are retained. |
| `unixify` | `boolean` | `undefined` | Alias for `posixSlashes`, for backwards compatitibility. |
<br>
## Options Examples
### options.expandRange
**Type**: `function`
**Default**: `undefined`
Custom function for expanding ranges in brace patterns. The [fill-range](https://github.com/jonschlinkert/fill-range) library is ideal for this purpose, or you can use custom code to do whatever you need.
**Example**
The following example shows how to create a glob that matches a folder
```js
const fill = require('fill-range');
const regex = pm.makeRe('foo/{01..25}/bar', {
expandRange(a, b) {
return `(${fill(a, b, { toRegex: true })})`;
}
});
console.log(regex);
//=> /^(?:foo\/((?:0[1-9]|1[0-9]|2[0-5]))\/bar)$/
console.log(regex.test('foo/00/bar')) // false
console.log(regex.test('foo/01/bar')) // true
console.log(regex.test('foo/10/bar')) // true
console.log(regex.test('foo/22/bar')) // true
console.log(regex.test('foo/25/bar')) // true
console.log(regex.test('foo/26/bar')) // false
```
### options.format
**Type**: `function`
**Default**: `undefined`
Custom function for formatting strings before they're matched.
**Example**
```js
// strip leading './' from strings
const format = str => str.replace(/^\.\//, '');
const isMatch = picomatch('foo/*.js', { format });
console.log(isMatch('./foo/bar.js')); //=> true
```
### options.onMatch
```js
const onMatch = ({ glob, regex, input, output }) => {
console.log({ glob, regex, input, output });
};
const isMatch = picomatch('*', { onMatch });
isMatch('foo');
isMatch('bar');
isMatch('baz');
```
### options.onIgnore
```js
const onIgnore = ({ glob, regex, input, output }) => {
console.log({ glob, regex, input, output });
};
const isMatch = picomatch('*', { onIgnore, ignore: 'f*' });
isMatch('foo');
isMatch('bar');
isMatch('baz');
```
### options.onResult
```js
const onResult = ({ glob, regex, input, output }) => {
console.log({ glob, regex, input, output });
};
const isMatch = picomatch('*', { onResult, ignore: 'f*' });
isMatch('foo');
isMatch('bar');
isMatch('baz');
```
<br>
<br>
# Globbing features
* [Basic globbing](#basic-globbing) (Wildcard matching)
* [Advanced globbing](#advanced-globbing) (extglobs, posix brackets, brace matching)
## Basic globbing
| **Character** | **Description** |
| --- | --- |
| `*` | Matches any character zero or more times, excluding path separators. Does _not match_ path separators or hidden files or directories ("dotfiles"), unless explicitly enabled by setting the `dot` option to `true`. |
| `**` | Matches any character zero or more times, including path separators. Note that `**` will only match path separators (`/`, and `\\` on Windows) when they are the only characters in a path segment. Thus, `foo**/bar` is equivalent to `foo*/bar`, and `foo/a**b/bar` is equivalent to `foo/a*b/bar`, and _more than two_ consecutive stars in a glob path segment are regarded as _a single star_. Thus, `foo/***/bar` is equivalent to `foo/*/bar`. |
| `?` | Matches any character excluding path separators one time. Does _not match_ path separators or leading dots. |
| `[abc]` | Matches any characters inside the brackets. For example, `[abc]` would match the characters `a`, `b` or `c`, and nothing else. |
### Matching behavior vs. Bash
Picomatch's matching features and expected results in unit tests are based on Bash's unit tests and the Bash 4.3 specification, with the following exceptions:
* Bash will match `foo/bar/baz` with `*`. Picomatch only matches nested directories with `**`.
* Bash greedily matches with negated extglobs. For example, Bash 4.3 says that `!(foo)*` should match `foo` and `foobar`, since the trailing `*` bracktracks to match the preceding pattern. This is very memory-inefficient, and IMHO, also incorrect. Picomatch would return `false` for both `foo` and `foobar`.
<br>
## Advanced globbing
* [extglobs](#extglobs)
* [POSIX brackets](#posix-brackets)
* [Braces](#brace-expansion)
### Extglobs
| **Pattern** | **Description** |
| --- | --- | --- |
| `@(pattern)` | Match _only one_ consecutive occurrence of `pattern` |
| `*(pattern)` | Match _zero or more_ consecutive occurrences of `pattern` |
| `+(pattern)` | Match _one or more_ consecutive occurrences of `pattern` |
| `?(pattern)` | Match _zero or **one**_ consecutive occurrences of `pattern` |
| `!(pattern)` | Match _anything but_ `pattern` |
**Examples**
```js
const pm = require('picomatch');
// *(pattern) matches ZERO or more of "pattern"
console.log(pm.isMatch('a', 'a*(z)')); // true
console.log(pm.isMatch('az', 'a*(z)')); // true
console.log(pm.isMatch('azzz', 'a*(z)')); // true
// +(pattern) matches ONE or more of "pattern"
console.log(pm.isMatch('a', 'a*(z)')); // true
console.log(pm.isMatch('az', 'a*(z)')); // true
console.log(pm.isMatch('azzz', 'a*(z)')); // true
// supports multiple extglobs
console.log(pm.isMatch('foo.bar', '!(foo).!(bar)')); // false
console.log(pm.isMatch('foo.bar', '!(foo).!(bar)')); // false
console.log(pm.isMatch('foo.bar', '!(foo).!(bar)')); // false
console.log(pm.isMatch('foo.bar', '!(foo).!(bar)')); // true
// supports nested extglobs
console.log(pm.isMatch('foo.bar', '!(!(foo)).!(!(bar))')); // true
```
### POSIX brackets
POSIX classes are disabled by default. Enable this feature by setting the `posix` option to true.
**Enable POSIX bracket support**
```js
console.log(pm.makeRe('[[:word:]]+', { posix: true }));
//=> /^(?:(?=.)[A-Za-z0-9_]+\/?)$/
```
**Supported POSIX classes**
The following named POSIX bracket expressions are supported:
* `[:alnum:]` - Alphanumeric characters, equ `[a-zA-Z0-9]`
* `[:alpha:]` - Alphabetical characters, equivalent to `[a-zA-Z]`.
* `[:ascii:]` - ASCII characters, equivalent to `[\\x00-\\x7F]`.
* `[:blank:]` - Space and tab characters, equivalent to `[ \\t]`.
* `[:cntrl:]` - Control characters, equivalent to `[\\x00-\\x1F\\x7F]`.
* `[:digit:]` - Numerical digits, equivalent to `[0-9]`.
* `[:graph:]` - Graph characters, equivalent to `[\\x21-\\x7E]`.
* `[:lower:]` - Lowercase letters, equivalent to `[a-z]`.
* `[:print:]` - Print characters, equivalent to `[\\x20-\\x7E ]`.
* `[:punct:]` - Punctuation and symbols, equivalent to `[\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~]`.
* `[:space:]` - Extended space characters, equivalent to `[ \\t\\r\\n\\v\\f]`.
* `[:upper:]` - Uppercase letters, equivalent to `[A-Z]`.
* `[:word:]` - Word characters (letters, numbers and underscores), equivalent to `[A-Za-z0-9_]`.
* `[:xdigit:]` - Hexadecimal digits, equivalent to `[A-Fa-f0-9]`.
See the [Bash Reference Manual](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html) for more information.
## Braces
Picomatch does not do brace expansion. For [brace expansion](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html) and advanced matching with braces, use [micromatch](https://github.com/micromatch/micromatch) instead. Picomatch has very basic support for braces.
## Matching special characters as literals
If you wish to match the following special characters in a filepath, and you want to use these characters in your glob pattern, they must be escaped with backslashes or quotes:
**Special Characters**
Some characters that are used for matching in regular expressions are also regarded as valid file path characters on some platforms.
To match any of the following characters as literals: `$^*+?()[]
Examples:
```js
console.log(pm.makeRe('foo/bar \\(1\\)'));
console.log(pm.makeRe('foo/bar \\(1\\)'));
```
<br>
<br>
## Library Comparisons
The following table shows which features are supported by [minimatch](https://github.com/isaacs/minimatch), [micromatch](https://github.com/micromatch/micromatch), [picomatch](https://github.com/micromatch/picomatch), [nanomatch](https://github.com/micromatch/nanomatch), [extglob](https://github.com/micromatch/extglob), [braces](https://github.com/micromatch/braces), and [expand-brackets](https://github.com/micromatch/expand-brackets).
| **Feature** | `minimatch` | `micromatch` | `picomatch` | `nanomatch` | `extglob` | `braces` | `expand-brackets` |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Wildcard matching (`*?+`) | ✔ | ✔ | ✔ | ✔ | - | - | - |
| Advancing globbing | ✔ | ✔ | ✔ | - | - | - | - |
| Brace _matching_ | ✔ | ✔ | ✔ | - | - | ✔ | - |
| Brace _expansion_ | ✔ | ✔ | - | - | - | ✔ | - |
| Extglobs | partial | ✔ | ✔ | - | ✔ | - | - |
| Posix brackets | - | ✔ | ✔ | - | - | - | ✔ |
| Regular expression syntax | - | ✔ | ✔ | ✔ | ✔ | - | ✔ |
| File system operations | - | - | - | - | - | - | - |
<br>
<br>
## Benchmarks
Performance comparison of picomatch and minimatch.
```
# .makeRe star
picomatch x 1,993,050 ops/sec ±0.51% (91 runs sampled)
minimatch x 627,206 ops/sec ±1.96% (87 runs sampled))
# .makeRe star; dot=true
picomatch x 1,436,640 ops/sec ±0.62% (91 runs sampled)
minimatch x 525,876 ops/sec ±0.60% (88 runs sampled)
# .makeRe globstar
picomatch x 1,592,742 ops/sec ±0.42% (90 runs sampled)
minimatch x 962,043 ops/sec ±1.76% (91 runs sampled)d)
# .makeRe globstars
picomatch x 1,615,199 ops/sec ±0.35% (94 runs sampled)
minimatch x 477,179 ops/sec ±1.33% (91 runs sampled)
# .makeRe with leading star
picomatch x 1,220,856 ops/sec ±0.40% (92 runs sampled)
minimatch x 453,564 ops/sec ±1.43% (94 runs sampled)
# .makeRe - basic braces
picomatch x 392,067 ops/sec ±0.70% (90 runs sampled)
minimatch x 99,532 ops/sec ±2.03% (87 runs sampled))
```
<br>
<br>
## Philosophies
The goal of this library is to be blazing fast, without compromising on accuracy.
**Accuracy**
The number one of goal of this libary is accuracy. However, it's not unusual for different glob implementations to have different rules for matching behavior, even with simple wildcard matching. It gets increasingly more complicated when combinations of different features are combined, like when extglobs are combined with globstars, braces, slashes, and so on: `!(**/{a,b,*/c})`.
Thus, given that there is no canonical glob specification to use as a single source of truth when differences of opinion arise regarding behavior, sometimes we have to implement our best judgement and rely on feedback from users to make improvements.
**Performance**
Although this library performs well in benchmarks, and in most cases it's faster than other popular libraries we benchmarked against, we will always choose accuracy over performance. It's not helpful to anyone if our library is faster at returning the wrong answer.
<br>
<br>
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
Please read the [contributing guide](.github/contributing.md) for advice on opening issues, pull requests, and coding standards.
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Author
**Jon Schlinkert**
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
### License
Copyright © 2017-present, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).

View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib/picomatch');

View File

@@ -0,0 +1,179 @@
'use strict';
const path = require('path');
const WIN_SLASH = '\\\\/';
const WIN_NO_SLASH = `[^${WIN_SLASH}]`;
/**
* Posix glob regex
*/
const DOT_LITERAL = '\\.';
const PLUS_LITERAL = '\\+';
const QMARK_LITERAL = '\\?';
const SLASH_LITERAL = '\\/';
const ONE_CHAR = '(?=.)';
const QMARK = '[^/]';
const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`;
const START_ANCHOR = `(?:^|${SLASH_LITERAL})`;
const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`;
const NO_DOT = `(?!${DOT_LITERAL})`;
const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`;
const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`;
const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`;
const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`;
const STAR = `${QMARK}*?`;
const POSIX_CHARS = {
DOT_LITERAL,
PLUS_LITERAL,
QMARK_LITERAL,
SLASH_LITERAL,
ONE_CHAR,
QMARK,
END_ANCHOR,
DOTS_SLASH,
NO_DOT,
NO_DOTS,
NO_DOT_SLASH,
NO_DOTS_SLASH,
QMARK_NO_DOT,
STAR,
START_ANCHOR
};
/**
* Windows glob regex
*/
const WINDOWS_CHARS = {
...POSIX_CHARS,
SLASH_LITERAL: `[${WIN_SLASH}]`,
QMARK: WIN_NO_SLASH,
STAR: `${WIN_NO_SLASH}*?`,
DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`,
NO_DOT: `(?!${DOT_LITERAL})`,
NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`,
NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`,
QMARK_NO_DOT: `[^.${WIN_SLASH}]`,
START_ANCHOR: `(?:^|[${WIN_SLASH}])`,
END_ANCHOR: `(?:[${WIN_SLASH}]|$)`
};
/**
* POSIX Bracket Regex
*/
const POSIX_REGEX_SOURCE = {
alnum: 'a-zA-Z0-9',
alpha: 'a-zA-Z',
ascii: '\\x00-\\x7F',
blank: ' \\t',
cntrl: '\\x00-\\x1F\\x7F',
digit: '0-9',
graph: '\\x21-\\x7E',
lower: 'a-z',
print: '\\x20-\\x7E ',
punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~',
space: ' \\t\\r\\n\\v\\f',
upper: 'A-Z',
word: 'A-Za-z0-9_',
xdigit: 'A-Fa-f0-9'
};
module.exports = {
MAX_LENGTH: 1024 * 64,
POSIX_REGEX_SOURCE,
// regular expressions
REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g,
REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/,
REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/,
REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g,
REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g,
REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g,
// Replace globs with equivalent patterns to reduce parsing time.
REPLACEMENTS: {
'***': '*',
'**/**': '**',
'**/**/**': '**'
},
// Digits
CHAR_0: 48, /* 0 */
CHAR_9: 57, /* 9 */
// Alphabet chars.
CHAR_UPPERCASE_A: 65, /* A */
CHAR_LOWERCASE_A: 97, /* a */
CHAR_UPPERCASE_Z: 90, /* Z */
CHAR_LOWERCASE_Z: 122, /* z */
CHAR_LEFT_PARENTHESES: 40, /* ( */
CHAR_RIGHT_PARENTHESES: 41, /* ) */
CHAR_ASTERISK: 42, /* * */
// Non-alphabetic chars.
CHAR_AMPERSAND: 38, /* & */
CHAR_AT: 64, /* @ */
CHAR_BACKWARD_SLASH: 92, /* \ */
CHAR_CARRIAGE_RETURN: 13, /* \r */
CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */
CHAR_COLON: 58, /* : */
CHAR_COMMA: 44, /* , */
CHAR_DOT: 46, /* . */
CHAR_DOUBLE_QUOTE: 34, /* " */
CHAR_EQUAL: 61, /* = */
CHAR_EXCLAMATION_MARK: 33, /* ! */
CHAR_FORM_FEED: 12, /* \f */
CHAR_FORWARD_SLASH: 47, /* / */
CHAR_GRAVE_ACCENT: 96, /* ` */
CHAR_HASH: 35, /* # */
CHAR_HYPHEN_MINUS: 45, /* - */
CHAR_LEFT_ANGLE_BRACKET: 60, /* < */
CHAR_LEFT_CURLY_BRACE: 123, /* { */
CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */
CHAR_LINE_FEED: 10, /* \n */
CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */
CHAR_PERCENT: 37, /* % */
CHAR_PLUS: 43, /* + */
CHAR_QUESTION_MARK: 63, /* ? */
CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */
CHAR_RIGHT_CURLY_BRACE: 125, /* } */
CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */
CHAR_SEMICOLON: 59, /* ; */
CHAR_SINGLE_QUOTE: 39, /* ' */
CHAR_SPACE: 32, /* */
CHAR_TAB: 9, /* \t */
CHAR_UNDERSCORE: 95, /* _ */
CHAR_VERTICAL_LINE: 124, /* | */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */
SEP: path.sep,
/**
* Create EXTGLOB_CHARS
*/
extglobChars(chars) {
return {
'!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` },
'?': { type: 'qmark', open: '(?:', close: ')?' },
'+': { type: 'plus', open: '(?:', close: ')+' },
'*': { type: 'star', open: '(?:', close: ')*' },
'@': { type: 'at', open: '(?:', close: ')' }
};
},
/**
* Create GLOB_CHARS
*/
globChars(win32) {
return win32 === true ? WINDOWS_CHARS : POSIX_CHARS;
}
};

View File

@@ -0,0 +1,315 @@
'use strict';
const path = require('path');
const scan = require('./scan');
const parse = require('./parse');
const utils = require('./utils');
/**
* Creates a matcher function from one or more glob patterns. The
* returned function takes a string to match as its first argument,
* and returns true if the string is a match. The returned matcher
* function also takes a boolean as the second argument that, when true,
* returns an object with additional information.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch(glob[, options]);
*
* const isMatch = picomatch('*.!(*a)');
* console.log(isMatch('a.a')); //=> false
* console.log(isMatch('a.b')); //=> true
* ```
* @name picomatch
* @param {String|Array} `globs` One or more glob patterns.
* @param {Object=} `options`
* @return {Function=} Returns a matcher function.
* @api public
*/
const picomatch = (glob, options, returnState = false) => {
if (Array.isArray(glob)) {
let fns = glob.map(input => picomatch(input, options, returnState));
return str => {
for (let isMatch of fns) {
let state = isMatch(str);
if (state) return state;
}
return false;
};
}
if (typeof glob !== 'string' || glob === '') {
throw new TypeError('Expected pattern to be a non-empty string');
}
let opts = options || {};
let posix = utils.isWindows(options);
let regex = picomatch.makeRe(glob, options, false, true);
let state = regex.state;
delete regex.state;
let isIgnored = () => false;
if (opts.ignore) {
let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null };
isIgnored = picomatch(opts.ignore, ignoreOpts, returnState);
}
const matcher = (input, returnObject = false) => {
let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix });
let result = { glob, state, regex, posix, input, output, match, isMatch };
if (typeof opts.onResult === 'function') {
opts.onResult(result);
}
if (isMatch === false) {
result.isMatch = false;
return returnObject ? result : false;
}
if (isIgnored(input)) {
if (typeof opts.onIgnore === 'function') {
opts.onIgnore(result);
}
result.isMatch = false;
return returnObject ? result : false;
}
if (typeof opts.onMatch === 'function') {
opts.onMatch(result);
}
return returnObject ? result : true;
};
if (returnState) {
matcher.state = state;
}
return matcher;
};
/**
* Test `input` with the given `regex`. This is used by the main
* `picomatch()` function to test the input string.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.test(input, regex[, options]);
*
* console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/));
* // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' }
* ```
* @param {String} `input` String to test.
* @param {RegExp} `regex`
* @return {Object} Returns an object with matching info.
* @api public
*/
picomatch.test = (input, regex, options, { glob, posix } = {}) => {
if (typeof input !== 'string') {
throw new TypeError('Expected input to be a string');
}
if (input === '') {
return { isMatch: false, output: '' };
}
let opts = options || {};
let format = opts.format || (posix ? utils.toPosixSlashes : null);
let match = input === glob;
let output = (match && format) ? format(input) : input;
if (match === false) {
output = format ? format(input) : input;
match = output === glob;
}
if (match === false || opts.capture === true) {
if (opts.matchBase === true || opts.basename === true) {
match = picomatch.matchBase(input, regex, options, posix);
} else {
match = regex.exec(output);
}
}
return { isMatch: !!match, match, output };
};
/**
* Match the basename of a filepath.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.matchBase(input, glob[, options]);
* console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true
* ```
* @param {String} `input` String to test.
* @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe).
* @return {Boolean}
* @api public
*/
picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => {
let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options);
return regex.test(path.basename(input));
};
/**
* Returns true if **any** of the given glob `patterns` match the specified `string`.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.isMatch(string, patterns[, options]);
*
* console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true
* console.log(picomatch.isMatch('a.a', 'b.*')); //=> false
* ```
* @param {String|Array} str The string to test.
* @param {String|Array} patterns One or more glob patterns to use for matching.
* @param {Object} [options] See available [options](#options).
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str);
/**
* Parse a glob pattern to create the source string for a regular
* expression.
*
* ```js
* const picomatch = require('picomatch');
* const result = picomatch.parse(glob[, options]);
* ```
* @param {String} `glob`
* @param {Object} `options`
* @return {Object} Returns an object with useful properties and output to be used as a regex source string.
* @api public
*/
picomatch.parse = (glob, options) => parse(glob, options);
/**
* Scan a glob pattern to separate the pattern into segments.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.scan(input[, options]);
*
* const result = picomatch.scan('!./foo/*.js');
* console.log(result);
* // { prefix: '!./',
* // input: '!./foo/*.js',
* // base: 'foo',
* // glob: '*.js',
* // negated: true,
* // isGlob: true }
* ```
* @param {String} `input` Glob pattern to scan.
* @param {Object} `options`
* @return {Object} Returns an object with
* @api public
*/
picomatch.scan = (input, options) => scan(input, options);
/**
* Create a regular expression from a glob pattern.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.makeRe(input[, options]);
*
* console.log(picomatch.makeRe('*.js'));
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
* ```
* @param {String} `input` A glob pattern to convert to regex.
* @param {Object} `options`
* @return {RegExp} Returns a regex created from the given pattern.
* @api public
*/
picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => {
if (!input || typeof input !== 'string') {
throw new TypeError('Expected a non-empty string');
}
let opts = options || {};
let prepend = opts.contains ? '' : '^';
let append = opts.contains ? '' : '$';
let state = { negated: false, fastpaths: true };
let prefix = '';
let output;
if (input.startsWith('./')) {
input = input.slice(2);
prefix = state.prefix = './';
}
if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
output = parse.fastpaths(input, options);
}
if (output === void 0) {
state = picomatch.parse(input, options);
state.prefix = prefix + (state.prefix || '');
output = state.output;
}
if (returnOutput === true) {
return output;
}
let source = `${prepend}(?:${output})${append}`;
if (state && state.negated === true) {
source = `^(?!${source}).*$`;
}
let regex = picomatch.toRegex(source, options);
if (returnState === true) {
regex.state = state;
}
return regex;
};
/**
* Create a regular expression from the given regex source string.
*
* ```js
* const picomatch = require('picomatch');
* // picomatch.toRegex(source[, options]);
*
* const { output } = picomatch.parse('*.js');
* console.log(picomatch.toRegex(output));
* //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/
* ```
* @param {String} `source` Regular expression source string.
* @param {Object} `options`
* @return {RegExp}
* @api public
*/
picomatch.toRegex = (source, options) => {
try {
let opts = options || {};
return new RegExp(source, opts.flags || (opts.nocase ? 'i' : ''));
} catch (err) {
if (options && options.debug === true) throw err;
return /$^/;
}
};
/**
* Picomatch constants.
* @return {Object}
*/
picomatch.constants = require('./constants');
/**
* Expose "picomatch"
*/
module.exports = picomatch;

View File

@@ -0,0 +1,219 @@
'use strict';
const utils = require('./utils');
const {
CHAR_ASTERISK, /* * */
CHAR_AT, /* @ */
CHAR_BACKWARD_SLASH, /* \ */
CHAR_COMMA, /* , */
CHAR_DOT, /* . */
CHAR_EXCLAMATION_MARK, /* ! */
CHAR_FORWARD_SLASH, /* / */
CHAR_LEFT_CURLY_BRACE, /* { */
CHAR_LEFT_PARENTHESES, /* ( */
CHAR_LEFT_SQUARE_BRACKET, /* [ */
CHAR_PLUS, /* + */
CHAR_QUESTION_MARK, /* ? */
CHAR_RIGHT_CURLY_BRACE, /* } */
CHAR_RIGHT_PARENTHESES, /* ) */
CHAR_RIGHT_SQUARE_BRACKET /* ] */
} = require('./constants');
const isPathSeparator = code => {
return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH;
};
/**
* Quickly scans a glob pattern and returns an object with a handful of
* useful properties, like `isGlob`, `path` (the leading non-glob, if it exists),
* `glob` (the actual pattern), and `negated` (true if the path starts with `!`).
*
* ```js
* const pm = require('picomatch');
* console.log(pm.scan('foo/bar/*.js'));
* { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' }
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {Object} Returns an object with tokens and regex source string.
* @api public
*/
module.exports = (input, options) => {
let opts = options || {};
let length = input.length - 1;
let index = -1;
let start = 0;
let lastIndex = 0;
let isGlob = false;
let backslashes = false;
let negated = false;
let braces = 0;
let prev;
let code;
let braceEscaped = false;
let eos = () => index >= length;
let advance = () => {
prev = code;
return input.charCodeAt(++index);
};
while (index < length) {
code = advance();
let next;
if (code === CHAR_BACKWARD_SLASH) {
backslashes = true;
next = advance();
if (next === CHAR_LEFT_CURLY_BRACE) {
braceEscaped = true;
}
continue;
}
if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) {
braces++;
while (!eos() && (next = advance())) {
if (next === CHAR_BACKWARD_SLASH) {
backslashes = true;
next = advance();
continue;
}
if (next === CHAR_LEFT_CURLY_BRACE) {
braces++;
continue;
}
if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) {
isGlob = true;
break;
}
if (!braceEscaped && next === CHAR_COMMA) {
isGlob = true;
break;
}
if (next === CHAR_RIGHT_CURLY_BRACE) {
braces--;
if (braces === 0) {
braceEscaped = false;
break;
}
}
}
}
if (code === CHAR_FORWARD_SLASH) {
if (prev === CHAR_DOT && index === (start + 1)) {
start += 2;
continue;
}
lastIndex = index + 1;
continue;
}
if (code === CHAR_ASTERISK) {
isGlob = true;
break;
}
if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) {
isGlob = true;
break;
}
if (code === CHAR_LEFT_SQUARE_BRACKET) {
while (!eos() && (next = advance())) {
if (next === CHAR_BACKWARD_SLASH) {
backslashes = true;
next = advance();
continue;
}
if (next === CHAR_RIGHT_SQUARE_BRACKET) {
isGlob = true;
break;
}
}
}
let isExtglobChar = code === CHAR_PLUS
|| code === CHAR_AT
|| code === CHAR_EXCLAMATION_MARK;
if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) {
isGlob = true;
break;
}
if (code === CHAR_EXCLAMATION_MARK && index === start) {
negated = true;
start++;
continue;
}
if (code === CHAR_LEFT_PARENTHESES) {
while (!eos() && (next = advance())) {
if (next === CHAR_BACKWARD_SLASH) {
backslashes = true;
next = advance();
continue;
}
if (next === CHAR_RIGHT_PARENTHESES) {
isGlob = true;
break;
}
}
}
if (isGlob) {
break;
}
}
let prefix = '';
let orig = input;
let base = input;
let glob = '';
if (start > 0) {
prefix = input.slice(0, start);
input = input.slice(start);
lastIndex -= start;
}
if (base && isGlob === true && lastIndex > 0) {
base = input.slice(0, lastIndex);
glob = input.slice(lastIndex);
} else if (isGlob === true) {
base = '';
glob = input;
} else {
base = input;
}
if (base && base !== '' && base !== '/' && base !== input) {
if (isPathSeparator(base.charCodeAt(base.length - 1))) {
base = base.slice(0, -1);
}
}
if (opts.unescape === true) {
if (glob) glob = utils.removeBackslashes(glob);
if (base && backslashes === true) {
base = utils.removeBackslashes(base);
}
}
return { prefix, input: orig, base, glob, negated, isGlob };
};

View File

@@ -0,0 +1,43 @@
'use strict';
const path = require('path');
const win32 = process.platform === 'win32';
const {
REGEX_SPECIAL_CHARS,
REGEX_SPECIAL_CHARS_GLOBAL,
REGEX_REMOVE_BACKSLASH
} = require('./constants');
exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val);
exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str);
exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str);
exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1');
exports.toPosixSlashes = str => str.replace(/\\/g, '/');
exports.removeBackslashes = str => {
return str.replace(REGEX_REMOVE_BACKSLASH, match => {
return match === '\\' ? '' : match;
});
}
exports.supportsLookbehinds = () => {
let segs = process.version.slice(1).split('.');
if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) {
return true;
}
return false;
};
exports.isWindows = options => {
if (options && typeof options.windows === 'boolean') {
return options.windows;
}
return win32 === true || path.sep === '\\';
};
exports.escapeLast = (input, char, lastIdx) => {
let idx = input.lastIndexOf(char, lastIdx);
if (idx === -1) return input;
if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1);
return input.slice(0, idx) + '\\' + input.slice(idx);
};

View File

@@ -0,0 +1,65 @@
{
"name": "picomatch",
"description": "Blazing fast and accurate glob matcher written in JavaScript, with no dependencies and full support for standard and extended Bash glob features, including braces, extglobs, POSIX brackets, and regular expressions.",
"version": "2.0.7",
"homepage": "https://github.com/micromatch/picomatch",
"author": "Jon Schlinkert (https://github.com/jonschlinkert)",
"repository": "micromatch/picomatch",
"bugs": {
"url": "https://github.com/micromatch/picomatch/issues"
},
"license": "MIT",
"files": [
"index.js",
"lib"
],
"main": "index.js",
"engines": {
"node": ">=8"
},
"scripts": {
"test": "mocha",
"cover": "nyc --reporter=text --reporter=html mocha"
},
"devDependencies": {
"fill-range": "^7.0.1",
"gulp-format-md": "^2.0.0",
"mocha": "^6.0.2",
"nyc": "^13.3.0",
"time-require": "github:jonschlinkert/time-require"
},
"keywords": [
"glob",
"match",
"picomatch"
],
"verb": {
"toc": false,
"layout": false,
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"related": {
"list": [
"micromatch",
"braces"
]
},
"reflinks": [
"braces",
"expand-brackets",
"extglob",
"fill-range",
"micromatch",
"minimatch",
"nanomatch",
"picomatch"
]
}
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present, Jon Schlinkert.
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.

View File

@@ -0,0 +1,305 @@
# to-regex-range [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=W8YFZ425KND68) [![NPM version](https://img.shields.io/npm/v/to-regex-range.svg?style=flat)](https://www.npmjs.com/package/to-regex-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![NPM total downloads](https://img.shields.io/npm/dt/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![Linux Build Status](https://img.shields.io/travis/micromatch/to-regex-range.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/to-regex-range)
> Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.78 million test assertions.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save to-regex-range
```
<details>
<summary><strong>What does this do?</strong></summary>
<br>
This libary generates the `source` string to be passed to `new RegExp()` for matching a range of numbers.
**Example**
```js
const toRegexRange = require('to-regex-range');
const regex = new RegExp(toRegexRange('15', '95'));
```
A string is returned so that you can do whatever you need with it before passing it to `new RegExp()` (like adding `^` or `$` boundaries, defining flags, or combining it another string).
<br>
</details>
<details>
<summary><strong>Why use this library?</strong></summary>
<br>
### Convenience
Creating regular expressions for matching numbers gets deceptively complicated pretty fast.
For example, let's say you need a validation regex for matching part of a user-id, postal code, social security number, tax id, etc:
* regex for matching `1` => `/1/` (easy enough)
* regex for matching `1` through `5` => `/[1-5]/` (not bad...)
* regex for matching `1` or `5` => `/(1|5)/` (still easy...)
* regex for matching `1` through `50` => `/([1-9]|[1-4][0-9]|50)/` (uh-oh...)
* regex for matching `1` through `55` => `/([1-9]|[1-4][0-9]|5[0-5])/` (no prob, I can do this...)
* regex for matching `1` through `555` => `/([1-9]|[1-9][0-9]|[1-4][0-9]{2}|5[0-4][0-9]|55[0-5])/` (maybe not...)
* regex for matching `0001` through `5555` => `/(0{3}[1-9]|0{2}[1-9][0-9]|0[1-9][0-9]{2}|[1-4][0-9]{3}|5[0-4][0-9]{2}|55[0-4][0-9]|555[0-5])/` (okay, I get the point!)
The numbers are contrived, but they're also really basic. In the real world you might need to generate a regex on-the-fly for validation.
**Learn more**
If you're interested in learning more about [character classes](http://www.regular-expressions.info/charclass.html) and other regex features, I personally have always found [regular-expressions.info](http://www.regular-expressions.info/charclass.html) to be pretty useful.
### Heavily tested
As of April 07, 2019, this library runs [>1m test assertions](./test/test.js) against generated regex-ranges to provide brute-force verification that results are correct.
Tests run in ~280ms on my MacBook Pro, 2.5 GHz Intel Core i7.
### Optimized
Generated regular expressions are optimized:
* duplicate sequences and character classes are reduced using quantifiers
* smart enough to use `?` conditionals when number(s) or range(s) can be positive or negative
* uses fragment caching to avoid processing the same exact string more than once
<br>
</details>
## Usage
Add this library to your javascript application with the following line of code
```js
const toRegexRange = require('to-regex-range');
```
The main export is a function that takes two integers: the `min` value and `max` value (formatted as strings or numbers).
```js
const source = toRegexRange('15', '95');
//=> 1[5-9]|[2-8][0-9]|9[0-5]
const regex = new RegExp(`^${source}$`);
console.log(regex.test('14')); //=> false
console.log(regex.test('50')); //=> true
console.log(regex.test('94')); //=> true
console.log(regex.test('96')); //=> false
```
## Options
### options.capture
**Type**: `boolean`
**Deafault**: `undefined`
Wrap the returned value in parentheses when there is more than one regex condition. Useful when you're dynamically generating ranges.
```js
console.log(toRegexRange('-10', '10'));
//=> -[1-9]|-?10|[0-9]
console.log(toRegexRange('-10', '10', { capture: true }));
//=> (-[1-9]|-?10|[0-9])
```
### options.shorthand
**Type**: `boolean`
**Deafault**: `undefined`
Use the regex shorthand for `[0-9]`:
```js
console.log(toRegexRange('0', '999999'));
//=> [0-9]|[1-9][0-9]{1,5}
console.log(toRegexRange('0', '999999', { shorthand: true }));
//=> \d|[1-9]\d{1,5}
```
### options.relaxZeros
**Type**: `boolean`
**Default**: `true`
This option relaxes matching for leading zeros when when ranges are zero-padded.
```js
const source = toRegexRange('-0010', '0010');
const regex = new RegExp(`^${source}$`);
console.log(regex.test('-10')); //=> true
console.log(regex.test('-010')); //=> true
console.log(regex.test('-0010')); //=> true
console.log(regex.test('10')); //=> true
console.log(regex.test('010')); //=> true
console.log(regex.test('0010')); //=> true
```
When `relaxZeros` is false, matching is strict:
```js
const source = toRegexRange('-0010', '0010', { relaxZeros: false });
const regex = new RegExp(`^${source}$`);
console.log(regex.test('-10')); //=> false
console.log(regex.test('-010')); //=> false
console.log(regex.test('-0010')); //=> true
console.log(regex.test('10')); //=> false
console.log(regex.test('010')); //=> false
console.log(regex.test('0010')); //=> true
```
## Examples
| **Range** | **Result** | **Compile time** |
| --- | --- | --- |
| `toRegexRange(-10, 10)` | `-[1-9]\|-?10\|[0-9]` | _132μs_ |
| `toRegexRange(-100, -10)` | `-1[0-9]\|-[2-9][0-9]\|-100` | _50μs_ |
| `toRegexRange(-100, 100)` | `-[1-9]\|-?[1-9][0-9]\|-?100\|[0-9]` | _42μs_ |
| `toRegexRange(001, 100)` | `0{0,2}[1-9]\|0?[1-9][0-9]\|100` | _109μs_ |
| `toRegexRange(001, 555)` | `0{0,2}[1-9]\|0?[1-9][0-9]\|[1-4][0-9]{2}\|5[0-4][0-9]\|55[0-5]` | _51μs_ |
| `toRegexRange(0010, 1000)` | `0{0,2}1[0-9]\|0{0,2}[2-9][0-9]\|0?[1-9][0-9]{2}\|1000` | _31μs_ |
| `toRegexRange(1, 50)` | `[1-9]\|[1-4][0-9]\|50` | _24μs_ |
| `toRegexRange(1, 55)` | `[1-9]\|[1-4][0-9]\|5[0-5]` | _23μs_ |
| `toRegexRange(1, 555)` | `[1-9]\|[1-9][0-9]\|[1-4][0-9]{2}\|5[0-4][0-9]\|55[0-5]` | _30μs_ |
| `toRegexRange(1, 5555)` | `[1-9]\|[1-9][0-9]{1,2}\|[1-4][0-9]{3}\|5[0-4][0-9]{2}\|55[0-4][0-9]\|555[0-5]` | _43μs_ |
| `toRegexRange(111, 555)` | `11[1-9]\|1[2-9][0-9]\|[2-4][0-9]{2}\|5[0-4][0-9]\|55[0-5]` | _38μs_ |
| `toRegexRange(29, 51)` | `29\|[34][0-9]\|5[01]` | _24μs_ |
| `toRegexRange(31, 877)` | `3[1-9]\|[4-9][0-9]\|[1-7][0-9]{2}\|8[0-6][0-9]\|87[0-7]` | _32μs_ |
| `toRegexRange(5, 5)` | `5` | _8μs_ |
| `toRegexRange(5, 6)` | `5\|6` | _11μs_ |
| `toRegexRange(1, 2)` | `1\|2` | _6μs_ |
| `toRegexRange(1, 5)` | `[1-5]` | _15μs_ |
| `toRegexRange(1, 10)` | `[1-9]\|10` | _22μs_ |
| `toRegexRange(1, 100)` | `[1-9]\|[1-9][0-9]\|100` | _25μs_ |
| `toRegexRange(1, 1000)` | `[1-9]\|[1-9][0-9]{1,2}\|1000` | _31μs_ |
| `toRegexRange(1, 10000)` | `[1-9]\|[1-9][0-9]{1,3}\|10000` | _34μs_ |
| `toRegexRange(1, 100000)` | `[1-9]\|[1-9][0-9]{1,4}\|100000` | _36μs_ |
| `toRegexRange(1, 1000000)` | `[1-9]\|[1-9][0-9]{1,5}\|1000000` | _42μs_ |
| `toRegexRange(1, 10000000)` | `[1-9]\|[1-9][0-9]{1,6}\|10000000` | _42μs_ |
## Heads up!
**Order of arguments**
When the `min` is larger than the `max`, values will be flipped to create a valid range:
```js
toRegexRange('51', '29');
```
Is effectively flipped to:
```js
toRegexRange('29', '51');
//=> 29|[3-4][0-9]|5[0-1]
```
**Steps / increments**
This library does not support steps (increments). A pr to add support would be welcome.
## History
### v2.0.0 - 2017-04-21
**New features**
Adds support for zero-padding!
### v1.0.0
**Optimizations**
Repeating ranges are now grouped using quantifiers. rocessing time is roughly the same, but the generated regex is much smaller, which should result in faster matching.
## Attribution
Inspired by the python library [range-regex](https://github.com/dimka665/range-regex).
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Related projects
You might also be interested in these projects:
* [expand-range](https://www.npmjs.com/package/expand-range): Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. Used… [more](https://github.com/jonschlinkert/expand-range) | [homepage](https://github.com/jonschlinkert/expand-range "Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. Used by micromatch.")
* [fill-range](https://www.npmjs.com/package/fill-range): Fill in a range of numbers or letters, optionally passing an increment or `step` to… [more](https://github.com/jonschlinkert/fill-range) | [homepage](https://github.com/jonschlinkert/fill-range "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`")
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/micromatch/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [repeat-element](https://www.npmjs.com/package/repeat-element): Create an array by repeating the given value n times. | [homepage](https://github.com/jonschlinkert/repeat-element "Create an array by repeating the given value n times.")
* [repeat-string](https://www.npmjs.com/package/repeat-string): Repeat the given string n times. Fastest implementation for repeating a string. | [homepage](https://github.com/jonschlinkert/repeat-string "Repeat the given string n times. Fastest implementation for repeating a string.")
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 63 | [jonschlinkert](https://github.com/jonschlinkert) |
| 3 | [doowb](https://github.com/doowb) |
| 2 | [realityking](https://github.com/realityking) |
### Author
**Jon Schlinkert**
* [GitHub Profile](https://github.com/jonschlinkert)
* [Twitter Profile](https://twitter.com/jonschlinkert)
* [LinkedIn Profile](https://linkedin.com/in/jonschlinkert)
Please consider supporting me on Patreon, or [start your own Patreon page](https://patreon.com/invite/bxpbvm)!
<a href="https://www.patreon.com/jonschlinkert">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" height="50">
</a>
### License
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 07, 2019._

View File

@@ -0,0 +1,288 @@
/*!
* to-regex-range <https://github.com/micromatch/to-regex-range>
*
* Copyright (c) 2015-present, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
const isNumber = require('is-number');
const toRegexRange = (min, max, options) => {
if (isNumber(min) === false) {
throw new TypeError('toRegexRange: expected the first argument to be a number');
}
if (max === void 0 || min === max) {
return String(min);
}
if (isNumber(max) === false) {
throw new TypeError('toRegexRange: expected the second argument to be a number.');
}
let opts = { relaxZeros: true, ...options };
if (typeof opts.strictZeros === 'boolean') {
opts.relaxZeros = opts.strictZeros === false;
}
let relax = String(opts.relaxZeros);
let shorthand = String(opts.shorthand);
let capture = String(opts.capture);
let wrap = String(opts.wrap);
let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap;
if (toRegexRange.cache.hasOwnProperty(cacheKey)) {
return toRegexRange.cache[cacheKey].result;
}
let a = Math.min(min, max);
let b = Math.max(min, max);
if (Math.abs(a - b) === 1) {
let result = min + '|' + max;
if (opts.capture) {
return `(${result})`;
}
if (opts.wrap === false) {
return result;
}
return `(?:${result})`;
}
let isPadded = hasPadding(min) || hasPadding(max);
let state = { min, max, a, b };
let positives = [];
let negatives = [];
if (isPadded) {
state.isPadded = isPadded;
state.maxLen = String(state.max).length;
}
if (a < 0) {
let newMin = b < 0 ? Math.abs(b) : 1;
negatives = splitToPatterns(newMin, Math.abs(a), state, opts);
a = state.a = 0;
}
if (b >= 0) {
positives = splitToPatterns(a, b, state, opts);
}
state.negatives = negatives;
state.positives = positives;
state.result = collatePatterns(negatives, positives, opts);
if (opts.capture === true) {
state.result = `(${state.result})`;
} else if (opts.wrap !== false && (positives.length + negatives.length) > 1) {
state.result = `(?:${state.result})`;
}
toRegexRange.cache[cacheKey] = state;
return state.result;
};
function collatePatterns(neg, pos, options) {
let onlyNegative = filterPatterns(neg, pos, '-', false, options) || [];
let onlyPositive = filterPatterns(pos, neg, '', false, options) || [];
let intersected = filterPatterns(neg, pos, '-?', true, options) || [];
let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive);
return subpatterns.join('|');
}
function splitToRanges(min, max) {
let nines = 1;
let zeros = 1;
let stop = countNines(min, nines);
let stops = new Set([max]);
while (min <= stop && stop <= max) {
stops.add(stop);
nines += 1;
stop = countNines(min, nines);
}
stop = countZeros(max + 1, zeros) - 1;
while (min < stop && stop <= max) {
stops.add(stop);
zeros += 1;
stop = countZeros(max + 1, zeros) - 1;
}
stops = [...stops];
stops.sort(compare);
return stops;
}
/**
* Convert a range to a regex pattern
* @param {Number} `start`
* @param {Number} `stop`
* @return {String}
*/
function rangeToPattern(start, stop, options) {
if (start === stop) {
return { pattern: start, count: [], digits: 0 };
}
let zipped = zip(start, stop);
let digits = zipped.length;
let pattern = '';
let count = 0;
for (let i = 0; i < digits; i++) {
let [startDigit, stopDigit] = zipped[i];
if (startDigit === stopDigit) {
pattern += startDigit;
} else if (startDigit !== '0' || stopDigit !== '9') {
pattern += toCharacterClass(startDigit, stopDigit, options);
} else {
count++;
}
}
if (count) {
pattern += options.shorthand === true ? '\\d' : '[0-9]';
}
return { pattern, count: [count], digits };
}
function splitToPatterns(min, max, tok, options) {
let ranges = splitToRanges(min, max);
let tokens = [];
let start = min;
let prev;
for (let i = 0; i < ranges.length; i++) {
let max = ranges[i];
let obj = rangeToPattern(String(start), String(max), options);
let zeros = '';
if (!tok.isPadded && prev && prev.pattern === obj.pattern) {
if (prev.count.length > 1) {
prev.count.pop();
}
prev.count.push(obj.count[0]);
prev.string = prev.pattern + toQuantifier(prev.count);
start = max + 1;
continue;
}
if (tok.isPadded) {
zeros = padZeros(max, tok, options);
}
obj.string = zeros + obj.pattern + toQuantifier(obj.count);
tokens.push(obj);
start = max + 1;
prev = obj;
}
return tokens;
}
function filterPatterns(arr, comparison, prefix, intersection, options) {
let result = [];
for (let ele of arr) {
let { string } = ele;
// only push if _both_ are negative...
if (!intersection && !contains(comparison, 'string', string)) {
result.push(prefix + string);
}
// or _both_ are positive
if (intersection && contains(comparison, 'string', string)) {
result.push(prefix + string);
}
}
return result;
}
/**
* Zip strings
*/
function zip(a, b) {
let arr = [];
for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]);
return arr;
}
function compare(a, b) {
return a > b ? 1 : b > a ? -1 : 0;
}
function contains(arr, key, val) {
return arr.some(ele => ele[key] === val);
}
function countNines(min, len) {
return Number(String(min).slice(0, -len) + '9'.repeat(len));
}
function countZeros(integer, zeros) {
return integer - (integer % Math.pow(10, zeros));
}
function toQuantifier(digits) {
let [start = 0, stop = ''] = digits;
if (stop || start > 1) {
return `{${start + (stop ? ',' + stop : '')}}`;
}
return '';
}
function toCharacterClass(a, b, options) {
return `[${a}${(b - a === 1) ? '' : '-'}${b}]`;
}
function hasPadding(str) {
return /^-?(0+)\d/.test(str);
}
function padZeros(value, tok, options) {
if (!tok.isPadded) {
return value;
}
let diff = Math.abs(tok.maxLen - String(value).length);
let relax = options.relaxZeros !== false;
switch (diff) {
case 0:
return '';
case 1:
return relax ? '0?' : '0';
case 2:
return relax ? '0{0,2}' : '00';
default: {
return relax ? `0{0,${diff}}` : `0{${diff}}`;
}
}
}
/**
* Cache
*/
toRegexRange.cache = {};
toRegexRange.clearCache = () => (toRegexRange.cache = {});
/**
* Expose `toRegexRange`
*/
module.exports = toRegexRange;

Some files were not shown because too many files have changed in this diff Show More