neiam /action-setup-beam
action-setup-beam
public · Issues · Pulls · Labels · Forks · Compare · Actions queued
⭐
Log in to mark this repository.
Support macOS via https://github.com/erlef/otp_builds (#332)
58f0e7a · Paulo F. Oliveira · 2025-05-29 10:38
Message
{commit_body(@commit)}
Files changed
modified
.github/workflows/action.yml
+17
−0
@@ -18,6 +18,8 @@ jobs:
| 18 | 18 | name: Version with commit unique id |
| 19 | 19 | runs-on: ubuntu-latest |
| 20 | 20 | if: github.ref == 'refs/heads/main' |
| 21 | + permissions: | |
| 22 | + contents: write | |
| 21 | 23 | steps: |
| 22 | 24 | - uses: actions/checkout@v4 |
| 23 | 25 | - name: Version it! |
@@ -39,6 +41,8 @@ jobs:
| 39 | 41 | name: Expected local npm actions |
| 40 | 42 | runs-on: ubuntu-latest |
| 41 | 43 | if: github.ref != 'refs/heads/main' |
| 44 | + permissions: | |
| 45 | + contents: read | |
| 42 | 46 | steps: |
| 43 | 47 | - uses: actions/checkout@v4 |
| 44 | 48 | - uses: actions/setup-node@v4 |
@@ -76,3 +80,16 @@ jobs:
| 76 | 80 | node-version: '20' |
| 77 | 81 | - run: npm install --production |
| 78 | 82 | - run: npm test |
| 83 | + | |
| 84 | + unit_tests_macos: | |
| 85 | + name: Unit tests (macOS) | |
| 86 | + runs-on: macos-latest | |
| 87 | + permissions: | |
| 88 | + contents: read | |
| 89 | + steps: | |
| 90 | + - uses: actions/checkout@v4 | |
| 91 | + - uses: actions/setup-node@v4 | |
| 92 | + with: | |
| 93 | + node-version: '20' | |
| 94 | + - run: npm ci | |
| 95 | + - run: npm test |
modified
.github/workflows/hexpm-mirrors.yml
+3
−3
@@ -17,9 +17,9 @@ jobs:
| 17 | 17 | strategy: |
| 18 | 18 | fail-fast: false |
| 19 | 19 | matrix: |
| 20 | − os: ['ubuntu-22.04', 'windows-2022'] | |
| 21 | − otp-version: ['24'] | |
| 22 | − elixir-version: ['v1.14', ''] | |
| 20 | + os: ['ubuntu-24.04', 'windows-2022', 'macos-15'] | |
| 21 | + otp-version: ['27'] | |
| 22 | + elixir-version: ['v1.18', ''] | |
| 23 | 23 | install-rebar: [true, false] |
| 24 | 24 | install-hex: [true, false] |
| 25 | 25 | steps: |
added
.github/workflows/macos.yml
+137
−0
@@ -0,0 +1,137 @@
| 1 | +--- | |
| 2 | +name: macos | |
| 3 | +permissions: | |
| 4 | + contents: read | |
| 5 | + | |
| 6 | +on: | |
| 7 | + push: | |
| 8 | + branches: | |
| 9 | + - main | |
| 10 | + pull_request: | |
| 11 | + branches: | |
| 12 | + - '*' | |
| 13 | + workflow_dispatch: {} | |
| 14 | + | |
| 15 | +jobs: | |
| 16 | + integration_test: | |
| 17 | + name: > | |
| 18 | + OTP ${{matrix.combo.otp-version}}, | |
| 19 | + Elixir ${{matrix.combo.elixir-version}}, | |
| 20 | + rebar3 ${{matrix.combo.rebar3-version}} | |
| 21 | + runs-on: ${{matrix.combo.os}} | |
| 22 | + strategy: | |
| 23 | + fail-fast: false | |
| 24 | + matrix: | |
| 25 | + combo: | |
| 26 | + - otp-version: '27' | |
| 27 | + elixir-version: 'v1.17.0' | |
| 28 | + rebar3-version: '3.23' | |
| 29 | + os: 'macos-latest' | |
| 30 | + - otp-version: '26.0' | |
| 31 | + elixir-version: 'v1.14-otp-25' | |
| 32 | + os: 'macos-latest' | |
| 33 | + version-type: 'strict' | |
| 34 | + - otp-version: '26.0' | |
| 35 | + elixir-version: '1.14.5' | |
| 36 | + os: 'macos-latest' | |
| 37 | + version-type: 'strict' | |
| 38 | + - otp-version: '25.2' | |
| 39 | + elixir-version: '1.14.2' | |
| 40 | + os: 'macos-latest' | |
| 41 | + - otp-version: '25.2' | |
| 42 | + elixir-version: '1.14.3' | |
| 43 | + os: 'macos-latest' | |
| 44 | + version-type: 'strict' | |
| 45 | + - otp-version: '27' | |
| 46 | + os: 'macos-15' | |
| 47 | + version-type: 'strict' | |
| 48 | + - otp-version: '26' | |
| 49 | + os: 'macos-15' | |
| 50 | + version-type: 'strict' | |
| 51 | + - otp-version: '25' | |
| 52 | + os: 'macos-15' | |
| 53 | + - otp-version: '26' | |
| 54 | + elixir-version: '1.16' | |
| 55 | + rebar3-version: '3.25' | |
| 56 | + os: 'macos-14' | |
| 57 | + - otp-version: '25.0' | |
| 58 | + elixir-version: 'v1.13.4-otp-25' | |
| 59 | + rebar3-version: '3.18.0' | |
| 60 | + os: 'macos-latest' | |
| 61 | + version-type: 'strict' | |
| 62 | + - otp-version: '25.0' | |
| 63 | + elixir-version: 'v1.13.4' | |
| 64 | + rebar3-version: '3.18.0' | |
| 65 | + os: 'macos-latest' | |
| 66 | + version-type: 'strict' | |
| 67 | + - otp-version: 'latest' | |
| 68 | + rebar3-version: 'latest' | |
| 69 | + os: 'macos-latest' | |
| 70 | + steps: | |
| 71 | + - uses: actions/checkout@v4 | |
| 72 | + - name: Use erlef/setup-beam | |
| 73 | + id: setup-beam | |
| 74 | + uses: ./ | |
| 75 | + with: | |
| 76 | + otp-version: ${{matrix.combo.otp-version}} | |
| 77 | + elixir-version: ${{matrix.combo.elixir-version}} | |
| 78 | + rebar3-version: ${{matrix.combo.rebar3-version}} | |
| 79 | + version-type: ${{matrix.combo.version-type}} | |
| 80 | + - name: Erlang/OTP version (action) | |
| 81 | + run: echo "Erlang/OTP ${{steps.setup-beam.outputs.otp-version}}" | |
| 82 | + if: ${{matrix.combo.otp-version}} | |
| 83 | + - name: Elixir version (action) | |
| 84 | + run: echo "Elixir ${{steps.setup-beam.outputs.elixir-version}}" | |
| 85 | + if: ${{matrix.combo.elixir-version}} | |
| 86 | + - name: rebar3 version (action) | |
| 87 | + run: echo "rebar3 ${{steps.setup-beam.outputs.rebar3-version}}" | |
| 88 | + if: ${{matrix.combo.rebar3-version}} | |
| 89 | + - name: mix version and help (CLI) | |
| 90 | + run: | | |
| 91 | + mix -v | |
| 92 | + mix help local.rebar | |
| 93 | + mix help local.hex | |
| 94 | + if: ${{matrix.combo.elixir-version}} | |
| 95 | + - name: Run Elixir/Mix project tests | |
| 96 | + run: | | |
| 97 | + cd test/projects/elixir_mix | |
| 98 | + mix deps.get | |
| 99 | + mix test | |
| 100 | + if: ${{matrix.combo.elixir-version}} | |
| 101 | + - name: Run Erlang/rebar3 project tests | |
| 102 | + run: | | |
| 103 | + cd test/projects/erlang_rebar3 | |
| 104 | + rebar3 ct | |
| 105 | + if: ${{matrix.combo.rebar3-version}} | |
| 106 | + - name: Run escript | |
| 107 | + run: | | |
| 108 | + mix escript.install --force ${{matrix.combo.escript_packages}} | |
| 109 | + ${{matrix.combo.escript_script}} | |
| 110 | + if: ${{matrix.combo.escript_packages && matrix.combo.escript_script}} | |
| 111 | + | |
| 112 | + environment_variables: | |
| 113 | + name: Environment variables | |
| 114 | + runs-on: ${{matrix.combo.os}} | |
| 115 | + strategy: | |
| 116 | + fail-fast: false | |
| 117 | + matrix: | |
| 118 | + combo: | |
| 119 | + - otp-version: latest | |
| 120 | + elixir-version: latest | |
| 121 | + rebar3-version: nightly | |
| 122 | + os: macos-latest | |
| 123 | + steps: | |
| 124 | + - uses: actions/checkout@v4 | |
| 125 | + - name: Use erlef/setup-beam | |
| 126 | + id: setup-beam | |
| 127 | + uses: ./ | |
| 128 | + with: | |
| 129 | + otp-version: ${{matrix.combo.otp-version}} | |
| 130 | + elixir-version: ${{matrix.combo.elixir-version}} | |
| 131 | + rebar3-version: ${{matrix.combo.rebar3-version}} | |
| 132 | + - run: env | |
| 133 | + - name: Check environment variables | |
| 134 | + run: | | |
| 135 | + ${INSTALL_DIR_FOR_ELIXIR}/bin/elixir -v | |
| 136 | + ${INSTALL_DIR_FOR_OTP}/bin/erl -version | |
| 137 | + ${INSTALL_DIR_FOR_REBAR3}/bin/rebar3 version |
modified
README.md
+36
−16
@@ -1,4 +1,4 @@
| 1 | −# setup-beam [![Action][action-img]][action] [![Ubuntu][ubuntu-img]][ubuntu] [![Windows][windows-img]][windows] | |
| 1 | +# setup-beam [![Action][action-img]][action] [![Ubuntu][ubuntu-img]][ubuntu] [![Windows][windows-img]][windows] [![macOS][macos-img]][macos] | |
| 2 | 2 | |
| 3 | 3 | [action]: https://github.com/erlef/setup-beam/actions/workflows/action.yml |
| 4 | 4 | [action-img]: https://github.com/erlef/setup-beam/actions/workflows/action.yml/badge.svg |
@@ -6,6 +6,8 @@
| 6 | 6 | [ubuntu-img]: https://github.com/erlef/setup-beam/actions/workflows/ubuntu.yml/badge.svg |
| 7 | 7 | [windows]: https://github.com/erlef/setup-beam/actions/workflows/windows.yml |
| 8 | 8 | [windows-img]: https://github.com/erlef/setup-beam/actions/workflows/windows.yml/badge.svg |
| 9 | +[macos]: https://github.com/erlef/setup-beam/actions/workflows/macos.yml | |
| 10 | +[macos-img]: https://github.com/erlef/setup-beam/actions/workflows/macos.yml/badge.svg | |
| 9 | 11 | |
| 10 | 12 | This action sets up an Erlang/OTP environment for use in a GitHub Actions |
| 11 | 13 | workflow by: |
@@ -21,8 +23,6 @@ workflow by:
| 21 | 23 | warnings and errors on pull requests |
| 22 | 24 | - optionally, using a version file (as explained in "Version file", below), to identify versions |
| 23 | 25 | |
| 24 | −**Note**: currently, this action only supports Actions' `ubuntu-` and `windows-` runtimes. | |
| 25 | − | |
| 26 | 26 | ## Usage |
| 27 | 27 | |
| 28 | 28 | See [action.yml](action.yml) for the action's specification. |
@@ -73,12 +73,13 @@ and Erlang/OTP.
| 73 | 73 | |
| 74 | 74 | | Operating system | Erlang/OTP | OTP Architecture | Status |
| 75 | 75 | |- |- | - |- |
| 76 | −| `ubuntu-18.04` | 17.0 - 25.3 | x86_64, arm64 | ✅ | |
| 77 | −| `ubuntu-20.04` | 21.0 - 27 | x86_64, arm64 | ✅ | |
| 78 | −| `ubuntu-22.04` | 24.2 - 27 | x86_64, arm64 | ✅ | |
| 79 | −| `ubuntu-24.04` | 24.3 - 27 | x86_64, arm64 | ✅ | |
| 76 | +| `ubuntu-22.04` | 24.2 - 28 | x86_64, arm64 | ✅ | |
| 77 | +| `ubuntu-24.04` | 24.3 - 28 | x86_64, arm64 | ✅ | |
| 80 | 78 | | `windows-2019` | 21\* - 25 | x86_64, x86 | ✅ |
| 81 | −| `windows-2022` | 21\* - 27 | x86_64, x86 | ✅ | |
| 79 | +| `windows-2022` | 21\* - 28 | x86_64, x86 | ✅ | |
| 80 | +| `macOS-13` | 25.0 - 28 | x86_64, arm64 | ✅ | |
| 81 | +| `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ | |
| 82 | +| `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅ | |
| 82 | 83 | |
| 83 | 84 | **Note** \*: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, |
| 84 | 85 | 22.0, etc. |
@@ -88,14 +89,15 @@ and Erlang/OTP.
| 88 | 89 | Self-hosted runners need to set env. variable `ImageOS` to one of the following, since the action |
| 89 | 90 | uses that to download assets: |
| 90 | 91 | |
| 91 | −| ImageOS | Operating system | |
| 92 | −|- |- | |
| 93 | −| `ubuntu18` | `ubuntu-18.04` | |
| 94 | −| `ubuntu20` | `ubuntu-20.04` | |
| 95 | −| `ubuntu22` | `ubuntu-22.04` | |
| 96 | −| `ubuntu24` | `ubuntu-24.04` | |
| 97 | −| `win19` | `windows-2019` | |
| 98 | −| `win22` | `windows-2022` | |
| 92 | +| ImageOS | Operating system | |
| 93 | +|- |- | |
| 94 | +| `ubuntu22` | `ubuntu-22.04` | |
| 95 | +| `ubuntu24` | `ubuntu-24.04` | |
| 96 | +| `win19` | `windows-2019` | |
| 97 | +| `win22` | `windows-2022` | |
| 98 | +| `macos13` | `macOS-13` | |
| 99 | +| `macos14` | `macOS-14` | |
| 100 | +| `macos15` | `macOS-15` | |
| 99 | 101 | |
| 100 | 102 | as per the following example: |
| 101 | 103 |
@@ -298,6 +300,24 @@ jobs:
| 298 | 300 | - run: rebar3 ct |
| 299 | 301 | ``` |
| 300 | 302 | |
| 303 | +### Erlang/OTP + `rebar3`, on macOS | |
| 304 | + | |
| 305 | +```yaml | |
| 306 | +# create this in .github/workflows/ci.yml | |
| 307 | +on: push | |
| 308 | + | |
| 309 | +jobs: | |
| 310 | + test: | |
| 311 | + runs-on: macos-15 | |
| 312 | + steps: | |
| 313 | + - uses: actions/checkout@v4 | |
| 314 | + - uses: erlef/setup-beam@v1 | |
| 315 | + with: | |
| 316 | + otp-version: '28' | |
| 317 | + rebar3-version: '3.25' | |
| 318 | + - run: rebar3 ct | |
| 319 | +``` | |
| 320 | + | |
| 301 | 321 | ### Gleam on Ubuntu |
| 302 | 322 | |
| 303 | 323 | ```yaml |
modified
dist/index.js
+1858
−16
@@ -9110,6 +9110,7 @@ const path = __nccwpck_require__(1017)
| 9110 | 9110 | const semver = __nccwpck_require__(1383) |
| 9111 | 9111 | const fs = __nccwpck_require__(7147) |
| 9112 | 9112 | const os = __nccwpck_require__(2037) |
| 9113 | +const csv = __nccwpck_require__(4393) | |
| 9113 | 9114 | |
| 9114 | 9115 | const MAX_HTTP_RETRIES = 3 |
| 9115 | 9116 |
@@ -9118,7 +9119,6 @@ main().catch((err) => {
| 9118 | 9119 | }) |
| 9119 | 9120 | |
| 9120 | 9121 | async function main() { |
| 9121 | − checkPlatform() | |
| 9122 | 9122 | checkOtpArchitecture() |
| 9123 | 9123 | |
| 9124 | 9124 | const versionFilePath = getInput('version-file', false) |
@@ -9377,13 +9377,28 @@ async function getOTPVersions(osVersion) {
| 9377 | 9377 | hexMirrors, |
| 9378 | 9378 | actionTitle: `fetch ${originListing}`, |
| 9379 | 9379 | action: async (hexMirror) => { |
| 9380 | − return get(`${hexMirror}${originListing}`, []) | |
| 9380 | + return get(`${hexMirror}${originListing}`) | |
| 9381 | 9381 | }, |
| 9382 | 9382 | }) |
| 9383 | 9383 | } else if (process.platform === 'win32') { |
| 9384 | 9384 | originListing = |
| 9385 | 9385 | 'https://api.github.com/repos/erlang/otp/releases?per_page=100' |
| 9386 | 9386 | otpVersionsListings = await get(originListing, [1, 2, 3]) |
| 9387 | + } else if (process.platform === 'darwin') { | |
| 9388 | + const arch = getRunnerOSArchitecture() | |
| 9389 | + let targetArch | |
| 9390 | + switch (arch) { | |
| 9391 | + case 'amd64': | |
| 9392 | + targetArch = 'x86_64' | |
| 9393 | + break | |
| 9394 | + case 'arm64': | |
| 9395 | + targetArch = 'aarch64' | |
| 9396 | + break | |
| 9397 | + } | |
| 9398 | + originListing = | |
| 9399 | + `https://raw.githubusercontent.com/erlef/otp_builds/refs/heads/main` + | |
| 9400 | + `/builds/${targetArch}-apple-darwin.csv` | |
| 9401 | + otpVersionsListings = await get(originListing) | |
| 9387 | 9402 | } |
| 9388 | 9403 | |
| 9389 | 9404 | debugLog( |
@@ -9421,6 +9436,18 @@ async function getOTPVersions(osVersion) {
| 9421 | 9436 | otpVersions[otpVersion] = otpVersion |
| 9422 | 9437 | }) |
| 9423 | 9438 | }) |
| 9439 | + } else if (process.platform === 'darwin') { | |
| 9440 | + csv | |
| 9441 | + .parse(otpVersionsListings, { | |
| 9442 | + columns: true, | |
| 9443 | + }) | |
| 9444 | + .forEach((line) => { | |
| 9445 | + const otpMatch = line.ref_name.match(/^([^-]+-)?(.+)$/) | |
| 9446 | + const otpVersion = otpMatch[2] | |
| 9447 | + const otpVersionOrig = otpMatch[0] | |
| 9448 | + debugLog('OTP line and parsing', [line, otpVersion, otpMatch]) | |
| 9449 | + otpVersions[otpVersion] = otpVersionOrig // we keep the original for later reference | |
| 9450 | + }) | |
| 9424 | 9451 | } |
| 9425 | 9452 | |
| 9426 | 9453 | debugLog( |
@@ -9438,7 +9465,7 @@ async function getElixirVersions() {
| 9438 | 9465 | hexMirrors, |
| 9439 | 9466 | actionTitle: `fetch ${originListing}`, |
| 9440 | 9467 | action: async (hexMirror) => { |
| 9441 | − return get(`${hexMirror}${originListing}`, []) | |
| 9468 | + return get(`${hexMirror}${originListing}`) | |
| 9442 | 9469 | }, |
| 9443 | 9470 | }) |
| 9444 | 9471 | const otpVersionsForElixirMap = {} |
@@ -9627,8 +9654,14 @@ function isRC(ver) {
| 9627 | 9654 | return ver.match(xyzAbcVersion('^', '(?:-rc\\.?\\d+)')) |
| 9628 | 9655 | } |
| 9629 | 9656 | |
| 9657 | +const knownBranches = ['main', 'master', 'maint'] | |
| 9658 | + | |
| 9630 | 9659 | function isKnownBranch(ver) { |
| 9631 | − return ['main', 'master', 'maint'].includes(ver) | |
| 9660 | + return knownBranches.includes(ver) | |
| 9661 | +} | |
| 9662 | + | |
| 9663 | +function isKnownVerBranch(ver) { | |
| 9664 | + return knownBranches.some((b) => ver.match(b)) | |
| 9632 | 9665 | } |
| 9633 | 9666 | |
| 9634 | 9667 | function githubARMRunnerArchs() { |
@@ -9658,13 +9691,15 @@ function getRunnerOSArchitecture() {
| 9658 | 9691 | } |
| 9659 | 9692 | |
| 9660 | 9693 | function getRunnerOSVersion() { |
| 9694 | + // List from https://github.com/actions/runner-images?tab=readme-ov-file#available-images | |
| 9661 | 9695 | const ImageOSToContainer = { |
| 9662 | − ubuntu18: 'ubuntu-18.04', | |
| 9663 | − ubuntu20: 'ubuntu-20.04', | |
| 9664 | 9696 | ubuntu22: 'ubuntu-22.04', |
| 9665 | 9697 | ubuntu24: 'ubuntu-24.04', |
| 9666 | 9698 | win19: 'windows-2019', |
| 9667 | 9699 | win22: 'windows-2022', |
| 9700 | + macos13: 'macOS-13', | |
| 9701 | + macos14: 'macOS-14', | |
| 9702 | + macos15: 'macOS-15', | |
| 9668 | 9703 | } |
| 9669 | 9704 | const containerFromEnvImageOS = ImageOSToContainer[process.env.ImageOS] |
| 9670 | 9705 | if (!containerFromEnvImageOS) { |
@@ -9721,7 +9756,7 @@ async function get(url0, pageIdxs) {
| 9721 | 9756 | headers.authorization = `Bearer ${GithubToken}` |
| 9722 | 9757 | } |
| 9723 | 9758 | |
| 9724 | − if (pageIdxs.length === 0) { | |
| 9759 | + if ((pageIdxs || []).length === 0) { | |
| 9725 | 9760 | return getUrlResponse(url, headers) |
| 9726 | 9761 | } else { |
| 9727 | 9762 | return Promise.all( |
@@ -9936,6 +9971,36 @@ async function install(toolName, opts) {
| 9936 | 9971 | const args = ['+V'] |
| 9937 | 9972 | const env = {} |
| 9938 | 9973 | |
| 9974 | + return [cmd, args, env] | |
| 9975 | + }, | |
| 9976 | + }, | |
| 9977 | + darwin: { | |
| 9978 | + downloadToolURL: (versionSpec) => { | |
| 9979 | + let suffix = '' | |
| 9980 | + if (isKnownVerBranch(versionSpec)) { | |
| 9981 | + // for these otp_builds adds `-latest` in the folder path | |
| 9982 | + suffix = '-latest' | |
| 9983 | + } | |
| 9984 | + return ( | |
| 9985 | + `https://github.com/erlef/otp_builds/releases/download/` + | |
| 9986 | + `${toolVersion}${suffix}/${toolVersion}-macos-${getRunnerOSArchitecture()}.tar.gz` | |
| 9987 | + ) | |
| 9988 | + }, | |
| 9989 | + extract: async (file) => { | |
| 9990 | + const dest = undefined | |
| 9991 | + const flags = ['zx'] | |
| 9992 | + const targetDir = await tc.extractTar(file, dest, flags) | |
| 9993 | + | |
| 9994 | + return ['dir', targetDir] | |
| 9995 | + }, | |
| 9996 | + postExtract: async (/*cachePath*/) => { | |
| 9997 | + // nothing to do | |
| 9998 | + }, | |
| 9999 | + reportVersion: () => { | |
| 10000 | + const cmd = 'erl' | |
| 10001 | + const args = ['-version'] | |
| 10002 | + const env = {} | |
| 10003 | + | |
| 9939 | 10004 | return [cmd, args, env] |
| 9940 | 10005 | }, |
| 9941 | 10006 | }, |
@@ -10045,6 +10110,7 @@ async function install(toolName, opts) {
| 10045 | 10110 | }, |
| 10046 | 10111 | }, |
| 10047 | 10112 | } |
| 10113 | + installOpts.darwin = installOpts.linux | |
| 10048 | 10114 | break |
| 10049 | 10115 | case 'rebar3': |
| 10050 | 10116 | installOpts = { |
@@ -10118,6 +10184,7 @@ async function install(toolName, opts) {
| 10118 | 10184 | }, |
| 10119 | 10185 | }, |
| 10120 | 10186 | } |
| 10187 | + installOpts.darwin = installOpts.linux | |
| 10121 | 10188 | break |
| 10122 | 10189 | default: |
| 10123 | 10190 | throw new Error(`no installer for ${toolName}`) |
@@ -10134,7 +10201,7 @@ async function installTool(opts) {
| 10134 | 10201 | core.debug(`Checking if ${installOpts.tool} is already cached...`) |
| 10135 | 10202 | if (cachePath === '') { |
| 10136 | 10203 | core.debug(" ... it isn't!") |
| 10137 | − const downloadToolURL = platformOpts.downloadToolURL() | |
| 10204 | + const downloadToolURL = platformOpts.downloadToolURL(versionSpec) | |
| 10138 | 10205 | const file = await tc.downloadTool(downloadToolURL) |
| 10139 | 10206 | const [targetElemType, targetElem] = await platformOpts.extract(file) |
| 10140 | 10207 |
@@ -10172,14 +10239,6 @@ async function installTool(opts) {
| 10172 | 10239 | await exec(cmd, args, { env: { ...process.env, ...env } }) |
| 10173 | 10240 | } |
| 10174 | 10241 | |
| 10175 | −function checkPlatform() { | |
| 10176 | − if (process.platform !== 'linux' && process.platform !== 'win32') { | |
| 10177 | − throw new Error( | |
| 10178 | − '@erlef/setup-beam only supports Ubuntu and Windows at this time', | |
| 10179 | − ) | |
| 10180 | − } | |
| 10181 | −} | |
| 10182 | − | |
| 10183 | 10242 | function checkOtpArchitecture() { |
| 10184 | 10243 | if (process.platform !== 'win32' && getInput('otp-architecture') == '32') { |
| 10185 | 10244 | throw new Error( |
@@ -10333,6 +10392,1789 @@ module.exports = require("tls");
| 10333 | 10392 | "use strict"; |
| 10334 | 10393 | module.exports = require("util"); |
| 10335 | 10394 | |
| 10395 | +/***/ }), | |
| 10396 | + | |
| 10397 | +/***/ 4393: | |
| 10398 | +/***/ ((__unused_webpack_module, exports) => { | |
| 10399 | + | |
| 10400 | +"use strict"; | |
| 10401 | + | |
| 10402 | + | |
| 10403 | +class CsvError extends Error { | |
| 10404 | + constructor(code, message, options, ...contexts) { | |
| 10405 | + if (Array.isArray(message)) message = message.join(" ").trim(); | |
| 10406 | + super(message); | |
| 10407 | + if (Error.captureStackTrace !== undefined) { | |
| 10408 | + Error.captureStackTrace(this, CsvError); | |
| 10409 | + } | |
| 10410 | + this.code = code; | |
| 10411 | + for (const context of contexts) { | |
| 10412 | + for (const key in context) { | |
| 10413 | + const value = context[key]; | |
| 10414 | + this[key] = Buffer.isBuffer(value) | |
| 10415 | + ? value.toString(options.encoding) | |
| 10416 | + : value == null | |
| 10417 | + ? value | |
| 10418 | + : JSON.parse(JSON.stringify(value)); | |
| 10419 | + } | |
| 10420 | + } | |
| 10421 | + } | |
| 10422 | +} | |
| 10423 | + | |
| 10424 | +const is_object = function (obj) { | |
| 10425 | + return typeof obj === "object" && obj !== null && !Array.isArray(obj); | |
| 10426 | +}; | |
| 10427 | + | |
| 10428 | +const normalize_columns_array = function (columns) { | |
| 10429 | + const normalizedColumns = []; | |
| 10430 | + for (let i = 0, l = columns.length; i < l; i++) { | |
| 10431 | + const column = columns[i]; | |
| 10432 | + if (column === undefined || column === null || column === false) { | |
| 10433 | + normalizedColumns[i] = { disabled: true }; | |
| 10434 | + } else if (typeof column === "string") { | |
| 10435 | + normalizedColumns[i] = { name: column }; | |
| 10436 | + } else if (is_object(column)) { | |
| 10437 | + if (typeof column.name !== "string") { | |
| 10438 | + throw new CsvError("CSV_OPTION_COLUMNS_MISSING_NAME", [ | |
| 10439 | + "Option columns missing name:", | |
| 10440 | + `property "name" is required at position ${i}`, | |
| 10441 | + "when column is an object literal", | |
| 10442 | + ]); | |
| 10443 | + } | |
| 10444 | + normalizedColumns[i] = column; | |
| 10445 | + } else { | |
| 10446 | + throw new CsvError("CSV_INVALID_COLUMN_DEFINITION", [ | |
| 10447 | + "Invalid column definition:", | |
| 10448 | + "expect a string or a literal object,", | |
| 10449 | + `got ${JSON.stringify(column)} at position ${i}`, | |
| 10450 | + ]); | |
| 10451 | + } | |
| 10452 | + } | |
| 10453 | + return normalizedColumns; | |
| 10454 | +}; | |
| 10455 | + | |
| 10456 | +class ResizeableBuffer { | |
| 10457 | + constructor(size = 100) { | |
| 10458 | + this.size = size; | |
| 10459 | + this.length = 0; | |
| 10460 | + this.buf = Buffer.allocUnsafe(size); | |
| 10461 | + } | |
| 10462 | + prepend(val) { | |
| 10463 | + if (Buffer.isBuffer(val)) { | |
| 10464 | + const length = this.length + val.length; | |
| 10465 | + if (length >= this.size) { | |
| 10466 | + this.resize(); | |
| 10467 | + if (length >= this.size) { | |
| 10468 | + throw Error("INVALID_BUFFER_STATE"); | |
| 10469 | + } | |
| 10470 | + } | |
| 10471 | + const buf = this.buf; | |
| 10472 | + this.buf = Buffer.allocUnsafe(this.size); | |
| 10473 | + val.copy(this.buf, 0); | |
| 10474 | + buf.copy(this.buf, val.length); | |
| 10475 | + this.length += val.length; | |
| 10476 | + } else { | |
| 10477 | + const length = this.length++; | |
| 10478 | + if (length === this.size) { | |
| 10479 | + this.resize(); | |
| 10480 | + } | |
| 10481 | + const buf = this.clone(); | |
| 10482 | + this.buf[0] = val; | |
| 10483 | + buf.copy(this.buf, 1, 0, length); | |
| 10484 | + } | |
| 10485 | + } | |
| 10486 | + append(val) { | |
| 10487 | + const length = this.length++; | |
| 10488 | + if (length === this.size) { | |
| 10489 | + this.resize(); | |
| 10490 | + } | |
| 10491 | + this.buf[length] = val; | |
| 10492 | + } | |
| 10493 | + clone() { | |
| 10494 | + return Buffer.from(this.buf.slice(0, this.length)); | |
| 10495 | + } | |
| 10496 | + resize() { | |
| 10497 | + const length = this.length; | |
| 10498 | + this.size = this.size * 2; | |
| 10499 | + const buf = Buffer.allocUnsafe(this.size); | |
| 10500 | + this.buf.copy(buf, 0, 0, length); | |
| 10501 | + this.buf = buf; | |
| 10502 | + } | |
| 10503 | + toString(encoding) { | |
| 10504 | + if (encoding) { | |
| 10505 | + return this.buf.slice(0, this.length).toString(encoding); | |
| 10506 | + } else { | |
| 10507 | + return Uint8Array.prototype.slice.call(this.buf.slice(0, this.length)); | |
| 10508 | + } | |
| 10509 | + } | |
| 10510 | + toJSON() { | |
| 10511 | + return this.toString("utf8"); | |
| 10512 | + } | |
| 10513 | + reset() { | |
| 10514 | + this.length = 0; | |
| 10515 | + } | |
| 10516 | +} | |
| 10517 | + | |
| 10518 | +// white space characters | |
| 10519 | +// https://en.wikipedia.org/wiki/Whitespace_character | |
| 10520 | +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#Types | |
| 10521 | +// \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff | |
| 10522 | +const np = 12; | |
| 10523 | +const cr$1 = 13; // `\r`, carriage return, 0x0D in hexadécimal, 13 in decimal | |
| 10524 | +const nl$1 = 10; // `\n`, newline, 0x0A in hexadecimal, 10 in decimal | |
| 10525 | +const space = 32; | |
| 10526 | +const tab = 9; | |
| 10527 | + | |
| 10528 | +const init_state = function (options) { | |
| 10529 | + return { | |
| 10530 | + bomSkipped: false, | |
| 10531 | + bufBytesStart: 0, | |
| 10532 | + castField: options.cast_function, | |
| 10533 | + commenting: false, | |
| 10534 | + // Current error encountered by a record | |
| 10535 | + error: undefined, | |
| 10536 | + enabled: options.from_line === 1, | |
| 10537 | + escaping: false, | |
| 10538 | + escapeIsQuote: | |
| 10539 | + Buffer.isBuffer(options.escape) && | |
| 10540 | + Buffer.isBuffer(options.quote) && | |
| 10541 | + Buffer.compare(options.escape, options.quote) === 0, | |
| 10542 | + // columns can be `false`, `true`, `Array` | |
| 10543 | + expectedRecordLength: Array.isArray(options.columns) | |
| 10544 | + ? options.columns.length | |
| 10545 | + : undefined, | |
| 10546 | + field: new ResizeableBuffer(20), | |
| 10547 | + firstLineToHeaders: options.cast_first_line_to_header, | |
| 10548 | + needMoreDataSize: Math.max( | |
| 10549 | + // Skip if the remaining buffer smaller than comment | |
| 10550 | + options.comment !== null ? options.comment.length : 0, | |
| 10551 | + // Skip if the remaining buffer can be delimiter | |
| 10552 | + ...options.delimiter.map((delimiter) => delimiter.length), | |
| 10553 | + // Skip if the remaining buffer can be escape sequence | |
| 10554 | + options.quote !== null ? options.quote.length : 0, | |
| 10555 | + ), | |
| 10556 | + previousBuf: undefined, | |
| 10557 | + quoting: false, | |
| 10558 | + stop: false, | |
| 10559 | + rawBuffer: new ResizeableBuffer(100), | |
| 10560 | + record: [], | |
| 10561 | + recordHasError: false, | |
| 10562 | + record_length: 0, | |
| 10563 | + recordDelimiterMaxLength: | |
| 10564 | + options.record_delimiter.length === 0 | |
| 10565 | + ? 0 | |
| 10566 | + : Math.max(...options.record_delimiter.map((v) => v.length)), | |
| 10567 | + trimChars: [ | |
| 10568 | + Buffer.from(" ", options.encoding)[0], | |
| 10569 | + Buffer.from("\t", options.encoding)[0], | |
| 10570 | + ], | |
| 10571 | + wasQuoting: false, | |
| 10572 | + wasRowDelimiter: false, | |
| 10573 | + timchars: [ | |
| 10574 | + Buffer.from(Buffer.from([cr$1], "utf8").toString(), options.encoding), | |
| 10575 | + Buffer.from(Buffer.from([nl$1], "utf8").toString(), options.encoding), | |
| 10576 | + Buffer.from(Buffer.from([np], "utf8").toString(), options.encoding), | |
| 10577 | + Buffer.from(Buffer.from([space], "utf8").toString(), options.encoding), | |
| 10578 | + Buffer.from(Buffer.from([tab], "utf8").toString(), options.encoding), | |
| 10579 | + ], | |
| 10580 | + }; | |
| 10581 | +}; | |
| 10582 | + | |
| 10583 | +const underscore = function (str) { | |
| 10584 | + return str.replace(/([A-Z])/g, function (_, match) { | |
| 10585 | + return "_" + match.toLowerCase(); | |
| 10586 | + }); | |
| 10587 | +}; | |
| 10588 | + | |
| 10589 | +const normalize_options = function (opts) { | |
| 10590 | + const options = {}; | |
| 10591 | + // Merge with user options | |
| 10592 | + for (const opt in opts) { | |
| 10593 | + options[underscore(opt)] = opts[opt]; | |
| 10594 | + } | |
| 10595 | + // Normalize option `encoding` | |
| 10596 | + // Note: defined first because other options depends on it | |
| 10597 | + // to convert chars/strings into buffers. | |
| 10598 | + if (options.encoding === undefined || options.encoding === true) { | |
| 10599 | + options.encoding = "utf8"; | |
| 10600 | + } else if (options.encoding === null || options.encoding === false) { | |
| 10601 | + options.encoding = null; | |
| 10602 | + } else if ( | |
| 10603 | + typeof options.encoding !== "string" && | |
| 10604 | + options.encoding !== null | |
| 10605 | + ) { | |
| 10606 | + throw new CsvError( | |
| 10607 | + "CSV_INVALID_OPTION_ENCODING", | |
| 10608 | + [ | |
| 10609 | + "Invalid option encoding:", | |
| 10610 | + "encoding must be a string or null to return a buffer,", | |
| 10611 | + `got ${JSON.stringify(options.encoding)}`, | |
| 10612 | + ], | |
| 10613 | + options, | |
| 10614 | + ); | |
| 10615 | + } | |
| 10616 | + // Normalize option `bom` | |
| 10617 | + if ( | |
| 10618 | + options.bom === undefined || | |
| 10619 | + options.bom === null || | |
| 10620 | + options.bom === false | |
| 10621 | + ) { | |
| 10622 | + options.bom = false; | |
| 10623 | + } else if (options.bom !== true) { | |
| 10624 | + throw new CsvError( | |
| 10625 | + "CSV_INVALID_OPTION_BOM", | |
| 10626 | + [ | |
| 10627 | + "Invalid option bom:", | |
| 10628 | + "bom must be true,", | |
| 10629 | + `got ${JSON.stringify(options.bom)}`, | |
| 10630 | + ], | |
| 10631 | + options, | |
| 10632 | + ); | |
| 10633 | + } | |
| 10634 | + // Normalize option `cast` | |
| 10635 | + options.cast_function = null; | |
| 10636 | + if ( | |
| 10637 | + options.cast === undefined || | |
| 10638 | + options.cast === null || | |
| 10639 | + options.cast === false || | |
| 10640 | + options.cast === "" | |
| 10641 | + ) { | |
| 10642 | + options.cast = undefined; | |
| 10643 | + } else if (typeof options.cast === "function") { | |
| 10644 | + options.cast_function = options.cast; | |
| 10645 | + options.cast = true; | |
| 10646 | + } else if (options.cast !== true) { | |
| 10647 | + throw new CsvError( | |
| 10648 | + "CSV_INVALID_OPTION_CAST", | |
| 10649 | + [ | |
| 10650 | + "Invalid option cast:", | |
| 10651 | + "cast must be true or a function,", | |
| 10652 | + `got ${JSON.stringify(options.cast)}`, | |
| 10653 | + ], | |
| 10654 | + options, | |
| 10655 | + ); | |
| 10656 | + } | |
| 10657 | + // Normalize option `cast_date` | |
| 10658 | + if ( | |
| 10659 | + options.cast_date === undefined || | |
| 10660 | + options.cast_date === null || | |
| 10661 | + options.cast_date === false || | |
| 10662 | + options.cast_date === "" | |
| 10663 | + ) { | |
| 10664 | + options.cast_date = false; | |
| 10665 | + } else if (options.cast_date === true) { | |
| 10666 | + options.cast_date = function (value) { | |
| 10667 | + const date = Date.parse(value); | |
| 10668 | + return !isNaN(date) ? new Date(date) : value; | |
| 10669 | + }; | |
| 10670 | + } else if (typeof options.cast_date !== "function") { | |
| 10671 | + throw new CsvError( | |
| 10672 | + "CSV_INVALID_OPTION_CAST_DATE", | |
| 10673 | + [ | |
| 10674 | + "Invalid option cast_date:", | |
| 10675 | + "cast_date must be true or a function,", | |
| 10676 | + `got ${JSON.stringify(options.cast_date)}`, | |
| 10677 | + ], | |
| 10678 | + options, | |
| 10679 | + ); | |
| 10680 | + } | |
| 10681 | + // Normalize option `columns` | |
| 10682 | + options.cast_first_line_to_header = null; | |
| 10683 | + if (options.columns === true) { | |
| 10684 | + // Fields in the first line are converted as-is to columns | |
| 10685 | + options.cast_first_line_to_header = undefined; | |
| 10686 | + } else if (typeof options.columns === "function") { | |
| 10687 | + options.cast_first_line_to_header = options.columns; | |
| 10688 | + options.columns = true; | |
| 10689 | + } else if (Array.isArray(options.columns)) { | |
| 10690 | + options.columns = normalize_columns_array(options.columns); | |
| 10691 | + } else if ( | |
| 10692 | + options.columns === undefined || | |
| 10693 | + options.columns === null || | |
| 10694 | + options.columns === false | |
| 10695 | + ) { | |
| 10696 | + options.columns = false; | |
| 10697 | + } else { | |
| 10698 | + throw new CsvError( | |
| 10699 | + "CSV_INVALID_OPTION_COLUMNS", | |
| 10700 | + [ | |
| 10701 | + "Invalid option columns:", | |
| 10702 | + "expect an array, a function or true,", | |
| 10703 | + `got ${JSON.stringify(options.columns)}`, | |
| 10704 | + ], | |
| 10705 | + options, | |
| 10706 | + ); | |
| 10707 | + } | |
| 10708 | + // Normalize option `group_columns_by_name` | |
| 10709 | + if ( | |
| 10710 | + options.group_columns_by_name === undefined || | |
| 10711 | + options.group_columns_by_name === null || | |
| 10712 | + options.group_columns_by_name === false | |
| 10713 | + ) { | |
| 10714 | + options.group_columns_by_name = false; | |
| 10715 | + } else if (options.group_columns_by_name !== true) { | |
| 10716 | + throw new CsvError( | |
| 10717 | + "CSV_INVALID_OPTION_GROUP_COLUMNS_BY_NAME", | |
| 10718 | + [ | |
| 10719 | + "Invalid option group_columns_by_name:", | |
| 10720 | + "expect an boolean,", | |
| 10721 | + `got ${JSON.stringify(options.group_columns_by_name)}`, | |
| 10722 | + ], | |
| 10723 | + options, | |
| 10724 | + ); | |
| 10725 | + } else if (options.columns === false) { | |
| 10726 | + throw new CsvError( | |
| 10727 | + "CSV_INVALID_OPTION_GROUP_COLUMNS_BY_NAME", | |
| 10728 | + [ | |
| 10729 | + "Invalid option group_columns_by_name:", | |
| 10730 | + "the `columns` mode must be activated.", | |
| 10731 | + ], | |
| 10732 | + options, | |
| 10733 | + ); | |
| 10734 | + } | |
| 10735 | + // Normalize option `comment` | |
| 10736 | + if ( | |
| 10737 | + options.comment === undefined || | |
| 10738 | + options.comment === null || | |
| 10739 | + options.comment === false || | |
| 10740 | + options.comment === "" | |
| 10741 | + ) { | |
| 10742 | + options.comment = null; | |
| 10743 | + } else { | |
| 10744 | + if (typeof options.comment === "string") { | |
| 10745 | + options.comment = Buffer.from(options.comment, options.encoding); | |
| 10746 | + } | |
| 10747 | + if (!Buffer.isBuffer(options.comment)) { | |
| 10748 | + throw new CsvError( | |
| 10749 | + "CSV_INVALID_OPTION_COMMENT", | |
| 10750 | + [ | |
| 10751 | + "Invalid option comment:", | |
| 10752 | + "comment must be a buffer or a string,", | |
| 10753 | + `got ${JSON.stringify(options.comment)}`, | |
| 10754 | + ], | |
| 10755 | + options, | |
| 10756 | + ); | |
| 10757 | + } | |
| 10758 | + } | |
| 10759 | + // Normalize option `comment_no_infix` | |
| 10760 | + if ( | |
| 10761 | + options.comment_no_infix === undefined || | |
| 10762 | + options.comment_no_infix === null || | |
| 10763 | + options.comment_no_infix === false | |
| 10764 | + ) { | |
| 10765 | + options.comment_no_infix = false; | |
| 10766 | + } else if (options.comment_no_infix !== true) { | |
| 10767 | + throw new CsvError( | |
| 10768 | + "CSV_INVALID_OPTION_COMMENT", | |
| 10769 | + [ | |
| 10770 | + "Invalid option comment_no_infix:", | |
| 10771 | + "value must be a boolean,", | |
| 10772 | + `got ${JSON.stringify(options.comment_no_infix)}`, | |
| 10773 | + ], | |
| 10774 | + options, | |
| 10775 | + ); | |
| 10776 | + } | |
| 10777 | + // Normalize option `delimiter` | |
| 10778 | + const delimiter_json = JSON.stringify(options.delimiter); | |
| 10779 | + if (!Array.isArray(options.delimiter)) | |
| 10780 | + options.delimiter = [options.delimiter]; | |
| 10781 | + if (options.delimiter.length === 0) { | |
| 10782 | + throw new CsvError( | |
| 10783 | + "CSV_INVALID_OPTION_DELIMITER", | |
| 10784 | + [ | |
| 10785 | + "Invalid option delimiter:", | |
| 10786 | + "delimiter must be a non empty string or buffer or array of string|buffer,", | |
| 10787 | + `got ${delimiter_json}`, | |
| 10788 | + ], | |
| 10789 | + options, | |
| 10790 | + ); | |
| 10791 | + } | |
| 10792 | + options.delimiter = options.delimiter.map(function (delimiter) { | |
| 10793 | + if (delimiter === undefined || delimiter === null || delimiter === false) { | |
| 10794 | + return Buffer.from(",", options.encoding); | |
| 10795 | + } | |
| 10796 | + if (typeof delimiter === "string") { | |
| 10797 | + delimiter = Buffer.from(delimiter, options.encoding); | |
| 10798 | + } | |
| 10799 | + if (!Buffer.isBuffer(delimiter) || delimiter.length === 0) { | |
| 10800 | + throw new CsvError( | |
| 10801 | + "CSV_INVALID_OPTION_DELIMITER", | |
| 10802 | + [ | |
| 10803 | + "Invalid option delimiter:", | |
| 10804 | + "delimiter must be a non empty string or buffer or array of string|buffer,", | |
| 10805 | + `got ${delimiter_json}`, | |
| 10806 | + ], | |
| 10807 | + options, | |
| 10808 | + ); | |
| 10809 | + } | |
| 10810 | + return delimiter; | |
| 10811 | + }); | |
| 10812 | + // Normalize option `escape` | |
| 10813 | + if (options.escape === undefined || options.escape === true) { | |
| 10814 | + options.escape = Buffer.from('"', options.encoding); | |
| 10815 | + } else if (typeof options.escape === "string") { | |
| 10816 | + options.escape = Buffer.from(options.escape, options.encoding); | |
| 10817 | + } else if (options.escape === null || options.escape === false) { | |
| 10818 | + options.escape = null; | |
| 10819 | + } | |
| 10820 | + if (options.escape !== null) { | |
| 10821 | + if (!Buffer.isBuffer(options.escape)) { | |
| 10822 | + throw new Error( | |
| 10823 | + `Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`, | |
| 10824 | + ); | |
| 10825 | + } | |
| 10826 | + } | |
| 10827 | + // Normalize option `from` | |
| 10828 | + if (options.from === undefined || options.from === null) { | |
| 10829 | + options.from = 1; | |
| 10830 | + } else { | |
| 10831 | + if (typeof options.from === "string" && /\d+/.test(options.from)) { | |
| 10832 | + options.from = parseInt(options.from); | |
| 10833 | + } | |
| 10834 | + if (Number.isInteger(options.from)) { | |
| 10835 | + if (options.from < 0) { | |
| 10836 | + throw new Error( | |
| 10837 | + `Invalid Option: from must be a positive integer, got ${JSON.stringify(opts.from)}`, | |
| 10838 | + ); | |
| 10839 | + } | |
| 10840 | + } else { | |
| 10841 | + throw new Error( | |
| 10842 | + `Invalid Option: from must be an integer, got ${JSON.stringify(options.from)}`, | |
| 10843 | + ); | |
| 10844 | + } | |
| 10845 | + } | |
| 10846 | + // Normalize option `from_line` | |
| 10847 | + if (options.from_line === undefined || options.from_line === null) { | |
| 10848 | + options.from_line = 1; | |
| 10849 | + } else { | |
| 10850 | + if ( | |
| 10851 | + typeof options.from_line === "string" && | |
| 10852 | + /\d+/.test(options.from_line) | |
| 10853 | + ) { | |
| 10854 | + options.from_line = parseInt(options.from_line); | |
| 10855 | + } | |
| 10856 | + if (Number.isInteger(options.from_line)) { | |
| 10857 | + if (options.from_line <= 0) { | |
| 10858 | + throw new Error( | |
| 10859 | + `Invalid Option: from_line must be a positive integer greater than 0, got ${JSON.stringify(opts.from_line)}`, | |
| 10860 | + ); | |
| 10861 | + } | |
| 10862 | + } else { | |
| 10863 | + throw new Error( | |
| 10864 | + `Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`, | |
| 10865 | + ); | |
| 10866 | + } | |
| 10867 | + } | |
| 10868 | + // Normalize options `ignore_last_delimiters` | |
| 10869 | + if ( | |
| 10870 | + options.ignore_last_delimiters === undefined || | |
| 10871 | + options.ignore_last_delimiters === null | |
| 10872 | + ) { | |
| 10873 | + options.ignore_last_delimiters = false; | |
| 10874 | + } else if (typeof options.ignore_last_delimiters === "number") { | |
| 10875 | + options.ignore_last_delimiters = Math.floor(options.ignore_last_delimiters); | |
| 10876 | + if (options.ignore_last_delimiters === 0) { | |
| 10877 | + options.ignore_last_delimiters = false; | |
| 10878 | + } | |
| 10879 | + } else if (typeof options.ignore_last_delimiters !== "boolean") { | |
| 10880 | + throw new CsvError( | |
| 10881 | + "CSV_INVALID_OPTION_IGNORE_LAST_DELIMITERS", | |
| 10882 | + [ | |
| 10883 | + "Invalid option `ignore_last_delimiters`:", | |
| 10884 | + "the value must be a boolean value or an integer,", | |
| 10885 | + `got ${JSON.stringify(options.ignore_last_delimiters)}`, | |
| 10886 | + ], | |
| 10887 | + options, | |
| 10888 | + ); | |
| 10889 | + } | |
| 10890 | + if (options.ignore_last_delimiters === true && options.columns === false) { | |
| 10891 | + throw new CsvError( | |
| 10892 | + "CSV_IGNORE_LAST_DELIMITERS_REQUIRES_COLUMNS", | |
| 10893 | + [ | |
| 10894 | + "The option `ignore_last_delimiters`", | |
| 10895 | + "requires the activation of the `columns` option", | |
| 10896 | + ], | |
| 10897 | + options, | |
| 10898 | + ); | |
| 10899 | + } | |
| 10900 | + // Normalize option `info` | |
| 10901 | + if ( | |
| 10902 | + options.info === undefined || | |
| 10903 | + options.info === null || | |
| 10904 | + options.info === false | |
| 10905 | + ) { | |
| 10906 | + options.info = false; | |
| 10907 | + } else if (options.info !== true) { | |
| 10908 | + throw new Error( | |
| 10909 | + `Invalid Option: info must be true, got ${JSON.stringify(options.info)}`, | |
| 10910 | + ); | |
| 10911 | + } | |
| 10912 | + // Normalize option `max_record_size` | |
| 10913 | + if ( | |
| 10914 | + options.max_record_size === undefined || | |
| 10915 | + options.max_record_size === null || | |
| 10916 | + options.max_record_size === false | |
| 10917 | + ) { | |
| 10918 | + options.max_record_size = 0; | |
| 10919 | + } else if ( | |
| 10920 | + Number.isInteger(options.max_record_size) && | |
| 10921 | + options.max_record_size >= 0 | |
| 10922 | + ) ; else if ( | |
| 10923 | + typeof options.max_record_size === "string" && | |
| 10924 | + /\d+/.test(options.max_record_size) | |
| 10925 | + ) { | |
| 10926 | + options.max_record_size = parseInt(options.max_record_size); | |
| 10927 | + } else { | |
| 10928 | + throw new Error( | |
| 10929 | + `Invalid Option: max_record_size must be a positive integer, got ${JSON.stringify(options.max_record_size)}`, | |
| 10930 | + ); | |
| 10931 | + } | |
| 10932 | + // Normalize option `objname` | |
| 10933 | + if ( | |
| 10934 | + options.objname === undefined || | |
| 10935 | + options.objname === null || | |
| 10936 | + options.objname === false | |
| 10937 | + ) { | |
| 10938 | + options.objname = undefined; | |
| 10939 | + } else if (Buffer.isBuffer(options.objname)) { | |
| 10940 | + if (options.objname.length === 0) { | |
| 10941 | + throw new Error(`Invalid Option: objname must be a non empty buffer`); | |
| 10942 | + } | |
| 10943 | + if (options.encoding === null) ; else { | |
| 10944 | + options.objname = options.objname.toString(options.encoding); | |
| 10945 | + } | |
| 10946 | + } else if (typeof options.objname === "string") { | |
| 10947 | + if (options.objname.length === 0) { | |
| 10948 | + throw new Error(`Invalid Option: objname must be a non empty string`); | |
| 10949 | + } | |
| 10950 | + // Great, nothing to do | |
| 10951 | + } else if (typeof options.objname === "number") ; else { | |
| 10952 | + throw new Error( | |
| 10953 | + `Invalid Option: objname must be a string or a buffer, got ${options.objname}`, | |
| 10954 | + ); | |
| 10955 | + } | |
| 10956 | + if (options.objname !== undefined) { | |
| 10957 | + if (typeof options.objname === "number") { | |
| 10958 | + if (options.columns !== false) { | |
| 10959 | + throw Error( | |
| 10960 | + "Invalid Option: objname index cannot be combined with columns or be defined as a field", | |
| 10961 | + ); | |
| 10962 | + } | |
| 10963 | + } else { | |
| 10964 | + // A string or a buffer | |
| 10965 | + if (options.columns === false) { | |
| 10966 | + throw Error( | |
| 10967 | + "Invalid Option: objname field must be combined with columns or be defined as an index", | |
| 10968 | + ); | |
| 10969 | + } | |
| 10970 | + } | |
| 10971 | + } | |
| 10972 | + // Normalize option `on_record` | |
| 10973 | + if (options.on_record === undefined || options.on_record === null) { | |
| 10974 | + options.on_record = undefined; | |
| 10975 | + } else if (typeof options.on_record !== "function") { | |
| 10976 | + throw new CsvError( | |
| 10977 | + "CSV_INVALID_OPTION_ON_RECORD", | |
| 10978 | + [ | |
| 10979 | + "Invalid option `on_record`:", | |
| 10980 | + "expect a function,", | |
| 10981 | + `got ${JSON.stringify(options.on_record)}`, | |
| 10982 | + ], | |
| 10983 | + options, | |
| 10984 | + ); | |
| 10985 | + } | |
| 10986 | + // Normalize option `on_skip` | |
| 10987 | + // options.on_skip ??= (err, chunk) => { | |
| 10988 | + // this.emit('skip', err, chunk); | |
| 10989 | + // }; | |
| 10990 | + if ( | |
| 10991 | + options.on_skip !== undefined && | |
| 10992 | + options.on_skip !== null && | |
| 10993 | + typeof options.on_skip !== "function" | |
| 10994 | + ) { | |
| 10995 | + throw new Error( | |
| 10996 | + `Invalid Option: on_skip must be a function, got ${JSON.stringify(options.on_skip)}`, | |
| 10997 | + ); | |
| 10998 | + } | |
| 10999 | + // Normalize option `quote` | |
| 11000 | + if ( | |
| 11001 | + options.quote === null || | |
| 11002 | + options.quote === false || | |
| 11003 | + options.quote === "" | |
| 11004 | + ) { | |
| 11005 | + options.quote = null; | |
| 11006 | + } else { | |
| 11007 | + if (options.quote === undefined || options.quote === true) { | |
| 11008 | + options.quote = Buffer.from('"', options.encoding); | |
| 11009 | + } else if (typeof options.quote === "string") { | |
| 11010 | + options.quote = Buffer.from(options.quote, options.encoding); | |
| 11011 | + } | |
| 11012 | + if (!Buffer.isBuffer(options.quote)) { | |
| 11013 | + throw new Error( | |
| 11014 | + `Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`, | |
| 11015 | + ); | |
| 11016 | + } | |
| 11017 | + } | |
| 11018 | + // Normalize option `raw` | |
| 11019 | + if ( | |
| 11020 | + options.raw === undefined || | |
| 11021 | + options.raw === null || | |
| 11022 | + options.raw === false | |
| 11023 | + ) { | |
| 11024 | + options.raw = false; | |
| 11025 | + } else if (options.raw !== true) { | |
| 11026 | + throw new Error( | |
| 11027 | + `Invalid Option: raw must be true, got ${JSON.stringify(options.raw)}`, | |
| 11028 | + ); | |
| 11029 | + } | |
| 11030 | + // Normalize option `record_delimiter` | |
| 11031 | + if (options.record_delimiter === undefined) { | |
| 11032 | + options.record_delimiter = []; | |
| 11033 | + } else if ( | |
| 11034 | + typeof options.record_delimiter === "string" || | |
| 11035 | + Buffer.isBuffer(options.record_delimiter) | |
| 11036 | + ) { | |
| 11037 | + if (options.record_delimiter.length === 0) { | |
| 11038 | + throw new CsvError( | |
| 11039 | + "CSV_INVALID_OPTION_RECORD_DELIMITER", | |
| 11040 | + [ | |
| 11041 | + "Invalid option `record_delimiter`:", | |
| 11042 | + "value must be a non empty string or buffer,", | |
| 11043 | + `got ${JSON.stringify(options.record_delimiter)}`, | |
| 11044 | + ], | |
| 11045 | + options, | |
| 11046 | + ); | |
| 11047 | + } | |
| 11048 | + options.record_delimiter = [options.record_delimiter]; | |
| 11049 | + } else if (!Array.isArray(options.record_delimiter)) { | |
| 11050 | + throw new CsvError( | |
| 11051 | + "CSV_INVALID_OPTION_RECORD_DELIMITER", | |
| 11052 | + [ | |
| 11053 | + "Invalid option `record_delimiter`:", | |
| 11054 | + "value must be a string, a buffer or array of string|buffer,", | |
| 11055 | + `got ${JSON.stringify(options.record_delimiter)}`, | |
| 11056 | + ], | |
| 11057 | + options, | |
| 11058 | + ); | |
| 11059 | + } | |
| 11060 | + options.record_delimiter = options.record_delimiter.map(function (rd, i) { | |
| 11061 | + if (typeof rd !== "string" && !Buffer.isBuffer(rd)) { | |
| 11062 | + throw new CsvError( | |
| 11063 | + "CSV_INVALID_OPTION_RECORD_DELIMITER", | |
| 11064 | + [ | |
| 11065 | + "Invalid option `record_delimiter`:", | |
| 11066 | + "value must be a string, a buffer or array of string|buffer", | |
| 11067 | + `at index ${i},`, | |
| 11068 | + `got ${JSON.stringify(rd)}`, | |
| 11069 | + ], | |
| 11070 | + options, | |
| 11071 | + ); | |
| 11072 | + } else if (rd.length === 0) { | |
| 11073 | + throw new CsvError( | |
| 11074 | + "CSV_INVALID_OPTION_RECORD_DELIMITER", | |
| 11075 | + [ | |
| 11076 | + "Invalid option `record_delimiter`:", | |
| 11077 | + "value must be a non empty string or buffer", | |
| 11078 | + `at index ${i},`, | |
| 11079 | + `got ${JSON.stringify(rd)}`, | |
| 11080 | + ], | |
| 11081 | + options, | |
| 11082 | + ); | |
| 11083 | + } | |
| 11084 | + if (typeof rd === "string") { | |
| 11085 | + rd = Buffer.from(rd, options.encoding); | |
| 11086 | + } | |
| 11087 | + return rd; | |
| 11088 | + }); | |
| 11089 | + // Normalize option `relax_column_count` | |
| 11090 | + if (typeof options.relax_column_count === "boolean") ; else if ( | |
| 11091 | + options.relax_column_count === undefined || | |
| 11092 | + options.relax_column_count === null | |
| 11093 | + ) { | |
| 11094 | + options.relax_column_count = false; | |
| 11095 | + } else { | |
| 11096 | + throw new Error( | |
| 11097 | + `Invalid Option: relax_column_count must be a boolean, got ${JSON.stringify(options.relax_column_count)}`, | |
| 11098 | + ); | |
| 11099 | + } | |
| 11100 | + if (typeof options.relax_column_count_less === "boolean") ; else if ( | |
| 11101 | + options.relax_column_count_less === undefined || | |
| 11102 | + options.relax_column_count_less === null | |
| 11103 | + ) { | |
| 11104 | + options.relax_column_count_less = false; | |
| 11105 | + } else { | |
| 11106 | + throw new Error( | |
| 11107 | + `Invalid Option: relax_column_count_less must be a boolean, got ${JSON.stringify(options.relax_column_count_less)}`, | |
| 11108 | + ); | |
| 11109 | + } | |
| 11110 | + if (typeof options.relax_column_count_more === "boolean") ; else if ( | |
| 11111 | + options.relax_column_count_more === undefined || | |
| 11112 | + options.relax_column_count_more === null | |
| 11113 | + ) { | |
| 11114 | + options.relax_column_count_more = false; | |
| 11115 | + } else { | |
| 11116 | + throw new Error( | |
| 11117 | + `Invalid Option: relax_column_count_more must be a boolean, got ${JSON.stringify(options.relax_column_count_more)}`, | |
| 11118 | + ); | |
| 11119 | + } | |
| 11120 | + // Normalize option `relax_quotes` | |
| 11121 | + if (typeof options.relax_quotes === "boolean") ; else if ( | |
| 11122 | + options.relax_quotes === undefined || | |
| 11123 | + options.relax_quotes === null | |
| 11124 | + ) { | |
| 11125 | + options.relax_quotes = false; | |
| 11126 | + } else { | |
| 11127 | + throw new Error( | |
| 11128 | + `Invalid Option: relax_quotes must be a boolean, got ${JSON.stringify(options.relax_quotes)}`, | |
| 11129 | + ); | |
| 11130 | + } | |
| 11131 | + // Normalize option `skip_empty_lines` | |
| 11132 | + if (typeof options.skip_empty_lines === "boolean") ; else if ( | |
| 11133 | + options.skip_empty_lines === undefined || | |
| 11134 | + options.skip_empty_lines === null | |
| 11135 | + ) { | |
| 11136 | + options.skip_empty_lines = false; | |
| 11137 | + } else { | |
| 11138 | + throw new Error( | |
| 11139 | + `Invalid Option: skip_empty_lines must be a boolean, got ${JSON.stringify(options.skip_empty_lines)}`, | |
| 11140 | + ); | |
| 11141 | + } | |
| 11142 | + // Normalize option `skip_records_with_empty_values` | |
| 11143 | + if (typeof options.skip_records_with_empty_values === "boolean") ; else if ( | |
| 11144 | + options.skip_records_with_empty_values === undefined || | |
| 11145 | + options.skip_records_with_empty_values === null | |
| 11146 | + ) { | |
| 11147 | + options.skip_records_with_empty_values = false; | |
| 11148 | + } else { | |
| 11149 | + throw new Error( | |
| 11150 | + `Invalid Option: skip_records_with_empty_values must be a boolean, got ${JSON.stringify(options.skip_records_with_empty_values)}`, | |
| 11151 | + ); | |
| 11152 | + } | |
| 11153 | + // Normalize option `skip_records_with_error` | |
| 11154 | + if (typeof options.skip_records_with_error === "boolean") ; else if ( | |
| 11155 | + options.skip_records_with_error === undefined || | |
| 11156 | + options.skip_records_with_error === null | |
| 11157 | + ) { | |
| 11158 | + options.skip_records_with_error = false; | |
| 11159 | + } else { | |
| 11160 | + throw new Error( | |
| 11161 | + `Invalid Option: skip_records_with_error must be a boolean, got ${JSON.stringify(options.skip_records_with_error)}`, | |
| 11162 | + ); | |
| 11163 | + } | |
| 11164 | + // Normalize option `rtrim` | |
| 11165 | + if ( | |
| 11166 | + options.rtrim === undefined || | |
| 11167 | + options.rtrim === null || | |
| 11168 | + options.rtrim === false | |
| 11169 | + ) { | |
| 11170 | + options.rtrim = false; | |
| 11171 | + } else if (options.rtrim !== true) { | |
| 11172 | + throw new Error( | |
| 11173 | + `Invalid Option: rtrim must be a boolean, got ${JSON.stringify(options.rtrim)}`, | |
| 11174 | + ); | |
| 11175 | + } | |
| 11176 | + // Normalize option `ltrim` | |
| 11177 | + if ( | |
| 11178 | + options.ltrim === undefined || | |
| 11179 | + options.ltrim === null || | |
| 11180 | + options.ltrim === false | |
| 11181 | + ) { | |
| 11182 | + options.ltrim = false; | |
| 11183 | + } else if (options.ltrim !== true) { | |
| 11184 | + throw new Error( | |
| 11185 | + `Invalid Option: ltrim must be a boolean, got ${JSON.stringify(options.ltrim)}`, | |
| 11186 | + ); | |
| 11187 | + } | |
| 11188 | + // Normalize option `trim` | |
| 11189 | + if ( | |
| 11190 | + options.trim === undefined || | |
| 11191 | + options.trim === null || | |
| 11192 | + options.trim === false | |
| 11193 | + ) { | |
| 11194 | + options.trim = false; | |
| 11195 | + } else if (options.trim !== true) { | |
| 11196 | + throw new Error( | |
| 11197 | + `Invalid Option: trim must be a boolean, got ${JSON.stringify(options.trim)}`, | |
| 11198 | + ); | |
| 11199 | + } | |
| 11200 | + // Normalize options `trim`, `ltrim` and `rtrim` | |
| 11201 | + if (options.trim === true && opts.ltrim !== false) { | |
| 11202 | + options.ltrim = true; | |
| 11203 | + } else if (options.ltrim !== true) { | |
| 11204 | + options.ltrim = false; | |
| 11205 | + } | |
| 11206 | + if (options.trim === true && opts.rtrim !== false) { | |
| 11207 | + options.rtrim = true; | |
| 11208 | + } else if (options.rtrim !== true) { | |
| 11209 | + options.rtrim = false; | |
| 11210 | + } | |
| 11211 | + // Normalize option `to` | |
| 11212 | + if (options.to === undefined || options.to === null) { | |
| 11213 | + options.to = -1; | |
| 11214 | + } else { | |
| 11215 | + if (typeof options.to === "string" && /\d+/.test(options.to)) { | |
| 11216 | + options.to = parseInt(options.to); | |
| 11217 | + } | |
| 11218 | + if (Number.isInteger(options.to)) { | |
| 11219 | + if (options.to <= 0) { | |
| 11220 | + throw new Error( | |
| 11221 | + `Invalid Option: to must be a positive integer greater than 0, got ${JSON.stringify(opts.to)}`, | |
| 11222 | + ); | |
| 11223 | + } | |
| 11224 | + } else { | |
| 11225 | + throw new Error( | |
| 11226 | + `Invalid Option: to must be an integer, got ${JSON.stringify(opts.to)}`, | |
| 11227 | + ); | |
| 11228 | + } | |
| 11229 | + } | |
| 11230 | + // Normalize option `to_line` | |
| 11231 | + if (options.to_line === undefined || options.to_line === null) { | |
| 11232 | + options.to_line = -1; | |
| 11233 | + } else { | |
| 11234 | + if (typeof options.to_line === "string" && /\d+/.test(options.to_line)) { | |
| 11235 | + options.to_line = parseInt(options.to_line); | |
| 11236 | + } | |
| 11237 | + if (Number.isInteger(options.to_line)) { | |
| 11238 | + if (options.to_line <= 0) { | |
| 11239 | + throw new Error( | |
| 11240 | + `Invalid Option: to_line must be a positive integer greater than 0, got ${JSON.stringify(opts.to_line)}`, | |
| 11241 | + ); | |
| 11242 | + } | |
| 11243 | + } else { | |
| 11244 | + throw new Error( | |
| 11245 | + `Invalid Option: to_line must be an integer, got ${JSON.stringify(opts.to_line)}`, | |
| 11246 | + ); | |
| 11247 | + } | |
| 11248 | + } | |
| 11249 | + return options; | |
| 11250 | +}; | |
| 11251 | + | |
| 11252 | +const isRecordEmpty = function (record) { | |
| 11253 | + return record.every( | |
| 11254 | + (field) => | |
| 11255 | + field == null || (field.toString && field.toString().trim() === ""), | |
| 11256 | + ); | |
| 11257 | +}; | |
| 11258 | + | |
| 11259 | +const cr = 13; // `\r`, carriage return, 0x0D in hexadécimal, 13 in decimal | |
| 11260 | +const nl = 10; // `\n`, newline, 0x0A in hexadecimal, 10 in decimal | |
| 11261 | + | |
| 11262 | +const boms = { | |
| 11263 | + // Note, the following are equals: | |
| 11264 | + // Buffer.from("\ufeff") | |
| 11265 | + // Buffer.from([239, 187, 191]) | |
| 11266 | + // Buffer.from('EFBBBF', 'hex') | |
| 11267 | + utf8: Buffer.from([239, 187, 191]), | |
| 11268 | + // Note, the following are equals: | |
| 11269 | + // Buffer.from "\ufeff", 'utf16le | |
| 11270 | + // Buffer.from([255, 254]) | |
| 11271 | + utf16le: Buffer.from([255, 254]), | |
| 11272 | +}; | |
| 11273 | + | |
| 11274 | +const transform = function (original_options = {}) { | |
| 11275 | + const info = { | |
| 11276 | + bytes: 0, | |
| 11277 | + comment_lines: 0, | |
| 11278 | + empty_lines: 0, | |
| 11279 | + invalid_field_length: 0, | |
| 11280 | + lines: 1, | |
| 11281 | + records: 0, | |
| 11282 | + }; | |
| 11283 | + const options = normalize_options(original_options); | |
| 11284 | + return { | |
| 11285 | + info: info, | |
| 11286 | + original_options: original_options, | |
| 11287 | + options: options, | |
| 11288 | + state: init_state(options), | |
| 11289 | + __needMoreData: function (i, bufLen, end) { | |
| 11290 | + if (end) return false; | |
| 11291 | + const { encoding, escape, quote } = this.options; | |
| 11292 | + const { quoting, needMoreDataSize, recordDelimiterMaxLength } = | |
| 11293 | + this.state; | |
| 11294 | + const numOfCharLeft = bufLen - i - 1; | |
| 11295 | + const requiredLength = Math.max( | |
| 11296 | + needMoreDataSize, | |
| 11297 | + // Skip if the remaining buffer smaller than record delimiter | |
| 11298 | + // If "record_delimiter" is yet to be discovered: | |
| 11299 | + // 1. It is equals to `[]` and "recordDelimiterMaxLength" equals `0` | |
| 11300 | + // 2. We set the length to windows line ending in the current encoding | |
| 11301 | + // Note, that encoding is known from user or bom discovery at that point | |
| 11302 | + // recordDelimiterMaxLength, | |
| 11303 | + recordDelimiterMaxLength === 0 | |
| 11304 | + ? Buffer.from("\r\n", encoding).length | |
| 11305 | + : recordDelimiterMaxLength, | |
| 11306 | + // Skip if remaining buffer can be an escaped quote | |
| 11307 | + quoting ? (escape === null ? 0 : escape.length) + quote.length : 0, | |
| 11308 | + // Skip if remaining buffer can be record delimiter following the closing quote | |
| 11309 | + quoting ? quote.length + recordDelimiterMaxLength : 0, | |
| 11310 | + ); | |
| 11311 | + return numOfCharLeft < requiredLength; | |
| 11312 | + }, | |
| 11313 | + // Central parser implementation | |
| 11314 | + parse: function (nextBuf, end, push, close) { | |
| 11315 | + const { | |
| 11316 | + bom, | |
| 11317 | + comment_no_infix, | |
| 11318 | + encoding, | |
| 11319 | + from_line, | |
| 11320 | + ltrim, | |
| 11321 | + max_record_size, | |
| 11322 | + raw, | |
| 11323 | + relax_quotes, | |
| 11324 | + rtrim, | |
| 11325 | + skip_empty_lines, | |
| 11326 | + to, | |
| 11327 | + to_line, | |
| 11328 | + } = this.options; | |
| 11329 | + let { comment, escape, quote, record_delimiter } = this.options; | |
| 11330 | + const { bomSkipped, previousBuf, rawBuffer, escapeIsQuote } = this.state; | |
| 11331 | + let buf; | |
| 11332 | + if (previousBuf === undefined) { | |
| 11333 | + if (nextBuf === undefined) { | |
| 11334 | + // Handle empty string | |
| 11335 | + close(); | |
| 11336 | + return; | |
| 11337 | + } else { | |
| 11338 | + buf = nextBuf; | |
| 11339 | + } | |
| 11340 | + } else if (previousBuf !== undefined && nextBuf === undefined) { | |
| 11341 | + buf = previousBuf; | |
| 11342 | + } else { | |
| 11343 | + buf = Buffer.concat([previousBuf, nextBuf]); | |
| 11344 | + } | |
| 11345 | + // Handle UTF BOM | |
| 11346 | + if (bomSkipped === false) { | |
| 11347 | + if (bom === false) { | |
| 11348 | + this.state.bomSkipped = true; | |
| 11349 | + } else if (buf.length < 3) { | |
| 11350 | + // No enough data | |
| 11351 | + if (end === false) { | |
| 11352 | + // Wait for more data | |
| 11353 | + this.state.previousBuf = buf; | |
| 11354 | + return; | |
| 11355 | + } | |
| 11356 | + } else { | |
| 11357 | + for (const encoding in boms) { | |
| 11358 | + if (boms[encoding].compare(buf, 0, boms[encoding].length) === 0) { | |
| 11359 | + // Skip BOM | |
| 11360 | + const bomLength = boms[encoding].length; | |
| 11361 | + this.state.bufBytesStart += bomLength; | |
| 11362 | + buf = buf.slice(bomLength); | |
| 11363 | + // Renormalize original options with the new encoding | |
| 11364 | + this.options = normalize_options({ | |
| 11365 | + ...this.original_options, | |
| 11366 | + encoding: encoding, | |
| 11367 | + }); | |
| 11368 | + // Options will re-evaluate the Buffer with the new encoding | |
| 11369 | + ({ comment, escape, quote } = this.options); | |
| 11370 | + break; | |
| 11371 | + } | |
| 11372 | + } | |
| 11373 | + this.state.bomSkipped = true; | |
| 11374 | + } | |
| 11375 | + } | |
| 11376 | + const bufLen = buf.length; | |
| 11377 | + let pos; | |
| 11378 | + for (pos = 0; pos < bufLen; pos++) { | |
| 11379 | + // Ensure we get enough space to look ahead | |
| 11380 | + // There should be a way to move this out of the loop | |
| 11381 | + if (this.__needMoreData(pos, bufLen, end)) { | |
| 11382 | + break; | |
| 11383 | + } | |
| 11384 | + if (this.state.wasRowDelimiter === true) { | |
| 11385 | + this.info.lines++; | |
| 11386 | + this.state.wasRowDelimiter = false; | |
| 11387 | + } | |
| 11388 | + if (to_line !== -1 && this.info.lines > to_line) { | |
| 11389 | + this.state.stop = true; | |
| 11390 | + close(); | |
| 11391 | + return; | |
| 11392 | + } | |
| 11393 | + // Auto discovery of record_delimiter, unix, mac and windows supported | |
| 11394 | + if (this.state.quoting === false && record_delimiter.length === 0) { | |
| 11395 | + const record_delimiterCount = this.__autoDiscoverRecordDelimiter( | |
| 11396 | + buf, | |
| 11397 | + pos, | |
| 11398 | + ); | |
| 11399 | + if (record_delimiterCount) { | |
| 11400 | + record_delimiter = this.options.record_delimiter; | |
| 11401 | + } | |
| 11402 | + } | |
| 11403 | + const chr = buf[pos]; | |
| 11404 | + if (raw === true) { | |
| 11405 | + rawBuffer.append(chr); | |
| 11406 | + } | |
| 11407 | + if ( | |
| 11408 | + (chr === cr || chr === nl) && | |
| 11409 | + this.state.wasRowDelimiter === false | |
| 11410 | + ) { | |
| 11411 | + this.state.wasRowDelimiter = true; | |
| 11412 | + } | |
| 11413 | + // Previous char was a valid escape char | |
| 11414 | + // treat the current char as a regular char | |
| 11415 | + if (this.state.escaping === true) { | |
| 11416 | + this.state.escaping = false; | |
| 11417 | + } else { | |
| 11418 | + // Escape is only active inside quoted fields | |
| 11419 | + // We are quoting, the char is an escape chr and there is a chr to escape | |
| 11420 | + // if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){ | |
| 11421 | + if ( | |
| 11422 | + escape !== null && | |
| 11423 | + this.state.quoting === true && | |
| 11424 | + this.__isEscape(buf, pos, chr) && | |
| 11425 | + pos + escape.length < bufLen | |
| 11426 | + ) { | |
| 11427 | + if (escapeIsQuote) { | |
| 11428 | + if (this.__isQuote(buf, pos + escape.length)) { | |
| 11429 | + this.state.escaping = true; | |
| 11430 | + pos += escape.length - 1; | |
| 11431 | + continue; | |
| 11432 | + } | |
| 11433 | + } else { | |
| 11434 | + this.state.escaping = true; | |
| 11435 | + pos += escape.length - 1; | |
| 11436 | + continue; | |
| 11437 | + } | |
| 11438 | + } | |
| 11439 | + // Not currently escaping and chr is a quote | |
| 11440 | + // TODO: need to compare bytes instead of single char | |
| 11441 | + if (this.state.commenting === false && this.__isQuote(buf, pos)) { | |
| 11442 | + if (this.state.quoting === true) { | |
| 11443 | + const nextChr = buf[pos + quote.length]; | |
| 11444 | + const isNextChrTrimable = | |
| 11445 | + rtrim && this.__isCharTrimable(buf, pos + quote.length); | |
| 11446 | + const isNextChrComment = | |
| 11447 | + comment !== null && | |
| 11448 | + this.__compareBytes(comment, buf, pos + quote.length, nextChr); | |
| 11449 | + const isNextChrDelimiter = this.__isDelimiter( | |
| 11450 | + buf, | |
| 11451 | + pos + quote.length, | |
| 11452 | + nextChr, | |
| 11453 | + ); | |
| 11454 | + const isNextChrRecordDelimiter = | |
| 11455 | + record_delimiter.length === 0 | |
| 11456 | + ? this.__autoDiscoverRecordDelimiter(buf, pos + quote.length) | |
| 11457 | + : this.__isRecordDelimiter(nextChr, buf, pos + quote.length); | |
| 11458 | + // Escape a quote | |
| 11459 | + // Treat next char as a regular character | |
| 11460 | + if ( | |
| 11461 | + escape !== null && | |
| 11462 | + this.__isEscape(buf, pos, chr) && | |
| 11463 | + this.__isQuote(buf, pos + escape.length) | |
| 11464 | + ) { | |
| 11465 | + pos += escape.length - 1; | |
| 11466 | + } else if ( | |
| 11467 | + !nextChr || | |
| 11468 | + isNextChrDelimiter || | |
| 11469 | + isNextChrRecordDelimiter || | |
| 11470 | + isNextChrComment || | |
| 11471 | + isNextChrTrimable | |
| 11472 | + ) { | |
| 11473 | + this.state.quoting = false; | |
| 11474 | + this.state.wasQuoting = true; | |
| 11475 | + pos += quote.length - 1; | |
| 11476 | + continue; | |
| 11477 | + } else if (relax_quotes === false) { | |
| 11478 | + const err = this.__error( | |
| 11479 | + new CsvError( | |
| 11480 | + "CSV_INVALID_CLOSING_QUOTE", | |
| 11481 | + [ | |
| 11482 | + "Invalid Closing Quote:", | |
| 11483 | + `got "${String.fromCharCode(nextChr)}"`, | |
| 11484 | + `at line ${this.info.lines}`, | |
| 11485 | + "instead of delimiter, record delimiter, trimable character", | |
| 11486 | + "(if activated) or comment", | |
| 11487 | + ], | |
| 11488 | + this.options, | |
| 11489 | + this.__infoField(), | |
| 11490 | + ), | |
| 11491 | + ); | |
| 11492 | + if (err !== undefined) return err; | |
| 11493 | + } else { | |
| 11494 | + this.state.quoting = false; | |
| 11495 | + this.state.wasQuoting = true; | |
| 11496 | + this.state.field.prepend(quote); | |
| 11497 | + pos += quote.length - 1; | |
| 11498 | + } | |
| 11499 | + } else { | |
| 11500 | + if (this.state.field.length !== 0) { | |
| 11501 | + // In relax_quotes mode, treat opening quote preceded by chrs as regular | |
| 11502 | + if (relax_quotes === false) { | |
| 11503 | + const info = this.__infoField(); | |
| 11504 | + const bom = Object.keys(boms) | |
| 11505 | + .map((b) => | |
| 11506 | + boms[b].equals(this.state.field.toString()) ? b : false, | |
| 11507 | + ) | |
| 11508 | + .filter(Boolean)[0]; | |
| 11509 | + const err = this.__error( | |
| 11510 | + new CsvError( | |
| 11511 | + "INVALID_OPENING_QUOTE", | |
| 11512 | + [ | |
| 11513 | + "Invalid Opening Quote:", | |
| 11514 | + `a quote is found on field ${JSON.stringify(info.column)} at line ${info.lines}, value is ${JSON.stringify(this.state.field.toString(encoding))}`, | |
| 11515 | + bom ? `(${bom} bom)` : undefined, | |
| 11516 | + ], | |
| 11517 | + this.options, | |
| 11518 | + info, | |
| 11519 | + { | |
| 11520 | + field: this.state.field, | |
| 11521 | + }, | |
| 11522 | + ), | |
| 11523 | + ); | |
| 11524 | + if (err !== undefined) return err; | |
| 11525 | + } | |
| 11526 | + } else { | |
| 11527 | + this.state.quoting = true; | |
| 11528 | + pos += quote.length - 1; | |
| 11529 | + continue; | |
| 11530 | + } | |
| 11531 | + } | |
| 11532 | + } | |
| 11533 | + if (this.state.quoting === false) { | |
| 11534 | + const recordDelimiterLength = this.__isRecordDelimiter( | |
| 11535 | + chr, | |
| 11536 | + buf, | |
| 11537 | + pos, | |
| 11538 | + ); | |
| 11539 | + if (recordDelimiterLength !== 0) { | |
| 11540 | + // Do not emit comments which take a full line | |
| 11541 | + const skipCommentLine = | |
| 11542 | + this.state.commenting && | |
| 11543 | + this.state.wasQuoting === false && | |
| 11544 | + this.state.record.length === 0 && | |
| 11545 | + this.state.field.length === 0; | |
| 11546 | + if (skipCommentLine) { | |
| 11547 | + this.info.comment_lines++; | |
| 11548 | + // Skip full comment line | |
| 11549 | + } else { | |
| 11550 | + // Activate records emition if above from_line | |
| 11551 | + if ( | |
| 11552 | + this.state.enabled === false && | |
| 11553 | + this.info.lines + | |
| 11554 | + (this.state.wasRowDelimiter === true ? 1 : 0) >= | |
| 11555 | + from_line | |
| 11556 | + ) { | |
| 11557 | + this.state.enabled = true; | |
| 11558 | + this.__resetField(); | |
| 11559 | + this.__resetRecord(); | |
| 11560 | + pos += recordDelimiterLength - 1; | |
| 11561 | + continue; | |
| 11562 | + } | |
| 11563 | + // Skip if line is empty and skip_empty_lines activated | |
| 11564 | + if ( | |
| 11565 | + skip_empty_lines === true && | |
| 11566 | + this.state.wasQuoting === false && | |
| 11567 | + this.state.record.length === 0 && | |
| 11568 | + this.state.field.length === 0 | |
| 11569 | + ) { | |
| 11570 | + this.info.empty_lines++; | |
| 11571 | + pos += recordDelimiterLength - 1; | |
| 11572 | + continue; | |
| 11573 | + } | |
| 11574 | + this.info.bytes = this.state.bufBytesStart + pos; | |
| 11575 | + const errField = this.__onField(); | |
| 11576 | + if (errField !== undefined) return errField; | |
| 11577 | + this.info.bytes = | |
| 11578 | + this.state.bufBytesStart + pos + recordDelimiterLength; | |
| 11579 | + const errRecord = this.__onRecord(push); | |
| 11580 | + if (errRecord !== undefined) return errRecord; | |
| 11581 | + if (to !== -1 && this.info.records >= to) { | |
| 11582 | + this.state.stop = true; | |
| 11583 | + close(); | |
| 11584 | + return; | |
| 11585 | + } | |
| 11586 | + } | |
| 11587 | + this.state.commenting = false; | |
| 11588 | + pos += recordDelimiterLength - 1; | |
| 11589 | + continue; | |
| 11590 | + } | |
| 11591 | + if (this.state.commenting) { | |
| 11592 | + continue; | |
| 11593 | + } | |
| 11594 | + if ( | |
| 11595 | + comment !== null && | |
| 11596 | + (comment_no_infix === false || | |
| 11597 | + (this.state.record.length === 0 && | |
| 11598 | + this.state.field.length === 0)) | |
| 11599 | + ) { | |
| 11600 | + const commentCount = this.__compareBytes(comment, buf, pos, chr); | |
| 11601 | + if (commentCount !== 0) { | |
| 11602 | + this.state.commenting = true; | |
| 11603 | + continue; | |
| 11604 | + } | |
| 11605 | + } | |
| 11606 | + const delimiterLength = this.__isDelimiter(buf, pos, chr); | |
| 11607 | + if (delimiterLength !== 0) { | |
| 11608 | + this.info.bytes = this.state.bufBytesStart + pos; | |
| 11609 | + const errField = this.__onField(); | |
| 11610 | + if (errField !== undefined) return errField; | |
| 11611 | + pos += delimiterLength - 1; | |
| 11612 | + continue; | |
| 11613 | + } | |
| 11614 | + } | |
| 11615 | + } | |
| 11616 | + if (this.state.commenting === false) { | |
| 11617 | + if ( | |
| 11618 | + max_record_size !== 0 && | |
| 11619 | + this.state.record_length + this.state.field.length > max_record_size | |
| 11620 | + ) { | |
| 11621 | + return this.__error( | |
| 11622 | + new CsvError( | |
| 11623 | + "CSV_MAX_RECORD_SIZE", | |
| 11624 | + [ | |
| 11625 | + "Max Record Size:", | |
| 11626 | + "record exceed the maximum number of tolerated bytes", | |
| 11627 | + `of ${max_record_size}`, | |
| 11628 | + `at line ${this.info.lines}`, | |
| 11629 | + ], | |
| 11630 | + this.options, | |
| 11631 | + this.__infoField(), | |
| 11632 | + ), | |
| 11633 | + ); | |
| 11634 | + } | |
| 11635 | + } | |
| 11636 | + const lappend = | |
| 11637 | + ltrim === false || | |
| 11638 | + this.state.quoting === true || | |
| 11639 | + this.state.field.length !== 0 || | |
| 11640 | + !this.__isCharTrimable(buf, pos); | |
| 11641 | + // rtrim in non quoting is handle in __onField | |
| 11642 | + const rappend = rtrim === false || this.state.wasQuoting === false; | |
| 11643 | + if (lappend === true && rappend === true) { | |
| 11644 | + this.state.field.append(chr); | |
| 11645 | + } else if (rtrim === true && !this.__isCharTrimable(buf, pos)) { | |
| 11646 | + return this.__error( | |
| 11647 | + new CsvError( | |
| 11648 | + "CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE", | |
| 11649 | + [ | |
| 11650 | + "Invalid Closing Quote:", | |
| 11651 | + "found non trimable byte after quote", | |
| 11652 | + `at line ${this.info.lines}`, | |
| 11653 | + ], | |
| 11654 | + this.options, | |
| 11655 | + this.__infoField(), | |
| 11656 | + ), | |
| 11657 | + ); | |
| 11658 | + } else { | |
| 11659 | + if (lappend === false) { | |
| 11660 | + pos += this.__isCharTrimable(buf, pos) - 1; | |
| 11661 | + } | |
| 11662 | + continue; | |
| 11663 | + } | |
| 11664 | + } | |
| 11665 | + if (end === true) { | |
| 11666 | + // Ensure we are not ending in a quoting state | |
| 11667 | + if (this.state.quoting === true) { | |
| 11668 | + const err = this.__error( | |
| 11669 | + new CsvError( | |
| 11670 | + "CSV_QUOTE_NOT_CLOSED", | |
| 11671 | + [ | |
| 11672 | + "Quote Not Closed:", | |
| 11673 | + `the parsing is finished with an opening quote at line ${this.info.lines}`, | |
| 11674 | + ], | |
| 11675 | + this.options, | |
| 11676 | + this.__infoField(), | |
| 11677 | + ), | |
| 11678 | + ); | |
| 11679 | + if (err !== undefined) return err; | |
| 11680 | + } else { | |
| 11681 | + // Skip last line if it has no characters | |
| 11682 | + if ( | |
| 11683 | + this.state.wasQuoting === true || | |
| 11684 | + this.state.record.length !== 0 || | |
| 11685 | + this.state.field.length !== 0 | |
| 11686 | + ) { | |
| 11687 | + this.info.bytes = this.state.bufBytesStart + pos; | |
| 11688 | + const errField = this.__onField(); | |
| 11689 | + if (errField !== undefined) return errField; | |
| 11690 | + const errRecord = this.__onRecord(push); | |
| 11691 | + if (errRecord !== undefined) return errRecord; | |
| 11692 | + } else if (this.state.wasRowDelimiter === true) { | |
| 11693 | + this.info.empty_lines++; | |
| 11694 | + } else if (this.state.commenting === true) { | |
| 11695 | + this.info.comment_lines++; | |
| 11696 | + } | |
| 11697 | + } | |
| 11698 | + } else { | |
| 11699 | + this.state.bufBytesStart += pos; | |
| 11700 | + this.state.previousBuf = buf.slice(pos); | |
| 11701 | + } | |
| 11702 | + if (this.state.wasRowDelimiter === true) { | |
| 11703 | + this.info.lines++; | |
| 11704 | + this.state.wasRowDelimiter = false; | |
| 11705 | + } | |
| 11706 | + }, | |
| 11707 | + __onRecord: function (push) { | |
| 11708 | + const { | |
| 11709 | + columns, | |
| 11710 | + group_columns_by_name, | |
| 11711 | + encoding, | |
| 11712 | + info, | |
| 11713 | + from, | |
| 11714 | + relax_column_count, | |
| 11715 | + relax_column_count_less, | |
| 11716 | + relax_column_count_more, | |
| 11717 | + raw, | |
| 11718 | + skip_records_with_empty_values, | |
| 11719 | + } = this.options; | |
| 11720 | + const { enabled, record } = this.state; | |
| 11721 | + if (enabled === false) { | |
| 11722 | + return this.__resetRecord(); | |
| 11723 | + } | |
| 11724 | + // Convert the first line into column names | |
| 11725 | + const recordLength = record.length; | |
| 11726 | + if (columns === true) { | |
| 11727 | + if (skip_records_with_empty_values === true && isRecordEmpty(record)) { | |
| 11728 | + this.__resetRecord(); | |
| 11729 | + return; | |
| 11730 | + } | |
| 11731 | + return this.__firstLineToColumns(record); | |
| 11732 | + } | |
| 11733 | + if (columns === false && this.info.records === 0) { | |
| 11734 | + this.state.expectedRecordLength = recordLength; | |
| 11735 | + } | |
| 11736 | + if (recordLength !== this.state.expectedRecordLength) { | |
| 11737 | + const err = | |
| 11738 | + columns === false | |
| 11739 | + ? new CsvError( | |
| 11740 | + "CSV_RECORD_INCONSISTENT_FIELDS_LENGTH", | |
| 11741 | + [ | |
| 11742 | + "Invalid Record Length:", | |
| 11743 | + `expect ${this.state.expectedRecordLength},`, | |
| 11744 | + `got ${recordLength} on line ${this.info.lines}`, | |
| 11745 | + ], | |
| 11746 | + this.options, | |
| 11747 | + this.__infoField(), | |
| 11748 | + { | |
| 11749 | + record: record, | |
| 11750 | + }, | |
| 11751 | + ) | |
| 11752 | + : new CsvError( | |
| 11753 | + "CSV_RECORD_INCONSISTENT_COLUMNS", | |
| 11754 | + [ | |
| 11755 | + "Invalid Record Length:", | |
| 11756 | + `columns length is ${columns.length},`, // rename columns | |
| 11757 | + `got ${recordLength} on line ${this.info.lines}`, | |
| 11758 | + ], | |
| 11759 | + this.options, | |
| 11760 | + this.__infoField(), | |
| 11761 | + { | |
| 11762 | + record: record, | |
| 11763 | + }, | |
| 11764 | + ); | |
| 11765 | + if ( | |
| 11766 | + relax_column_count === true || | |
| 11767 | + (relax_column_count_less === true && | |
| 11768 | + recordLength < this.state.expectedRecordLength) || | |
| 11769 | + (relax_column_count_more === true && | |
| 11770 | + recordLength > this.state.expectedRecordLength) | |
| 11771 | + ) { | |
| 11772 | + this.info.invalid_field_length++; | |
| 11773 | + this.state.error = err; | |
| 11774 | + // Error is undefined with skip_records_with_error | |
| 11775 | + } else { | |
| 11776 | + const finalErr = this.__error(err); | |
| 11777 | + if (finalErr) return finalErr; | |
| 11778 | + } | |
| 11779 | + } | |
| 11780 | + if (skip_records_with_empty_values === true && isRecordEmpty(record)) { | |
| 11781 | + this.__resetRecord(); | |
| 11782 | + return; | |
| 11783 | + } | |
| 11784 | + if (this.state.recordHasError === true) { | |
| 11785 | + this.__resetRecord(); | |
| 11786 | + this.state.recordHasError = false; | |
| 11787 | + return; | |
| 11788 | + } | |
| 11789 | + this.info.records++; | |
| 11790 | + if (from === 1 || this.info.records >= from) { | |
| 11791 | + const { objname } = this.options; | |
| 11792 | + // With columns, records are object | |
| 11793 | + if (columns !== false) { | |
| 11794 | + const obj = {}; | |
| 11795 | + // Transform record array to an object | |
| 11796 | + for (let i = 0, l = record.length; i < l; i++) { | |
| 11797 | + if (columns[i] === undefined || columns[i].disabled) continue; | |
| 11798 | + // Turn duplicate columns into an array | |
| 11799 | + if ( | |
| 11800 | + group_columns_by_name === true && | |
| 11801 | + obj[columns[i].name] !== undefined | |
| 11802 | + ) { | |
| 11803 | + if (Array.isArray(obj[columns[i].name])) { | |
| 11804 | + obj[columns[i].name] = obj[columns[i].name].concat(record[i]); | |
| 11805 | + } else { | |
| 11806 | + obj[columns[i].name] = [obj[columns[i].name], record[i]]; | |
| 11807 | + } | |
| 11808 | + } else { | |
| 11809 | + obj[columns[i].name] = record[i]; | |
| 11810 | + } | |
| 11811 | + } | |
| 11812 | + // Without objname (default) | |
| 11813 | + if (raw === true || info === true) { | |
| 11814 | + const extRecord = Object.assign( | |
| 11815 | + { record: obj }, | |
| 11816 | + raw === true | |
| 11817 | + ? { raw: this.state.rawBuffer.toString(encoding) } | |
| 11818 | + : {}, | |
| 11819 | + info === true ? { info: this.__infoRecord() } : {}, | |
| 11820 | + ); | |
| 11821 | + const err = this.__push( | |
| 11822 | + objname === undefined ? extRecord : [obj[objname], extRecord], | |
| 11823 | + push, | |
| 11824 | + ); | |
| 11825 | + if (err) { | |
| 11826 | + return err; | |
| 11827 | + } | |
| 11828 | + } else { | |
| 11829 | + const err = this.__push( | |
| 11830 | + objname === undefined ? obj : [obj[objname], obj], | |
| 11831 | + push, | |
| 11832 | + ); | |
| 11833 | + if (err) { | |
| 11834 | + return err; | |
| 11835 | + } | |
| 11836 | + } | |
| 11837 | + // Without columns, records are array | |
| 11838 | + } else { | |
| 11839 | + if (raw === true || info === true) { | |
| 11840 | + const extRecord = Object.assign( | |
| 11841 | + { record: record }, | |
| 11842 | + raw === true | |
| 11843 | + ? { raw: this.state.rawBuffer.toString(encoding) } | |
| 11844 | + : {}, | |
| 11845 | + info === true ? { info: this.__infoRecord() } : {}, | |
| 11846 | + ); | |
| 11847 | + const err = this.__push( | |
| 11848 | + objname === undefined ? extRecord : [record[objname], extRecord], | |
| 11849 | + push, | |
| 11850 | + ); | |
| 11851 | + if (err) { | |
| 11852 | + return err; | |
| 11853 | + } | |
| 11854 | + } else { | |
| 11855 | + const err = this.__push( | |
| 11856 | + objname === undefined ? record : [record[objname], record], | |
| 11857 | + push, | |
| 11858 | + ); | |
| 11859 | + if (err) { | |
| 11860 | + return err; | |
| 11861 | + } | |
| 11862 | + } | |
| 11863 | + } | |
| 11864 | + } | |
| 11865 | + this.__resetRecord(); | |
| 11866 | + }, | |
| 11867 | + __firstLineToColumns: function (record) { | |
| 11868 | + const { firstLineToHeaders } = this.state; | |
| 11869 | + try { | |
| 11870 | + const headers = | |
| 11871 | + firstLineToHeaders === undefined | |
| 11872 | + ? record | |
| 11873 | + : firstLineToHeaders.call(null, record); | |
| 11874 | + if (!Array.isArray(headers)) { | |
| 11875 | + return this.__error( | |
| 11876 | + new CsvError( | |
| 11877 | + "CSV_INVALID_COLUMN_MAPPING", | |
| 11878 | + [ | |
| 11879 | + "Invalid Column Mapping:", | |
| 11880 | + "expect an array from column function,", | |
| 11881 | + `got ${JSON.stringify(headers)}`, | |
| 11882 | + ], | |
| 11883 | + this.options, | |
| 11884 | + this.__infoField(), | |
| 11885 | + { | |
| 11886 | + headers: headers, | |
| 11887 | + }, | |
| 11888 | + ), | |
| 11889 | + ); | |
| 11890 | + } | |
| 11891 | + const normalizedHeaders = normalize_columns_array(headers); | |
| 11892 | + this.state.expectedRecordLength = normalizedHeaders.length; | |
| 11893 | + this.options.columns = normalizedHeaders; | |
| 11894 | + this.__resetRecord(); | |
| 11895 | + return; | |
| 11896 | + } catch (err) { | |
| 11897 | + return err; | |
| 11898 | + } | |
| 11899 | + }, | |
| 11900 | + __resetRecord: function () { | |
| 11901 | + if (this.options.raw === true) { | |
| 11902 | + this.state.rawBuffer.reset(); | |
| 11903 | + } | |
| 11904 | + this.state.error = undefined; | |
| 11905 | + this.state.record = []; | |
| 11906 | + this.state.record_length = 0; | |
| 11907 | + }, | |
| 11908 | + __onField: function () { | |
| 11909 | + const { cast, encoding, rtrim, max_record_size } = this.options; | |
| 11910 | + const { enabled, wasQuoting } = this.state; | |
| 11911 | + // Short circuit for the from_line options | |
| 11912 | + if (enabled === false) { | |
| 11913 | + return this.__resetField(); | |
| 11914 | + } | |
| 11915 | + let field = this.state.field.toString(encoding); | |
| 11916 | + if (rtrim === true && wasQuoting === false) { | |
| 11917 | + field = field.trimRight(); | |
| 11918 | + } | |
| 11919 | + if (cast === true) { | |
| 11920 | + const [err, f] = this.__cast(field); | |
| 11921 | + if (err !== undefined) return err; | |
| 11922 | + field = f; | |
| 11923 | + } | |
| 11924 | + this.state.record.push(field); | |
| 11925 | + // Increment record length if record size must not exceed a limit | |
| 11926 | + if (max_record_size !== 0 && typeof field === "string") { | |
| 11927 | + this.state.record_length += field.length; | |
| 11928 | + } | |
| 11929 | + this.__resetField(); | |
| 11930 | + }, | |
| 11931 | + __resetField: function () { | |
| 11932 | + this.state.field.reset(); | |
| 11933 | + this.state.wasQuoting = false; | |
| 11934 | + }, | |
| 11935 | + __push: function (record, push) { | |
| 11936 | + const { on_record } = this.options; | |
| 11937 | + if (on_record !== undefined) { | |
| 11938 | + const info = this.__infoRecord(); | |
| 11939 | + try { | |
| 11940 | + record = on_record.call(null, record, info); | |
| 11941 | + } catch (err) { | |
| 11942 | + return err; | |
| 11943 | + } | |
| 11944 | + if (record === undefined || record === null) { | |
| 11945 | + return; | |
| 11946 | + } | |
| 11947 | + } | |
| 11948 | + push(record); | |
| 11949 | + }, | |
| 11950 | + // Return a tuple with the error and the casted value | |
| 11951 | + __cast: function (field) { | |
| 11952 | + const { columns, relax_column_count } = this.options; | |
| 11953 | + const isColumns = Array.isArray(columns); | |
| 11954 | + // Dont loose time calling cast | |
| 11955 | + // because the final record is an object | |
| 11956 | + // and this field can't be associated to a key present in columns | |
| 11957 | + if ( | |
| 11958 | + isColumns === true && | |
| 11959 | + relax_column_count && | |
| 11960 | + this.options.columns.length <= this.state.record.length | |
| 11961 | + ) { | |
| 11962 | + return [undefined, undefined]; | |
| 11963 | + } | |
| 11964 | + if (this.state.castField !== null) { | |
| 11965 | + try { | |
| 11966 | + const info = this.__infoField(); | |
| 11967 | + return [undefined, this.state.castField.call(null, field, info)]; | |
| 11968 | + } catch (err) { | |
| 11969 | + return [err]; | |
| 11970 | + } | |
| 11971 | + } | |
| 11972 | + if (this.__isFloat(field)) { | |
| 11973 | + return [undefined, parseFloat(field)]; | |
| 11974 | + } else if (this.options.cast_date !== false) { | |
| 11975 | + const info = this.__infoField(); | |
| 11976 | + return [undefined, this.options.cast_date.call(null, field, info)]; | |
| 11977 | + } | |
| 11978 | + return [undefined, field]; | |
| 11979 | + }, | |
| 11980 | + // Helper to test if a character is a space or a line delimiter | |
| 11981 | + __isCharTrimable: function (buf, pos) { | |
| 11982 | + const isTrim = (buf, pos) => { | |
| 11983 | + const { timchars } = this.state; | |
| 11984 | + loop1: for (let i = 0; i < timchars.length; i++) { | |
| 11985 | + const timchar = timchars[i]; | |
| 11986 | + for (let j = 0; j < timchar.length; j++) { | |
| 11987 | + if (timchar[j] !== buf[pos + j]) continue loop1; | |
| 11988 | + } | |
| 11989 | + return timchar.length; | |
| 11990 | + } | |
| 11991 | + return 0; | |
| 11992 | + }; | |
| 11993 | + return isTrim(buf, pos); | |
| 11994 | + }, | |
| 11995 | + // Keep it in case we implement the `cast_int` option | |
| 11996 | + // __isInt(value){ | |
| 11997 | + // // return Number.isInteger(parseInt(value)) | |
| 11998 | + // // return !isNaN( parseInt( obj ) ); | |
| 11999 | + // return /^(\-|\+)?[1-9][0-9]*$/.test(value) | |
| 12000 | + // } | |
| 12001 | + __isFloat: function (value) { | |
| 12002 | + return value - parseFloat(value) + 1 >= 0; // Borrowed from jquery | |
| 12003 | + }, | |
| 12004 | + __compareBytes: function (sourceBuf, targetBuf, targetPos, firstByte) { | |
| 12005 | + if (sourceBuf[0] !== firstByte) return 0; | |
| 12006 | + const sourceLength = sourceBuf.length; | |
| 12007 | + for (let i = 1; i < sourceLength; i++) { | |
| 12008 | + if (sourceBuf[i] !== targetBuf[targetPos + i]) return 0; | |
| 12009 | + } | |
| 12010 | + return sourceLength; | |
| 12011 | + }, | |
| 12012 | + __isDelimiter: function (buf, pos, chr) { | |
| 12013 | + const { delimiter, ignore_last_delimiters } = this.options; | |
| 12014 | + if ( | |
| 12015 | + ignore_last_delimiters === true && | |
| 12016 | + this.state.record.length === this.options.columns.length - 1 | |
| 12017 | + ) { | |
| 12018 | + return 0; | |
| 12019 | + } else if ( | |
| 12020 | + ignore_last_delimiters !== false && | |
| 12021 | + typeof ignore_last_delimiters === "number" && | |
| 12022 | + this.state.record.length === ignore_last_delimiters - 1 | |
| 12023 | + ) { | |
| 12024 | + return 0; | |
| 12025 | + } | |
| 12026 | + loop1: for (let i = 0; i < delimiter.length; i++) { | |
| 12027 | + const del = delimiter[i]; | |
| 12028 | + if (del[0] === chr) { | |
| 12029 | + for (let j = 1; j < del.length; j++) { | |
| 12030 | + if (del[j] !== buf[pos + j]) continue loop1; | |
| 12031 | + } | |
| 12032 | + return del.length; | |
| 12033 | + } | |
| 12034 | + } | |
| 12035 | + return 0; | |
| 12036 | + }, | |
| 12037 | + __isRecordDelimiter: function (chr, buf, pos) { | |
| 12038 | + const { record_delimiter } = this.options; | |
| 12039 | + const recordDelimiterLength = record_delimiter.length; | |
| 12040 | + loop1: for (let i = 0; i < recordDelimiterLength; i++) { | |
| 12041 | + const rd = record_delimiter[i]; | |
| 12042 | + const rdLength = rd.length; | |
| 12043 | + if (rd[0] !== chr) { | |
| 12044 | + continue; | |
| 12045 | + } | |
| 12046 | + for (let j = 1; j < rdLength; j++) { | |
| 12047 | + if (rd[j] !== buf[pos + j]) { | |
| 12048 | + continue loop1; | |
| 12049 | + } | |
| 12050 | + } | |
| 12051 | + return rd.length; | |
| 12052 | + } | |
| 12053 | + return 0; | |
| 12054 | + }, | |
| 12055 | + __isEscape: function (buf, pos, chr) { | |
| 12056 | + const { escape } = this.options; | |
| 12057 | + if (escape === null) return false; | |
| 12058 | + const l = escape.length; | |
| 12059 | + if (escape[0] === chr) { | |
| 12060 | + for (let i = 0; i < l; i++) { | |
| 12061 | + if (escape[i] !== buf[pos + i]) { | |
| 12062 | + return false; | |
| 12063 | + } | |
| 12064 | + } | |
| 12065 | + return true; | |
| 12066 | + } | |
| 12067 | + return false; | |
| 12068 | + }, | |
| 12069 | + __isQuote: function (buf, pos) { | |
| 12070 | + const { quote } = this.options; | |
| 12071 | + if (quote === null) return false; | |
| 12072 | + const l = quote.length; | |
| 12073 | + for (let i = 0; i < l; i++) { | |
| 12074 | + if (quote[i] !== buf[pos + i]) { | |
| 12075 | + return false; | |
| 12076 | + } | |
| 12077 | + } | |
| 12078 | + return true; | |
| 12079 | + }, | |
| 12080 | + __autoDiscoverRecordDelimiter: function (buf, pos) { | |
| 12081 | + const { encoding } = this.options; | |
| 12082 | + // Note, we don't need to cache this information in state, | |
| 12083 | + // It is only called on the first line until we find out a suitable | |
| 12084 | + // record delimiter. | |
| 12085 | + const rds = [ | |
| 12086 | + // Important, the windows line ending must be before mac os 9 | |
| 12087 | + Buffer.from("\r\n", encoding), | |
| 12088 | + Buffer.from("\n", encoding), | |
| 12089 | + Buffer.from("\r", encoding), | |
| 12090 | + ]; | |
| 12091 | + loop: for (let i = 0; i < rds.length; i++) { | |
| 12092 | + const l = rds[i].length; | |
| 12093 | + for (let j = 0; j < l; j++) { | |
| 12094 | + if (rds[i][j] !== buf[pos + j]) { | |
| 12095 | + continue loop; | |
| 12096 | + } | |
| 12097 | + } | |
| 12098 | + this.options.record_delimiter.push(rds[i]); | |
| 12099 | + this.state.recordDelimiterMaxLength = rds[i].length; | |
| 12100 | + return rds[i].length; | |
| 12101 | + } | |
| 12102 | + return 0; | |
| 12103 | + }, | |
| 12104 | + __error: function (msg) { | |
| 12105 | + const { encoding, raw, skip_records_with_error } = this.options; | |
| 12106 | + const err = typeof msg === "string" ? new Error(msg) : msg; | |
| 12107 | + if (skip_records_with_error) { | |
| 12108 | + this.state.recordHasError = true; | |
| 12109 | + if (this.options.on_skip !== undefined) { | |
| 12110 | + this.options.on_skip( | |
| 12111 | + err, | |
| 12112 | + raw ? this.state.rawBuffer.toString(encoding) : undefined, | |
| 12113 | + ); | |
| 12114 | + } | |
| 12115 | + // this.emit('skip', err, raw ? this.state.rawBuffer.toString(encoding) : undefined); | |
| 12116 | + return undefined; | |
| 12117 | + } else { | |
| 12118 | + return err; | |
| 12119 | + } | |
| 12120 | + }, | |
| 12121 | + __infoDataSet: function () { | |
| 12122 | + return { | |
| 12123 | + ...this.info, | |
| 12124 | + columns: this.options.columns, | |
| 12125 | + }; | |
| 12126 | + }, | |
| 12127 | + __infoRecord: function () { | |
| 12128 | + const { columns, raw, encoding } = this.options; | |
| 12129 | + return { | |
| 12130 | + ...this.__infoDataSet(), | |
| 12131 | + error: this.state.error, | |
| 12132 | + header: columns === true, | |
| 12133 | + index: this.state.record.length, | |
| 12134 | + raw: raw ? this.state.rawBuffer.toString(encoding) : undefined, | |
| 12135 | + }; | |
| 12136 | + }, | |
| 12137 | + __infoField: function () { | |
| 12138 | + const { columns } = this.options; | |
| 12139 | + const isColumns = Array.isArray(columns); | |
| 12140 | + return { | |
| 12141 | + ...this.__infoRecord(), | |
| 12142 | + column: | |
| 12143 | + isColumns === true | |
| 12144 | + ? columns.length > this.state.record.length | |
| 12145 | + ? columns[this.state.record.length].name | |
| 12146 | + : null | |
| 12147 | + : this.state.record.length, | |
| 12148 | + quoting: this.state.wasQuoting, | |
| 12149 | + }; | |
| 12150 | + }, | |
| 12151 | + }; | |
| 12152 | +}; | |
| 12153 | + | |
| 12154 | +const parse = function (data, opts = {}) { | |
| 12155 | + if (typeof data === "string") { | |
| 12156 | + data = Buffer.from(data); | |
| 12157 | + } | |
| 12158 | + const records = opts && opts.objname ? {} : []; | |
| 12159 | + const parser = transform(opts); | |
| 12160 | + const push = (record) => { | |
| 12161 | + if (parser.options.objname === undefined) records.push(record); | |
| 12162 | + else { | |
| 12163 | + records[record[0]] = record[1]; | |
| 12164 | + } | |
| 12165 | + }; | |
| 12166 | + const close = () => {}; | |
| 12167 | + const err1 = parser.parse(data, false, push, close); | |
| 12168 | + if (err1 !== undefined) throw err1; | |
| 12169 | + const err2 = parser.parse(undefined, true, push, close); | |
| 12170 | + if (err2 !== undefined) throw err2; | |
| 12171 | + return records; | |
| 12172 | +}; | |
| 12173 | + | |
| 12174 | +exports.CsvError = CsvError; | |
| 12175 | +exports.parse = parse; | |
| 12176 | + | |
| 12177 | + | |
| 10336 | 12178 | /***/ }) |
| 10337 | 12179 | |
| 10338 | 12180 | /******/ }); |
modified
package-lock.json
+16
−7
Click to load diff…
modified
package.json
+1
−0
Click to load diff…
modified
src/setup-beam.js
+75
−16
Click to load diff…
added
test/otp/macos/aarch64-apple-darwin.csv
+97
−0
Click to load diff…
added
test/otp/macos/x86_64-apple-darwin.csv
+97
−0
Click to load diff…
modified
test/setup-beam.test.js
+52
−0
Click to load diff…
Parents: 46e0610