Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe6612e54 | ||
|
|
5230d73968 | ||
|
|
ef36b163b3 | ||
|
|
a622a8ef92 | ||
|
|
66bb6da847 | ||
|
|
eee55adcd0 | ||
|
|
cbd5ffce3e | ||
|
|
a3dda22035 | ||
|
|
7184679788 | ||
|
|
aeb2ea1dda | ||
|
|
7b20c281d5 | ||
|
|
c69f4407fd | ||
|
|
88f65ae1a6 | ||
|
|
14d406fcd9 | ||
|
|
08a9d4b9fc | ||
|
|
b3ab3696ec | ||
|
|
afe4704666 | ||
|
|
c51acfcbc5 | ||
|
|
332ff949a0 | ||
|
|
ab17a3c050 | ||
|
|
2522bba1bf | ||
|
|
d00f172c59 | ||
|
|
0eb5289cd0 | ||
|
|
7e01628b5d | ||
|
|
6432e8655c | ||
|
|
e34495e304 | ||
|
|
64c4ecf9c7 | ||
|
|
ef002e2b15 | ||
|
|
f89c5076f9 | ||
|
|
18d367c01b | ||
|
|
65baed8681 | ||
|
|
76c64a0f32 | ||
|
|
6ac1d10f77 | ||
|
|
ca57cfe6f3 | ||
|
|
c32f72581a | ||
|
|
8115bd38a1 | ||
|
|
e35ffc5a90 | ||
|
|
178643f9eb | ||
|
|
75677b9dab | ||
|
|
25ab648502 | ||
|
|
8493f1934a | ||
|
|
c63aeb9f77 | ||
|
|
d89fabe5e4 | ||
|
|
d51153b56e | ||
|
|
69990a5d1d | ||
|
|
930ec69739 | ||
|
|
d7478095e0 | ||
|
|
b823f9efdf | ||
|
|
6ecb430614 | ||
|
|
b615c8435a | ||
|
|
120467f83c | ||
|
|
4b09a836e0 | ||
|
|
55c05d943e | ||
|
|
8e61baacab | ||
|
|
7426acb1b9 | ||
|
|
175c8243b2 | ||
|
|
2c1a836db3 | ||
|
|
f29e18bca1 | ||
|
|
329442c481 | ||
|
|
01a88e7137 | ||
|
|
4def31f8ab | ||
|
|
88c75bcbf5 | ||
|
|
786f3ce797 | ||
|
|
98aeee34b6 | ||
|
|
6f7eaf1122 | ||
|
|
9593cef2f5 | ||
|
|
c78f426bf5 | ||
|
|
ff29628602 | ||
|
|
f5e42ba11a | ||
|
|
b0dae83353 | ||
|
|
40da7e8857 | ||
|
|
ac910c1cc2 | ||
|
|
5c340e488e | ||
|
|
defef37856 | ||
|
|
432d6fa1c2 | ||
|
|
24714c68a7 | ||
|
|
f088fc9cdb | ||
|
|
dddb8f7b19 | ||
|
|
2da3300bfc | ||
|
|
c7a841baae | ||
|
|
f3dfb72c69 | ||
|
|
7e149619f5 | ||
|
|
6dbb932bf3 | ||
|
|
0cb79b0ba1 | ||
|
|
265b9aaeb3 | ||
|
|
b9bd0e3dd9 | ||
|
|
6a3301f237 | ||
|
|
4911916f62 | ||
|
|
546a0db392 | ||
|
|
463f1aa593 | ||
|
|
a3a632efd1 | ||
|
|
b7a5fd7b56 | ||
|
|
04378bd9a6 | ||
|
|
fae36c5e49 | ||
|
|
712f24af0a | ||
|
|
8c9f303e37 | ||
|
|
1a2be16109 | ||
|
|
4adaac702e | ||
|
|
a1be4fbc86 | ||
|
|
d4e6a558cb | ||
|
|
4e3203f19a | ||
|
|
4a5fb1eb66 | ||
|
|
0fe6f68c34 | ||
|
|
5aa049ccb3 | ||
|
|
e4c86d4b5f | ||
|
|
e5033c94ce | ||
|
|
5d65d60721 | ||
|
|
3564c68cdc | ||
|
|
9df42f5a0a | ||
|
|
c6597bcb20 | ||
|
|
ca6edfd389 | ||
|
|
ed83c407a5 | ||
|
|
a426520b66 | ||
|
|
1c0f585f40 | ||
|
|
7c86c4bdb3 | ||
|
|
4931ab40a5 | ||
|
|
c7f78e8015 | ||
|
|
6a24bafbda | ||
|
|
8ddef06e81 | ||
|
|
472c4e1a24 | ||
|
|
305d8e603c | ||
|
|
1c7fc86e9c | ||
|
|
617dc72759 | ||
|
|
4329a3c2f2 | ||
|
|
8e8c9a7334 | ||
|
|
36c06c3a56 | ||
|
|
048cc2ddb1 | ||
|
|
048ae58d6d | ||
|
|
85e00cddd5 | ||
|
|
6c866e8ab2 | ||
|
|
55eca5dff5 | ||
|
|
070a6d51bf | ||
|
|
9c28fb7a39 | ||
|
|
cda1a09c58 | ||
|
|
3ca0bddd9d | ||
|
|
e07a91594b | ||
|
|
dfc659104c | ||
|
|
a4fa71ab23 | ||
|
|
2273bafb47 | ||
|
|
a78f1715dc | ||
|
|
b709865976 | ||
|
|
da7e04a540 | ||
|
|
66c5908619 | ||
|
|
31d8da6c77 | ||
|
|
3bdf5a912c | ||
|
|
0e9b5ec070 |
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
[
|
||||
inputs: [
|
||||
"lib/*/{lib,scripts,unicode,test}/**/*.{ex,exs}",
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
lib/elixir/test/elixir/fixtures/*.txt text eol=lf
|
||||
*.ex diff=elixir
|
||||
*.exs diff=elixir
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
---
|
||||
blank_issues_enabled: true
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
---
|
||||
name: Report an issue
|
||||
description: Tell us about something that is not working the way we (probably) intend
|
||||
description:
|
||||
Tell us about something that is not working the way we (probably) intend
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -12,17 +10,11 @@ body:
|
||||
|
||||
|
||||
Please, do not use this form for guidance, questions or support.
|
||||
Try instead in [Elixir Forum](https://elixirforum.com) or any of
|
||||
our online communities (Slack, Discord, etc).
|
||||
|
||||
- type: checkboxes
|
||||
id: existing-issue
|
||||
attributes:
|
||||
label: Existing issue
|
||||
description: Please search [existing issues](https://github.com/elixir-lang/elixir/issues) before continuing.
|
||||
options:
|
||||
- label: I have searched existing issues and could not find a duplicate.
|
||||
required: true
|
||||
Try instead in [Elixir Forum](https://elixirforum.com),
|
||||
the [IRC Chat](https://web.libera.chat/#elixir),
|
||||
[Stack Overflow](https://stackoverflow.com/questions/tagged/elixir),
|
||||
[Slack](https://elixir-slackin.herokuapp.com),
|
||||
[Discord](https://discord.gg/elixir) or in other online communities.
|
||||
|
||||
- type: textarea
|
||||
id: elixir-and-otp-version
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: CI for Markdown content
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "lib/**/*.md"
|
||||
pull_request:
|
||||
paths:
|
||||
- "lib/**/*.md"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint Markdown content
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Run markdownlint
|
||||
uses: DavidAnson/markdownlint-cli2-action@v18.0.0
|
||||
with:
|
||||
globs: |
|
||||
lib/elixir/pages/**/*.md
|
||||
README.md
|
||||
+47
-73
@@ -1,13 +1,12 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- "lib/**/*.md"
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
paths-ignore:
|
||||
- "lib/**/*.md"
|
||||
|
||||
env:
|
||||
ELIXIR_ASSERT_TIMEOUT: 2000
|
||||
@@ -19,133 +18,108 @@ permissions:
|
||||
|
||||
jobs:
|
||||
test_linux:
|
||||
name: Ubuntu 24.04, OTP ${{ matrix.otp_version }}${{ matrix.deterministic && ' (deterministic)' || '' }}${{ matrix.coverage && ' (coverage)' || '' }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
name: Ubuntu 24.04, Erlang/OTP ${{ matrix.otp_version }}${{ matrix.deterministic && ' (deterministic)' || '' }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- otp_version: "29.0"
|
||||
- otp_version: "27.1"
|
||||
deterministic: true
|
||||
- otp_version: "28.4"
|
||||
docs: true
|
||||
coverage: true
|
||||
- otp_version: "28.1"
|
||||
- otp_version: "27.3"
|
||||
- otp_version: "27.1"
|
||||
otp_latest: true
|
||||
- otp_version: "27.0"
|
||||
- otp_version: "26.0"
|
||||
- otp_version: "25.3"
|
||||
- otp_version: "25.0"
|
||||
- otp_version: master
|
||||
development: true
|
||||
- otp_version: maint
|
||||
development: true
|
||||
|
||||
env:
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ matrix.otp_version }}
|
||||
|
||||
- name: Set ERL_COMPILER_OPTIONS
|
||||
if: ${{ matrix.deterministic }}
|
||||
run: echo "ERL_COMPILER_OPTIONS=deterministic" >> $GITHUB_ENV
|
||||
|
||||
- name: Compile Elixir
|
||||
run: |
|
||||
make compile
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Build info
|
||||
run: bin/elixir --version
|
||||
|
||||
- name: Check format
|
||||
run: make test_formatted && echo "All Elixir source code files are properly formatted."
|
||||
|
||||
- name: Erlang test suite
|
||||
run: make test_erlang
|
||||
continue-on-error: ${{ matrix.development == true }}
|
||||
|
||||
continue-on-error: ${{ matrix.development }}
|
||||
- name: Elixir test suite
|
||||
run: make test_elixir
|
||||
continue-on-error: ${{ matrix.development == true }}
|
||||
env:
|
||||
COVER: "${{ matrix.coverage }}"
|
||||
|
||||
continue-on-error: ${{ matrix.development }}
|
||||
- name: Build docs (ExDoc main)
|
||||
if: ${{ matrix.docs }}
|
||||
if: ${{ matrix.otp_latest }}
|
||||
run: |
|
||||
cd ..
|
||||
git clone https://github.com/elixir-lang/ex_doc.git --depth 1
|
||||
cd ex_doc
|
||||
../elixir/bin/mix do local.rebar --force + local.hex --force + deps.get + compile
|
||||
cd ../elixir/
|
||||
git fetch --tags
|
||||
DOCS_OPTIONS="--warnings-as-errors" make docs
|
||||
|
||||
- name: "Calculate Coverage"
|
||||
if: ${{ matrix.coverage }}
|
||||
run: make cover | tee "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: "Upload Coverage Artifact"
|
||||
if: ${{ matrix.coverage }}
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: TestCoverage
|
||||
path: cover/*
|
||||
|
||||
make docs
|
||||
- name: Check reproducible builds
|
||||
if: ${{ matrix.deterministic }}
|
||||
run: taskset 1 make check_reproducible
|
||||
|
||||
- name: Check git is not required
|
||||
if: ${{ matrix.deterministic }}
|
||||
run: |
|
||||
rm -rf .git
|
||||
cd lib/elixir
|
||||
elixirc --ignore-module-conflict -o ebin "lib/**/*.ex"
|
||||
# Recompile System without .git
|
||||
cd lib/elixir && ../../bin/elixirc -o ebin lib/system.ex && cd -
|
||||
taskset 1 make check_reproducible
|
||||
|
||||
test_windows:
|
||||
name: Windows Server 2022, OTP ${{ matrix.otp_version }}
|
||||
runs-on: windows-2022
|
||||
|
||||
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
otp_version:
|
||||
- "29.0"
|
||||
- "28.1"
|
||||
- "27.3"
|
||||
|
||||
otp_version: ["25.3", "26.2", "27.3"]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Configure Git
|
||||
run: git config --global core.autocrlf input
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ matrix.otp_version }}
|
||||
|
||||
- name: Compile Elixir
|
||||
run: |
|
||||
Remove-Item -Recurse -Force '.git'
|
||||
make compile
|
||||
|
||||
- name: Build info
|
||||
run: bin/elixir --version
|
||||
|
||||
- name: Check format
|
||||
run: make test_formatted && echo "All Elixir source code files are properly formatted."
|
||||
|
||||
- name: Erlang test suite
|
||||
run: make test_erlang
|
||||
|
||||
run: make --keep-going test_erlang
|
||||
- name: Elixir test suite
|
||||
run: |
|
||||
Remove-Item 'c:/Windows/System32/drivers/etc/hosts'
|
||||
make test_elixir
|
||||
make --keep-going test_elixir
|
||||
|
||||
check_posix_compliant:
|
||||
name: Check POSIX-compliant
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Install Shellcheck
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y shellcheck
|
||||
- name: Check POSIX-compliant
|
||||
run: |
|
||||
shellcheck -e SC2039,2086 bin/elixir && echo "bin/elixir is POSIX compliant"
|
||||
shellcheck bin/elixirc && echo "bin/elixirc is POSIX compliant"
|
||||
shellcheck bin/iex && echo "bin/iex is POSIX compliant"
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2026 The Elixir Team
|
||||
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
schedule:
|
||||
- cron: "29 8 * * 1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: "ubuntu-latest"
|
||||
permissions:
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
zizmor:
|
||||
name: Zizmor
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
@@ -1,41 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: License Compliance
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
LANG: C.UTF-8
|
||||
|
||||
jobs:
|
||||
license_compliance:
|
||||
name: Check License Compliance
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Use HTTPS instead of SSH for Git cloning
|
||||
id: git-config
|
||||
shell: bash
|
||||
run: git config --global url.https://github.com/.insteadOf ssh://git@github.com/
|
||||
|
||||
- name: Checkout project
|
||||
id: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run OSS Review Toolkit
|
||||
id: ort
|
||||
uses: ./.github/workflows/ort
|
||||
with:
|
||||
upload-reports: true
|
||||
fail-on-violation: true
|
||||
report-formats: "WebApp"
|
||||
version: "${{ github.sha }}"
|
||||
@@ -1,41 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Markdown Content
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
|
||||
paths: &paths-filter
|
||||
- "**/*.md"
|
||||
- .github/workflows/markdown.yml
|
||||
- .markdownlint-cli2.jsonc
|
||||
|
||||
pull_request:
|
||||
paths: *paths-filter
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
LANG: C.UTF-8
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint Markdown content
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run markdownlint-cli2
|
||||
uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd # v23.2.0
|
||||
@@ -1,8 +1,4 @@
|
||||
# #!/usr/bin/env elixir
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
[tag] = System.argv()
|
||||
|
||||
Mix.install([
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Release Notifications
|
||||
name: Notify
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -15,20 +12,17 @@ jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
name: Notify
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: "27.3"
|
||||
elixir-version: "1.18.3"
|
||||
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
ELIXIR_LANG_ANN_TOKEN: ${{ secrets.ELIXIR_LANG_ANN_TOKEN }}
|
||||
run: |
|
||||
elixir .github/workflows/notify.exs "$GITHUB_REF_NAME"
|
||||
elixir .github/workflows/notify.exs ${{ github.ref_name }}
|
||||
@@ -1,110 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: "Run OSS Review Toolkit"
|
||||
description: "Runs OSS Review Toolkit & generates SBoMs"
|
||||
inputs:
|
||||
report-formats:
|
||||
description: "ORT Report Formats"
|
||||
required: true
|
||||
fail-on-violation:
|
||||
description: "Whether to fail on violation."
|
||||
required: false
|
||||
default: false
|
||||
upload-reports:
|
||||
description: "Whether to upload all reports"
|
||||
required: false
|
||||
default: false
|
||||
version:
|
||||
description: "Elixir Version (Tag / SHA)"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
results-path:
|
||||
description: "See oss-review-toolkit/ort-ci-github-action action"
|
||||
value: "${{ steps.ort.outputs.results-path }}"
|
||||
results-sbom-cyclonedx-xml-path:
|
||||
description: "See oss-review-toolkit/ort-ci-github-action action"
|
||||
value: "${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}"
|
||||
results-sbom-cyclonedx-json-path:
|
||||
description: "See oss-review-toolkit/ort-ci-github-action action"
|
||||
value: "${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}"
|
||||
results-sbom-spdx-yml-path:
|
||||
description: "See oss-review-toolkit/ort-ci-github-action action"
|
||||
value: "${{ steps.ort.outputs.results-sbom-spdx-yml-path }}"
|
||||
results-sbom-spdx-json-path:
|
||||
description: "See oss-review-toolkit/ort-ci-github-action action"
|
||||
value: "${{ steps.ort.outputs.results-sbom-spdx-json-path }}"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Fetch Default ORT Config
|
||||
id: fetch-default-ort-config
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: oss-review-toolkit/ort-config
|
||||
ref: "main"
|
||||
path: ".ort-config"
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup ORT Config
|
||||
id: setup-ort-config
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "/$HOME/.ort/"
|
||||
|
||||
# Move Fetched Default Config into Place
|
||||
mv .ort-config "$HOME/.ort/config"
|
||||
|
||||
# Append Global ORT Config
|
||||
cat .ort/config/config.yml >> "$HOME/.ort/config/config.yml"
|
||||
|
||||
# Override Default Evaluator Rules
|
||||
cp .ort/config/evaluator.rules.kts "$HOME/.ort/config/evaluator.rules.kts"
|
||||
|
||||
# Add Package Configurations
|
||||
mkdir -p "$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team"
|
||||
for FILE in .ort/package-configurations/*.yml; do
|
||||
COMPONENT="$(basename "$FILE")"
|
||||
cp "$FILE" "$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team/$COMPONENT"
|
||||
sed -i -E \
|
||||
"s/(\"SpdxDocumentFile:The Elixir Team:.+:)\"/\1${ELIXIR_VERSION}\"/" \
|
||||
"$HOME/.ort/config/package-configurations/SpdxDocumentFile/The Elixir Team/$COMPONENT"
|
||||
done
|
||||
|
||||
# Set Version in SPDX & Config
|
||||
sed -i "s/# elixir-version-insert/versionInfo: '${ELIXIR_VERSION}'/" project.spdx.yml
|
||||
sed -i -E "s/(\"SpdxDocumentFile:The Elixir Team:.+:)\"/\1${ELIXIR_VERSION}\"/" .ort.yml
|
||||
sed -i "s|https://github.com/elixir-lang/elixir.git|${ELIXIR_REPO}@${ELIXIR_VERSION}|" project.spdx.yml
|
||||
env:
|
||||
ELIXIR_VERSION: "${{ inputs.version }}"
|
||||
ELIXIR_REPO: "${{ github.server_url }}/${{ github.repository }}.git"
|
||||
|
||||
- name: "Cache ScanCode"
|
||||
uses: actions/cache@d4323d4df104b026a6aa633fdb11d772146be0bf # v4.2.2
|
||||
with:
|
||||
path: "~/.cache/scancode-tk"
|
||||
key: ${{ runner.os }}-scancode
|
||||
|
||||
- name: Run OSS Review Toolkit
|
||||
id: ort
|
||||
uses: oss-review-toolkit/ort-ci-github-action@1805edcf1f4f55f35ae6e4d2d9795ccfb29b6021 # v1.1.0
|
||||
with:
|
||||
image: ghcr.io/oss-review-toolkit/ort-minimal:65.0.0
|
||||
run: >-
|
||||
labels,
|
||||
cache-dependencies,
|
||||
cache-scan-results,
|
||||
analyzer,
|
||||
scanner,
|
||||
advisor,
|
||||
evaluator,
|
||||
reporter,
|
||||
${{ inputs.upload-reports == 'true' && 'upload-results' || '' }}
|
||||
fail-on: "${{ inputs.fail-on-violation == 'true' && 'violations,issues' || '' }}"
|
||||
report-formats: "${{ inputs.report-formats }}"
|
||||
ort-cli-report-args: >-
|
||||
-O CycloneDX=output.file.formats=json,xml
|
||||
-O SpdxDocument=outputFileFormats=JSON,YAML
|
||||
sw-version: "${{ inputs.version }}"
|
||||
@@ -1,53 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
name: POSIX Compliance
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: &paths-filter
|
||||
- .github/workflows/posix_compliance.yml
|
||||
- bin/elixir
|
||||
- bin/elixirc
|
||||
- bin/iex
|
||||
|
||||
pull_request:
|
||||
paths: *paths-filter
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
LANG: C.UTF-8
|
||||
|
||||
jobs:
|
||||
check_posix_compliance:
|
||||
name: Check POSIX compliance
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install ShellCheck
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y shellcheck
|
||||
|
||||
- name: Run ShellCheck on bin/ dir
|
||||
run: |
|
||||
shellcheck -e SC2039,2086 bin/elixir && \
|
||||
echo "bin/elixir is POSIX compliant"
|
||||
|
||||
shellcheck bin/elixirc && \
|
||||
echo "bin/elixirc is POSIX compliant"
|
||||
|
||||
shellcheck bin/iex && \
|
||||
echo "bin/iex is POSIX compliant"
|
||||
+85
-174
@@ -1,90 +1,76 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Releases
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
|
||||
tags:
|
||||
- v*
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
jobs:
|
||||
create_draft_release:
|
||||
name: Create draft release
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Create draft release
|
||||
if: github.ref_type != 'branch'
|
||||
run: |
|
||||
gh release create \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$GITHUB_REF_NAME" \
|
||||
--repo ${{ github.repository }} \
|
||||
--title ${{ github.ref_name }} \
|
||||
--notes '' \
|
||||
--draft \
|
||||
"$GITHUB_REF_NAME"
|
||||
${{ github.ref_name }}
|
||||
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
# zizmor: ignore[artipacked]
|
||||
- uses: actions/checkout@v4
|
||||
if: github.ref_type == 'branch'
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: Update ${{ github.ref_name }}-latest
|
||||
if: github.ref_type == 'branch'
|
||||
run: |
|
||||
ref_name="${GITHUB_REF_NAME}-latest"
|
||||
ref_name=${{ github.ref_name }}-latest
|
||||
|
||||
if ! gh release view "$ref_name"; then
|
||||
if ! gh release view $ref_name; then
|
||||
gh release create \
|
||||
--latest=false \
|
||||
--title "$ref_name" \
|
||||
--notes "Automated release for latest ${GITHUB_REF_NAME}." \
|
||||
"$ref_name"
|
||||
--title $ref_name \
|
||||
--notes "Automated release for latest ${{ github.ref_name }}." \
|
||||
$ref_name
|
||||
fi
|
||||
|
||||
git tag "$ref_name" --force
|
||||
git push origin "$ref_name" --force
|
||||
git tag $ref_name --force
|
||||
git push origin $ref_name --force
|
||||
|
||||
build:
|
||||
name: Ubuntu 24.04, OTP ${{ matrix.otp_version }}${{ matrix.build_docs && ' (build docs)' || '' }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
include:
|
||||
- otp: 25
|
||||
otp_version: "25.3"
|
||||
- otp: 26
|
||||
otp_version: "26.0"
|
||||
- otp: 27
|
||||
otp_version: "27.0"
|
||||
|
||||
- otp: 28
|
||||
otp_version: "28.0"
|
||||
build_docs: build_docs
|
||||
|
||||
- otp: 29
|
||||
otp_version: "29.0"
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 50
|
||||
|
||||
- name: "Build Release"
|
||||
uses: ./.github/workflows/release_pre_built
|
||||
@@ -93,66 +79,76 @@ jobs:
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
|
||||
- name: "Attest docs provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-docs-provenance
|
||||
if: ${{ matrix.build_docs }}
|
||||
with:
|
||||
subject-path: "Docs.zip"
|
||||
- name: "Copy docs provenance"
|
||||
if: ${{ matrix.build_docs }}
|
||||
run: cp "$ATTESTATION" Docs.zip.sigstore
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-docs-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: Create Docs Hashes
|
||||
if: matrix.build_docs
|
||||
if: ${{ matrix.build_docs }}
|
||||
run: |
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
|
||||
- name: "Upload Linux release artifacts"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- name: "Upload linux release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-linux-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.zip
|
||||
|
||||
- name: "Upload Windows release artifacts"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- name: "Upload windows release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-windows-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.exe
|
||||
|
||||
- name: "Upload doc artifacts"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: matrix.build_docs
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.build_docs }}
|
||||
with:
|
||||
name: Docs
|
||||
path: Docs.zip*
|
||||
|
||||
sign:
|
||||
name: Sign files, ${{ matrix.flavor == 'windows' && 'Windows' || matrix.flavor == 'linux' && 'Linux' || matrix.flavor }}, OTP ${{ matrix.otp }}
|
||||
needs: [build]
|
||||
environment: release
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
otp: [27, 28, 29]
|
||||
otp: [25, 26, 27]
|
||||
flavor: [windows, linux]
|
||||
|
||||
env:
|
||||
RELEASE_FILE: elixir-otp-${{ matrix.otp }}.${{ matrix.flavor == 'linux' && 'zip' || 'exe' }}
|
||||
|
||||
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-24.04' || 'windows-2022' }}
|
||||
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
steps:
|
||||
- name: "Download build"
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
|
||||
- name: Log in to Azure
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
- name: "Sign files with Trusted Signing"
|
||||
uses: azure/trusted-signing-action@b443cf8ea4124818d2ea9f043cba29fc3ec47b16 # v1.2.0
|
||||
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
@@ -164,6 +160,17 @@ jobs:
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
- name: "Attest release provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-provenance
|
||||
with:
|
||||
subject-path: ${{ env.RELEASE_FILE }}
|
||||
- name: "Copy release .zip provenance"
|
||||
shell: bash
|
||||
run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'windows'
|
||||
shell: pwsh
|
||||
@@ -181,125 +188,35 @@ jobs:
|
||||
shasum -a 1 "$RELEASE_FILE" > "${RELEASE_FILE}.sha1sum"
|
||||
shasum -a 256 "$RELEASE_FILE" > "${RELEASE_FILE}.sha256sum"
|
||||
|
||||
- name: "Upload Linux release artifacts"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- name: "Upload linux release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sign-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
path: ${{ env.RELEASE_FILE }}*
|
||||
|
||||
sbom:
|
||||
name: Generate SBoM
|
||||
needs: [build, sign]
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
upload-release:
|
||||
needs: [create_draft_release, build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Use HTTPS instead of SSH for Git cloning
|
||||
id: git-config
|
||||
shell: bash
|
||||
run: git config --global url.https://github.com/.insteadOf ssh://git@github.com/
|
||||
|
||||
- name: Checkout project
|
||||
id: checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Download Build Artifacts"
|
||||
id: download-build-artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
path: /tmp/build-artifacts/
|
||||
|
||||
- name: "Run OSS Review Toolkit"
|
||||
id: ort
|
||||
uses: ./.github/workflows/ort
|
||||
with:
|
||||
report-formats: "CycloneDx,SpdxDocument"
|
||||
version: "${{ github.ref_type == 'tag' && github.ref_name || github.sha }}"
|
||||
|
||||
- name: Attest Distribution Assets with SBoM
|
||||
id: attest-sbom
|
||||
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
|
||||
with:
|
||||
subject-path: |
|
||||
/tmp/build-artifacts/{elixir-otp-*.*,Docs.zip}
|
||||
${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}
|
||||
${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}
|
||||
${{ steps.ort.outputs.results-sbom-spdx-yml-path }}
|
||||
${{ steps.ort.outputs.results-sbom-spdx-json-path }}
|
||||
sbom-path: "${{ steps.ort.outputs.results-sbom-spdx-json-path }}"
|
||||
|
||||
- name: "Copy SBoM provenance"
|
||||
id: sbom-provenance
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir attestations
|
||||
|
||||
for FILE in /tmp/build-artifacts/{elixir-otp-*.*,Docs.zip}; do
|
||||
cp "$ATTESTATION" "attestations/$(basename "$FILE").sigstore"
|
||||
done
|
||||
|
||||
cp "$ATTESTATION" "attestations/$(basename "$SBOM_CYCLONEDX_XML").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "$SBOM_CYCLONEDX_JSON").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "$SBOM_SPDX_YML").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "$SBOM_SPDX_JSON").sigstore"
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-sbom.outputs.bundle-path }}"
|
||||
SBOM_CYCLONEDX_XML: "${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}"
|
||||
SBOM_CYCLONEDX_JSON: "${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}"
|
||||
SBOM_SPDX_YML: "${{ steps.ort.outputs.results-sbom-spdx-yml-path }}"
|
||||
SBOM_SPDX_JSON: "${{ steps.ort.outputs.results-sbom-spdx-json-path }}"
|
||||
|
||||
- name: "Assemble Release SBoM Artifacts"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: "SBoM"
|
||||
path: |
|
||||
${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}
|
||||
${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}
|
||||
${{ steps.ort.outputs.results-sbom-spdx-yml-path }}
|
||||
${{ steps.ort.outputs.results-sbom-spdx-json-path }}
|
||||
|
||||
- name: "Assemble Distribution Attestations"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: "Attestations"
|
||||
path: "attestations/*.sigstore"
|
||||
|
||||
upload-release:
|
||||
name: Upload release
|
||||
needs: [create_draft_release, build, sign, sbom]
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs,SBoM,Attestations}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Upload Pre-build
|
||||
- name: Upload Pre-built
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ "$GITHUB_REF_TYPE" == "branch" ]; then
|
||||
tag="${GITHUB_REF_NAME}-latest"
|
||||
if [ "${{ github.ref_type }}" == "branch" ]; then
|
||||
tag=${{ github.ref_name }}-latest
|
||||
else
|
||||
tag="$GITHUB_REF_NAME"
|
||||
tag="${{ github.ref_name }}"
|
||||
fi
|
||||
|
||||
gh release upload \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--repo ${{ github.repository }} \
|
||||
--clobber \
|
||||
"$tag" \
|
||||
elixir-otp-*.zip \
|
||||
@@ -310,22 +227,19 @@ jobs:
|
||||
elixir-otp-*.exe.sigstore \
|
||||
Docs.zip \
|
||||
Docs.zip.sha{1,256}sum \
|
||||
Docs.zip.sigstore \
|
||||
bom.*
|
||||
Docs.zip.sigstore
|
||||
|
||||
upload-builds-hex-pm:
|
||||
name: Upload builds to hex.pm
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
concurrency: builds-hex-pm
|
||||
environment: release
|
||||
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ vars.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }}
|
||||
|
||||
OTP_GENERIC_VERSION: "25"
|
||||
steps:
|
||||
- name: "Check if variables are set up"
|
||||
if: "${{ ! vars.HEX_AWS_REGION }}"
|
||||
@@ -333,7 +247,7 @@ jobs:
|
||||
echo "Required variables for uploading to hex.pm are not set up, skipping..."
|
||||
exit 1
|
||||
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
@@ -344,10 +258,10 @@ jobs:
|
||||
|
||||
- name: Upload Precompiled to S3
|
||||
run: |
|
||||
oldest_otp=$(find . -type f -name 'elixir-otp-*.zip' | sed -r 's/^.*elixir-otp-([[:digit:]]+)\.zip$/\1/' | sort -n | head -n 1)
|
||||
ref_name=${{ github.ref_name }}
|
||||
|
||||
for zip in $(find . -type f -name 'elixir-otp-*.zip' | sed 's/^\.\///'); do
|
||||
dest=${zip/elixir/${GITHUB_REF_NAME}}
|
||||
dest=${zip/elixir/${ref_name}}
|
||||
surrogate_key=${dest/.zip$/}
|
||||
|
||||
aws s3 cp "${zip}" "s3://${AWS_S3_BUCKET}/builds/elixir/${dest}" \
|
||||
@@ -355,17 +269,17 @@ jobs:
|
||||
--metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
echo "builds/elixir/${surrogate_key}" >> purge_keys.txt
|
||||
|
||||
if [ "$zip" == "elixir-otp-${oldest_otp}.zip" ]; then
|
||||
aws s3 cp "${zip}" "s3://${AWS_S3_BUCKET}/builds/elixir/${GITHUB_REF_NAME}.zip" \
|
||||
if [ "$zip" == "elixir-otp-${OTP_GENERIC_VERSION}.zip" ]; then
|
||||
aws s3 cp "${zip}" "s3://${AWS_S3_BUCKET}/builds/elixir/${ref_name}.zip" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${GITHUB_REF_NAME}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
echo builds/elixir/${GITHUB_REF_NAME} >> purge_keys.txt
|
||||
--metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${ref_name}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
echo builds/elixir/${ref_name} >> purge_keys.txt
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Upload Docs to S3
|
||||
run: |
|
||||
version=$(echo "$GITHUB_REF_NAME" | sed -e 's/^v//g')
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
|
||||
unzip Docs.zip
|
||||
|
||||
@@ -386,9 +300,7 @@ jobs:
|
||||
- name: Update builds txt
|
||||
run: |
|
||||
date="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
ref_name="$GITHUB_REF_NAME"
|
||||
|
||||
oldest_otp=$(find . -name 'elixir-otp-*.zip.sha256sum' | sed -r 's/^.*elixir-otp-([[:digit:]]+)\.zip\.sha256sum$/\1/' | sort -n | head -n 1)
|
||||
ref_name=${{ github.ref_name }}
|
||||
|
||||
aws s3 cp "s3://${AWS_S3_BUCKET}/builds/elixir/builds.txt" builds.txt || true
|
||||
touch builds.txt
|
||||
@@ -400,7 +312,7 @@ jobs:
|
||||
sed -i "/^${ref_name}-${otp_version} /d" builds.txt
|
||||
echo -e "${ref_name}-${otp_version} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
|
||||
|
||||
if [ "${otp_version}" == "otp-${oldest_otp}" ]; then
|
||||
if [ "${otp_version}" == "otp-${OTP_GENERIC_VERSION}" ]; then
|
||||
sed -i "/^${ref_name} /d" builds.txt
|
||||
echo -e "${ref_name} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
|
||||
fi
|
||||
@@ -439,7 +351,6 @@ jobs:
|
||||
for key in $(cat purge_keys.txt); do
|
||||
purge "${key}"
|
||||
done
|
||||
|
||||
env:
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
|
||||
@@ -1,70 +1,53 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Release Pre-build
|
||||
description: "Builds Elixir release, ExDoc and generates docs"
|
||||
|
||||
name: "Release pre built"
|
||||
description: "Builds elixir release, ExDoc and generates docs"
|
||||
inputs:
|
||||
otp:
|
||||
description: "The major OTP version"
|
||||
|
||||
otp_version:
|
||||
description: "The exact OTP version (major.minor[.patch])"
|
||||
|
||||
build_docs:
|
||||
description: "Whether docs have to be built"
|
||||
|
||||
description: "If docs have to be built or not"
|
||||
runs:
|
||||
using: "composite"
|
||||
|
||||
steps:
|
||||
- uses: erlef/setup-beam@5304e04ea2b355f03681464e683d92e3b2f18451 # v1.18.2
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ inputs.otp_version }}
|
||||
version-type: strict
|
||||
|
||||
- name: Build Elixir Release
|
||||
shell: bash
|
||||
run: | # zizmor: ignore[github-env]
|
||||
run: |
|
||||
make Precompiled.zip
|
||||
mv Precompiled.zip "elixir-otp-${INPUT_OTP}.zip"
|
||||
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
env:
|
||||
INPUT_OTP: ${{ inputs.otp }}
|
||||
|
||||
- name: Install NSIS
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y nsis
|
||||
|
||||
- name: Build Elixir Windows Installer
|
||||
shell: bash
|
||||
run: |
|
||||
export OTP_VERSION="$INPUT_OTP_VERSION"
|
||||
export ELIXIR_ZIP="$PWD/elixir-otp-${INPUT_OTP}.zip"
|
||||
export OTP_VERSION=${{ inputs.otp_version }}
|
||||
export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
|
||||
(cd lib/elixir/scripts/windows_installer && ./build.sh)
|
||||
mv "lib/elixir/scripts/windows_installer/tmp/elixir-otp-${INPUT_OTP}.exe" .
|
||||
env:
|
||||
INPUT_OTP: ${{ inputs.otp }}
|
||||
INPUT_OTP_VERSION: ${{ inputs.otp_version }}
|
||||
mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
|
||||
- name: Get ExDoc ref
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: | # zizmor: ignore[github-env]
|
||||
if [ "$GITHUB_REF_NAME" = "main" ]; then
|
||||
run: |
|
||||
if [ "${{ github.ref_name }}" = "main" ]; then
|
||||
ref=main
|
||||
else
|
||||
ref=v$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
|
||||
fi
|
||||
echo "EX_DOC_REF=$ref" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ inputs.build_docs }}
|
||||
with:
|
||||
repository: elixir-lang/ex_doc
|
||||
ref: ${{ env.EX_DOC_REF }}
|
||||
path: ex_doc
|
||||
persist-credentials: false
|
||||
- name: Build ex_doc
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
|
||||
+2
-8
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
/doc/
|
||||
/lib/*/ebin/
|
||||
/lib/*/_build/
|
||||
@@ -10,10 +6,8 @@
|
||||
/lib/elixir/test/ebin/
|
||||
/man/elixir.1
|
||||
/man/iex.1
|
||||
/Docs.zip
|
||||
/Precompiled.zip
|
||||
/Docs-v*.zip
|
||||
/Precompiled-v*.zip
|
||||
/.eunit
|
||||
.elixir.plt
|
||||
erl_crash.dump
|
||||
/cover/
|
||||
.tool-versions
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
{
|
||||
"globs": [
|
||||
"**/*.md"
|
||||
],
|
||||
"ignores": [
|
||||
".git/**"
|
||||
],
|
||||
"gitignore": true,
|
||||
"config": {
|
||||
// Consecutive header levels (h1 -> h2 -> h3).
|
||||
"MD001": false,
|
||||
// Header style. We use #s.
|
||||
"MD003": {
|
||||
"style": "atx"
|
||||
},
|
||||
// Style of unordered lists..
|
||||
"MD007": {
|
||||
"indent": 2,
|
||||
"start_indented": true
|
||||
},
|
||||
// Line length. Who cares.
|
||||
"MD013": false,
|
||||
// This warns if you have "console" or "shell" code blocks with a dollar sign $ that
|
||||
// don't show output. We use those a lot, so this is fine for us.
|
||||
"MD014": false,
|
||||
// Multiple headings with the same content.
|
||||
"MD024": {
|
||||
// Duplication is allowed for headings with different parents.
|
||||
"siblings_only": true
|
||||
},
|
||||
// Trailing punctuation in heading.
|
||||
// Some headers finish with ! because it refers to a function name. Therefore we remove ! from
|
||||
// the default values.
|
||||
"MD026": {
|
||||
"punctuation": ".,;:。,;:!"
|
||||
},
|
||||
// Allow empty line between block quotes. Used by contiguous admonition blocks.
|
||||
"MD028": false,
|
||||
// Allowed HTML inline elements.
|
||||
"MD033": {
|
||||
"allowed_elements": [
|
||||
"h1",
|
||||
"a",
|
||||
"br",
|
||||
"img",
|
||||
"picture",
|
||||
"source",
|
||||
"noscript",
|
||||
"p",
|
||||
"script"
|
||||
]
|
||||
},
|
||||
// This warns if you have spaces in code blocks. Sometimes, that's fine.
|
||||
"MD038": false,
|
||||
// Code block style. We don't care if it's fenced or indented.
|
||||
"MD046": false,
|
||||
// Our tables are too large to align.
|
||||
"MD060": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
// Consecutive header levels (h1 -> h2 -> h3). We don't care about this.
|
||||
"MD001": false,
|
||||
// Header style. We use #s.
|
||||
"MD003": {
|
||||
"style": "atx"
|
||||
},
|
||||
// Style of unordered lists..
|
||||
"MD007": {
|
||||
"indent": 2,
|
||||
"start_indented": true
|
||||
},
|
||||
// Line length. Who cares.
|
||||
"MD013": false,
|
||||
// This warns if you have "console" or "shell" code blocks with a dollar sign $ that
|
||||
// don't show output. We use those a lot, so this is fine for us.
|
||||
"MD014": false,
|
||||
// Multiple headings with the same content. That's fine.
|
||||
"MD024": false,
|
||||
// Some headers finish with ! because it refers to a function name
|
||||
"MD026": false,
|
||||
// Allow empty line between block quotes. Used by contiguous admonition blocks.
|
||||
"MD028": false,
|
||||
// Allowed HTML inline elements.
|
||||
"MD033": {
|
||||
"allowed_elements": [
|
||||
"h1",
|
||||
"a",
|
||||
"br",
|
||||
"img",
|
||||
"picture",
|
||||
"source",
|
||||
"noscript",
|
||||
"p",
|
||||
"script"
|
||||
]
|
||||
},
|
||||
// This warns if you have spaces in code blocks. Sometimes, that's fine.
|
||||
"MD038": false,
|
||||
// Code block style. We don't care if it's fenced or indented.
|
||||
"MD046": false
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
excludes:
|
||||
paths:
|
||||
- pattern: "man/*"
|
||||
reason: "DOCUMENTATION_OF"
|
||||
comment: "Documentation"
|
||||
- pattern: ".github/**/*"
|
||||
reason: "BUILD_TOOL_OF"
|
||||
comment: "Documentation"
|
||||
- pattern: ".ort/**/*"
|
||||
reason: "BUILD_TOOL_OF"
|
||||
comment: "Documentation"
|
||||
|
||||
# Unfortunately we'll have to repeat all package level excludes here
|
||||
# Make sure to keep them in sync with the package configuration in
|
||||
# .ort/package-configurations
|
||||
- pattern: "lib/*/pages/**/*"
|
||||
reason: "DOCUMENTATION_OF"
|
||||
comment: "Documentation"
|
||||
- pattern: "lib/*/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
- pattern: "lib/*/scripts/**/*"
|
||||
reason: "BUILD_TOOL_OF"
|
||||
comment: "Build Tool"
|
||||
- pattern: "lib/*/examples/**/*"
|
||||
reason: "EXAMPLE_OF"
|
||||
comment: "Example"
|
||||
|
||||
curations:
|
||||
license_findings:
|
||||
# Version File
|
||||
- path: "VERSION"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply Trademark Policy to VERSION file"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Wrongly Identified
|
||||
- path: ".gitignore"
|
||||
reason: "INCORRECT"
|
||||
comment: "Ignored by ScanCode"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
- path: ".gitattributes"
|
||||
reason: "INCORRECT"
|
||||
comment: "Ignored by ScanCode"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
- path: "CONTRIBUTING.md"
|
||||
reason: "INCORRECT"
|
||||
comment: "Wrongly identified TSL license"
|
||||
detected_license: "Apache-2.0 OR NOASSERTION OR LicenseRef-scancode-tsl-2020"
|
||||
concluded_license: "Apache-2.0"
|
||||
- path: "OPEN_SOURCE_POLICY.md"
|
||||
reason: "INCORRECT"
|
||||
comment: "Wrongly identified NOASSERTION"
|
||||
detected_license: "NOASSERTION"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Unfortunately we'll have to repeat all package level license curations here
|
||||
# Make sure to keep them in sync with the package configuration in
|
||||
# .ort/package-configurations
|
||||
|
||||
# Test Fixtures
|
||||
- path: "lib/*/test/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Logos
|
||||
- path: "lib/elixir/pages/images/logo.png"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply Trademark Policy to Elixir Logo"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-elixir-trademark-policy"
|
||||
- path: "lib/elixir/scripts/windows_installer/assets/Elixir.ico"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply Trademark Policy to Elixir Logo"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-elixir-trademark-policy"
|
||||
|
||||
# Documentation Images
|
||||
- path: "lib/elixir/pages/images/**/*.png"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to all images"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Test Fixtures
|
||||
- path: "lib/elixir/test/elixir/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Unicode
|
||||
- path: "lib/elixir/unicode/*.txt"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to unicode files"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-scancode-unicode"
|
||||
|
||||
# Wrongly Identified
|
||||
- path: "lib/elixir/pages/references/library-guidelines.md"
|
||||
reason: "INCORRECT"
|
||||
comment: |
|
||||
The guide mentions multiple licenses for users to choose from.
|
||||
It however is not licensed itself by the mentioned licenses.
|
||||
concluded_license: "Apache-2.0"
|
||||
- path: "lib/elixir/scripts/windows_installer/.gitignore"
|
||||
reason: "INCORRECT"
|
||||
comment: "Ignored by ScanCode"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
@@ -1,21 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
ort:
|
||||
enableRepositoryPackageCurations: true
|
||||
enableRepositoryPackageConfigurations: true
|
||||
|
||||
scanner:
|
||||
skipConcluded: false
|
||||
includeFilesWithoutFindings: true
|
||||
|
||||
analyzer:
|
||||
allowDynamicVersions: true
|
||||
enabledPackageManagers: [SpdxDocumentFile]
|
||||
|
||||
reporter:
|
||||
reporters:
|
||||
SpdxDocument:
|
||||
options:
|
||||
creationInfoOrganization: The Elixir Team
|
||||
documentName: "Elixir Source SPDX Document"
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The ORT Project Authors (see <https://github.com/oss-review-toolkit/ort/blob/main/NOTICE>)
|
||||
* Copyright (c) 2021 The Elixir Team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Docs: https://oss-review-toolkit.org/ort/docs/configuration/evaluator-rules
|
||||
|
||||
val whitelistedLicenses = listOf(
|
||||
// License for Elixir & Imported Erlang Projects
|
||||
"Apache-2.0",
|
||||
// License for the Elixir Logo
|
||||
"LicenseRef-elixir-trademark-policy",
|
||||
"LicenseRef-scancode-elixir-trademark-policy",
|
||||
// License for included Unicode Files
|
||||
"LicenseRef-scancode-unicode",
|
||||
// DCO for committers
|
||||
"LicenseRef-scancode-dco-1.1"
|
||||
).map { SpdxSingleLicenseExpression.parse(it) }.toSet()
|
||||
|
||||
fun PackageRule.howToFixDefault() = """
|
||||
* Check if this license violation is intended
|
||||
* Adjust evaluation rules in `.ort/config/evaluator.rules.kts`
|
||||
""".trimIndent()
|
||||
|
||||
fun PackageRule.LicenseRule.isHandled() =
|
||||
object : RuleMatcher {
|
||||
override val description = "isHandled($license)"
|
||||
|
||||
override fun matches() = license in whitelistedLicenses
|
||||
}
|
||||
|
||||
fun RuleSet.unhandledLicenseRule() = packageRule("UNHANDLED_LICENSE") {
|
||||
// Do not trigger this rule on packages that have been excluded in the .ort.yml.
|
||||
require {
|
||||
-isExcluded()
|
||||
}
|
||||
|
||||
// Define a rule that is executed for each license of the package.
|
||||
licenseRule("UNHANDLED_LICENSE", LicenseView.CONCLUDED_OR_DECLARED_AND_DETECTED) {
|
||||
require {
|
||||
-isExcluded()
|
||||
-isHandled()
|
||||
}
|
||||
|
||||
// Throw an error message including guidance how to fix the issue.
|
||||
error(
|
||||
"The license $license is currently not covered by policy rules. " +
|
||||
"The license was ${licenseSource.name.lowercase()} in package " +
|
||||
"${pkg.metadata.id.toCoordinates()}.",
|
||||
howToFixDefault()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun RuleSet.unmappedDeclaredLicenseRule() = packageRule("UNMAPPED_DECLARED_LICENSE") {
|
||||
require {
|
||||
-isExcluded()
|
||||
}
|
||||
|
||||
resolvedLicenseInfo.licenseInfo.declaredLicenseInfo.processed.unmapped.forEach { unmappedLicense ->
|
||||
warning(
|
||||
"The declared license '$unmappedLicense' could not be mapped to a valid license or parsed as an SPDX " +
|
||||
"expression. The license was found in package ${pkg.metadata.id.toCoordinates()}.",
|
||||
howToFixDefault()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val ruleSet = ruleSet(ortResult, licenseInfoResolver, resolutionProvider) {
|
||||
unhandledLicenseRule()
|
||||
unmappedDeclaredLicenseRule()
|
||||
}
|
||||
|
||||
ruleViolations += ruleSet.violations
|
||||
@@ -1,15 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
id: "SpdxDocumentFile:The Elixir Team:eex:"
|
||||
path_excludes:
|
||||
- pattern: "lib/eex/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
license_finding_curations:
|
||||
# Test Fixtures
|
||||
- path: "lib/eex/test/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
@@ -1,60 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
id: "SpdxDocumentFile:The Elixir Team:elixir:"
|
||||
path_excludes:
|
||||
- pattern: "lib/elixir/pages/**/*"
|
||||
reason: "DOCUMENTATION_OF"
|
||||
comment: "Documentation"
|
||||
- pattern: "lib/elixir/scripts/**/*"
|
||||
reason: "BUILD_TOOL_OF"
|
||||
comment: "Build Tool"
|
||||
- pattern: "lib/elixir/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
license_finding_curations:
|
||||
# Logos
|
||||
- path: "lib/elixir/pages/images/logo.png"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply Trademark Policy to Elixir Logo"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-elixir-trademark-policy"
|
||||
- path: "lib/elixir/scripts/windows_installer/assets/Elixir.ico"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply Trademark Policy to Elixir Logo"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-elixir-trademark-policy"
|
||||
|
||||
# Documentation Images
|
||||
- path: "lib/elixir/pages/images/**/*.png"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to all images"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Test Fixtures
|
||||
- path: "lib/elixir/test/elixir/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
|
||||
# Unicode
|
||||
- path: "lib/elixir/unicode/*.txt"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to unicode files"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "LicenseRef-scancode-unicode"
|
||||
|
||||
# Wrongly Identified
|
||||
- path: "lib/elixir/pages/references/library-guidelines.md"
|
||||
reason: "INCORRECT"
|
||||
comment: |
|
||||
The guide mentions multiple licenses for users to choose from.
|
||||
It however is not licensed itself by the mentioned licenses.
|
||||
concluded_license: "Apache-2.0"
|
||||
- path: "lib/elixir/scripts/windows_installer/.gitignore"
|
||||
reason: "INCORRECT"
|
||||
comment: "Ignored by ScanCode"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
@@ -1,18 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
id: "SpdxDocumentFile:The Elixir Team:exunit:"
|
||||
path_excludes:
|
||||
- pattern: "lib/ex_unit/examples/**/*"
|
||||
reason: "EXAMPLE_OF"
|
||||
comment: "Example"
|
||||
- pattern: "lib/ex_unit/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
license_finding_curations:
|
||||
# Test Fixtures
|
||||
- path: "lib/ex_unit/test/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
@@ -1,8 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
id: "SpdxDocumentFile:The Elixir Team:logger:"
|
||||
path_excludes:
|
||||
- pattern: "lib/logger/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
@@ -1,15 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
id: "SpdxDocumentFile:The Elixir Team:mix:"
|
||||
path_excludes:
|
||||
- pattern: "lib/mix/test/**/*"
|
||||
reason: "TEST_OF"
|
||||
comment: "Tests"
|
||||
license_finding_curations:
|
||||
# Test Fixtures
|
||||
- path: "lib/mix/test/fixtures/**/*"
|
||||
reason: "NOT_DETECTED"
|
||||
comment: "Apply default license to test fixtures"
|
||||
detected_license: "NONE"
|
||||
concluded_license: "Apache-2.0"
|
||||
+407
-333
@@ -1,431 +1,505 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
# Changelog for Elixir v1.18
|
||||
|
||||
# Changelog for Elixir v1.20
|
||||
|
||||
This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
|
||||
Elixir v1.18 is an impressive release with improvements across the two main efforts happening within the Elixir ecosystem right now: set-theoretic types and language servers. It also comes with built-in JSON support and adds new capabilities to its unit testing library. Here is a quick break down.
|
||||
|
||||
## Type system improvements
|
||||
|
||||
Elixir's type system now understands all language constructs and can infer types for your function definitions, using typing information from Elixir's standard library and your dependencies, to find verified bugs and dead code.
|
||||
|
||||
This has been achieved through a series of improvements, such as type refinement across clauses, occurrence typing, typing of map keys and domains, and more.
|
||||
|
||||
### Type inference of guards
|
||||
|
||||
This release also performs inference of guards! Let's see some examples:
|
||||
|
||||
```elixir
|
||||
def example(x, y) when is_list(x) and is_integer(y)
|
||||
```
|
||||
|
||||
The code above correctly infers `x` is a list and `y` is an integer.
|
||||
|
||||
```elixir
|
||||
def example({:ok, x} = y) when is_binary(x) or is_integer(x)
|
||||
```
|
||||
|
||||
The one above infers x is a binary or an integer, and `y` is a two element tuple with `:ok` as first element and a binary or integer as second.
|
||||
|
||||
```elixir
|
||||
def example(x) when is_map_key(x, :foo)
|
||||
```
|
||||
|
||||
The code above infers `x` is a map which has the `:foo` key, represented as `%{..., foo: dynamic()}`. Remember the leading `...` indicates the map may have other keys.
|
||||
|
||||
```elixir
|
||||
def example(x) when not is_map_key(x, :foo)
|
||||
```
|
||||
|
||||
And the code above infers `x` does not have the `:foo` key (hence `x.foo` will raise a typing violation), which has the type: `%{..., foo: not_set()}`.
|
||||
|
||||
You can also have expressions that assert on the size of data structures:
|
||||
|
||||
```elixir
|
||||
def example(x) when tuple_size(x) < 3
|
||||
```
|
||||
|
||||
Elixir will correctly track the tuple has at most two elements, and therefore accessing `elem(x, 3)` will emit a typing violation. In other words, Elixir can look at complex guards, infer types, and use this information to find bugs in our code, without a need to introduce type signatures (yet).
|
||||
|
||||
### Whole-body type inference
|
||||
|
||||
Elixir also performs inference based on the function body itself. Take the following code:
|
||||
|
||||
```elixir
|
||||
def add_foo_and_bar(data) do
|
||||
data.foo + data.bar
|
||||
end
|
||||
```
|
||||
|
||||
Elixir now infers that the function expects a `map` as first argument, and the map must have the keys `.foo` and `.bar` whose values are either `integer()` or `float()`. The return type will be either `integer()` or `float()`.
|
||||
|
||||
Here is another example:
|
||||
|
||||
```elixir
|
||||
def sum_to_string(a, b) do
|
||||
Integer.to_string(a + b)
|
||||
end
|
||||
```
|
||||
|
||||
Even though the `+` operator works with both integers and floats, Elixir infers that `a` and `b` must be both integers, as the result of `+` is given to a function that expects an integer. The inferred type information is then used during type checking to find possible typing errors. The typing inferred from your dependencies are also used to help infer more precise types for your own applications.
|
||||
|
||||
### Typing across clauses
|
||||
|
||||
Elixir now infers the type of a given clause based on previous clauses. Let's see an example:
|
||||
|
||||
```elixir
|
||||
case System.get_env("SOME_VAR") do
|
||||
nil -> :not_found
|
||||
value -> {:ok, String.upcase(value)}
|
||||
end
|
||||
```
|
||||
|
||||
`System.get_env("SOME_VAR")` returns either `nil` or a `binary()`. Because the first clause matches on `nil`, the type system knows `value` can no longer be `nil`, and therefore it must only be a `binary()`, which allows the second clause to also type check without violations.
|
||||
|
||||
This type inference across clauses also helps the type system find redundant clauses and dead code in existing codebases. Elixir v1.20 also implements occurrence typing for `cond`, `case`, and `with`, providing more precise types within each clause.
|
||||
|
||||
### Typing of atom and domain keys in maps
|
||||
|
||||
Maps were one of the first data-structures we implemented within the Elixir type system however, up to this point, they only supported atom keys. If they had additional keys, those keys were simply marked as `dynamic()`.
|
||||
|
||||
As of Elixir v1.20, we can track all possible domains as map keys. For example, the map:
|
||||
|
||||
```elixir
|
||||
%{123 => "hello", 456.0 => :ok}
|
||||
```
|
||||
|
||||
will have the type:
|
||||
|
||||
```elixir
|
||||
%{integer() => binary(), float() => :ok}
|
||||
```
|
||||
|
||||
It is also possible to mix domain keys, as above, with atom keys, yielding the following:
|
||||
|
||||
```elixir
|
||||
%{integer() => integer(), root: integer()}
|
||||
```
|
||||
|
||||
This system is an implementation of [Typing Records, Maps, and Structs, by Giuseppe Castagna (2023)](https://www.irif.fr/~gc/papers/icfp23.pdf).
|
||||
|
||||
### Typing of map operations
|
||||
|
||||
We have typed the majority of the functions in the `Map` module, allowing the type system to track how keys are added, updated, and removed across all possible key types.
|
||||
|
||||
For example, imagine we are calling the following `Map` functions with a variable `map`, which we don't know the exact shape of, and an atom key:
|
||||
|
||||
```elixir
|
||||
Map.put(map, :key, 123)
|
||||
#=> returns type %{..., key: integer()}
|
||||
|
||||
Map.delete(map, :key)
|
||||
#=> returns type %{..., key: not_set()}
|
||||
```
|
||||
|
||||
As you can see, we track when keys are set and also when they are removed.
|
||||
|
||||
Some operations, like `Map.replace/3`, only replace the key if it exists, and that is also propagated by the type system:
|
||||
|
||||
```elixir
|
||||
Map.replace(map, :key, 123)
|
||||
#=> returns type %{..., key: if_set(integer())}
|
||||
```
|
||||
|
||||
In other words, if the key exists, it would have been replaced by an integer value. Furthermore, whenever calling a function in the `Map` module and the given key is statically proven to never exist in the map, an error is emitted.
|
||||
|
||||
By combining full type inference with bang operations like `Map.fetch!/2`, `Map.pop!/2`, `Map.replace!/3`, and `Map.update!/3`, Elixir is able to propagate information about the desired keys. Take this module:
|
||||
The most exciting change in Elixir v1.18 is type checking of function calls, alongside gradual inference of patterns and return types. To understand how this will impact your programs, consider the following code in "lib/user.ex":
|
||||
|
||||
```elixir
|
||||
defmodule User do
|
||||
def name(map), do: Map.fetch!(map, :name)
|
||||
end
|
||||
defstruct [:age, :car_choice]
|
||||
|
||||
defmodule CallsUser do
|
||||
def calls_name do
|
||||
User.name(%{})
|
||||
def drive(%User{age: age, car_choice: car}, car_choices) when age >= 18 do
|
||||
if car in car_choices do
|
||||
{:ok, car}
|
||||
else
|
||||
{:error, :no_choice}
|
||||
end
|
||||
end
|
||||
|
||||
def drive(%User{}, _car_choices) do
|
||||
{:error, :not_allowed}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The code above has a type violation, which is now caught by the type system:
|
||||
Elixir's type system will infer that the `drive/2` function expects a `%User{}` struct and returns either `{:ok, dynamic()}`, `{:error, :no_choice}`, or `{:error, :not_allowed}`.
|
||||
|
||||
```text
|
||||
warning: incompatible types given to User.name/1:
|
||||
Therefore, the following code in a separate module (either in a separate or the same file), should emit a violation, due to an invalid argument:
|
||||
|
||||
User.name(%{})
|
||||
```elixir
|
||||
User.drive({:ok, %User{}}, car_choices)
|
||||
```
|
||||
|
||||
Here is the warning:
|
||||
|
||||
```
|
||||
warning: incompatible types given to User.drive/2:
|
||||
|
||||
User.drive({:ok, %User{age: nil, car_choice: nil}}, car_choices)
|
||||
|
||||
given types:
|
||||
|
||||
%{name: not_set()}
|
||||
{:ok, %User{age: nil, car_choice: nil}}, empty_list()
|
||||
|
||||
but expected one of:
|
||||
|
||||
dynamic(%{..., name: term()})
|
||||
dynamic(%User{age: term(), car_choice: term()}), dynamic()
|
||||
|
||||
type warning found at:
|
||||
where "car_choices" was given the type:
|
||||
|
||||
# type: empty_list()
|
||||
# from: lib/foo.ex:21:17
|
||||
car_choices = []
|
||||
|
||||
typing violation found at:
|
||||
│
|
||||
16 │ User.name(%{})
|
||||
│ ~
|
||||
22 │ User.drive({:ok, %User{}}, car_choices)
|
||||
│ ~
|
||||
│
|
||||
└─ lib/calls_user.ex:7:5: CallsUser.calls_name/0
|
||||
└─ lib/foo.ex:22:10: Example.run/0
|
||||
```
|
||||
|
||||
### Acknowledgements
|
||||
> The mismatched arguments are shown in red, if your terminal supports ANSI coloring.
|
||||
|
||||
The type system was made possible thanks to a partnership between [CNRS](https://www.cnrs.fr/) and [Remote](https://remote.com/). The development work is currently sponsored by [Fresha](https://www.fresha.com/) and [Tidewave](https://tidewave.ai/).
|
||||
And the next snippet will warn because the `:error` clause will never match, as that's not a valid return type of the `User.drive/2` call:
|
||||
|
||||
## Compile-time improvements
|
||||
```elixir
|
||||
case User.drive(user, car_choices) do
|
||||
{:ok, car} -> car
|
||||
:error -> Logger.error("User cannot drive")
|
||||
end
|
||||
```
|
||||
|
||||
Elixir's v1.20 improves compilation times once more, especially on applications with many cores.
|
||||
And here is the warning:
|
||||
|
||||
It also introduces a new compiler option called `:module_definition`, which if the module definition should be `:compiled` (the default) or `:interpreted`. Note this does not affect the `.beam` file written to disk, only how the contents inside `defmodule` are executed. Using the `:interpreted` mode may offer better compilation times for large projects, especially on machines with high core count, however, it comes with some downsides:
|
||||
```
|
||||
warning: the following clause will never match:
|
||||
|
||||
* Errors during compilation may have less precise stacktraces
|
||||
:error
|
||||
|
||||
* Anonymous functions within `defmodule` can have only up to 20 arguments.
|
||||
If this is an issue, you can use maps or tuples to group the data.
|
||||
Note the functions themselves inside `defmodule`, such as the ones defined
|
||||
inside `def` and friends, can still have up to 255 arguments
|
||||
because it attempts to match on the result of:
|
||||
|
||||
You can enable it by setting `elixirc_options: [module_definition: :interpreted]` in your `mix.exs`.
|
||||
User.drive(user, car_choices)
|
||||
|
||||
## v1.20.4 (2026-08-28)
|
||||
which has type:
|
||||
|
||||
dynamic({:ok, term()} or {:error, :no_choice} or {:error, :not_allowed})
|
||||
|
||||
typing violation found at:
|
||||
│
|
||||
26 │ :error -> Logger.error("User cannot drive")
|
||||
│ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
│
|
||||
└─ lib/foo.ex:26: Example.run/0
|
||||
```
|
||||
|
||||
For more details on typing inference and the trade-offs made by the Elixir team, [see our official documentation](https://hexdocs.pm/elixir/1.18/gradual-set-theoretic-types.html#type-inference).
|
||||
|
||||
There are many other improvements to the type system, which we will go in detail within the official release. Meanwhile, here is a list summary of the overall improvements done to the type system:
|
||||
|
||||
* Type inference of patterns (typing inference of guards will be part of an upcoming release)
|
||||
|
||||
* Type checking of all language constructs, including local and remote calls, except `for`, `with`, and closures
|
||||
|
||||
* Type checking of all functions inlined by the compiler found in `Kernel`
|
||||
|
||||
* Type checking of all conversion functions inlined by the compiler
|
||||
|
||||
* [Support for tuples and lists as composite types](https://elixir-lang.org/blog/2024/08/28/typing-lists-and-tuples/) as well as type checking of their basic operations
|
||||
|
||||
* Detection of clauses and patterns that will never match from `case`, `cond`, and `=`
|
||||
|
||||
* Detection of unused clauses in private functions
|
||||
|
||||
## ExUnit improvements
|
||||
|
||||
ExUnit now supports parameterized tests to run the same test module multiple times under different parameters.
|
||||
|
||||
For example, Elixir ships a local, decentralized and scalable key-value process storage called `Registry`. The registry can be partitioned and its implementation differs depending if partitioning is enabled or not. Therefore, during tests, we want to ensure both modes are exercised. With Elixir v1.18, we can achieve this by writing:
|
||||
|
||||
```elixir
|
||||
defmodule Registry.Test do
|
||||
use ExUnit.Case,
|
||||
async: true,
|
||||
parameterize: [
|
||||
%{partitions: 1},
|
||||
%{partitions: 8}
|
||||
]
|
||||
|
||||
# ... the actual tests ...
|
||||
end
|
||||
```
|
||||
|
||||
ExUnit parameterizes whole test modules. If your modules are configured to run concurrently, as above, so will the parameterized ones.
|
||||
|
||||
ExUnit also comes with the ability of specifying test groups. While ExUnit supports running tests concurrently, those tests must not have shared state between them. However, in large applications, it may be common for some tests to depend on some shared state, and other tests to depend on a completely separate state. For example, part of your tests may depend on Cassandra, while others depend on Redis. Prior to Elixir v1.18, these tests could not run concurrently, but in v1.18 they might as long as they are assigned to different groups. Tests modules within the same group do not run concurrently, but across groups, they might.
|
||||
|
||||
With features like async tests, suite partitioning, and now grouping, Elixir developers have plenty of flexibility to make the most use of their machine resources, both in development and in CI.
|
||||
|
||||
## `mix format --migrate`
|
||||
|
||||
The `mix format` command now supports an explicit `--migrate` flag, which will convert constructs that have been deprecated in Elixir to their latest version. Because this flag rewrites the AST, it is not guaranteed the migrated format will always be valid when used in combination with macros that also perform AST rewriting.
|
||||
|
||||
As of this release, the following migrations are executed:
|
||||
|
||||
* Normalize parens in bitstring modifiers - it removes unnecessary parentheses in known bitstring modifiers, for example `<<foo::binary()>>` becomes `<<foo::binary>>`, or adds parentheses for custom modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
|
||||
|
||||
* Charlists as sigils - formats charlists as `~c` sigils, for example `'foo'` becomes `~c"foo"`.
|
||||
|
||||
* `unless` as negated `if`s - rewrites `unless` expressions using `if` with a negated condition, for example `unless foo do` becomes `if !foo do`.
|
||||
|
||||
More migrations may be added in future releases.
|
||||
|
||||
## JSON support
|
||||
|
||||
This release includes official support for JSON encoding and decoding.
|
||||
|
||||
Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html/rfc8259) and [ECMA 404](https://ecma-international.org/publications-and-standards/standards/ecma-404/) standards.
|
||||
|
||||
### Encoding
|
||||
|
||||
Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
|
||||
|
||||
| **Elixir** | **JSON** |
|
||||
|-----------------------------|----------|
|
||||
| `integer() \| float()` | Number |
|
||||
| `true \| false ` | Boolean |
|
||||
| `nil` | Null |
|
||||
| `binary()` | String |
|
||||
| `atom()` | String |
|
||||
| `list()` | Array |
|
||||
| `%{String.Chars.t() => _}` | Object |
|
||||
|
||||
You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
|
||||
|
||||
If you have a struct, you can derive the implementation of the `JSON.Encoder` by specifying which fields should be encoded to JSON:
|
||||
|
||||
```elixir
|
||||
@derive {JSON.Encoder, only: [...]}
|
||||
defstruct ...
|
||||
```
|
||||
|
||||
### Decoding
|
||||
|
||||
Decoding can be done via `JSON.decode/2` and `JSON.decode!/2` functions. The default decoding rules are applied as follows:
|
||||
|
||||
| **JSON** | **Elixir** |
|
||||
|----------|------------------------|
|
||||
| Number | `integer() \| float()` |
|
||||
| Boolean | `true \| false` |
|
||||
| Null | `nil` |
|
||||
| String | `binary()` |
|
||||
| Object | `%{binary() => _}` |
|
||||
|
||||
## Language server listeners
|
||||
|
||||
4 months ago, we welcomed [the Official Language Server team](https://elixir-lang.org/blog/2024/08/15/welcome-elixir-language-server-team/), with the goal of unifying the efforts behind code intelligence, tools, and editors in Elixir. Elixir v1.18 brings new features on this front by introducing locks and listeners to its compilation. Let's understand what it means.
|
||||
|
||||
At the moment, all language server implementations have their own compilation environment. This means that your project and dependencies during development are compiled once, for your own use, and then again for the language server. This duplicate effort could cause the language server experience to lag, when it could be relying on the already compiled artifacts of your project.
|
||||
|
||||
This release address by introducing a compiler lock, ensuring that only a single operating system process running Elixir compiles your project at a given moment, and by providing the ability for one operating system process to listen to the compilation results of others. In other words, different Elixir instances can now communicate over the same compilation build, instead of racing each other.
|
||||
|
||||
These enhancements do not only improve editor tooling, but they also directly benefit projects like IEx and Phoenix. For example, you can invoke `IEx.configure(auto_reload: true)` and IEx will automatically reload modules changed elsewhere, either by a separate terminal or your IDE.
|
||||
|
||||
## Potential incompatibilities
|
||||
|
||||
This release no longer supports WERL (a graphical user interface on Windows used by Erlang 25 and earlier). For a better user experience on Windows terminals, use Erlang/OTP 26+ (this is also the last Elixir release to support Erlang/OTP 25).
|
||||
|
||||
Furthermore, in order to support inference of patterns, Elixir will raise if it finds recursive variable definitions. This means patterns that never match, such as this one, will no longer compile:
|
||||
|
||||
def foo(x = {:ok, y}, x = y)
|
||||
|
||||
However, recursion of root variables (where variables directly point to each other), will also fail to compile:
|
||||
|
||||
def foo(x = y, y = z, z = x)
|
||||
|
||||
While the definition above could succeed (as long as all three arguments are equal), the cycle is not necessary and could be removed, as below:
|
||||
|
||||
def foo(x = y, y = z, z)
|
||||
|
||||
You may also prefer to write using guards:
|
||||
|
||||
def foo(x, y, z) when x == y and y == z
|
||||
|
||||
## v1.18.5 (2026-08-28)
|
||||
|
||||
### 1. Security
|
||||
|
||||
* [List] Avoid recursion when invalid charlists are given to `List.to_string/1` or `List.to_charlist/1` (CVE-2026-75758, GHSA-jf5q-v438-665c)
|
||||
|
||||
### 2. Bug fixes
|
||||
## v1.18.4 (2025-05-21)
|
||||
|
||||
#### Elixir
|
||||
This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28. Note, however, that Erlang/OTP 28 no longer allows regexes to be defined in the module body and interpolated into an attribute. If you do this:
|
||||
|
||||
* [Base] Fix false positives in Base64 validation
|
||||
* [Kernel] Fix inferred type of `:erlang.split_binary/2` when given a bitstring
|
||||
* [Kernel] Fix type checker crash on comprehensions with a non-returning `:into` expression
|
||||
* [String] Fix final sigma handling in Greek casing
|
||||
* [Task] Fix `Task.yield_many/2` waiting indefinitely when `:limit` exceeds the task count
|
||||
```elixir
|
||||
@some_attribute ~r/foo/
|
||||
def some_fun, do: @some_attribute
|
||||
```
|
||||
|
||||
## v1.20.3 (2026-08-04)
|
||||
You must rewrite it to:
|
||||
|
||||
```elixir
|
||||
def some_fun, do: ~r/foo/
|
||||
```
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel.ParallelCompiler] Batch type checker cache operations to improve compilation times
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Improve type precision of the `++/2` operator
|
||||
* [Kernel] Improve type precision of the `send/2` return type
|
||||
* [Kernel] Improve type refinement for `and/2` and `or/2`
|
||||
* [Kernel] Fix type checking of `Map.put/3` on empty map types
|
||||
* [Kernel] Fix type checking of tuple insertion on equivalent tuple types
|
||||
* [Kernel] Fix type checking of tuple insertion on gradual types with empty static parts
|
||||
* [Kernel] Fix variable refinements leaking from invalid `receive/after` timeout expressions
|
||||
* [Kernel] Fix rendering inferred types containing unions of static and dynamic functions
|
||||
* [Kernel] Ensure typing errors in `:maps.values/1` reference the correct function
|
||||
* [Kernel] Mark `%_{}` patterns as precise so subsequent redundant struct clauses are detected
|
||||
* [elixirc] Fix parsing of the `--profile time` option
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Fix `break!/1` with unknown expressions
|
||||
* [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
|
||||
|
||||
## v1.20.2 (2026-06-23)
|
||||
#### Mix
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel.ParallelCompiler] Include per-module type checking times when compiler profiling is enabled with `profile: :time`
|
||||
* [mix compile] Support the `--no-listeners` option
|
||||
* [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
|
||||
* [mix local.hex] Install Hex per OTP release
|
||||
* [mix local.rebar] Install Hex per OTP release
|
||||
* [mix run] Support the `--no-listeners` option
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix binary comprehensions with sizes when options such as `:uniq` or `:into` are used
|
||||
* [Kernel] Improve compiler error messages when `quote` with `unquote` is used inside a pattern or guard
|
||||
* [Kernel] Restore the compiler optimization of `Kernel.put_elem/3` to emit `:erlang.setelement/3`
|
||||
* [Module] Fix type checking when applying an empty function type
|
||||
* [Module] Fix type checking of bitstring patterns that reuse variables
|
||||
* [Module] Fix type information for `__info__(:struct)` to include the `:required` key
|
||||
* [Module] Fix type operations on map and optional keys during difference/intersection, including open keys and empty intersections
|
||||
* [Module] Fix typing of list types involving dynamic or empty lists
|
||||
* [Module] Include bitstrings as a possible domain key in the type system
|
||||
* [Module] Preserve file metadata from each clause in type system warnings
|
||||
* [Module] Fix type warnings for protocol implementations whose protocol module defines additional callbacks
|
||||
* [Module] Raise clearer type checking errors when an expected struct is removed or redefined during compilation
|
||||
* [Kernel] Emit trace events for `@on_definition` callbacks
|
||||
* [Kernel] Emit trace events for `@on_load` callbacks
|
||||
* [Kernel] Emit trace events for `super` calls
|
||||
* [Kernel] Emit trace events for imported function calls
|
||||
* [Kernel] Optimize map unions to avoid building long lists
|
||||
* [Kernel] Do not crash when type checking nested bitstrings in patterns
|
||||
* [Kernel] Do not crash when non-binary bitstring is given as struct default value
|
||||
* [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
|
||||
* [Kernel] Preserve backwards compatibility in `elixir_erl`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Avoid unnecessary umbrella recompilation when a path dependency's manifest is newer but unchanged
|
||||
* [mix deps.compile] Recompile fetched dependencies when their compile-time environment changes
|
||||
* [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
|
||||
* [mix loadpaths] Do not run listeners when not checking the deps
|
||||
|
||||
## v1.20.1 (2026-06-09)
|
||||
## v1.18.3 (2025-03-06)
|
||||
|
||||
### 1. Security
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Version] Limit integer components in Version to 14 decimal bytes, to avoid parsing too large integers from untrusted user input. We strongly advise developers parsing versions from user input to limit the data size given to the `Version` module (CVE-2026-49762, GHSA-w2h8-8x3g-278p)
|
||||
* [JSON] Encode any JSON key to string
|
||||
* [Kernel] Allow `<<_::3*8>>` in typespecs
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix loadpaths] Support `--no-listeners` option
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Cap width in `Calendar.strftime/2` to 1024 characters
|
||||
* [Code] Ensure `Code.require_file` releases the file if compilation fails
|
||||
* [Kernel] Fix documentation generation to use the correct version in search
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix archive.install] Validate paths and files when extracting archives
|
||||
* [mix format] Honor `--no-compile` option when loading plugins
|
||||
|
||||
## v1.20.0 (2026-06-03)
|
||||
|
||||
This release requires Erlang/OTP 27+ and is compatible with Erlang/OTP 29.
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Optimize compiler by flattening expr list only once
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Base] Optimize Base validation functions by using SWAR techniques
|
||||
* [Calendar] Optimize `date_from_iso_days` by using the Neri-Schneider algorithm
|
||||
* [Code] Add `:dbg_callback` option to eval functions
|
||||
* [Code] Add `module_definition: :interpreted` option to `Code` which allows module definitions to be evaluated instead of compiled. In some applications/architectures, this can lead to drastic improvements to compilation times. Note this does not affect the generated `.beam` file, which will have the same performance/behaviour as before
|
||||
* [Code] Make module purging opt-in and move temporary module deletion to the background to speed up compilation times
|
||||
* [Code.Fragment] Allow preserving sigil metadata in `container_cursor_to_quoted`
|
||||
* [Enum] Add `Enum.min_max` sorter
|
||||
* [File] Add support for `[:raw]` opts in `File.read/2`
|
||||
* [File] Skip device, named pipes, etc in `File.cp_r/3` instead of erroring with reason `:eio`
|
||||
* [Float] Optimize `Float.round/2` by avoiding big integers
|
||||
* [Inspect] Increase inspect limit to help print deeply nested data structures
|
||||
* [Inspect] Support printing Erlang records (using Erlang notation)
|
||||
* [Integer] Add `Integer.ceil_div/2`
|
||||
* [Integer] Add `Integer.popcount/1`
|
||||
* [IO] Add `IO.iodata_empty?/1`
|
||||
* [Kernel] Add type inference across clauses. For example, if one clause says `x when is_integer(x)`, then the next clause may no longer be an integer
|
||||
* [Kernel] Add occurrence typing on `case`, `cond`, and `with`
|
||||
* [Kernel] Detect and warn on redundant clauses
|
||||
* [Kernel] Perform type inference across applications
|
||||
* [Kernel] Print intermediate results of `dbg` for pipes
|
||||
* [Kernel] Show undefined function errors even when missing variables (this helps debug errors caused when the developer forgets to require a macro)
|
||||
* [Kernel] Warn on unused requires
|
||||
* [List] Add `List.first!/1` and `List.last!/1`
|
||||
* [Module] Purge and delete modules if `after_compile/2` callback fails
|
||||
* [PartitionSupervisor] Support via tuples in `count_children/1` and `stop/3`
|
||||
* [Process] Add `Process.get_label/1`
|
||||
* [Registry] Switch `keys: {:duplicate, :key}` to `ordered_set` with composite keys
|
||||
* [Regex] Add `Regex.import/1` to import regexes defined with `/E`
|
||||
* [String] SWAR-optimize ASCII fast paths in `String.length/1` and `String.slice/3`
|
||||
* Add Software Bill of Materials guide to the Documentation
|
||||
* [CLI] Fix `--no-color` not setting `:ansi_enabled` to false
|
||||
* [Protocol] Return correct implementation for an invalid struct pointing to `nil`
|
||||
* [Stream] Do not raise when `Stream.cycle/1` is explicitly halted
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Show remaining runs when using `--repeat-until-failure`
|
||||
* [ExUnit.CaptureLog] Add `:formatter` option for custom log formatting
|
||||
* [ExUnit.Diff] Fix regression when diffing nested improper lists
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Optimize autocompleting modules
|
||||
* [IEx.Helpers] Add `source/1`
|
||||
* [IEx.Autocomplete] Fix autocomplete crash when expanding struct with `__MODULE__`
|
||||
* [IEx.Helpers] Do not purge on `recompile` if IEx is not running
|
||||
|
||||
## v1.18.2 (2025-01-22)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
|
||||
* [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
|
||||
* [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix app.tree] Support `--output` option
|
||||
* [mix compile] Add `module_definition: :interpreted` option to `Code` which allows module definitions to be evaluated instead of compiled. In some applications/architectures, this can lead to drastic improvements to compilation times. Note this does not affect the generated `.beam` file, which will have the same performance/behaviour as before
|
||||
* [mix compile] Enforce `:elixirc_paths` to be a list of strings to avoid paths from being discarded (the only documented type was lists of strings)
|
||||
* [mix deps] Parallelize dep lock status checks during `deps.loadpaths`, improving boot times in projects with many git dependencies
|
||||
* [mix deps] Support filtering `mix deps` output
|
||||
* [mix deps.tree] Support `--output` option
|
||||
* [mix format] Support `--no-compile` option
|
||||
* [mix help] Support printing docs for types and callbacks
|
||||
* [mix source] Add `mix source MODULE` to print or open a given module/function location
|
||||
* [mix test] Add `mix test --dry-run`
|
||||
* [mix] Print compilation lock waiting message to stderr
|
||||
* [mix] Add an environment variable to optionally disable compilation locking
|
||||
|
||||
### 2. Potential breaking changes
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Disallow raw CR line ending in strings, comments, and after `?` for security reasons
|
||||
* [Kernel] `require SomeModule` no longer expands to the given module at compile-time, but it still returns the module at runtime. Note Elixir does not guarantee macros will expand to certain constructs, only what its execution result, but since this can break code relying on the previous behaviour, such as `require(SomeMod).some_macro()`, we are adding this note to the CHANGELOG
|
||||
|
||||
### 3. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] Fix `Enum.slice/2` for ranges with step > 1 sliced by step > 1
|
||||
* [File] Allowing preserving directory permissions in `File.cp_r/3`
|
||||
* [File] Fix `File.cp_r/3` infinite loop with symlink cycles
|
||||
* [File] Fix `File.cp_r/3` infinite loop when copying into subdirectory of source
|
||||
* [File] Fix `File.Stream`'s `Enumerable.count` for files without trailing newline
|
||||
* [File] Warn when defining `@type record()` for Erlang/OTP 29
|
||||
* [Float] Fix `Float.parse/1` inconsistent error handling for non-scientific notation overflow
|
||||
* [Integer] Fix `Integer.extended_gcd/2` returning negative GCD for zero base cases
|
||||
* [Integer] Raise when negative out-of-range digits are given to `Integer.undigits/2`
|
||||
* [Kernel] Fix a compiler crash when importing a module with `only: :sigils` option when the imported module exports non-sigil symbols with `sigil_` prefix
|
||||
* [Kernel] Protocols should not add compile-time dependencies on `Any` implementation
|
||||
* [Kernel] Preserve evaluation order when rewriting function calls from Elixir modules into Erlang ones
|
||||
* [Kernel] Reject negative Duration in `to_timeout/1`
|
||||
* [Keyword] Raise `ArgumentError` in `Keyword.from_keys/2` for non-atom keys
|
||||
* [Macro] Fix generation of heredocs in `Macro.to_string/1` with escaped trailing newline
|
||||
* [Path] Consistently return path as binary in `Path.relative_to_cwd/2`
|
||||
* [Stream] Raise in `Stream.cycle/1` when enumerable reduce call yields no elements
|
||||
* [String] Support empty pattern list in `String.count/2`
|
||||
* [URI] Fix `URI.merge` leaking `:+` marker when base path is empty string
|
||||
* [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Diff] Avoid false positives when diffing bitstrings
|
||||
* [ExUnit] Fix crash when diffing bitstring specifiers
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Ensure pry works across remote nodes
|
||||
* [IEx] Ensure warnings emitted during IEx parsing are properly displayed/printed
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Persist log level to app env in `Logger.configure/1`
|
||||
* [IEx.Autocomplete] Fix crashing when autocompleting structs with runtime values
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Use `non_executable_binary_to_term` on loopback pubsub
|
||||
* [mix compile] Add a build lock around protocol consolidation in umbrellas
|
||||
* [mix compile] Ensure compilation of sibling deps do not mark path deps as changed
|
||||
* [mix compile] Fix compile env change triggering full recompilation of path dependencies
|
||||
* [mix compile.elixir] Fix scenario where Elixir would tag mtimes in the future
|
||||
* [mix compile.erlang] Topsort Erlang modules before compilation for proper dependency resolution
|
||||
* [mix deps] Use config files to pass project state to avoid argv limits on Windows when using `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`
|
||||
* [mix test] Fix `--warnings-as-errors` not catching misnamed test file warnings
|
||||
* [mix test] Respect `--raise` when `mix test --warnings-as-errors` passes with warnings
|
||||
* [mix] Track compilation locks per user to avoid permission errors
|
||||
* [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
|
||||
|
||||
## v1.18.1 (2024-12-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [Kernel] Do not emit type violation warnings when comparing or matching against literals
|
||||
* [Kernel] Do not validate clauses of private overridable functions
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Fragment] Ensure `Code.Fragment.container_cursor_to_quoted/2` with `:trailing_fragment` parses expressions that were supported in previous versions
|
||||
* [Kernel] Do not crash when typing violation is detected on dynamic dispatch
|
||||
* [Kernel] Properly annotate the source for warnings emitted by the compiler with the `@file` annotation
|
||||
* [Kernel] Properly annotate the source for warnings emitted by the type system with the `@file` annotation
|
||||
* [Kernel] Remove `:no_parens` metadata when using capture with arity on all cases
|
||||
* [Kernel] Ensure diagnostic traces are kept backwards compatible
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Case] Ensure async groups do not run concurrenly while the test suite is still loading
|
||||
* [ExUnit.Case] Ensure `--repeat-until-failure` can be combined with groups
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Store compilation results if compilation fails due to `--warnings-as-errors`
|
||||
* [mix deps.loadpaths] Add build lock
|
||||
* [mix escript.build] Ensure build succeeds when protocol consolidation is disabled
|
||||
* [Mix.Shell] Ensure encoding is properly respected on Windows and Unix systems
|
||||
|
||||
## v1.18.0 (2024-12-19)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Add experimental PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows. Those provide a safer entry point for running Elixir from other platforms
|
||||
* [Calendar] Add `Duration.to_string/1`
|
||||
* [Code] Support several migration options in `Code.format_string!/2`
|
||||
* [Code] Add parenthesis around `--` and `---` in `Code.format_string!/2` to make precedence clearer
|
||||
* [Code] Include more metadata in `Code.string_to_quoted/2` when `token_metadata: true` to help compute ranges from the AST
|
||||
* [Code.Fragment] Have `:capture_arg` as its own entry in `Code.Fragment.surround_context/2`
|
||||
* [Config] Add `Config.read_config/1`
|
||||
* [Enumerable] Add `Enum.product_by/2` and `Enum.sum_by/2`
|
||||
* [Exception] Add `MissingApplicationsError` exception to denote missing applications
|
||||
* [JSON] Add a new `JSON` module with encoding and decoding functionality
|
||||
* [JSON] Implement `JSON.Encoder` for all Calendar types
|
||||
* [Kernel] Update source code parsing to match [UTS #55](https://www.unicode.org/reports/tr55/) latest recommendations. In particular, mixed script is allowed in identifiers as long as they are separate by underscores (`_`), such as `http_сервер`. Previously allowed highly restrictive identifiers, which mixed Latin and other scripts, such as the japanese word for t-shirt, `Tシャツ`, now require the underscore as well
|
||||
* [Kernel] Warn on bidirectional confusability in identifiers
|
||||
* [Kernel] Verify the type of the binary generators
|
||||
* [Kernel] Track the type of tuples in patterns and inside `elem/2`
|
||||
* [Kernel] Perform validation of root AST nodes in `unquote` and `unquote_splicing` to catch bugs earlier
|
||||
* [Kernel] Add source, behaviour, and record information to Docs chunk metadata
|
||||
* [Kernel] Support deterministic builds in tandem with Erlang by setting `ERL_COMPILER_OPTIONS=deterministic`. Keep in mind deterministic builds strip source and other compile time information, which may be relevant for programs
|
||||
* [Kernel] Allow aliases and imports to be enabled conditionally in module body
|
||||
* [List] Add `List.ends_with?/2`
|
||||
* [Macro] Improve `dbg` handling of `if/2`, `with/1` and of code blocks
|
||||
* [Macro] Add `Macro.struct_info!/2` to return struct information mirroring `mod.__info__(:struct)`
|
||||
* [Registry] Add `Registry.lock/3` for local locking
|
||||
* [PartitionSupervisor] Add `PartitionSupervisor.resize!/2` to resize the number of partitions in a supervisor (up to the limit it was started with)
|
||||
* [Process] Handle arbitrarily high integer values in `Process.sleep/1`
|
||||
* [Protocol] Add `@undefined_impl_description` to customize error message when an implementation is undefined
|
||||
* [Protocol] Add `__deriving__/1` as optional macro callback to `Protocol`, no longer requiring empty implementations
|
||||
* [String] Inspect special whitespace and zero-width characters using their Unicode representation
|
||||
* [String] Update Unicode to 16.0
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Support parameterized tests on `ExUnit.Case`
|
||||
* [ExUnit] Support test groups: tests in the same group never run concurrently
|
||||
* [ExUnit.Case] Add `test_pid` as a tag
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Add `IEx.configure(auto_reload: true)` to automatically pick up modules recompiled from other operating system processes
|
||||
* [IEx] Add `:dot_iex` support to `IEx.configure/1`
|
||||
* [IEx] Add report for normal/shutdown exits in IEx
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure only a single operating system process can compile at a given time
|
||||
* [mix deps.get] Ensure only a single operating system process can fetch deps at a given time
|
||||
* [mix format] Add `mix format --migrate` to migrate from deprecated functionality
|
||||
* [mix format] Add new options and metadata to improve formatting applying by editors and other environments
|
||||
* [mix test] Taint failure manifest if requiring or compiling tests fail
|
||||
* [Mix.Project] Add a `:listeners` configuration to listen to compilation events from the current and other operating system processes
|
||||
* [Mix.Task.Compiler] Add API for fetching all persisted compiler diagnostics
|
||||
* [Mix.Task.Compiler] Add API for fetching all compiler tasks
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Fix delimiter metadata for single quoted atoms and remote calls in `Code.string_to_quoted/2`
|
||||
* [Code.Formatter] Fix formatter adding extra escapes to quoted remote calls
|
||||
* [Code.Fragment] Properly handle keyword keys as their own entry
|
||||
* [Inspect.Algebra] Ensure `next_break_fits` respects `line_length`
|
||||
* [Kernel] Validate AST on `unquote` and `unquote_splicing` to provide better error reports instead of failing too late inside the compiler
|
||||
* [Kernel] Avoid crashes when emitting diagnostics on code using \t for indentation
|
||||
* [Module] Include module attribute line and name when tracing its aliases
|
||||
* [Stream] Do not halt streams twice in `Stream.transform/5`
|
||||
* [URI] Fix a bug when a schemaless URI is given to `URI.merge/2`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Raise if guards are used in `assert/1` with `=`
|
||||
* [ExUnit.Assertions] Format inserted/deleted maps in list assertions
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] `IEx.Helpers.recompile/0` will reload modules changed by other operating system processes
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Ensure warnings from external resources are emitted with `--all-warnings` when files do not change
|
||||
* [mix deps.compile] Fix escaping issues when invoking `rebar3` in some cases
|
||||
* [mix escript] Fix escript layout and support storing `priv` directories
|
||||
* [mix release] Make `.app` files deterministic in releases
|
||||
* [Mix.Shell] Fix `Mix.Shell` on Windows when outputting non UTF-8 characters
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect.Algebra] `color/3` is deprecated in favor of `color_doc/3`
|
||||
* [Inspect.Algebra] `fold_doc/2` is deprecated in favor of `fold/2`
|
||||
* [Kernel] Deprecate `unless` in favor of `if`. Use `mix format --migrate` to automate the migration
|
||||
* [Macro] `Macro.struct!/2` is deprecated in favor of `Macro.struct_info!/2`
|
||||
* [Protocol] Defining `__deriving__/3` inside the `Any` implementation is deprecated, derive it inside the protocol definition itself
|
||||
|
||||
### 4. Hard deprecations
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] `<%#` is deprecated in favor of `<%!--` or `<% #`
|
||||
* [EEx] `c:EEx.handle_text/2` is deprecated in favor of `c:EEx.handle_text/3`
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [File] `File.stream!(path, modes, lines_or_bytes)` is deprecated in favor of `File.stream!(path, lines_or_bytes, modes)`
|
||||
* [Kernel] Matching on the size inside a bit pattern now requires the pin operator for consistency, such as `<<x::size(^existing_var)>>`
|
||||
* [Kernel.ParallelCompiler] `Kernel.ParallelCompiler.async/1` is deprecated in favor of `Kernel.ParallelCompiler.pmap/2`, which is more performant and addresses known limitations
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] `Logger.*_backend` functions are deprecated in favor of handlers. If you really want to keep on using backends, see the `:logger_backends` package
|
||||
* [Logger] `Logger.enable/1` and `Logger.disable/1` have been deprecated in favor of `Logger.put_process_level/2` and `Logger.delete_process_level/1`
|
||||
* [Code] Setting `:warnings_as_errors` is deprecated via `Code.put_compiler_option/2`. This must not affect developers, as the `:warnings_as_errors` option is managed by Mix tasks, and not directly used via the `Code` module
|
||||
* [Enumerable] Deprecate returning a two-arity function in `Enumerable.slice/1`
|
||||
* [List] `List.zip/1` is deprecated in favor of `Enum.zip/1`
|
||||
* [Module] Deprecate `Module.eval_quoted/3` in favor of `Code.eval_quoted/3`
|
||||
* [Range] Deprecate inferring negative ranges on `Range.new/2`
|
||||
* [Tuple] `Tuple.append/2` is deprecated, use `Tuple.insert_at/3` instead
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] `xref: [exclude: ...]` in your `mix.exs` is deprecated in favor of `elixirc_options: [no_warn_undefined: ...]`
|
||||
* [mix cmd] Deprecate `mix cmd --app APP` in favor of `mix do --app APP`
|
||||
* [mix compile] `:warnings_as_errors` configuration in `:elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix compile`. Alternatively, you might alias the task: `aliases: [compile: "compile --warnings-as-errors"]`
|
||||
* [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
|
||||
* [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
|
||||
|
||||
## v1.19
|
||||
### 5. Potential breaking changes
|
||||
|
||||
The CHANGELOG for v1.19 releases can be found [in the v1.19 branch](https://github.com/elixir-lang/elixir/blob/v1.19/CHANGELOG.md).
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Using unquote is deprecated when quote is used inside a pattern or guard
|
||||
|
||||
## v1.17
|
||||
|
||||
The CHANGELOG for v1.17 releases can be found [in the v1.17 branch](https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md).
|
||||
|
||||
+5
-11
@@ -1,12 +1,6 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
Contact: <elixir-lang-conduct@googlegroups.com>
|
||||
Contact: elixir-lang-conduct@googlegroups.com
|
||||
|
||||
## Why have a Code of Conduct?
|
||||
|
||||
@@ -51,15 +45,15 @@ If you participate in or contribute to the Elixir ecosystem in any way, you are
|
||||
|
||||
Explicit enforcement of the Code of Conduct applies to the official mediums operated by the Elixir project:
|
||||
|
||||
* The [official GitHub projects][1] and code reviews.
|
||||
* The official elixir-lang mailing lists.
|
||||
* The **[#elixir][2]** IRC channel on [Libera.Chat][3].
|
||||
* The [official GitHub projects][1] and code reviews.
|
||||
* The official elixir-lang mailing lists.
|
||||
* The **[#elixir][2]** IRC channel on [Libera.Chat][3].
|
||||
|
||||
Other Elixir activities (such as conferences, meetups, and unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information.
|
||||
|
||||
Project maintainers may block, remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by emailing: <elixir-lang-conduct@googlegroups.com>. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. **All reports will be kept confidential**.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by emailing: elixir-lang-conduct@googlegroups.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. **All reports will be kept confidential**.
|
||||
|
||||
**The goal of the Code of Conduct is to resolve conflicts in the most harmonious way possible**. We hope that in most cases issues may be resolved through polite discussion and mutual agreement. Bannings and other forceful measures are to be employed only as a last resort. **Do not** post about the issue publicly or try to rally sentiment against a particular individual or group.
|
||||
|
||||
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
# Contributing to Elixir
|
||||
|
||||
We invite contributions to Elixir. To contribute, there are a few
|
||||
things you need to know about the code. First, Elixir code is divided
|
||||
by each application inside the `lib` folder:
|
||||
|
||||
* `elixir` - Elixir's kernel and standard library
|
||||
|
||||
* `eex` - EEx is the template engine that allows you to embed Elixir
|
||||
|
||||
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
|
||||
|
||||
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
|
||||
|
||||
* `logger` - Logger is the built-in logger
|
||||
|
||||
* `mix` - Mix is Elixir's build tool
|
||||
|
||||
You can run all tests in the root directory with `make test`. You can
|
||||
also run tests for a specific framework with `make test_#{APPLICATION}`, for example,
|
||||
`make test_ex_unit`. If you just changed something in Elixir's standard
|
||||
library, you can run only that portion through `make test_stdlib`.
|
||||
|
||||
If you are only changing one file, you can choose to compile and run tests
|
||||
for that specific file for faster development cycles. For example, if you
|
||||
are changing the String module, you can compile it and run its tests as:
|
||||
|
||||
```sh
|
||||
bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
|
||||
bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
```
|
||||
|
||||
Some test files need their `test_helper.exs` to be explicitly required
|
||||
before, such as:
|
||||
|
||||
```sh
|
||||
bin/elixir -r lib/logger/test/test_helper.exs lib/logger/test/logger_test.exs
|
||||
```
|
||||
|
||||
You can also use the `LINE` env var to run a single test:
|
||||
|
||||
```sh
|
||||
LINE=123 bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
````
|
||||
|
||||
To recompile all (including Erlang modules):
|
||||
|
||||
```sh
|
||||
make compile
|
||||
```
|
||||
|
||||
After your changes are done, please remember to run `make format` to guarantee
|
||||
all files are properly formatted, then run the full suite with
|
||||
`make test`.
|
||||
|
||||
If your contribution fails during the bootstrapping of the language,
|
||||
you can rebuild the language from scratch with:
|
||||
|
||||
```sh
|
||||
make clean_elixir compile
|
||||
```
|
||||
|
||||
Similarly, if you can not get Elixir to compile or the tests to pass after
|
||||
updating an existing checkout, run `make clean compile`. You can check
|
||||
[the official build status](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml).
|
||||
More tasks can be found by reading the [Makefile](Makefile).
|
||||
|
||||
We encourage contributors to write tests that capture both existing and newly
|
||||
introduced behavior, especially for bug fixes and major changes:
|
||||
|
||||
* **Bug Fixes:** If you are fixing a bug, please try to include a test that
|
||||
*fails* before your change and *passes* afterward. This makes it easier to
|
||||
confirm that the fix addresses the underlying issue and helps prevent
|
||||
regressions in the future.
|
||||
|
||||
* **New Features or Major Changes:** If you are adding a new feature or making
|
||||
major changes to existing functionality, please add tests that cover the
|
||||
major parts of that functionality. Aim to have the best code coverage possible.
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
[send a pull request](https://help.github.com/articles/using-pull-requests/).
|
||||
We have saved some excellent pull requests we have received in the past in
|
||||
case you are looking for some examples:
|
||||
|
||||
* [Implement Enum.member? - Pull request](https://github.com/elixir-lang/elixir/pull/992)
|
||||
|
||||
* [Add String.valid? - Pull request](https://github.com/elixir-lang/elixir/pull/1058)
|
||||
|
||||
* [Implement capture_io for ExUnit - Pull request](https://github.com/elixir-lang/elixir/pull/1059)
|
||||
|
||||
## Reviewing changes
|
||||
|
||||
Once a pull request is sent, the Elixir team will review your changes.
|
||||
We outline our process below to clarify the roles of everyone involved.
|
||||
|
||||
All pull requests must be reviewed before being merged into the repository.
|
||||
If changes are necessary, the team will leave appropriate comments requesting
|
||||
changes to the code. Unfortunately, we cannot guarantee a pull request will
|
||||
be merged, even when modifications are requested, as the Elixir team will
|
||||
re-evaluate the contribution as it changes.
|
||||
|
||||
The Elixir team may optionally assign someone to review a pull request.
|
||||
If someone is assigned, they must explicitly approve the code before
|
||||
another team member can merge it.
|
||||
|
||||
When the review finishes, your pull request will be squashed and merged
|
||||
into the repository. If you have carefully organized your commits and
|
||||
believe they should be merged without squashing, please mention it in
|
||||
a comment.
|
||||
|
||||
## Building documentation
|
||||
|
||||
Building the documentation requires that [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
is installed and built alongside Elixir.
|
||||
|
||||
After cloning and compiling Elixir, run:
|
||||
|
||||
```sh
|
||||
elixir_dir=$(pwd)
|
||||
cd .. && git clone https://github.com/elixir-lang/ex_doc.git
|
||||
cd ex_doc && "${elixir_dir}/bin/elixir" "${elixir_dir}/bin/mix" do deps.get + compile
|
||||
|
||||
# Now we will go back to Elixir's root directory,
|
||||
cd "${elixir_dir}"
|
||||
|
||||
# and generate HTML and EPUB documents:
|
||||
make docs
|
||||
```
|
||||
|
||||
This will produce documentation sets for `elixir`, `eex`, `ex_unit`, `iex`, `logger`,
|
||||
and `mix` under the `doc` directory. If you are planning to contribute documentation,
|
||||
[please check our best practices for writing documentation](https://hexdocs.pm/elixir/writing-documentation.html).
|
||||
|
||||
## Licensing and Compliance Requirements
|
||||
|
||||
Please review our [Open Source Policy](OPEN_SOURCE_POLICY.md) for complete
|
||||
guidelines on licensing and compliance. Below is a summary of the key points
|
||||
affecting **all external contributors**:
|
||||
|
||||
* Accepted Licenses: Any code contributed must be licensed under the
|
||||
`Apache-2.0` license.
|
||||
|
||||
* SPDX License Headers: With the exception of approved test fixture files,
|
||||
all new or modified files in a pull request must include correct SPDX
|
||||
headers. If you are creating a new file under the `Apache-2.0` license, for
|
||||
instance, please use:
|
||||
|
||||
```elixir
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
```
|
||||
|
||||
* No Executable Binaries: Contributions must **not** include any executable
|
||||
binary files. If you require an exception (for example, certain test artifacts),
|
||||
please see the policy on how to request approval and document exceptions.
|
||||
|
||||
* Preserving Copyright and License Info: If you copy code from elsewhere,
|
||||
ensure that **all original copyright and license notices remain intact**. If
|
||||
they are missing or incomplete, you must add them.
|
||||
|
||||
* Failure to Comply: Pull requests that do not meet these licensing and
|
||||
compliance standards will be rejected or require modifications before merging.
|
||||
|
||||
* Developer Certificate of Origin: All contributions are subject to the
|
||||
Developer Certificate of Origin.
|
||||
|
||||
```text
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the
|
||||
best of my knowledge, is covered under an appropriate open
|
||||
source license and I have the right under that license to
|
||||
submit that work with modifications, whether created in whole
|
||||
or in part by me, under the same open source license (unless
|
||||
I am permitted to submit under a different license), as
|
||||
Indicated in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including
|
||||
all personal information I submit with it, including my
|
||||
sign-off) is maintained indefinitely and may be redistributed
|
||||
consistent with this project or the open source license(s)
|
||||
involved.
|
||||
```
|
||||
|
||||
See <https://developercertificate.org/> for a copy of the Developer Certificate
|
||||
of Origin license.
|
||||
|
||||
## Using AI and coding agents
|
||||
|
||||
While we allow the use of AI on contributions and discussions, please be mindful
|
||||
when doing so. Generally speaking, Elixir maintainers already have access to AI
|
||||
(like many other developers). Therefore, if we need the feedback or help of a
|
||||
coding agent, we can request so ourselves. For this reason, we often find
|
||||
the point of view of the human behind the agent more valuable.
|
||||
|
||||
That said, here are examples of how one might (or might not) use AI and coding
|
||||
agents in Elixir spaces:
|
||||
|
||||
* When it comes to discussions, using AI to help express yourself is welcome,
|
||||
but avoid directly copy and pasting AI generated content. If there is a language
|
||||
barrier, use AI to translate, review, and improve your text, but do not use AI
|
||||
to respond on your behalf.
|
||||
|
||||
* Do not use coding agents to tackle existing issues unless they have the
|
||||
"Contributions Welcome" label.
|
||||
|
||||
* If you request a feature on the mailing list and it is accepted, you may
|
||||
use coding agents to implement it, as long as it follows the AI Contributions
|
||||
guidelines below.
|
||||
|
||||
* When automating AI usage on the Elixir codebase for performance improvements
|
||||
or security fixes, pair it with a separate set of agents whose job is to argue
|
||||
against and try to invalidate any proposed change. And treat their approval as
|
||||
advisory: a human must still validate it before opening issues or pull requests.
|
||||
|
||||
If any code is written by AI, then you must follow the guidelines below.
|
||||
|
||||
### AI contributions
|
||||
|
||||
AI agents MUST NOT add Signed-off-by tags. Only humans can legally certify the Developer
|
||||
Certificate of Origin (DCO). The human submitter is responsible for:
|
||||
|
||||
* Reviewing all AI-generated code
|
||||
* Ensuring compliance with licensing requirements
|
||||
* Adding their own Signed-off-by tag to certify the DCO
|
||||
* Taking full responsibility for the contribution
|
||||
* Disclosing use of AI for comments and code contributions
|
||||
|
||||
When AI tools contribute to Elixir, proper attribution helps track the evolving role of
|
||||
AI in the development process. Contributions should include an Assisted-by tag in the
|
||||
following format:
|
||||
|
||||
Assisted-by: AGENT_NAME:MODEL_VERSION
|
||||
@@ -1,73 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,98 +0,0 @@
|
||||
ELIXIR TEAM TRADEMARKS POLICY
|
||||
|
||||
This document outlines the policy for allowed usage of the “Elixir” word and the
|
||||
Elixir logo by other parties.
|
||||
|
||||
“Elixir” and the Elixir logo are registered trademarks of the Elixir Team. The
|
||||
Elixir Team believes in a decentralized approach to growing the community and
|
||||
the ecosystem, independent of the Elixir project and the Elixir Team.
|
||||
|
||||
Anyone can use the Elixir trademarks if that use of the trademark is nominative.
|
||||
The trademarks must not be used to disparage the project and its community, nor
|
||||
be used in any way to imply ownership, endorsement, or association with the
|
||||
Elixir project and the Elixir Team.
|
||||
|
||||
You must not visually combine the Elixir logo with any other images, or change
|
||||
the logo in any way other than ways required by printing restrictions. If you
|
||||
want to create your own visual identity in relation to Elixir, you might use the
|
||||
shape of an unrelated “water drop” as part of your design, as seen in many
|
||||
community projects and initiatives. You must not combine or modify the Elixir
|
||||
logo.
|
||||
|
||||
The Elixir logo is available in our repository in both vertical and horizontal
|
||||
versions.
|
||||
|
||||
Nominative use
|
||||
The “nominative use” (or “nominative fair use”) is a legal doctrine that
|
||||
authorizes everyone (even commercial companies) to use or refer to the trademark
|
||||
of another if:
|
||||
|
||||
The product or service in question must be one not readily identifiable without
|
||||
use of the trademark.
|
||||
|
||||
Only so much of the mark or marks may be used as is reasonably necessary to
|
||||
identify the product or service.
|
||||
|
||||
The organization using the mark must do nothing that would, in conjunction with
|
||||
the mark, suggest sponsorship or endorsement by the trademark holder.
|
||||
|
||||
Our trademarks must be used to refer to the Elixir programming language.
|
||||
|
||||
Examples of permitted use
|
||||
All examples listed next must strictly adhere to the terms outlined in the
|
||||
previous sections:
|
||||
|
||||
Usage of the Elixir logo to say a technology is “powered by Elixir” under
|
||||
nominative use. Linking back to the Elixir website, if possible, is appreciated.
|
||||
|
||||
Usage of the Elixir logo to display it as a supported technology in a service or
|
||||
platform. For instance, you may say “we support Elixir” and use the Elixir logo,
|
||||
but you may not refer to yourself as “the Elixir platform” nor imply any form of
|
||||
endorsement or association with Elixir.
|
||||
|
||||
Usage of the Elixir logo in non-commercial community meetups, in presentations,
|
||||
and in courses when referring to the language and its ecosystem under nominative
|
||||
use.
|
||||
|
||||
Usage of the Elixir logo in non-commercial swag (stickers, t-shirts, mugs, etc)
|
||||
to promote the Elixir programming language. The Elixir marks must be the only
|
||||
marks featured in the product. You need permission to make swag that include
|
||||
Elixir and other third party marks in them.
|
||||
|
||||
Inclusion of the Elixir logo in non-commercial icon sets. Use of the Elixir
|
||||
icons must still adhere to Elixir’s trademark policies.
|
||||
|
||||
Usage of the “Elixir” word in book titles, meetups, conferences, and podcasts.
|
||||
You must not use the word to imply uniqueness or endorsement from the Elixir
|
||||
team. “The Elixir book” and “The Elixir podcast” are not permitted.
|
||||
“Elixir in Action”, “Thinking Elixir”, and “Kraków Elixir User Group” are valid
|
||||
examples already in use today.
|
||||
|
||||
Usage of the “Elixir” word in the names of freely distributed software and
|
||||
hardware products is allowed when referring to use with or suitability for the
|
||||
Elixir programming language, such as wxElixir, Elixirsense, etc. If the product
|
||||
includes the Elixir programming language itself, then you must also respect its
|
||||
license.
|
||||
|
||||
Examples of not permitted use
|
||||
Here is a non-exhaustive list of non permitted uses of the marks:
|
||||
|
||||
Usage of the Elixir logo in book covers, conferences, and podcasts.
|
||||
|
||||
Usage of the Elixir logo as the mark of third party projects, even in combination
|
||||
with other marks.
|
||||
|
||||
Naming any company or product after Elixir, such as “The Elixir Hosting”,
|
||||
“The Elixir Consultants”, etc.
|
||||
|
||||
Examples that require permission
|
||||
Here are some examples that may be granted permission upon request:
|
||||
|
||||
Selling merchandise (stickers, t-shirts, mugs, etc).
|
||||
You can request permission by emailing trademarks@elixir-lang.org.
|
||||
|
||||
Important note
|
||||
Nothing in this page shall be interpreted to allow any third party to claim any
|
||||
association with the Elixir project and the Elixir Team, or to imply any
|
||||
approval or support by the Elixir project and the Elixir Team for any third
|
||||
party products, services, or events.
|
||||
@@ -1,58 +0,0 @@
|
||||
UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
|
||||
|
||||
Unicode Data Files include all data files under the directories
|
||||
http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
|
||||
http://www.unicode.org/cldr/data/ . Unicode Software includes any source
|
||||
code published in the Unicode Standard or under the directories
|
||||
http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
|
||||
http://www.unicode.org/cldr/data/.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA
|
||||
FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY
|
||||
ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF THIS
|
||||
AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE
|
||||
OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © Unicode, Inc. All rights reserved. Distributed under
|
||||
the Terms of Use in http://www.unicode.org/copyright.html.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Unicode data files and any associated documentation (the
|
||||
"Data Files") or Unicode software and any associated documentation (the
|
||||
"Software") to deal in the Data Files or Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, and/or sell copies of the Data Files or Software,
|
||||
and to permit persons to whom the Data Files or Software are furnished
|
||||
to do so, provided that
|
||||
|
||||
(a) the above copyright notice(s) and this permission notice appear with
|
||||
all copies of the Data Files or Software,
|
||||
|
||||
(b) both the above copyright notice(s) and this permission notice appear
|
||||
in associated documentation, and
|
||||
|
||||
(c) there is clear notice in each modified Data File or in the Software
|
||||
as well as in the documentation associated with the Data File(s) or
|
||||
Software that the data or software has been modified.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR
|
||||
ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or
|
||||
other dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
Unicode and the Unicode logo are trademarks of Unicode, Inc., and may be
|
||||
registered in some jurisdictions. All other trademarks and registered
|
||||
trademarks mentioned herein are the property of their respective owners.
|
||||
@@ -1,14 +1,9 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
TEST_FILES ?= "*_test.exs"
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
# CANONICAL := main/
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ELIXIRC_MIN_SIG := $(ELIXIRC) -e 'Code.put_compiler_option :infer_signatures, []'
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL_MAKE := erl -make
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
@@ -26,15 +21,15 @@ GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$hea
|
||||
SOURCE_DATE_EPOCH_PATH = lib/elixir/tmp/ebin_reproducible
|
||||
SOURCE_DATE_EPOCH_FILE = $(SOURCE_DATE_EPOCH_PATH)/SOURCE_DATE_EPOCH
|
||||
|
||||
.PHONY: cover install install_man build_plt clean_plt dialyze test check_reproducible clean clean_elixir clean_man format docs Docs.zip Precompiled.zip zips
|
||||
.PHONY: install install_man build_plt clean_plt dialyze test check_reproducible clean clean_elixir clean_man format docs Docs.zip Precompiled.zip zips
|
||||
.NOTPARALLEL:
|
||||
|
||||
#==> Functions
|
||||
|
||||
define CHECK_ERLANG_RELEASE
|
||||
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 27)])' -s erlang halt | grep -q '^true'; \
|
||||
erl -noshell -eval '{V,_} = string:to_integer(erlang:system_info(otp_release)), io:fwrite("~s", [is_integer(V) and (V >= 25)])' -s erlang halt | grep -q '^true'; \
|
||||
if [ $$? != 0 ]; then \
|
||||
echo "At least Erlang/OTP 27.0 is required to build Elixir"; \
|
||||
echo "At least Erlang/OTP 25.0 is required to build Elixir"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endef
|
||||
@@ -53,10 +48,6 @@ lib/$(1)/ebin/Elixir.$(2).beam: $(wildcard lib/$(1)/lib/*.ex) $(wildcard lib/$(1
|
||||
test_$(1): test_formatted $(1)
|
||||
@ echo "==> $(1) (ex_unit)"
|
||||
$(Q) cd lib/$(1) && ../../bin/elixir -r "test/test_helper.exs" -pr "test/**/$(TEST_FILES)";
|
||||
|
||||
cover/ex_unit_$(1).coverdata:
|
||||
$(Q) COVER="1" $(MAKE) test_$(1)
|
||||
cover/combined.coverdata: cover/ex_unit_$(1).coverdata
|
||||
endef
|
||||
|
||||
define WRITE_SOURCE_DATE_EPOCH
|
||||
@@ -106,19 +97,17 @@ $(KERNEL): lib/elixir/src/* lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir
|
||||
"$(MAKE)" unicode; \
|
||||
fi
|
||||
@ echo "==> elixir (compile)";
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC_MIN_SIG) "lib/**/*.ex" -o ebin;
|
||||
$(Q) $(GENERATE_APP) $(VERSION)
|
||||
$(Q) bin/elixir lib/elixir/scripts/infer.exs;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
|
||||
$(APP): lib/elixir/src/elixir.app.src $(GENERATE_APP)
|
||||
$(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP)
|
||||
$(Q) $(GENERATE_APP) $(VERSION)
|
||||
|
||||
unicode: $(UNICODE)
|
||||
$(UNICODE): lib/elixir/unicode/*
|
||||
@ echo "==> unicode (compile)";
|
||||
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/tokenizer.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC_MIN_SIG) lib/elixir/unicode/security.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/unicode.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/tokenizer.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/security.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,logger,Logger))
|
||||
@@ -181,7 +170,6 @@ clean: clean_man
|
||||
rm -rf lib/mix/test/fixtures/git_sparse_repo/
|
||||
rm -rf lib/mix/test/fixtures/archive/ebin/
|
||||
rm -f erl_crash.dump
|
||||
rm -rf cover
|
||||
|
||||
clean_elixir:
|
||||
$(Q) rm -f lib/*/ebin/Elixir.*.beam
|
||||
@@ -189,7 +177,7 @@ clean_elixir:
|
||||
#==> Documentation tasks
|
||||
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
|
||||
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" --logo lib/elixir/pages/images/logo.png --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" $(DOCS_OPTIONS) $(4)
|
||||
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" --logo lib/elixir/pages/images/logo.png --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" $(4)
|
||||
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
|
||||
@@ -231,19 +219,19 @@ docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
$(call DOCS_CONFIG,logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See CONTRIBUTING.md for more information."
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@ false
|
||||
|
||||
#==> Zip tasks
|
||||
|
||||
Docs.zip: docs
|
||||
rm -f Docs.zip
|
||||
zip -9 -r Docs.zip CHANGELOG.md doc LICENSE README.md
|
||||
zip -9 -r Docs.zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs.zip"
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled.zip
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man README.md VERSION
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
|
||||
|
||||
#==> Test tasks
|
||||
@@ -294,15 +282,6 @@ test_stdlib: compile
|
||||
cd lib/elixir && ../../bin/elixir --sname primary -r "test/elixir/test_helper.exs" -pr "test/elixir/**/$(TEST_FILES)"; \
|
||||
fi
|
||||
|
||||
cover/ex_unit_elixir.coverdata:
|
||||
$(Q) COVER="1" $(MAKE) test_stdlib
|
||||
cover/combined.coverdata: cover/ex_unit_elixir.coverdata
|
||||
|
||||
cover/combined.coverdata:
|
||||
bin/elixir ./lib/elixir/scripts/cover.exs
|
||||
|
||||
cover: cover/combined.coverdata
|
||||
|
||||
#==> Dialyzer tasks
|
||||
|
||||
DIALYZER_OPTS = --no_check_plt --fullpath -Werror_handling -Wunmatched_returns -Wunderspecs
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
LEGAL NOTICE INFORMATION
|
||||
------------------------
|
||||
|
||||
All the files in this distribution are copyright to the terms below.
|
||||
|
||||
== lib/elixir/src/elixir_json.erl
|
||||
== lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
|
||||
Copyright Ericsson AB 1996-2024
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
== All other files
|
||||
|
||||
Copyright 2012 Plataformatec
|
||||
Copyright 2021 The Elixir Team
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,165 +0,0 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
-->
|
||||
|
||||
# Open Source Policy
|
||||
|
||||
## 1. Introduction
|
||||
|
||||
This Open Source Policy outlines the licensing, contribution, and compliance
|
||||
requirements for all code released under the Elixir project. By adhering to
|
||||
these guidelines, we ensure that our community, maintainers, and contributors
|
||||
uphold both legal and ethical standards while fostering a collaborative,
|
||||
transparent environment.
|
||||
|
||||
This policy exists to support and protect the Elixir community. It aims to
|
||||
balance openness, collaboration, and respect for all contributors’ rights,
|
||||
ensuring that Elixir remains a trusted and innovative open source project.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
This policy applies to the Elixir Programming language, located at
|
||||
<https://github.com/elixir-lang/elixir>. It covers every file, and contribution
|
||||
made, including documentation and any associated assets.
|
||||
|
||||
## 3. Licensing
|
||||
|
||||
All code released by the Elixir team is licensed under the
|
||||
[Apache-2.0](./LICENSES/Apache-2.0.txt) license. Additionally, the following
|
||||
licenses are recognized as permissible in this project:
|
||||
|
||||
- The Unicode license, as documented at
|
||||
[LicenseRef-scancode-unicode](./LICENSES/LicenseRef-scancode-unicode.txt)
|
||||
|
||||
- The Elixir Trademark Policy, as documented at
|
||||
[LicenseRef-elixir-trademark-policy](./LICENSES/LicenseRef-elixir-trademark-policy.txt)
|
||||
|
||||
These licenses are considered acceptable for any files or code that form part of
|
||||
an Elixir repository. If a contribution requires a different license, it must
|
||||
either be rejected or prompt an update to this policy.
|
||||
|
||||
## 4. Contributing to the Elixir repository
|
||||
|
||||
Any code contributed to the Elixir repository must fall under one of the accepted
|
||||
licenses (Apache-2.0, Unicode, or Elixir Trademark). Contributions under any
|
||||
other license will be rejected unless this policy is formally revised to include
|
||||
that license. All files except those specifically exempted (e.g., certain test
|
||||
fixture files) must contain SPDX license and copyright headers
|
||||
(`SPDX-License-Identifier` and `SPDX-FileCopyrightText`). If a file qualifies
|
||||
for an exception, this must be configured in the ORT (Open Source Review Toolkit)
|
||||
configuration and undergo review.
|
||||
|
||||
Contributions must not introduce executable binary files into the codebase.
|
||||
|
||||
## 5. Preservation of Copyright and License Information
|
||||
|
||||
Any third-party code incorporated into the Elixir repository must retain original
|
||||
copyright and license headers. If no such headers exist in the source, they must
|
||||
be added. This practice ensures that original authors receive proper credit and
|
||||
that the licensing lineage is preserved.
|
||||
|
||||
## 6. Objectives
|
||||
|
||||
The Elixir project aims to promote a culture of responsible open source usage.
|
||||
Specifically, our objectives include:
|
||||
|
||||
### 6.1 Clearly Define and Communicate Licensing & Compliance Policies
|
||||
|
||||
We will identify and document all third-party dependencies, ensure that license
|
||||
information is communicated clearly, and maintain a project-wide license policy
|
||||
or compliance handbook.
|
||||
|
||||
### 6.2 Implement Clear Processes for Reviewing Contributions
|
||||
|
||||
We will provide well-defined contribution guidelines. We implement the
|
||||
Developer Certificate of Origin (DCO) for additional clarity regarding
|
||||
contributor rights and obligations.
|
||||
|
||||
### 6.3 Track and Audit Third-Party Code Usage
|
||||
|
||||
All projects will implement a Software Bill of Materials (SBoM) strategy and
|
||||
regularly verify license compliance for direct and transitive dependencies.
|
||||
|
||||
### 6.4 Monitor and Continuously Improve Open Source Compliance
|
||||
|
||||
We will conduct periodic internal audits, integrate compliance checks into
|
||||
continuous integration (CI/CD) pipelines, and regularly review and refine these
|
||||
objectives to align with best practices.
|
||||
|
||||
## 7. Roles and Responsibilities
|
||||
|
||||
### 7.1 Core Team Member
|
||||
|
||||
Core Team Members are responsible for being familiar with this policy and
|
||||
ensuring it is consistently enforced. They must demonstrate sufficient
|
||||
competencies to understand the policy requirements and must reject or request
|
||||
changes to any pull requests that violate these standards.
|
||||
|
||||
### 7.2 Contributor
|
||||
|
||||
Contributors are expected to follow this policy when submitting code. If a
|
||||
contributor submits a pull request that does not comply with the policy
|
||||
(e.g., introduces a disallowed license), Core Team Members have the authority to
|
||||
reject it or request changes. No special competencies are required for
|
||||
contributors beyond awareness and adherence to the policy.
|
||||
|
||||
### 7.3 EEF CISO
|
||||
|
||||
The CISO designated by the Erlang Ecosystem Foundation (EEF) provides oversight
|
||||
on queries and guidance regarding open source compliance or legal matters for
|
||||
Elixir. The CISO is responsible for checking ongoing compliance with the policy,
|
||||
escalating potential violations to the Core Team, and involving legal counsel if
|
||||
necessary. This role does not require legal expertise but does involve
|
||||
initiating legal or community discussions when needed.
|
||||
|
||||
## 8. Implications of Failing to Follow the Program Requirements
|
||||
|
||||
If a violation of this policy is identified, the Elixir Core Team will undertake
|
||||
the following actions:
|
||||
|
||||
## 8.1 Review the Codebase for Additional Violations
|
||||
|
||||
We will investigate the codebase thoroughly to detect any similar instances of
|
||||
non-compliance.
|
||||
|
||||
## 8.2 Review and Update the Process or Policy
|
||||
|
||||
In collaboration with the EEF CISO, the Elixir Core Team will assess the policy
|
||||
and our internal workflows, making any necessary clarifications or amendments to
|
||||
reduce the likelihood of recurrence.
|
||||
|
||||
## 8.3 Notify and Train Core Team Members
|
||||
|
||||
We will ensure that all active Core Team Members are informed about any policy
|
||||
changes and understand how to apply them in everyday development.
|
||||
|
||||
## 8.4 Remove or Replace the Offending Code
|
||||
|
||||
If required, we will remove or replace the non-compliant code.
|
||||
|
||||
## 9. Contact
|
||||
|
||||
The project maintains a private mailing list at
|
||||
[policy@elixir-lang.org](mailto:policy@elixir-lang.org) for handling licensing
|
||||
and policy-related queries. Email is the preferred communication channel, and
|
||||
the EEF CISO will be included on this list to provide assistance and ensure
|
||||
timely responses. While solutions may take longer to implement, the project
|
||||
commits to acknowledging all queries within five business days.
|
||||
|
||||
## 10. External Contributions of Core Team Members
|
||||
|
||||
When Core Team Members contribute to repositories outside Elixir, they do so in
|
||||
a personal capacity or via their employer. They will not act as official
|
||||
representatives of the Elixir team in those external contexts.
|
||||
|
||||
## 11. Policy Review and Amendments
|
||||
|
||||
This policy will be revisited annually to address new concerns, accommodate
|
||||
changes in community standards, or adjust to emerging legal or technical
|
||||
requirements. Proposed amendments must be reviewed by the Core Team and, if
|
||||
necessary, by the EEF CISO. Any significant changes will be communicated to
|
||||
contributors and made publicly available.
|
||||
|
||||
*Effective Date: 2025-02-20*
|
||||
*Last Reviewed: 2025-11-20*
|
||||
@@ -1,9 +1,3 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
<h1>
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/elixir-lang/elixir-lang.github.com/raw/main/images/logo/logo-dark.png">
|
||||
@@ -12,7 +6,6 @@
|
||||
</h1>
|
||||
|
||||
[](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml?query=branch%3Amain)
|
||||
[](https://www.bestpractices.dev/projects/10187)
|
||||
|
||||
Elixir is a dynamic, functional language designed for building scalable
|
||||
and maintainable applications.
|
||||
@@ -32,18 +25,19 @@ information, please read our [Security Policy][9].
|
||||
All interactions in our official communication channels follow our
|
||||
[Code of Conduct][1].
|
||||
|
||||
All contributions are required to conform to our [Open Source Policy][11].
|
||||
|
||||
## Bug reports
|
||||
|
||||
For reporting bugs, [visit our issue tracker][2] and follow the steps
|
||||
for reporting a new issue. **Please disclose security vulnerabilities
|
||||
privately [in our Security page](https://github.com/elixir-lang/elixir/security)**.
|
||||
privately at <elixir-security@googlegroups.com>**.
|
||||
|
||||
All currently open bugs related to Elixir are listed in the issues tracker.
|
||||
The Elixir team uses the issues tracker to focus on *actionable items*,
|
||||
including planned enhancements in the short and medium term. We also do
|
||||
our best to label entries for clarity and to ease collaboration.
|
||||
## Issues tracker management
|
||||
|
||||
All currently open bugs related to the Elixir repository are listed
|
||||
in the issues tracker. The Elixir team uses the issues tracker to focus
|
||||
on *actionable items*, including planned enhancements in the short and
|
||||
medium term. We also do our best to label entries for clarity and to ease
|
||||
collaboration.
|
||||
|
||||
Our *actionable item policy* has some important consequences, such as:
|
||||
|
||||
@@ -55,42 +49,38 @@ Our *actionable item policy* has some important consequences, such as:
|
||||
elsewhere if appropriate).
|
||||
|
||||
* We actively close unrelated and non-actionable issues to keep the
|
||||
issues tracker tidy. If you believe we got something wrong, drop a
|
||||
comment and we can always reopen the issue.
|
||||
issues tracker tidy. We may get things wrong from time to
|
||||
time and will gladly revisit issues, reopening when necessary.
|
||||
|
||||
By keeping the overall issues tracker tidy and organized, the community
|
||||
can easily peek at what is coming in new releases and also get involved
|
||||
by commenting on existing issues and submitting pull requests. Please
|
||||
remember to keep the tone positive and be kind! For more information,
|
||||
see the [Code of Conduct][1].
|
||||
Keep the tone positive and be kind! For more information, see the
|
||||
[Code of Conduct][1].
|
||||
|
||||
## Discussions, support, and help
|
||||
### Proposing new features
|
||||
|
||||
For proposing new features, please start a discussion in the
|
||||
[Elixir Core mailing list][3]. The [language development history and
|
||||
its focus are described on our website](https://elixir-lang.org/development.html).
|
||||
|
||||
Keep in mind that it is your responsibility to argue and explain
|
||||
why a feature is useful and how it will impact the codebase and
|
||||
the community. A good proposal includes the problem description
|
||||
and how the proposed solution compares with existing alternatives
|
||||
in the Elixir ecosystem (as well as in other languages). To iron
|
||||
out a proposal before submission, consider using and gathering
|
||||
feedback from the community spaces [listed on the sidebar of the
|
||||
Elixir website](https://elixir-lang.org/).
|
||||
|
||||
Once a proposal is accepted, it will be added to [the issue tracker][2].
|
||||
Features and bug fixes that have already been merged and will be included
|
||||
in the next release are then "closed" and added to the [changelog][7].
|
||||
|
||||
### Discussions, support, and help
|
||||
|
||||
For general discussions, support, and help, please use the community
|
||||
spaces [listed on the sidebar of the Elixir website](https://elixir-lang.org/),
|
||||
such as forums, chat platforms, etc, where the wider community will be available
|
||||
to help you.
|
||||
|
||||
## Proposing new features
|
||||
|
||||
We encourage you to first propose new features in the community spaces
|
||||
listed above. These discussions help refine ideas and gather feedback before
|
||||
submission. Our website also includes [a general outline of the language
|
||||
history and its current development focus](https://elixir-lang.org/development.html).
|
||||
|
||||
Once you are ready, you can submit your proposal to the [Elixir Core
|
||||
mailing list][3], either through the web interface or by subscribing to
|
||||
it at <elixir-lang-core+subscribe@googlegroups.com>. Remember to include
|
||||
a clear problem description, compare the proposed solution to existing
|
||||
alternatives in the Elixir ecosystem (and in other languages if possible),
|
||||
and consider the potential impact your changes will have on the codebase and
|
||||
community.
|
||||
|
||||
Once a proposal is accepted, it will be added to [the issue tracker][2].
|
||||
Features and bug fixes that have already been merged and will be included
|
||||
in the next release are then "closed" and added to the [changelog][7]
|
||||
before release.
|
||||
|
||||
## Compiling from source
|
||||
|
||||
For the many different ways to install Elixir,
|
||||
@@ -119,13 +109,125 @@ variable `ERL_COMPILER_OPTIONS=deterministic`.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions to Elixir are always welcome! Before you get started, please check
|
||||
out our [CONTRIBUTING.md](CONTRIBUTING.md) file. There you will find detailed
|
||||
guidelines on how to set up your environment, run the test suite, format your
|
||||
code, and submit pull requests.
|
||||
We invite contributions to Elixir. To contribute, there are a few
|
||||
things you need to know about the code. First, Elixir code is divided
|
||||
by each application inside the `lib` folder:
|
||||
|
||||
Note you must disclose the use of coding agents and AI written code in your
|
||||
contributions. See the "Using AI and coding agents" in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
* `elixir` - Elixir's kernel and standard library
|
||||
|
||||
* `eex` - EEx is the template engine that allows you to embed Elixir
|
||||
|
||||
* `ex_unit` - ExUnit is a simple test framework that ships with Elixir
|
||||
|
||||
* `iex` - IEx stands for Interactive Elixir: Elixir's interactive shell
|
||||
|
||||
* `logger` - Logger is the built-in logger
|
||||
|
||||
* `mix` - Mix is Elixir's build tool
|
||||
|
||||
You can run all tests in the root directory with `make test`. You can
|
||||
also run tests for a specific framework with `make test_#{APPLICATION}`, for example,
|
||||
`make test_ex_unit`. If you just changed something in Elixir's standard
|
||||
library, you can run only that portion through `make test_stdlib`.
|
||||
|
||||
If you are only changing one file, you can choose to compile and run tests
|
||||
for that specific file for faster development cycles. For example, if you
|
||||
are changing the String module, you can compile it and run its tests as:
|
||||
|
||||
```sh
|
||||
bin/elixirc lib/elixir/lib/string.ex -o lib/elixir/ebin
|
||||
bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
```
|
||||
|
||||
Some test files need their `test_helper.exs` to be explicitly required
|
||||
before, such as:
|
||||
|
||||
```sh
|
||||
bin/elixir -r lib/logger/test/test_helper.exs lib/logger/test/logger_test.exs
|
||||
```
|
||||
|
||||
You can also use the `LINE` env var to run a single test:
|
||||
|
||||
```sh
|
||||
LINE=123 bin/elixir lib/elixir/test/elixir/string_test.exs
|
||||
````
|
||||
|
||||
To recompile all (including Erlang modules):
|
||||
|
||||
```sh
|
||||
make compile
|
||||
```
|
||||
|
||||
After your changes are done, please remember to run `make format` to guarantee
|
||||
all files are properly formatted, then run the full suite with
|
||||
`make test`.
|
||||
|
||||
If your contribution fails during the bootstrapping of the language,
|
||||
you can rebuild the language from scratch with:
|
||||
|
||||
```sh
|
||||
make clean_elixir compile
|
||||
```
|
||||
|
||||
Similarly, if you can not get Elixir to compile or the tests to pass after
|
||||
updating an existing checkout, run `make clean compile`. You can check
|
||||
[the official build status](https://github.com/elixir-lang/elixir/actions/workflows/ci.yml).
|
||||
More tasks can be found by reading the [Makefile](Makefile).
|
||||
|
||||
With tests running and passing, you are ready to contribute to Elixir and
|
||||
[send a pull request](https://help.github.com/articles/using-pull-requests/).
|
||||
We have saved some excellent pull requests we have received in the past in
|
||||
case you are looking for some examples:
|
||||
|
||||
* [Implement Enum.member? - Pull request](https://github.com/elixir-lang/elixir/pull/992)
|
||||
* [Add String.valid? - Pull request](https://github.com/elixir-lang/elixir/pull/1058)
|
||||
* [Implement capture_io for ExUnit - Pull request](https://github.com/elixir-lang/elixir/pull/1059)
|
||||
|
||||
### Reviewing changes
|
||||
|
||||
Once a pull request is sent, the Elixir team will review your changes.
|
||||
We outline our process below to clarify the roles of everyone involved.
|
||||
|
||||
All pull requests must be approved by two committers before being merged into
|
||||
the repository. If changes are necessary, the team will leave appropriate
|
||||
comments requesting changes to the code. Unfortunately, we cannot guarantee a
|
||||
pull request will be merged, even when modifications are requested, as the Elixir
|
||||
team will re-evaluate the contribution as it changes.
|
||||
|
||||
Committers may also push style changes directly to your branch. If you would
|
||||
rather manage all changes yourself, you can disable the "Allow edits from maintainers"
|
||||
feature when submitting your pull request.
|
||||
|
||||
The Elixir team may optionally assign someone to review a pull request.
|
||||
If someone is assigned, they must explicitly approve the code before
|
||||
another team member can merge it.
|
||||
|
||||
When the review finishes, your pull request will be squashed and merged
|
||||
into the repository. If you have carefully organized your commits and
|
||||
believe they should be merged without squashing, please mention it in
|
||||
a comment.
|
||||
|
||||
## Building documentation
|
||||
|
||||
Building the documentation requires that [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
is installed and built alongside Elixir:
|
||||
|
||||
```sh
|
||||
# After cloning and compiling Elixir, in its parent directory:
|
||||
git clone https://github.com/elixir-lang/ex_doc.git
|
||||
cd ex_doc && ../elixir/bin/elixir ../elixir/bin/mix do deps.get + compile
|
||||
```
|
||||
|
||||
Now go back to Elixir's root directory and run:
|
||||
|
||||
```sh
|
||||
make docs # to generate HTML pages
|
||||
make docs DOCS_FORMAT=epub # to generate EPUB documents
|
||||
```
|
||||
|
||||
This will produce documentation sets for `elixir`, `eex`, `ex_unit`, `iex`, `logger`,
|
||||
and `mix` under the `doc` directory. If you are planning to contribute documentation,
|
||||
[please check our best practices for writing documentation](https://hexdocs.pm/elixir/writing-documentation.html).
|
||||
|
||||
## Development links
|
||||
|
||||
@@ -148,7 +250,6 @@ contributions. See the "Using AI and coding agents" in [CONTRIBUTING.md](CONTRIB
|
||||
[8]: https://groups.google.com/group/elixir-lang-ann
|
||||
[9]: SECURITY.md
|
||||
[10]: https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date
|
||||
[11]: OPEN_SOURCE_POLICY.md
|
||||
|
||||
## License
|
||||
|
||||
@@ -156,4 +257,4 @@ contributions. See the "Using AI and coding agents" in [CONTRIBUTING.md](CONTRIB
|
||||
|
||||
Elixir source code is released under Apache License 2.0.
|
||||
|
||||
Check [LICENSE](LICENSE) file for more information.
|
||||
Check [NOTICE](NOTICE) and [LICENSE](LICENSE) files for more information.
|
||||
|
||||
+4
-10
@@ -1,9 +1,3 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
# Release process
|
||||
|
||||
## Shipping a new version
|
||||
@@ -24,11 +18,11 @@
|
||||
|
||||
8. Update `_data/elixir-versions.yml` (except for RCs) in `elixir-lang/elixir-lang.github.com`
|
||||
|
||||
## Creating a new vMAJOR.MINOR branch (usually before first rc)
|
||||
## Creating a new vMAJOR.MINOR branch (before first rc)
|
||||
|
||||
### In the new branch
|
||||
|
||||
1. Comment out `CANONICAL := main/` in /Makefile
|
||||
1. Comment out `CANONICAL=` in /Makefile
|
||||
|
||||
2. Update tables in /SECURITY.md and "Compatibility and Deprecations"
|
||||
|
||||
@@ -36,7 +30,7 @@
|
||||
|
||||
### Back in main
|
||||
|
||||
1. Bump /VERSION file, bin/elixir, and bin/elixir.bat
|
||||
1. Bump /VERSION file, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
|
||||
|
||||
2. Start new /CHANGELOG.md
|
||||
|
||||
@@ -50,6 +44,6 @@
|
||||
|
||||
2. Update `otp_release` checks in `/Makefile` and `/lib/elixir/src/elixir.erl`
|
||||
|
||||
3. Update relevant CI workflows in `/.github/workflows/*.yml` - for release workflows, outdated/recently added Erlang/OTP versions must run conditionally
|
||||
3. Update relevant CI workflows in `/.github/workflows/*.yml`
|
||||
|
||||
4. Remove `otp_release` version checks that are no longer needed
|
||||
|
||||
+4
-10
@@ -1,9 +1,3 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
# Security Policy
|
||||
|
||||
## Supported versions
|
||||
@@ -12,15 +6,15 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.20 | Bug fixes and security patches
|
||||
1.19 | Security patches only
|
||||
1.18 | Security patches only
|
||||
1.18 | Bug fixes and security patches
|
||||
1.17 | Security patches only
|
||||
1.16 | Security patches only
|
||||
1.15 | Security patches only
|
||||
1.14 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to <elixir-lang-ann+subscribe@googlegroups.com> and replying to the confirmation email. Security notifications [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). You can subscribe by sending an email to elixir-lang-ann+subscribe@googlegroups.com and replying to the confirmation email. Security notifications [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||
|
||||
You may also see [all releases](https://github.com/elixir-lang/elixir/releases) and [consult all disclosed vulnerabilities](https://github.com/elixir-lang/elixir/security) on GitHub.
|
||||
|
||||
|
||||
+5
-6
@@ -1,12 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.20.4
|
||||
ELIXIR_VERSION=1.18.5
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -221,6 +216,10 @@ SCRIPT_PATH=$(dirname "$SELF")
|
||||
if [ "$OSTYPE" = "cygwin" ]; then SCRIPT_PATH=$(cygpath -m "$SCRIPT_PATH"); fi
|
||||
if [ "$MODE" != "iex" ]; then ERL="-s elixir start_cli $ERL"; fi
|
||||
|
||||
if [ "$OS" != "Windows_NT" ] && [ -z "$NO_COLOR" ]; then
|
||||
if test -t 1 -a -t 2; then ERL="-elixir ansi_enabled true $ERL"; fi
|
||||
fi
|
||||
|
||||
# One MAY change ERTS_BIN= but you MUST NOT change
|
||||
# ERTS_BIN=$ERTS_BIN as it is handled by Elixir releases.
|
||||
ERTS_BIN=
|
||||
|
||||
+1
-5
@@ -1,10 +1,6 @@
|
||||
@echo off
|
||||
|
||||
:: SPDX-License-Identifier: Apache-2.0
|
||||
:: SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
:: SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set ELIXIR_VERSION=1.20.4
|
||||
set ELIXIR_VERSION=1.18.5
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# -eq 0 ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
@echo off
|
||||
|
||||
:: SPDX-License-Identifier: Apache-2.0
|
||||
:: SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
:: SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
setlocal
|
||||
set argc=0
|
||||
for %%A in (%*) do (
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
@echo off
|
||||
|
||||
:: SPDX-License-Identifier: Apache-2.0
|
||||
:: SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
:: SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
setlocal
|
||||
if /I ""%1""==""--help"" goto documentation
|
||||
if /I ""%1""==""-h"" goto documentation
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
#!/usr/bin/env elixir
|
||||
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
Mix.CLI.main()
|
||||
|
||||
@@ -1,7 +1,2 @@
|
||||
@echo off
|
||||
|
||||
:: SPDX-License-Identifier: Apache-2.0
|
||||
:: SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
:: SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
call "%~dp0\elixir.bat" "%~dp0\mix" %*
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
+6
-27
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule EEx.SyntaxError do
|
||||
defexception [:file, :line, :column, :snippet, message: "syntax error"]
|
||||
|
||||
@@ -118,19 +114,6 @@ defmodule EEx do
|
||||
| {:expr | :start_expr | :middle_expr | :end_expr, marker, charlist, metadata}
|
||||
| {:eof, metadata}
|
||||
|
||||
@type tokenize_opt ::
|
||||
{:file, binary()}
|
||||
| {:line, line}
|
||||
| {:column, column}
|
||||
| {:indentation, non_neg_integer}
|
||||
| {:trim, boolean()}
|
||||
|
||||
@type compile_opt ::
|
||||
tokenize_opt
|
||||
| {:engine, module()}
|
||||
| {:parser_options, Code.parser_opts()}
|
||||
| {atom(), term()}
|
||||
|
||||
@doc """
|
||||
Generates a function definition from the given string.
|
||||
|
||||
@@ -141,7 +124,6 @@ defmodule EEx do
|
||||
template.
|
||||
|
||||
The supported `options` are described [in the module docs](#module-options).
|
||||
Additional options are passed to the underlying engine.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -234,11 +216,9 @@ defmodule EEx do
|
||||
"3"
|
||||
|
||||
"""
|
||||
@spec compile_string(String.t(), [compile_opt]) :: Macro.t()
|
||||
@spec compile_string(String.t(), keyword) :: Macro.t()
|
||||
def compile_string(source, options \\ []) when is_binary(source) and is_list(options) do
|
||||
tokenize_opts = Keyword.take(options, [:file, :line, :column, :indentation, :trim])
|
||||
|
||||
case tokenize(source, tokenize_opts) do
|
||||
case tokenize(source, options) do
|
||||
{:ok, tokens} ->
|
||||
EEx.Compiler.compile(tokens, source, options)
|
||||
|
||||
@@ -275,7 +255,7 @@ defmodule EEx do
|
||||
#=> "3"
|
||||
|
||||
"""
|
||||
@spec compile_file(Path.t(), [compile_opt]) :: Macro.t()
|
||||
@spec compile_file(Path.t(), keyword) :: Macro.t()
|
||||
def compile_file(filename, options \\ []) when is_list(options) do
|
||||
filename = IO.chardata_to_string(filename)
|
||||
options = Keyword.merge([file: filename, line: 1], options)
|
||||
@@ -293,7 +273,7 @@ defmodule EEx do
|
||||
"foo baz"
|
||||
|
||||
"""
|
||||
@spec eval_string(String.t(), keyword, [compile_opt]) :: term()
|
||||
@spec eval_string(String.t(), keyword, keyword) :: String.t()
|
||||
def eval_string(source, bindings \\ [], options \\ [])
|
||||
when is_binary(source) and is_list(bindings) and is_list(options) do
|
||||
compiled = compile_string(source, options)
|
||||
@@ -315,7 +295,7 @@ defmodule EEx do
|
||||
#=> "foo baz"
|
||||
|
||||
"""
|
||||
@spec eval_file(Path.t(), keyword, [compile_opt]) :: String.t()
|
||||
@spec eval_file(Path.t(), keyword, keyword) :: String.t()
|
||||
def eval_file(filename, bindings \\ [], options \\ [])
|
||||
when is_list(bindings) and is_list(options) do
|
||||
filename = IO.chardata_to_string(filename)
|
||||
@@ -355,7 +335,7 @@ defmodule EEx do
|
||||
Note new tokens may be added in the future.
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec tokenize([char()] | String.t(), [tokenize_opt]) ::
|
||||
@spec tokenize([char()] | String.t(), opts :: keyword) ::
|
||||
{:ok, [token()]} | {:error, String.t(), metadata()}
|
||||
def tokenize(contents, opts \\ []) do
|
||||
EEx.Compiler.tokenize(contents, opts)
|
||||
@@ -364,7 +344,6 @@ defmodule EEx do
|
||||
### Helpers
|
||||
|
||||
defp do_eval(compiled, bindings, options) do
|
||||
options = Keyword.take(options, [:file, :line, :module, :prune_binding])
|
||||
{result, _} = Code.eval_quoted(compiled, bindings, options)
|
||||
result
|
||||
end
|
||||
|
||||
+15
-32
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule EEx.Compiler do
|
||||
@moduledoc false
|
||||
|
||||
@@ -96,7 +92,7 @@ defmodule EEx.Compiler do
|
||||
"unexpected beginning of EEx tag \"<%#{marker}\" on \"<%#{marker}#{expr}%>\", " <>
|
||||
"please remove \"#{marker}\""
|
||||
|
||||
IO.warn(message, file: state.file, line: line, column: column)
|
||||
:elixir_errors.erl_warn({line, column}, state.file, message)
|
||||
~c""
|
||||
else
|
||||
marker
|
||||
@@ -303,8 +299,8 @@ defmodule EEx.Compiler do
|
||||
file: file,
|
||||
source: source,
|
||||
line: line,
|
||||
quoted: %{},
|
||||
parser_options: [indentation: indentation] ++ parser_options,
|
||||
quoted: [],
|
||||
parser_options: parser_options,
|
||||
indentation: indentation
|
||||
}
|
||||
|
||||
@@ -347,7 +343,7 @@ defmodule EEx.Compiler do
|
||||
state.parser_options
|
||||
|
||||
expr = Code.string_to_quoted!(chars, options)
|
||||
buffer = handle_expr(buffer, mark, expr, meta, state)
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), expr)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -366,17 +362,17 @@ defmodule EEx.Compiler do
|
||||
rest,
|
||||
state.engine.handle_begin(buffer),
|
||||
[{contents, start_line, start_column} | scope],
|
||||
%{state | quoted: %{}, line: line}
|
||||
%{state | quoted: [], line: line}
|
||||
)
|
||||
|
||||
if mark == ~c"" and not match?({:=, _, [_, _]}, contents) do
|
||||
message =
|
||||
"the contents of this expression won't be output unless the EEx block starts with \"<%=\""
|
||||
|
||||
IO.warn(message, file: state.file, line: meta.line, column: meta.column)
|
||||
:elixir_errors.erl_warn({meta.line, meta.column}, state.file, message)
|
||||
end
|
||||
|
||||
buffer = handle_expr(buffer, mark, contents, meta, state)
|
||||
buffer = state.engine.handle_expr(buffer, IO.chardata_to_string(mark), contents)
|
||||
generate_buffer(rest, buffer, scope, state)
|
||||
end
|
||||
|
||||
@@ -410,7 +406,7 @@ defmodule EEx.Compiler do
|
||||
) do
|
||||
{wrapped, state} = wrap_expr(current, meta.line, buffer, chars, state)
|
||||
options = [file: state.file, line: line, column: column] ++ state.parser_options
|
||||
tuples = Code.string_to_quoted!(:lists.flatten(wrapped), options)
|
||||
tuples = Code.string_to_quoted!(wrapped, options)
|
||||
buffer = insert_quoted(tuples, state.quoted)
|
||||
{buffer, rest}
|
||||
end
|
||||
@@ -426,7 +422,7 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp generate_buffer([{:eof, _meta}], _buffer, [{content, line, column} | _scope], state) do
|
||||
message = "expected a closing '<% end %>' for block expression in EEx"
|
||||
expr_meta = non_whitespace_meta(:lists.flatten(content), line, column, state)
|
||||
expr_meta = non_whitespace_meta(content, line, column, state)
|
||||
syntax_error!(message, expr_meta, state)
|
||||
end
|
||||
|
||||
@@ -443,10 +439,10 @@ defmodule EEx.Compiler do
|
||||
|
||||
defp wrap_expr(current, line, buffer, chars, state) do
|
||||
new_lines = List.duplicate(?\n, line - state.line)
|
||||
key = map_size(state.quoted)
|
||||
placeholder = [~c"__EEX__(", Integer.to_charlist(key), ~c");"]
|
||||
count = [current, placeholder, new_lines, chars]
|
||||
new_state = %{state | quoted: Map.put(state.quoted, key, state.engine.handle_end(buffer))}
|
||||
key = length(state.quoted)
|
||||
placeholder = ~c"__EEX__(" ++ Integer.to_charlist(key) ++ ~c");"
|
||||
count = current ++ placeholder ++ new_lines ++ chars
|
||||
new_state = %{state | quoted: [{key, state.engine.handle_end(buffer)} | state.quoted]}
|
||||
|
||||
{count, new_state}
|
||||
end
|
||||
@@ -479,7 +475,8 @@ defmodule EEx.Compiler do
|
||||
# Changes placeholder to real expression
|
||||
|
||||
defp insert_quoted({:__EEX__, _, [key]}, quoted) do
|
||||
Map.fetch!(quoted, key)
|
||||
{^key, value} = List.keyfind(quoted, key, 0)
|
||||
value
|
||||
end
|
||||
|
||||
defp insert_quoted({left, line, right}, quoted) do
|
||||
@@ -512,20 +509,6 @@ defmodule EEx.Compiler do
|
||||
column: meta.column
|
||||
end
|
||||
|
||||
defp handle_expr(buffer, mark, expr, meta, state) do
|
||||
state.engine.handle_expr(buffer, IO.chardata_to_string(mark), expr)
|
||||
rescue
|
||||
e in EEx.SyntaxError ->
|
||||
reraise %{
|
||||
e
|
||||
| file: e.file || state.file,
|
||||
line: e.line || meta.line,
|
||||
column: e.column || meta.column,
|
||||
snippet: e.snippet || code_snippet(state.source, state.indentation, meta)
|
||||
},
|
||||
__STACKTRACE__
|
||||
end
|
||||
|
||||
defp code_snippet(source, indentation, meta) do
|
||||
line_start = max(meta.line - 3, 1)
|
||||
line_end = meta.line
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule EEx.Engine do
|
||||
@moduledoc ~S"""
|
||||
Basic EEx engine that ships with Elixir.
|
||||
@@ -17,10 +13,6 @@ defmodule EEx.Engine do
|
||||
@doc """
|
||||
Called at the beginning of every template.
|
||||
|
||||
It receives the options during compilation, including the
|
||||
ones managed by EEx, such as `:line` and `:file`, as well
|
||||
as custom engine options.
|
||||
|
||||
It must return the initial state.
|
||||
"""
|
||||
@callback init(opts :: keyword) :: state
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule EEx.SmartEngine do
|
||||
@moduledoc """
|
||||
The default engine used by EEx.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule EEx.MixProject do
|
||||
use Mix.Project
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
Code.require_file("../test_helper.exs", __DIR__)
|
||||
|
||||
defmodule EEx.SmartEngineTest do
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
Code.require_file("../test_helper.exs", __DIR__)
|
||||
|
||||
defmodule EEx.TokenizerTest do
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
Code.require_file("test_helper.exs", __DIR__)
|
||||
|
||||
require EEx
|
||||
@@ -502,59 +498,6 @@ defmodule EExTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "from Elixir parser" do
|
||||
line = __ENV__.line + 6
|
||||
|
||||
message =
|
||||
assert_raise TokenMissingError, fn ->
|
||||
EEx.compile_string(
|
||||
"""
|
||||
<li>
|
||||
<strong>Some:</strong>
|
||||
<%= true && @some[ %>
|
||||
</li>
|
||||
""",
|
||||
file: __ENV__.file,
|
||||
line: line,
|
||||
indentation: 12
|
||||
)
|
||||
end
|
||||
|
||||
assert message |> Exception.message() |> strip_ansi() =~ """
|
||||
│
|
||||
514 │ true && @some[\s
|
||||
│ │ └ missing closing delimiter (expected "]")
|
||||
│ └ unclosed delimiter
|
||||
"""
|
||||
end
|
||||
|
||||
test "from Elixir parser with line breaks" do
|
||||
line = __ENV__.line + 6
|
||||
|
||||
message =
|
||||
assert_raise TokenMissingError, fn ->
|
||||
EEx.compile_string(
|
||||
"""
|
||||
<li>
|
||||
<strong>Some:</strong>
|
||||
<%= true &&
|
||||
@some[ %>
|
||||
</li>
|
||||
""",
|
||||
file: __ENV__.file,
|
||||
line: line,
|
||||
indentation: 12
|
||||
)
|
||||
end
|
||||
|
||||
assert message |> Exception.message() |> strip_ansi() =~ """
|
||||
│
|
||||
#{line + 3} │ @some[\s
|
||||
│ │ └ missing closing delimiter (expected "]")
|
||||
│ └ unclosed delimiter
|
||||
"""
|
||||
end
|
||||
|
||||
test "honor line numbers" do
|
||||
assert_raise EEx.SyntaxError,
|
||||
"nofile:100:6: expected closing '%>' for EEx expression",
|
||||
@@ -575,18 +518,6 @@ defmodule EExTest do
|
||||
EEx.compile_string("foo <%= bar", file: "my_file.eex")
|
||||
end
|
||||
end
|
||||
|
||||
test "unsupported marker error carries template location metadata" do
|
||||
error =
|
||||
assert_raise EEx.SyntaxError, fn ->
|
||||
EEx.compile_string("<%/ true %>", file: "sample.eex", line: 7)
|
||||
end
|
||||
|
||||
assert error.file == "sample.eex"
|
||||
assert error.line == 7
|
||||
assert error.column == 1
|
||||
assert Exception.message(error) =~ "sample.eex:7:1:"
|
||||
end
|
||||
end
|
||||
|
||||
describe "warnings" do
|
||||
@@ -929,13 +860,13 @@ defmodule EExTest do
|
||||
file = to_charlist(Path.relative_to_cwd(__ENV__.file))
|
||||
|
||||
assert EExTest.Compiled.before_compile() ==
|
||||
{11, {EExTest.Compiled, :before_compile, 0, [file: file, line: 11]}}
|
||||
{7, {EExTest.Compiled, :before_compile, 0, [file: file, line: 7]}}
|
||||
|
||||
assert EExTest.Compiled.after_compile() ==
|
||||
{25, {EExTest.Compiled, :after_compile, 0, [file: file, line: 25]}}
|
||||
{21, {EExTest.Compiled, :after_compile, 0, [file: file, line: 21]}}
|
||||
|
||||
assert EExTest.Compiled.unknown() ==
|
||||
{30, {EExTest.Compiled, :unknown, 0, [file: ~c"unknown", line: 30]}}
|
||||
{26, {EExTest.Compiled, :unknown, 0, [file: ~c"unknown", line: 26]}}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1013,12 +944,6 @@ defmodule EExTest do
|
||||
end
|
||||
end
|
||||
|
||||
@strip_ansi [IO.ANSI.green(), IO.ANSI.red(), IO.ANSI.reset()]
|
||||
|
||||
defp strip_ansi(doc) do
|
||||
String.replace(doc, @strip_ansi, "")
|
||||
end
|
||||
|
||||
defp assert_eval(expected, actual, binding \\ [], opts \\ []) do
|
||||
opts = Keyword.merge([file: __ENV__.file, engine: opts[:engine] || EEx.Engine], opts)
|
||||
result = EEx.eval_string(actual, binding, opts)
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
{line_exclude, line_include} =
|
||||
if line = System.get_env("LINE"), do: {[:test], [line: line]}, else: {[], []}
|
||||
|
||||
Code.require_file("../../elixir/scripts/cover_record.exs", __DIR__)
|
||||
CoverageRecorder.maybe_record("eex")
|
||||
|
||||
maybe_seed_opt = if seed = System.get_env("SEED"), do: [seed: String.to_integer(seed)], else: []
|
||||
|
||||
ex_unit_opts =
|
||||
[
|
||||
trace: !!System.get_env("TRACE"),
|
||||
include: line_include,
|
||||
exclude: line_exclude
|
||||
] ++ maybe_seed_opt
|
||||
|
||||
ExUnit.start(ex_unit_opts)
|
||||
ExUnit.start(
|
||||
trace: !!System.get_env("TRACE"),
|
||||
include: line_include,
|
||||
exclude: line_exclude
|
||||
)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
%% SPDX-License-Identifier: Apache-2.0
|
||||
%% SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
%% SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
{'src/*', [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
|
||||
+21
-110
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Access do
|
||||
@moduledoc """
|
||||
Key-based access to data structures.
|
||||
@@ -517,8 +513,8 @@ defmodule Access do
|
||||
An error is raised if the accessed structure is not a map or a struct:
|
||||
|
||||
iex> get_in([], [Access.key(:foo)])
|
||||
** (BadMapError) expected a map, got:
|
||||
...
|
||||
** (BadMapError) expected a map, got: []
|
||||
|
||||
"""
|
||||
@spec key(key, term) :: access_fun(data :: struct | map, current_value :: term)
|
||||
def key(key, default \\ nil) do
|
||||
@@ -556,8 +552,7 @@ defmodule Access do
|
||||
iex> pop_in(map, [Access.key!(:user), Access.key!(:name)])
|
||||
{"john", %{user: %{}}}
|
||||
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
|
||||
** (KeyError) key :unknown not found in:
|
||||
...
|
||||
** (KeyError) key :unknown not found in: %{name: \"john\"}
|
||||
|
||||
The examples above could be partially written as:
|
||||
|
||||
@@ -812,7 +807,7 @@ defmodule Access do
|
||||
iex> get_in([:a, :b, :c], [Access.at!(2)])
|
||||
:c
|
||||
iex> get_in([:a, :b, :c], [Access.at!(3)])
|
||||
** (Enum.OutOfBoundsError) out of bounds error at position 3 when traversing enumerable [:a, :b, :c]
|
||||
** (Enum.OutOfBoundsError) out of bounds error
|
||||
|
||||
"""
|
||||
@doc since: "1.11.0"
|
||||
@@ -824,14 +819,12 @@ defmodule Access do
|
||||
defp at!(:get, data, index, next) when is_list(data) do
|
||||
case Enum.fetch(data, index) do
|
||||
{:ok, value} -> next.(value)
|
||||
:error -> raise Enum.OutOfBoundsError, index: index, enumerable: data
|
||||
:error -> raise Enum.OutOfBoundsError
|
||||
end
|
||||
end
|
||||
|
||||
defp at!(:get_and_update, data, index, next) when is_list(data) do
|
||||
get_and_update_at(data, index, next, [], fn ->
|
||||
raise Enum.OutOfBoundsError, index: index, enumerable: data
|
||||
end)
|
||||
get_and_update_at(data, index, next, [], fn -> raise Enum.OutOfBoundsError end)
|
||||
end
|
||||
|
||||
defp at!(_op, data, _index, _next) do
|
||||
@@ -873,6 +866,11 @@ defmodule Access do
|
||||
...> end)
|
||||
{[], [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
An error is raised if the predicate is not a function or is of the incorrect arity:
|
||||
|
||||
iex> get_in([], [Access.filter(5)])
|
||||
** (FunctionClauseError) no function clause matching in Access.filter/1
|
||||
|
||||
An error is raised if the accessed structure is not a list:
|
||||
|
||||
iex> get_in(%{}, [Access.filter(fn a -> a == 10 end)])
|
||||
@@ -886,7 +884,7 @@ defmodule Access do
|
||||
end
|
||||
|
||||
defp filter(:get, data, func, next) when is_list(data) do
|
||||
for elem <- data, func.(elem), do: next.(elem)
|
||||
data |> Enum.filter(func) |> Enum.map(next)
|
||||
end
|
||||
|
||||
defp filter(:get_and_update, data, func, next) when is_list(data) do
|
||||
@@ -994,93 +992,6 @@ defmodule Access do
|
||||
raise ArgumentError, "Access.slice/1 expected a list, got: #{inspect(data)}"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a function that accesses all values in a map or a keyword list.
|
||||
|
||||
The returned function is typically passed as an accessor to `Kernel.get_in/2`,
|
||||
`Kernel.get_and_update_in/3`, and friends.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
|
||||
iex> get_in(users, [Access.values(), :age]) |> Enum.sort()
|
||||
[23, 27]
|
||||
iex> update_in(users, [Access.values(), :age], fn age -> age + 1 end)
|
||||
%{"john" => %{age: 28}, "meg" => %{age: 24}}
|
||||
iex> put_in(users, [Access.values(), :planet], "Earth")
|
||||
%{"john" => %{age: 27, planet: "Earth"}, "meg" => %{age: 23, planet: "Earth"}}
|
||||
|
||||
Values in keyword lists can be accessed as well:
|
||||
|
||||
iex> users = [john: %{age: 27}, meg: %{age: 23}]
|
||||
iex> get_and_update_in(users, [Access.values(), :age], fn age -> {age, age + 1} end)
|
||||
{[27, 23], [john: %{age: 28}, meg: %{age: 24}]}
|
||||
|
||||
By returning `:pop` from an accessor function, you can remove the accessed key and value
|
||||
from the map or keyword list:
|
||||
|
||||
iex> require Integer
|
||||
iex> numbers = [one: 1, two: 2, three: 3, four: 4]
|
||||
iex> get_and_update_in(numbers, [Access.values()], fn num ->
|
||||
...> if Integer.is_even(num), do: :pop, else: {num, to_string(num)}
|
||||
...> end)
|
||||
{[1, 2, 3, 4], [one: "1", three: "3"]}
|
||||
|
||||
An error is raised if the accessed structure is not a map nor a keyword list:
|
||||
|
||||
iex> get_in([1, 2, 3], [Access.values()])
|
||||
** (RuntimeError) Access.values/0 expected a map or a keyword list, got: [1, 2, 3]
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec values() :: Access.access_fun(data :: map() | keyword(), current_value :: list())
|
||||
def values do
|
||||
&values/3
|
||||
end
|
||||
|
||||
defp values(:get, data = %{}, next) do
|
||||
Enum.map(data, fn {_key, value} -> next.(value) end)
|
||||
end
|
||||
|
||||
defp values(:get_and_update, data = %{}, next) do
|
||||
{reverse_gets, updated_data} =
|
||||
Enum.reduce(data, {[], %{}}, fn {key, value}, {gets, data_acc} ->
|
||||
case next.(value) do
|
||||
{get, update} -> {[get | gets], Map.put(data_acc, key, update)}
|
||||
:pop -> {[value | gets], data_acc}
|
||||
end
|
||||
end)
|
||||
|
||||
{Enum.reverse(reverse_gets), updated_data}
|
||||
end
|
||||
|
||||
defp values(op, data = [], next) do
|
||||
values_keyword(op, data, next)
|
||||
end
|
||||
|
||||
defp values(op, data = [{key, _value} | _tail], next) when is_atom(key) do
|
||||
values_keyword(op, data, next)
|
||||
end
|
||||
|
||||
defp values(_op, data, _next) do
|
||||
raise "Access.values/0 expected a map or a keyword list, got: #{inspect(data)}"
|
||||
end
|
||||
|
||||
defp values_keyword(:get, data, next) do
|
||||
Enum.map(data, fn {key, value} when is_atom(key) -> next.(value) end)
|
||||
end
|
||||
|
||||
defp values_keyword(:get_and_update, data, next) do
|
||||
{reverse_gets, reverse_updated_data} =
|
||||
Enum.reduce(data, {[], []}, fn {key, value}, {gets, data_acc} when is_atom(key) ->
|
||||
case next.(value) do
|
||||
{get, update} -> {[get | gets], [{key, update} | data_acc]}
|
||||
:pop -> {[value | gets], data_acc}
|
||||
end
|
||||
end)
|
||||
|
||||
{Enum.reverse(reverse_gets), Enum.reverse(reverse_updated_data)}
|
||||
end
|
||||
|
||||
defp normalize_range(%Range{first: first, last: last, step: step}, list)
|
||||
when first < 0 or last < 0 do
|
||||
count = length(list)
|
||||
@@ -1091,11 +1002,6 @@ defmodule Access do
|
||||
|
||||
defp normalize_range(range, _list), do: range
|
||||
|
||||
defp get_and_update_slice(rest, %Range{last: last}, _next, updates, gets, index)
|
||||
when index > last do
|
||||
{:lists.reverse(gets), :lists.reverse(updates, rest)}
|
||||
end
|
||||
|
||||
defp get_and_update_slice([head | rest], range, next, updates, gets, index) do
|
||||
if index in range do
|
||||
case next.(head) do
|
||||
@@ -1132,9 +1038,9 @@ defmodule Access do
|
||||
iex> list = [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]
|
||||
iex> get_in(list, [Access.find(&(&1.salary > 20)), :name])
|
||||
"francine"
|
||||
iex> get_and_update_in(list, [Access.find(&(&1.salary <= 40)), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
iex> get_and_update_in(list, [Access.find(&(&1.salary <= 40)), :name], fn prev ->
|
||||
...> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
`find/1` can also be used to pop the first found element out of a list or
|
||||
@@ -1154,9 +1060,14 @@ defmodule Access do
|
||||
...> end)
|
||||
{nil, [%{name: "john", salary: 10}, %{name: "francine", salary: 30}]}
|
||||
|
||||
An error is raised if the predicate is not a function or is of the incorrect arity:
|
||||
|
||||
iex> get_in([], [Access.find(5)])
|
||||
** (FunctionClauseError) no function clause matching in Access.find/1
|
||||
|
||||
An error is raised if the accessed structure is not a list:
|
||||
|
||||
iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
|
||||
iex> get_in(%{}, [Access.find(fn a -> a == 10 end)])
|
||||
** (RuntimeError) Access.find/1 expected a list, got: %{}
|
||||
"""
|
||||
@doc since: "1.17.0"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Agent do
|
||||
@moduledoc """
|
||||
Agents are a simple abstraction around state.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Agent.Server do
|
||||
@moduledoc false
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Application do
|
||||
@moduledoc """
|
||||
A module for working with applications and defining application callbacks.
|
||||
@@ -247,7 +243,7 @@ defmodule Application do
|
||||
invoked if it hasn't been done yet. Then, it checks if the dependencies listed
|
||||
in the `applications` key of the resource file are already started. Having at
|
||||
least one dependency not started is an error condition. Functions like
|
||||
`ensure_all_started/1` take care of starting an application and all of its
|
||||
`ensure_all_started/1` takes care of starting an application and all of its
|
||||
dependencies for you.
|
||||
|
||||
If the application does not have a callback module configured, starting is
|
||||
@@ -507,7 +503,7 @@ defmodule Application do
|
||||
of all loaded applications. Returns `nil` if
|
||||
the module is not listed in any application spec.
|
||||
"""
|
||||
@spec get_application(module) :: app | nil
|
||||
@spec get_application(atom) :: atom | nil
|
||||
def get_application(module) when is_atom(module) do
|
||||
case :application.get_application(module) do
|
||||
{:ok, app} -> app
|
||||
@@ -680,13 +676,13 @@ defmodule Application do
|
||||
## Examples
|
||||
|
||||
`get_env/3` is commonly used to read the configuration of your OTP applications.
|
||||
Since Mix configurations are commonly used to configure applications (including
|
||||
your dependencies), we will use this as a point of illustration.
|
||||
Since Mix configurations are commonly used to configure applications, we will use
|
||||
this as a point of illustration.
|
||||
|
||||
Consider a new application `:my_app`. `:my_app` contains a database engine which
|
||||
supports a pool of databases. The database engine needs to know the configuration for
|
||||
each of those databases, and that configuration is supplied by key-value pairs in
|
||||
environment of `:my_app`. For example, your `config/runtime.exs` file might have:
|
||||
environment of `:my_app`.
|
||||
|
||||
config :my_app, Databases.RepoOne,
|
||||
# A database configuration
|
||||
@@ -696,7 +692,7 @@ defmodule Application do
|
||||
config :my_app, Databases.RepoTwo,
|
||||
# Another database configuration (for the same OTP app)
|
||||
ip: "localhost",
|
||||
port: 20_717
|
||||
port: 20717
|
||||
|
||||
config :my_app, my_app_databases: [Databases.RepoOne, Databases.RepoTwo]
|
||||
|
||||
@@ -714,11 +710,6 @@ defmodule Application do
|
||||
config = Application.get_env(:my_app, Databases.RepoOne)
|
||||
config[:ip]
|
||||
|
||||
The sample `config/runtime.exs` above could be used both for `:my_app` to
|
||||
configure itself but also to allow any application that depends on `:my_app`
|
||||
to configure how it works. However, one should keep in mind the caveats described
|
||||
in the `Application` module documentation: the application environment is global
|
||||
state which should be avoided if possible.
|
||||
"""
|
||||
@spec get_env(app, key, value) :: value
|
||||
def get_env(app, key, default \\ nil) when is_atom(app) do
|
||||
@@ -819,7 +810,7 @@ defmodule Application do
|
||||
stick after the application is loaded and also on application reload.
|
||||
"""
|
||||
@spec put_env(app, key, value, timeout: timeout, persistent: boolean) :: :ok
|
||||
def put_env(app, key, value, opts \\ []) when is_atom(app) and is_list(opts) do
|
||||
def put_env(app, key, value, opts \\ []) when is_atom(app) do
|
||||
maybe_warn_on_app_env_key(app, key)
|
||||
:application.set_env(app, key, value, opts)
|
||||
end
|
||||
@@ -861,7 +852,7 @@ defmodule Application do
|
||||
It receives the same options as `put_env/4`. Returns `:ok`.
|
||||
"""
|
||||
@spec delete_env(app, key, timeout: timeout, persistent: boolean) :: :ok
|
||||
def delete_env(app, key, opts \\ []) when is_atom(app) and is_list(opts) do
|
||||
def delete_env(app, key, opts \\ []) when is_atom(app) do
|
||||
maybe_warn_on_app_env_key(app, key)
|
||||
:application.unset_env(app, key, opts)
|
||||
end
|
||||
@@ -908,16 +899,17 @@ defmodule Application do
|
||||
@doc """
|
||||
Ensures the given `app` or `apps` and their child applications are started.
|
||||
|
||||
The second argument is either the `t:restart_type/0` (for consistency with
|
||||
The second argument is either the `t:restart_type/1` (for consistency with
|
||||
`start/2`) or a keyword list.
|
||||
|
||||
## Options
|
||||
|
||||
* `:type` - if the application should be started `:temporary` (default),
|
||||
`:permanent`, or `:transient`. See `t:restart_type/0` for more information.
|
||||
`:permanent`, or `:transient`. See `t:restart_type/1` for more information.
|
||||
|
||||
* `:mode` - (since v1.15.0) if the applications should be started serially
|
||||
(`:serial`, default) or concurrently (`:concurrent`).
|
||||
(`:serial`, default) or concurrently (`:concurrent`). This option requires
|
||||
Erlang/OTP 26+.
|
||||
|
||||
"""
|
||||
@spec ensure_all_started(app | [app], type: restart_type(), mode: :serial | :concurrent) ::
|
||||
@@ -926,11 +918,11 @@ defmodule Application do
|
||||
{:ok, [app]} | {:error, term}
|
||||
def ensure_all_started(app_or_apps, type_or_opts \\ [])
|
||||
|
||||
def ensure_all_started(app_or_apps, type) when is_atom(type) do
|
||||
ensure_all_started(app_or_apps, type: type)
|
||||
def ensure_all_started(app, type) when is_atom(type) do
|
||||
ensure_all_started(app, type: type)
|
||||
end
|
||||
|
||||
def ensure_all_started(app, opts) when is_atom(app) and is_list(opts) do
|
||||
def ensure_all_started(app, opts) when is_atom(app) do
|
||||
ensure_all_started([app], opts)
|
||||
end
|
||||
|
||||
@@ -938,7 +930,18 @@ defmodule Application do
|
||||
|
||||
def ensure_all_started(apps, opts) when is_list(apps) and is_list(opts) do
|
||||
opts = Keyword.validate!(opts, type: :temporary, mode: :serial)
|
||||
:application.ensure_all_started(apps, opts[:type], opts[:mode])
|
||||
|
||||
if function_exported?(:application, :ensure_all_started, 3) do
|
||||
:application.ensure_all_started(apps, opts[:type], opts[:mode])
|
||||
else
|
||||
# TODO: Remove this clause when we require Erlang/OTP 26+
|
||||
Enum.reduce_while(apps, {:ok, []}, fn app, {:ok, acc} ->
|
||||
case :application.ensure_all_started(app, opts[:type]) do
|
||||
{:ok, apps} -> {:cont, {:ok, apps ++ acc}}
|
||||
{:error, e} -> {:halt, {:error, e}}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1061,8 +1064,7 @@ defmodule Application do
|
||||
Returns a list with information about the applications which are currently running.
|
||||
"""
|
||||
@spec started_applications(timeout) :: [{app, description :: charlist(), vsn :: charlist()}]
|
||||
def started_applications(timeout \\ 5000)
|
||||
when timeout == :infinity or (is_integer(timeout) and timeout >= 0) do
|
||||
def started_applications(timeout \\ 5000) do
|
||||
:application.which_applications(timeout)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Atom do
|
||||
@moduledoc """
|
||||
Atoms are constants whose values are their own name.
|
||||
|
||||
+150
-807
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Behaviour do
|
||||
@moduledoc """
|
||||
Mechanism for handling behaviours.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Bitwise do
|
||||
@moduledoc """
|
||||
A set of functions that perform calculations on bits.
|
||||
|
||||
+23
-111
@@ -1,10 +1,4 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Calendar do
|
||||
@strftime_max_width 1024
|
||||
|
||||
@moduledoc """
|
||||
This module defines the responsibilities for working with
|
||||
calendars, dates, times and datetimes in Elixir.
|
||||
@@ -60,20 +54,9 @@ defmodule Calendar do
|
||||
@typedoc """
|
||||
Microseconds with stored precision.
|
||||
|
||||
`value` always represents the total value in microseconds.
|
||||
|
||||
The `precision` represents the number of digits that must be used when
|
||||
The precision represents the number of digits that must be used when
|
||||
representing the microseconds to external format. If the precision is `0`,
|
||||
it means microseconds must be skipped. If the precision is `6`, it means
|
||||
that `value` represents exactly the number of microseconds to be used.
|
||||
|
||||
## Examples
|
||||
|
||||
* `{0, 0}` means no microseconds.
|
||||
* `{1, 6}` means 1µs.
|
||||
* `{1000, 6}` means 1000µs (which is 1ms but measured at the microsecond precision).
|
||||
* `{1000, 3}` means 1ms (which is measured at the millisecond precision).
|
||||
|
||||
it means microseconds must be skipped.
|
||||
"""
|
||||
@type microsecond :: {value :: non_neg_integer, precision :: non_neg_integer}
|
||||
|
||||
@@ -164,22 +147,6 @@ defmodule Calendar do
|
||||
"""
|
||||
@type time_zone_database :: module()
|
||||
|
||||
@typedoc """
|
||||
Options for formatting dates and times with `strftime/3`.
|
||||
"""
|
||||
@type strftime_opts :: [
|
||||
preferred_datetime: String.t(),
|
||||
preferred_date: String.t(),
|
||||
preferred_time: String.t(),
|
||||
am_pm_names: (:am | :pm -> String.t()) | (:am | :pm, map() -> String.t()),
|
||||
month_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
|
||||
abbreviated_month_names:
|
||||
(pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
|
||||
day_of_week_names: (pos_integer() -> String.t()) | (pos_integer(), map() -> String.t()),
|
||||
abbreviated_day_of_week_names:
|
||||
(pos_integer() -> String.t()) | (pos_integer(), map() -> String.t())
|
||||
]
|
||||
|
||||
@doc """
|
||||
Returns how many days there are in the given month of the given year.
|
||||
"""
|
||||
@@ -205,15 +172,6 @@ defmodule Calendar do
|
||||
`starting_on` represents the starting day of the week. All
|
||||
calendars must support at least the `:default` value. They may
|
||||
also support other values representing their days of the week.
|
||||
|
||||
The value of `day_of_week` is an ordinal number meaning that a
|
||||
value of `1` is defined to mean "first day of the week". It is
|
||||
specifically not defined to mean `1` is `Monday`.
|
||||
|
||||
It is a requirement that `first_day_of_week` is less than `last_day_of_week`
|
||||
and that `day_of_week` must be within that range. Therefore it can be said
|
||||
that `day_of_week in first_day_of_week..last_day_of_week//1` must be
|
||||
`true` for all values of `day_of_week`.
|
||||
"""
|
||||
@callback day_of_week(year, month, day, starting_on :: :default | atom) ::
|
||||
{day_of_week(), first_day_of_week :: non_neg_integer(),
|
||||
@@ -494,30 +452,25 @@ defmodule Calendar do
|
||||
it can't contain the `%X` format and defaults to `"%H:%M:%S"`
|
||||
if the option is not received
|
||||
|
||||
* `:am_pm_names` - a function that receives either `:am` or `:pm`
|
||||
(and also the datetime if the function is arity/2) and returns
|
||||
* `:am_pm_names` - a function that receives either `:am` or `:pm` and returns
|
||||
the name of the period of the day, if the option is not received it defaults
|
||||
to a function that returns `"am"` and `"pm"`, respectively
|
||||
|
||||
* `:month_names` - a function that receives a number (and also the
|
||||
datetime if the function is arity/2) and returns the name of
|
||||
* `:month_names` - a function that receives a number and returns the name of
|
||||
the corresponding month, if the option is not received it defaults to a
|
||||
function that returns the month names in English
|
||||
|
||||
* `:abbreviated_month_names` - a function that receives a number (and also
|
||||
the datetime if the function is arity/2) and returns the
|
||||
* `:abbreviated_month_names` - a function that receives a number and returns the
|
||||
abbreviated name of the corresponding month, if the option is not received it
|
||||
defaults to a function that returns the abbreviated month names in English
|
||||
|
||||
* `:day_of_week_names` - a function that receives a number and (and also the
|
||||
datetime if the function is arity/2) returns the name of
|
||||
* `:day_of_week_names` - a function that receives a number and returns the name of
|
||||
the corresponding day of week, if the option is not received it defaults to a
|
||||
function that returns the day of week names in English
|
||||
|
||||
* `:abbreviated_day_of_week_names` - a function that receives a number (and also
|
||||
the datetime if the function is arity/2) and returns the abbreviated name of
|
||||
the corresponding day of week, if the option is not received it defaults to a
|
||||
function that returns the abbreviated day of week names in English
|
||||
* `:abbreviated_day_of_week_names` - a function that receives a number and returns
|
||||
the abbreviated name of the corresponding day of week, if the option is not received
|
||||
it defaults to a function that returns the abbreviated day of week names in English
|
||||
|
||||
## Formatting syntax
|
||||
|
||||
@@ -531,7 +484,6 @@ defmodule Calendar do
|
||||
* `%`: indicates the start of a formatted section
|
||||
* `<padding>`: set the padding (see below)
|
||||
* `<width>`: a number indicating the minimum size of the formatted section
|
||||
(maximum #{@strftime_max_width})
|
||||
* `<format>`: the format itself (see below)
|
||||
|
||||
### Accepted padding options
|
||||
@@ -552,7 +504,7 @@ defmodule Calendar do
|
||||
B | Full month name | January
|
||||
c | Preferred date+time representation | 2018-10-17 12:34:56
|
||||
d | Day of the month | 01, 31
|
||||
f | Microseconds (uses its precision for width and padding) | 000000, 999999, 0123
|
||||
f | Microseconds *(does not support width and padding modifiers)* | 000000, 999999, 0123
|
||||
H | Hour using a 24-hour clock | 00, 23
|
||||
I | Hour using a 12-hour clock | 01, 12
|
||||
j | Day of the year | 001, 366
|
||||
@@ -574,12 +526,6 @@ defmodule Calendar do
|
||||
|
||||
Any other character will be interpreted as an invalid format and raise an error.
|
||||
|
||||
### `%f` Microseconds
|
||||
|
||||
`%f` does not support width and padding modifiers. It will be formatted by truncating
|
||||
the microseconds to the precision of the `microseconds` field of the struct, with a
|
||||
minimum precision of 1.
|
||||
|
||||
## Examples
|
||||
|
||||
Without user options:
|
||||
@@ -623,20 +569,9 @@ defmodule Calendar do
|
||||
...>)
|
||||
"серпень"
|
||||
|
||||
Microsecond formatting:
|
||||
|
||||
iex> Calendar.strftime(~U[2019-08-26 13:52:06Z], "%y-%m-%d %H:%M:%S.%f")
|
||||
"19-08-26 13:52:06.0"
|
||||
|
||||
iex> Calendar.strftime(~U[2019-08-26 13:52:06.048Z], "%y-%m-%d %H:%M:%S.%f")
|
||||
"19-08-26 13:52:06.048"
|
||||
|
||||
iex> Calendar.strftime(~U[2019-08-26 13:52:06.048531Z], "%y-%m-%d %H:%M:%S.%f")
|
||||
"19-08-26 13:52:06.048531"
|
||||
|
||||
"""
|
||||
@doc since: "1.11.0"
|
||||
@spec strftime(map(), String.t(), strftime_opts()) :: String.t()
|
||||
@spec strftime(map(), String.t(), keyword()) :: String.t()
|
||||
def strftime(date_or_time_or_datetime, string_format, user_options \\ [])
|
||||
when is_map(date_or_time_or_datetime) and is_binary(string_format) do
|
||||
parse(
|
||||
@@ -670,13 +605,9 @@ defmodule Calendar do
|
||||
end
|
||||
|
||||
defp parse_modifiers(<<digit, rest::binary>>, width, pad, parser_data) when digit in ?0..?9 do
|
||||
width = (width || 0) * 10 + (digit - ?0)
|
||||
new_width = (width || 0) * 10 + (digit - ?0)
|
||||
|
||||
if width > @strftime_max_width do
|
||||
raise ArgumentError, "invalid strftime format: width must be at most #{@strftime_max_width}"
|
||||
end
|
||||
|
||||
parse_modifiers(rest, width, pad, parser_data)
|
||||
parse_modifiers(rest, new_width, pad, parser_data)
|
||||
end
|
||||
|
||||
# set default padding if none was specified
|
||||
@@ -693,12 +624,12 @@ defmodule Calendar do
|
||||
format_modifiers(rest, width, pad, datetime, format_options, acc)
|
||||
end
|
||||
|
||||
defp am_pm(hour, format_options, datetime) when hour > 11 do
|
||||
apply_format(:pm, format_options.am_pm_names, datetime)
|
||||
defp am_pm(hour, format_options) when hour > 11 do
|
||||
format_options.am_pm_names.(:pm)
|
||||
end
|
||||
|
||||
defp am_pm(hour, format_options, datetime) when hour <= 11 do
|
||||
apply_format(:am, format_options.am_pm_names, datetime)
|
||||
defp am_pm(hour, format_options) when hour <= 11 do
|
||||
format_options.am_pm_names.(:am)
|
||||
end
|
||||
|
||||
defp default_pad(format) when format in ~c"aAbBpPZ", do: ?\s
|
||||
@@ -719,7 +650,7 @@ defmodule Calendar do
|
||||
result =
|
||||
datetime
|
||||
|> Date.day_of_week()
|
||||
|> apply_format(format_options.abbreviated_day_of_week_names, datetime)
|
||||
|> format_options.abbreviated_day_of_week_names.()
|
||||
|> pad_leading(width, pad)
|
||||
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
@@ -730,7 +661,7 @@ defmodule Calendar do
|
||||
result =
|
||||
datetime
|
||||
|> Date.day_of_week()
|
||||
|> apply_format(format_options.day_of_week_names, datetime)
|
||||
|> format_options.day_of_week_names.()
|
||||
|> pad_leading(width, pad)
|
||||
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
@@ -740,7 +671,7 @@ defmodule Calendar do
|
||||
defp format_modifiers("b" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result =
|
||||
datetime.month
|
||||
|> apply_format(format_options.abbreviated_month_names, datetime)
|
||||
|> format_options.abbreviated_month_names.()
|
||||
|> pad_leading(width, pad)
|
||||
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
@@ -748,10 +679,7 @@ defmodule Calendar do
|
||||
|
||||
# Full month name
|
||||
defp format_modifiers("B" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result =
|
||||
datetime.month
|
||||
|> apply_format(format_options.month_names, datetime)
|
||||
|> pad_leading(width, pad)
|
||||
result = datetime.month |> format_options.month_names.() |> pad_leading(width, pad)
|
||||
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
end
|
||||
@@ -829,11 +757,7 @@ defmodule Calendar do
|
||||
|
||||
# "AM" or "PM" (noon is "PM", midnight as "AM")
|
||||
defp format_modifiers("p" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result =
|
||||
datetime.hour
|
||||
|> am_pm(format_options, datetime)
|
||||
|> String.upcase()
|
||||
|> pad_leading(width, pad)
|
||||
result = datetime.hour |> am_pm(format_options) |> String.upcase() |> pad_leading(width, pad)
|
||||
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
end
|
||||
@@ -842,7 +766,7 @@ defmodule Calendar do
|
||||
defp format_modifiers("P" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result =
|
||||
datetime.hour
|
||||
|> am_pm(format_options, datetime)
|
||||
|> am_pm(format_options)
|
||||
|> String.downcase()
|
||||
|> pad_leading(width, pad)
|
||||
|
||||
@@ -1008,18 +932,6 @@ defmodule Calendar do
|
||||
defp do_pad_leading(count, padding, acc),
|
||||
do: do_pad_leading(count - 1, padding, [padding | acc])
|
||||
|
||||
defp apply_format(term, formatter, _datetime) when is_function(formatter, 1) do
|
||||
formatter.(term)
|
||||
end
|
||||
|
||||
defp apply_format(term, formatter, datetime) when is_function(formatter, 2) do
|
||||
formatter.(term, datetime)
|
||||
end
|
||||
|
||||
defp apply_format(_term, formatter, _datetime) do
|
||||
raise ArgumentError, "formatter functions must be of arity 1 or 2, got: #{inspect(formatter)}"
|
||||
end
|
||||
|
||||
defp options(user_options) do
|
||||
default_options = %{
|
||||
preferred_date: "%Y-%m-%d",
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Date do
|
||||
@moduledoc """
|
||||
A Date struct and functions.
|
||||
@@ -35,12 +31,11 @@ defmodule Date do
|
||||
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `Date` struct fields. For proper comparison between
|
||||
dates, use the `compare/2`, `after?/2` and `before?/2` functions.
|
||||
The existence of the `compare/2` function in this module also allows
|
||||
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
|
||||
maximum date of an `Enum`. For example:
|
||||
dates, use the `compare/2` function. The existence of the `compare/2`
|
||||
function in this module also allows using `Enum.min/2` and `Enum.max/2`
|
||||
functions to get the minimum and maximum date of an `Enum`. For example:
|
||||
|
||||
iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]], Date)
|
||||
iex> Enum.min([~D[2017-03-31], ~D[2017-04-01]], Date)
|
||||
~D[2017-03-31]
|
||||
|
||||
## Using epochs
|
||||
@@ -53,7 +48,7 @@ defmodule Date do
|
||||
iex> Date.diff(~D[2010-04-17], ~D[1970-01-01])
|
||||
14716
|
||||
|
||||
iex> Date.add(~D[1970-01-01], 14_716)
|
||||
iex> Date.add(~D[1970-01-01], 14716)
|
||||
~D[2010-04-17]
|
||||
|
||||
iex> Date.shift(~D[1970-01-01], year: 40, month: 3, week: 2, day: 2)
|
||||
@@ -193,8 +188,9 @@ defmodule Date do
|
||||
end
|
||||
|
||||
def utc_today(calendar) do
|
||||
%{year: year, month: month, day: day} = DateTime.utc_now(calendar)
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
calendar
|
||||
|> DateTime.utc_now()
|
||||
|> DateTime.to_date()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -321,7 +317,7 @@ defmodule Date do
|
||||
@doc """
|
||||
Converts the given date to a string according to its calendar.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> Date.to_string(~D[2000-02-28])
|
||||
"2000-02-28"
|
||||
@@ -399,7 +395,7 @@ defmodule Date do
|
||||
or other calendars in which the days also start at midnight.
|
||||
Attempting to convert dates from other calendars will raise an `ArgumentError`.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> Date.to_iso8601(~D[2000-02-28])
|
||||
"2000-02-28"
|
||||
@@ -633,7 +629,7 @@ defmodule Date do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> Date.convert(~D[2000-01-01], Calendar.Holocene)
|
||||
@@ -667,7 +663,7 @@ defmodule Date do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> Date.convert!(~D[2000-01-01], Calendar.Holocene)
|
||||
@@ -691,15 +687,10 @@ defmodule Date do
|
||||
@doc """
|
||||
Adds the number of days to the given `date`.
|
||||
|
||||
> #### Prefer `shift/2` {: .info}
|
||||
>
|
||||
> Prefer `shift/2` over `add/2`, as it offers a more ergonomic API.
|
||||
>
|
||||
> `add/2` always considers a day to be measured according to the
|
||||
> `Calendar.ISO`.
|
||||
The days are counted as Gregorian days. The date is returned in the same
|
||||
calendar as it was given in.
|
||||
|
||||
The days are counted as Gregorian days, independent of the underlying
|
||||
calendar. The date is returned in the same calendar as it was given in.
|
||||
To shift a date by a `Duration` and according to its underlying calendar, use `Date.shift/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -861,7 +852,7 @@ defmodule Date do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculates the ordinal day of the week of a given `date`.
|
||||
Calculates the day of the week of a given `date`.
|
||||
|
||||
Returns the day of the week as an integer. For the ISO 8601
|
||||
calendar (the default), it is an integer from 1 to 7, where
|
||||
@@ -870,19 +861,10 @@ defmodule Date do
|
||||
An optional `starting_on` value may be supplied, which
|
||||
configures the weekday the week starts on. The default value
|
||||
for it is `:default`, which translates to `:monday` for the
|
||||
built-in ISO 8601 calendar. Any other weekday may be used for
|
||||
`starting_on`, in such cases, that weekday will be considered the first
|
||||
day of the week, and therefore it will be assigned the ordinal number 1.
|
||||
|
||||
The other calendars, the value returned is an ordinal day of week.
|
||||
For example, `1` may mean "first day of the week" and `7` is
|
||||
defined to mean "seventh day of the week". Custom calendars may
|
||||
also accept their own variations of the `starting_on` parameter
|
||||
with their own meaning.
|
||||
built-in ISO calendar. Any other weekday may be given to.
|
||||
|
||||
## Examples
|
||||
|
||||
# 2016-10-31 is a Monday and by default Monday is the first day of the week
|
||||
iex> Date.day_of_week(~D[2016-10-31])
|
||||
1
|
||||
iex> Date.day_of_week(~D[2016-11-01])
|
||||
@@ -892,7 +874,6 @@ defmodule Date do
|
||||
iex> Date.day_of_week(~D[-0015-10-30])
|
||||
3
|
||||
|
||||
# 2016-10-31 is a Monday but, as we start the week on Sunday, now it returns 2
|
||||
iex> Date.day_of_week(~D[2016-10-31], :sunday)
|
||||
2
|
||||
iex> Date.day_of_week(~D[2016-11-01], :sunday)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Date.Range do
|
||||
@moduledoc """
|
||||
Returns an inclusive range between dates.
|
||||
@@ -59,20 +55,11 @@ defmodule Date.Range do
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
member? =
|
||||
quote generated: true do
|
||||
member?(
|
||||
%{
|
||||
__struct__: Date.Range,
|
||||
first_in_iso_days: var!(first_days),
|
||||
last_in_iso_days: var!(last_days)
|
||||
} =
|
||||
var!(date_range),
|
||||
var!(date)
|
||||
)
|
||||
end
|
||||
|
||||
def unquote(member?) do
|
||||
def member?(
|
||||
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
|
||||
date_range,
|
||||
date
|
||||
) do
|
||||
step = if first_days <= last_days, do: 1, else: -1
|
||||
member?(Map.put(date_range, :step, step), date)
|
||||
end
|
||||
@@ -104,7 +91,7 @@ defmodule Date.Range do
|
||||
[date_from_iso_days(current, calendar)]
|
||||
end
|
||||
|
||||
defp slice(current, step, remaining, calendar) when remaining > 1 do
|
||||
defp slice(current, step, remaining, calendar) do
|
||||
[
|
||||
date_from_iso_days(current, calendar)
|
||||
| slice(current + step, step, remaining - 1, calendar)
|
||||
@@ -180,12 +167,8 @@ defmodule Date.Range do
|
||||
when step < 0 and first_days < last_days,
|
||||
do: 0
|
||||
|
||||
defp size(%Date.Range{
|
||||
first_in_iso_days: first_days,
|
||||
last_in_iso_days: last_days,
|
||||
step: step
|
||||
}),
|
||||
do: abs(div(last_days - first_days, step)) + 1
|
||||
defp size(%Date.Range{first_in_iso_days: first_days, last_in_iso_days: last_days, step: step}),
|
||||
do: abs(div(last_days - first_days, step)) + 1
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
defp size(
|
||||
@@ -227,11 +210,11 @@ defmodule Date.Range do
|
||||
defimpl Inspect do
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(%Date.Range{first: first, last: last, step: 1}, %Inspect.Opts{}) do
|
||||
def inspect(%Date.Range{first: first, last: last, step: 1}, _) do
|
||||
"Date.range(" <> inspect(first) <> ", " <> inspect(last) <> ")"
|
||||
end
|
||||
|
||||
def inspect(%Date.Range{first: first, last: last, step: step}, %Inspect.Opts{}) do
|
||||
def inspect(%Date.Range{first: first, last: last, step: step}, _) do
|
||||
"Date.range(" <> inspect(first) <> ", " <> inspect(last) <> ", #{step})"
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule DateTime do
|
||||
@moduledoc """
|
||||
A datetime implementation with a time zone.
|
||||
@@ -17,8 +13,8 @@ defmodule DateTime do
|
||||
|
||||
Remember, comparisons in Elixir using `==/2`, `>/2`, `</2` and friends
|
||||
are structural and based on the DateTime struct fields. For proper
|
||||
comparison between datetimes, use the `compare/2`, `after?/2` and `before?/2` functions.
|
||||
The existence of the `compare/2` function in this module also allows
|
||||
comparison between datetimes, use the `compare/2` function. The
|
||||
existence of the `compare/2` function in this module also allows
|
||||
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
|
||||
maximum datetime of an `Enum`. For example:
|
||||
|
||||
@@ -383,12 +379,13 @@ defmodule DateTime do
|
||||
@doc """
|
||||
Converts the given Unix time to `DateTime`.
|
||||
|
||||
The integer can be given in different unit, according to `System.convert_time_unit/3`,
|
||||
and it will be converted to microseconds internally, which is the maximum precision
|
||||
supported by `DateTime`. In other words, any precision higher than microseconds will
|
||||
lead to truncation.
|
||||
The integer can be given in different unit
|
||||
according to `System.convert_time_unit/3` and it will
|
||||
be converted to microseconds internally. Up to
|
||||
253402300799 seconds is supported.
|
||||
|
||||
Unix times are always in UTC. Therefore the DateTime will be returned in UTC.
|
||||
Unix times are always in UTC and therefore the DateTime
|
||||
will be returned in UTC.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -884,10 +881,8 @@ defmodule DateTime do
|
||||
The `datetime` is expected to be using the ISO calendar
|
||||
with a year greater than or equal to 0.
|
||||
|
||||
It will return the integer with the given unit, according
|
||||
to `System.convert_time_unit/3`. If the given unit is different
|
||||
than microseconds, the returned value will be either truncated
|
||||
or padded accordingly.
|
||||
It will return the integer with the given unit,
|
||||
according to `System.convert_time_unit/3`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1046,7 +1041,7 @@ defmodule DateTime do
|
||||
its abbreviation, which means information is lost when converting to such
|
||||
format.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
|
||||
...> hour: 23, minute: 0, second: 7, microsecond: {0, 0},
|
||||
@@ -1096,21 +1091,8 @@ defmodule DateTime do
|
||||
@spec to_iso8601(Calendar.datetime(), :basic | :extended, nil | integer()) :: String.t()
|
||||
def to_iso8601(datetime, format \\ :extended, offset \\ nil)
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, offset)
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, nil)
|
||||
when format in [:extended, :basic] do
|
||||
datetime
|
||||
|> to_iso8601_iodata(format, offset)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = datetime, format, offset)
|
||||
when format in [:extended, :basic] do
|
||||
datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format, offset)
|
||||
end
|
||||
|
||||
defp to_iso8601_iodata(datetime, format, nil) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
@@ -1124,51 +1106,35 @@ defmodule DateTime do
|
||||
std_offset: std_offset
|
||||
} = datetime
|
||||
|
||||
[
|
||||
datetime_to_iodata(year, month, day, hour, minute, second, microsecond, format),
|
||||
Calendar.ISO.offset_to_iodata(utc_offset, std_offset, time_zone, format)
|
||||
]
|
||||
datetime_to_string(year, month, day, hour, minute, second, microsecond, format) <>
|
||||
Calendar.ISO.offset_to_string(utc_offset, std_offset, time_zone, format)
|
||||
end
|
||||
|
||||
defp to_iso8601_iodata(
|
||||
%{microsecond: {_, precision}, time_zone: "Etc/UTC"} = datetime,
|
||||
format,
|
||||
0
|
||||
) do
|
||||
def to_iso8601(
|
||||
%{calendar: Calendar.ISO, microsecond: {_, precision}, time_zone: "Etc/UTC"} = datetime,
|
||||
format,
|
||||
0
|
||||
)
|
||||
when format in [:extended, :basic] do
|
||||
{year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, 0)
|
||||
|
||||
[
|
||||
datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
{microsecond, precision},
|
||||
format
|
||||
),
|
||||
?Z
|
||||
]
|
||||
datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
|
||||
"Z"
|
||||
end
|
||||
|
||||
defp to_iso8601_iodata(datetime, format, offset) do
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = datetime, format, offset)
|
||||
when format in [:extended, :basic] do
|
||||
{_, precision} = datetime.microsecond
|
||||
{year, month, day, hour, minute, second, {microsecond, _}} = shift_by_offset(datetime, offset)
|
||||
|
||||
[
|
||||
datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
{microsecond, precision},
|
||||
format
|
||||
),
|
||||
Calendar.ISO.offset_to_iodata(offset, 0, nil, format)
|
||||
]
|
||||
datetime_to_string(year, month, day, hour, minute, second, {microsecond, precision}, format) <>
|
||||
Calendar.ISO.offset_to_string(offset, 0, nil, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = datetime, format, offset) when format in [:extended, :basic] do
|
||||
datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format, offset)
|
||||
end
|
||||
|
||||
defp shift_by_offset(%{calendar: calendar} = datetime, offset) do
|
||||
@@ -1181,12 +1147,10 @@ defmodule DateTime do
|
||||
|> calendar.naive_datetime_from_iso_days()
|
||||
end
|
||||
|
||||
defp datetime_to_iodata(year, month, day, hour, minute, second, microsecond, format) do
|
||||
[
|
||||
Calendar.ISO.date_to_iodata(year, month, day, format),
|
||||
?T,
|
||||
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond, format)
|
||||
]
|
||||
defp datetime_to_string(year, month, day, hour, minute, second, microsecond, format) do
|
||||
Calendar.ISO.date_to_string(year, month, day, format) <>
|
||||
"T" <>
|
||||
Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1390,7 +1354,7 @@ defmodule DateTime do
|
||||
custom (but relatively common) representation which appends the time
|
||||
zone abbreviation and full name to the datetime.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> dt = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "CET",
|
||||
...> hour: 23, minute: 0, second: 7, microsecond: {0, 0},
|
||||
@@ -1599,57 +1563,42 @@ defmodule DateTime do
|
||||
end
|
||||
|
||||
naive_diff =
|
||||
(datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond)) -
|
||||
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(:microsecond))
|
||||
(datetime1 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit)) -
|
||||
(datetime2 |> to_iso_days() |> Calendar.ISO.iso_days_to_unit(unit))
|
||||
|
||||
offset_diff = utc_offset2 + std_offset2 - (utc_offset1 + std_offset1)
|
||||
|
||||
System.convert_time_unit(naive_diff, :microsecond, unit) +
|
||||
System.convert_time_unit(offset_diff, :second, unit)
|
||||
naive_diff + System.convert_time_unit(offset_diff, :second, unit)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a specified amount of time to a `DateTime`.
|
||||
|
||||
> #### Prefer `shift/2` {: .info}
|
||||
>
|
||||
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
|
||||
>
|
||||
> `add/3` provides a lower-level API which only supports fixed units
|
||||
> such as `:hour` and `:second`, but not `:month` (as the exact length
|
||||
> of a month depends on the current month). `add/3` always considers
|
||||
> the unit to be computed according to the `Calendar.ISO`.
|
||||
|
||||
Accepts an `amount_to_add` in any `unit`. `unit` can be `:day`,
|
||||
`:hour`, `:minute`, `:second` or any subsecond precision from
|
||||
`t:System.time_unit/0` for convenience but ultimately they are
|
||||
all converted to microseconds. Negative values will move backwards
|
||||
in time and the default precision is `:second`.
|
||||
`t:System.time_unit/0`. It defaults to `:second`. Negative values
|
||||
will move backwards in time.
|
||||
|
||||
This function always considers the unit to be computed according
|
||||
to the `Calendar.ISO`.
|
||||
|
||||
This function relies on a contiguous representation of time,
|
||||
ignoring timezone changes. For example, if you add one day when there
|
||||
are summer time/daylight saving time changes, it will also change the
|
||||
time forward or backward by one hour, so the elapsed time is precisely
|
||||
24 hours. Similarly, adding just a few seconds to a datetime just before
|
||||
"spring forward" can cause wall time to increase by more than an hour.
|
||||
ignoring the wall time and timezone changes. For example, if you add
|
||||
one day when there are summer time/daylight saving time changes,
|
||||
it will also change the time forward or backward by one hour,
|
||||
so the elapsed time is precisely 24 hours. Similarly, adding just
|
||||
a few seconds to a datetime just before "spring forward" can cause
|
||||
wall time to increase by more than an hour.
|
||||
|
||||
While this means this function is precise in terms of elapsed time,
|
||||
its result may be confusing in certain use cases. For example, if a
|
||||
its result may be misleading in certain use cases. For example, if a
|
||||
user requests a meeting to happen every day at 15:00 and you use this
|
||||
function to compute all future meetings by adding day after day, this
|
||||
function may change the meeting time to 14:00 or 16:00 if there are
|
||||
changes to the current timezone.
|
||||
changes to the current timezone. Computing of recurring datetimes is
|
||||
not currently supported in Elixir's standard library but it is available
|
||||
by third-party libraries.
|
||||
|
||||
In case you don't want these changes to happen automatically or you
|
||||
want to surface time zone conflicts to the user, you can add to
|
||||
the datetime as a naive datetime and then use `from_naive/2`:
|
||||
|
||||
dt |> NaiveDateTime.add(1, :day) |> DateTime.from_naive(dt.time_zone)
|
||||
|
||||
The above will surface time jumps and ambiguous datetimes, allowing you
|
||||
to deal with them accordingly.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> dt = DateTime.from_naive!(~N[2018-11-15 10:00:00], "Europe/Copenhagen", FakeTimeZoneDatabase)
|
||||
iex> dt |> DateTime.add(3600, :second, FakeTimeZoneDatabase)
|
||||
@@ -1677,6 +1626,8 @@ defmodule DateTime do
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
To shift a datetime by a `Duration` and according to its underlying calendar, use `DateTime.shift/3`.
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec add(
|
||||
@@ -1750,7 +1701,7 @@ defmodule DateTime do
|
||||
to UTC, and finally computing the new timezone in case of shifts.
|
||||
This ensures `shift/3` always returns a valid datetime.
|
||||
|
||||
Consequently, time zones that observe "Daylight Saving Time"
|
||||
On the other hand, time zones that observe "Daylight Saving Time"
|
||||
or other changes, across summer/winter time will add/remove hours
|
||||
from the resulting datetime:
|
||||
|
||||
@@ -1762,22 +1713,12 @@ defmodule DateTime do
|
||||
DateTime.shift(dt, hour: 2)
|
||||
#=> #DateTime<2018-11-04 01:00:00-08:00 PST America/Los_Angeles>
|
||||
|
||||
Although the first example shows a difference of 2 hours when
|
||||
comparing the wall clocks of the given datetime with the returned one,
|
||||
due to the "spring forward" time jump, the actual elapsed time is
|
||||
still exactly of 1 hour.
|
||||
|
||||
In case you don't want these changes to happen automatically or you
|
||||
want to surface time zone conflicts to the user, you can shift
|
||||
the datetime as a naive datetime and then use `from_naive/2`:
|
||||
|
||||
dt |> NaiveDateTime.shift(duration) |> DateTime.from_naive(dt.time_zone)
|
||||
|
||||
The above will surface time jumps and ambiguous datetimes, allowing you
|
||||
to deal with them accordingly.
|
||||
|
||||
## ISO calendar considerations
|
||||
|
||||
When using the default ISO calendar, durations are collapsed and
|
||||
applied in the order of months, then seconds and microseconds:
|
||||
|
||||
@@ -1943,7 +1884,7 @@ defmodule DateTime do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
|
||||
@@ -1972,7 +1913,7 @@ defmodule DateTime do
|
||||
if Calendar.compatible_calendars?(dt_calendar, calendar) do
|
||||
result_datetime =
|
||||
datetime
|
||||
|> to_iso_days()
|
||||
|> to_iso_days
|
||||
|> from_iso_days(datetime, calendar, precision)
|
||||
|
||||
{:ok, result_datetime}
|
||||
@@ -1990,7 +1931,7 @@ defmodule DateTime do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> dt1 = %DateTime{year: 2000, month: 2, day: 29, zone_abbr: "AMT",
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
defmodule Duration do
|
||||
@moduledoc """
|
||||
Struct and functions for handling durations.
|
||||
@@ -91,30 +88,6 @@ defmodule Duration do
|
||||
The second example consistently points to the last day of the month,
|
||||
as it performs operations on the duration, rather than shifting date
|
||||
after date.
|
||||
|
||||
## Comparing durations
|
||||
|
||||
In order to accurately compare durations, you need to either compare
|
||||
only certain fields or use a reference time instant. This is because
|
||||
some fields are relative to others. For example, you may say that
|
||||
1 month is the same as 30 days, but if you add both of these durations
|
||||
to `~D[2015-02-01]`, you would get different results, as that month
|
||||
has only 28 days.
|
||||
|
||||
Therefore, if you wish to compare durations, one option is to use
|
||||
`Date.shift/2` (or `DateTime.shift/2` or similar), and then compare
|
||||
the dates:
|
||||
|
||||
iex> date = ~D[2015-02-01]
|
||||
iex> Date.compare(Date.shift(date, month: 1), Date.shift(date, day: 30))
|
||||
:lt
|
||||
|
||||
Or alternatively convert the durations to a fixed unit by using `to_timeout/1`,
|
||||
which supports durations only up to weeks, raising if it has the month or year
|
||||
fields set.
|
||||
|
||||
iex> to_timeout(hour: 24) == to_timeout(day: 1)
|
||||
true
|
||||
"""
|
||||
|
||||
@moduledoc since: "1.17.0"
|
||||
@@ -140,7 +113,7 @@ defmodule Duration do
|
||||
hour: integer,
|
||||
minute: integer,
|
||||
second: integer,
|
||||
microsecond: Calendar.microsecond()
|
||||
microsecond: {integer, 0..6}
|
||||
}
|
||||
|
||||
@typedoc """
|
||||
@@ -154,29 +127,13 @@ defmodule Duration do
|
||||
| {:hour, integer}
|
||||
| {:minute, integer}
|
||||
| {:second, integer}
|
||||
| {:microsecond, Calendar.microsecond()}
|
||||
| {:microsecond, {integer, 0..6}}
|
||||
|
||||
@typedoc """
|
||||
The duration type specifies a `%Duration{}` struct or a keyword list of valid duration unit pairs.
|
||||
"""
|
||||
@type duration :: t | [unit_pair]
|
||||
|
||||
@typedoc """
|
||||
Options for `Duration.to_string/2`.
|
||||
"""
|
||||
@type to_string_opts :: [
|
||||
units: [
|
||||
year: String.t(),
|
||||
month: String.t(),
|
||||
week: String.t(),
|
||||
day: String.t(),
|
||||
hour: String.t(),
|
||||
minute: String.t(),
|
||||
second: String.t()
|
||||
],
|
||||
separator: String.t()
|
||||
]
|
||||
|
||||
@microseconds_per_second 1_000_000
|
||||
|
||||
@doc """
|
||||
@@ -232,7 +189,7 @@ defmodule Duration do
|
||||
@doc """
|
||||
Adds units of given durations `d1` and `d2`.
|
||||
|
||||
Respects the highest microsecond precision of the two.
|
||||
Respects the the highest microsecond precision of the two.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -262,7 +219,7 @@ defmodule Duration do
|
||||
@doc """
|
||||
Subtracts units of given durations `d1` and `d2`.
|
||||
|
||||
Respects the highest microsecond precision of the two.
|
||||
Respects the the highest microsecond precision of the two.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -452,7 +409,6 @@ defmodule Duration do
|
||||
|
||||
"""
|
||||
@doc since: "1.18.0"
|
||||
@spec to_string(t, to_string_opts) :: String.t()
|
||||
def to_string(%Duration{} = duration, opts \\ []) do
|
||||
units = Keyword.get(opts, :units, [])
|
||||
separator = Keyword.get(opts, :separator, " ")
|
||||
@@ -576,7 +532,7 @@ defmodule Duration do
|
||||
sign,
|
||||
Integer.to_string(second),
|
||||
?.,
|
||||
Calendar.ISO.microseconds_to_iodata(ms, p)
|
||||
ms |> Integer.to_string() |> String.pad_leading(6, "0") |> binary_part(0, p)
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+209
-346
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Calendar.ISO do
|
||||
@moduledoc """
|
||||
The default calendar implementation, a Gregorian calendar following ISO 8601.
|
||||
@@ -182,7 +178,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
@type day_of_year :: 1..366
|
||||
@type quarter_of_year :: 1..4
|
||||
@type year_of_era :: {1..10_000, era}
|
||||
@type year_of_era :: {1..10000, era}
|
||||
|
||||
@seconds_per_minute 60
|
||||
@seconds_per_hour 60 * 60
|
||||
@@ -196,28 +192,14 @@ defmodule Calendar.ISO do
|
||||
@ext_date_sep ?-
|
||||
@ext_time_sep ?:
|
||||
|
||||
@days_per_nonleap_year 365
|
||||
@days_per_leap_year 366
|
||||
|
||||
# The ISO epoch starts, in this implementation,
|
||||
# with ~D[0000-01-01]. Era "1" starts
|
||||
# on ~D[0001-01-01] which is 366 days later.
|
||||
@iso_epoch 366
|
||||
|
||||
# Constants for date calculations using 400-year era cycles.
|
||||
# The algorithm uses a March-based year where March 1 is day 0.
|
||||
# Reference: Neri C, Schneider L. "Euclidean Affine Functions and
|
||||
# their Application to Calendar Algorithms". Softw Pract Exper. 2022.
|
||||
@days_per_year 365
|
||||
@years_per_era 400
|
||||
@days_per_era @years_per_era * @days_per_year + 97
|
||||
@days_per_4_years 4 * @days_per_year
|
||||
@days_per_100_years 100 * @days_per_year + 24
|
||||
@march_1_offset 31 + 29
|
||||
@unix_epoch_days 719_528
|
||||
|
||||
# Month calculation constants: in a March-based year, each 5-month
|
||||
# cycle has exactly 153 days (31+30+31+30+31 or 31+30+31+30+31).
|
||||
@days_per_5_months 153
|
||||
@months_per_cycle 5
|
||||
|
||||
[match_basic_date, match_ext_date, guard_date, read_date] =
|
||||
quote do
|
||||
[
|
||||
@@ -666,12 +648,12 @@ defmodule Calendar.ISO do
|
||||
day_fraction = time_to_day_fraction(hour, minute, second, {0, 0})
|
||||
|
||||
{{year, month, day}, {hour, minute, second, _}} =
|
||||
case add_day_fraction_to_iso_days({0, day_fraction}, -offset, 86_400) do
|
||||
case add_day_fraction_to_iso_days({0, day_fraction}, -offset, 86400) do
|
||||
{0, day_fraction} ->
|
||||
{{year, month, day}, time_from_day_fraction(day_fraction)}
|
||||
|
||||
{extra_days, day_fraction} ->
|
||||
base_days = valid_date_to_iso_days(year, month, day)
|
||||
base_days = date_to_iso_days(year, month, day)
|
||||
{date_from_iso_days(base_days + extra_days), time_from_day_fraction(day_fraction)}
|
||||
end
|
||||
|
||||
@@ -798,13 +780,13 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({0, {0, 86_400}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({0, {0, 86400}})
|
||||
{0, 1, 1, 0, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {0, 86_400}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {0, 86400}})
|
||||
{2000, 1, 1, 0, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {43_200, 86_400}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({730_485, {43200, 86400}})
|
||||
{2000, 1, 1, 12, 0, 0, {0, 6}}
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({-365, {0, 86_400_000_000}})
|
||||
iex> Calendar.ISO.naive_datetime_from_iso_days({-365, {0, 86400000000}})
|
||||
{-1, 1, 1, 0, 0, 0, {0, 6}}
|
||||
|
||||
"""
|
||||
@@ -892,52 +874,28 @@ defmodule Calendar.ISO do
|
||||
|
||||
# Converts year, month, day to count of days since 0000-01-01.
|
||||
@doc false
|
||||
def date_to_iso_days(year, month, day) do
|
||||
ensure_day_in_month!(year, month, day)
|
||||
valid_date_to_iso_days(year, month, day)
|
||||
def date_to_iso_days(0, 1, 1) do
|
||||
0
|
||||
end
|
||||
|
||||
defp valid_date_to_iso_days(0, 1, 1), do: 0
|
||||
defp valid_date_to_iso_days(1970, 1, 1), do: @unix_epoch_days
|
||||
def date_to_iso_days(1970, 1, 1) do
|
||||
719_528
|
||||
end
|
||||
|
||||
defp valid_date_to_iso_days(year, month, day) do
|
||||
y = if month <= 2, do: year - 1, else: year
|
||||
era = if y >= 0, do: div(y, @years_per_era), else: div(y - 399, @years_per_era)
|
||||
year_of_era = y - era * @years_per_era
|
||||
month_prime = if month > 2, do: month - 3, else: month + 9
|
||||
day_of_year = div(@days_per_5_months * month_prime + 2, @months_per_cycle) + day - 1
|
||||
def date_to_iso_days(year, month, day) do
|
||||
ensure_day_in_month!(year, month, day)
|
||||
|
||||
day_of_era =
|
||||
@days_per_year * year_of_era + div(year_of_era, 4) - div(year_of_era, 100) + day_of_year
|
||||
|
||||
era * @days_per_era + day_of_era + @march_1_offset
|
||||
days_in_previous_years(year) + days_before_month(month) + leap_day_offset(year, month) + day -
|
||||
1
|
||||
end
|
||||
|
||||
# Converts count of days since 0000-01-01 to {year, month, day} tuple.
|
||||
@doc false
|
||||
def date_from_iso_days(days) do
|
||||
z = days - @march_1_offset
|
||||
era = if z >= 0, do: div(z, @days_per_era), else: div(z - @days_per_era + 1, @days_per_era)
|
||||
day_of_era = z - era * @days_per_era
|
||||
|
||||
year_of_era =
|
||||
div(
|
||||
day_of_era - div(day_of_era, @days_per_4_years) + div(day_of_era, @days_per_100_years) -
|
||||
div(day_of_era, @days_per_era - 1),
|
||||
@days_per_year
|
||||
)
|
||||
|
||||
day_of_year =
|
||||
day_of_era -
|
||||
(@days_per_year * year_of_era + div(year_of_era, 4) - div(year_of_era, 100))
|
||||
|
||||
month_prime = div(@months_per_cycle * day_of_year + 2, @days_per_5_months)
|
||||
day = day_of_year - div(@days_per_5_months * month_prime + 2, @months_per_cycle) + 1
|
||||
month = if month_prime < 10, do: month_prime + 3, else: month_prime - 9
|
||||
year = year_of_era + era * @years_per_era
|
||||
year = if month <= 2, do: year + 1, else: year
|
||||
|
||||
{year, month, day}
|
||||
{year, day_of_year} = days_to_year(days)
|
||||
extra_day = if leap_year?(year), do: 1, else: 0
|
||||
{month, day_in_month} = year_day_to_year_date(extra_day, day_of_year)
|
||||
{year, month, day_in_month + 1}
|
||||
end
|
||||
|
||||
defp div_rem(int1, int2) do
|
||||
@@ -951,9 +909,6 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
end
|
||||
|
||||
defp floor_div_positive_divisor(int1, int2) when int1 >= 0, do: div(int1, int2)
|
||||
defp floor_div_positive_divisor(int1, int2), do: -div(-int1 - 1, int2) - 1
|
||||
|
||||
@doc """
|
||||
Returns how many days there are in the given year-month.
|
||||
|
||||
@@ -1174,7 +1129,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec year_of_era(year) :: {1..10_000, era}
|
||||
@spec year_of_era(year) :: {1..10000, era}
|
||||
def year_of_era(year) when is_year_CE(year), do: {year, 1}
|
||||
def year_of_era(year) when is_year_BCE(year), do: {abs(year) + 1, 0}
|
||||
|
||||
@@ -1200,7 +1155,7 @@ defmodule Calendar.ISO do
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@impl true
|
||||
@spec year_of_era(year, month, day) :: {1..10_000, era}
|
||||
@spec year_of_era(year, month, day) :: {1..10000, era}
|
||||
def year_of_era(year, _month, _day), do: year_of_era(year)
|
||||
|
||||
@doc """
|
||||
@@ -1265,37 +1220,6 @@ defmodule Calendar.ISO do
|
||||
:basic | :extended
|
||||
) :: String.t()
|
||||
def time_to_string(
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format \\ :extended
|
||||
) do
|
||||
time_to_iodata(hour, minute, second, microsecond, format)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given time into a iodata.
|
||||
|
||||
See `time_to_string/5` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> data = Calendar.ISO.time_to_iodata(2, 2, 2, {2, 6})
|
||||
iex> IO.iodata_to_binary(data)
|
||||
"02:02:02.000002"
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec time_to_iodata(
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond(),
|
||||
:basic | :extended
|
||||
) :: iodata
|
||||
def time_to_iodata(
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
@@ -1304,43 +1228,24 @@ defmodule Calendar.ISO do
|
||||
)
|
||||
when is_hour(hour) and is_minute(minute) and is_second(second) and
|
||||
is_microsecond(ms_value, ms_precision) and format in [:basic, :extended] do
|
||||
time_to_iodata_guarded(hour, minute, second, microsecond, format)
|
||||
time_to_string_guarded(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
defp time_to_iodata_guarded(hour, minute, second, {_, 0}, format) do
|
||||
time_to_iodata_format(hour, minute, second, format)
|
||||
defp time_to_string_guarded(hour, minute, second, {_, 0}, format) do
|
||||
time_to_string_format(hour, minute, second, format)
|
||||
end
|
||||
|
||||
defp time_to_iodata_guarded(hour, minute, second, {microsecond, precision}, format) do
|
||||
[
|
||||
time_to_iodata_format(hour, minute, second, format),
|
||||
?.
|
||||
| microseconds_to_iodata(microsecond, precision)
|
||||
]
|
||||
defp time_to_string_guarded(hour, minute, second, {microsecond, precision}, format) do
|
||||
time_to_string_format(hour, minute, second, format) <>
|
||||
"." <> (microsecond |> zero_pad(6) |> binary_part(0, precision))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def microseconds_to_iodata(_microsecond, 0), do: []
|
||||
def microseconds_to_iodata(microsecond, 6), do: zero_pad(microsecond, 6)
|
||||
|
||||
def microseconds_to_iodata(microsecond, precision) do
|
||||
num = div(microsecond, scale_factor(precision))
|
||||
zero_pad(num, precision)
|
||||
defp time_to_string_format(hour, minute, second, :extended) do
|
||||
zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2) <> ":" <> zero_pad(second, 2)
|
||||
end
|
||||
|
||||
defp scale_factor(1), do: 100_000
|
||||
defp scale_factor(2), do: 10_000
|
||||
defp scale_factor(3), do: 1_000
|
||||
defp scale_factor(4), do: 100
|
||||
defp scale_factor(5), do: 10
|
||||
defp scale_factor(6), do: 1
|
||||
|
||||
defp time_to_iodata_format(hour, minute, second, :extended) do
|
||||
[zero_pad(hour, 2), ?:, zero_pad(minute, 2), ?: | zero_pad(second, 2)]
|
||||
end
|
||||
|
||||
defp time_to_iodata_format(hour, minute, second, :basic) do
|
||||
[zero_pad(hour, 2), zero_pad(minute, 2) | zero_pad(second, 2)]
|
||||
defp time_to_string_format(hour, minute, second, :basic) do
|
||||
zero_pad(hour, 2) <> zero_pad(minute, 2) <> zero_pad(second, 2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1368,36 +1273,18 @@ defmodule Calendar.ISO do
|
||||
@doc since: "1.4.0"
|
||||
@spec date_to_string(year, month, day, :basic | :extended) :: String.t()
|
||||
@impl true
|
||||
def date_to_string(year, month, day, format \\ :extended) do
|
||||
date_to_iodata(year, month, day, format)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given date into a iodata.
|
||||
|
||||
See `date_to_string/4` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> data = Calendar.ISO.date_to_iodata(2015, 2, 28)
|
||||
iex> IO.iodata_to_binary(data)
|
||||
"2015-02-28"
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec date_to_iodata(year, month, day, :basic | :extended) :: iodata
|
||||
def date_to_iodata(year, month, day, format \\ :extended)
|
||||
def date_to_string(year, month, day, format \\ :extended)
|
||||
when is_integer(year) and is_integer(month) and is_integer(day) and
|
||||
format in [:basic, :extended] do
|
||||
date_to_iodata_guarded(year, month, day, format)
|
||||
date_to_string_guarded(year, month, day, format)
|
||||
end
|
||||
|
||||
defp date_to_iodata_guarded(year, month, day, :extended) do
|
||||
[zero_pad(year, 4), ?-, zero_pad(month, 2), ?- | zero_pad(day, 2)]
|
||||
defp date_to_string_guarded(year, month, day, :extended) do
|
||||
zero_pad(year, 4) <> "-" <> zero_pad(month, 2) <> "-" <> zero_pad(day, 2)
|
||||
end
|
||||
|
||||
defp date_to_iodata_guarded(year, month, day, :basic) do
|
||||
[zero_pad(year, 4), zero_pad(month, 2) | zero_pad(day, 2)]
|
||||
defp date_to_string_guarded(year, month, day, :basic) do
|
||||
zero_pad(year, 4) <> zero_pad(month, 2) <> zero_pad(day, 2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1440,61 +1327,8 @@ defmodule Calendar.ISO do
|
||||
microsecond,
|
||||
format \\ :extended
|
||||
) do
|
||||
naive_datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format
|
||||
)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given naive_datetime into a iodata.
|
||||
|
||||
See `naive_datetime_to_iodata/8` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :basic)
|
||||
iex> IO.iodata_to_binary(data)
|
||||
"20150228 010203.000004"
|
||||
|
||||
iex> data = Calendar.ISO.naive_datetime_to_iodata(2015, 2, 28, 1, 2, 3, {4, 6}, :extended)
|
||||
iex> IO.iodata_to_binary(data)
|
||||
"2015-02-28 01:02:03.000004"
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec naive_datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond(),
|
||||
:basic | :extended
|
||||
) :: iodata
|
||||
def naive_datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
format \\ :extended
|
||||
) do
|
||||
[
|
||||
date_to_iodata(year, month, day, format),
|
||||
?\s
|
||||
| time_to_iodata(hour, minute, second, microsecond, format)
|
||||
]
|
||||
date_to_string(year, month, day, format) <>
|
||||
" " <> time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1560,89 +1394,20 @@ defmodule Calendar.ISO do
|
||||
utc_offset,
|
||||
std_offset,
|
||||
format \\ :extended
|
||||
) do
|
||||
datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset,
|
||||
format
|
||||
)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given datetime into a iodata.
|
||||
|
||||
See `datetime_to_iodata/12` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> time_zone = "Etc/UTC"
|
||||
iex> data = Calendar.ISO.datetime_to_iodata(2017, 8, 1, 1, 2, 3, {4, 5}, time_zone, "UTC", 0, 0)
|
||||
iex> IO.iodata_to_binary(data)
|
||||
"2017-08-01 01:02:03.00000Z"
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond(),
|
||||
Calendar.time_zone(),
|
||||
Calendar.zone_abbr(),
|
||||
Calendar.utc_offset(),
|
||||
Calendar.std_offset(),
|
||||
:basic | :extended
|
||||
) :: iodata
|
||||
def datetime_to_iodata(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
time_zone,
|
||||
zone_abbr,
|
||||
utc_offset,
|
||||
std_offset,
|
||||
format \\ :extended
|
||||
)
|
||||
when is_time_zone(time_zone) and is_zone_abbr(zone_abbr) and is_utc_offset(utc_offset) and
|
||||
is_std_offset(std_offset) do
|
||||
[
|
||||
date_to_iodata(year, month, day, format),
|
||||
?\s,
|
||||
time_to_iodata(hour, minute, second, microsecond, format),
|
||||
offset_to_iodata(utc_offset, std_offset, time_zone, format),
|
||||
zone_to_iodata(utc_offset, std_offset, zone_abbr, time_zone)
|
||||
]
|
||||
date_to_string(year, month, day, format) <>
|
||||
" " <>
|
||||
time_to_string(hour, minute, second, microsecond, format) <>
|
||||
offset_to_string(utc_offset, std_offset, time_zone, format) <>
|
||||
zone_to_string(utc_offset, std_offset, zone_abbr, time_zone)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def offset_to_string(0, 0, "Etc/UTC", _format), do: "Z"
|
||||
|
||||
def offset_to_string(utc, std, zone, format) do
|
||||
offset_to_iodata(utc, std, zone, format)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def offset_to_iodata(0, 0, "Etc/UTC", _format), do: ?Z
|
||||
|
||||
def offset_to_iodata(utc, std, _zone, format) do
|
||||
def offset_to_string(utc, std, _zone, format) do
|
||||
total = utc + std
|
||||
second = abs(total)
|
||||
minute = second |> rem(3600) |> div(60)
|
||||
@@ -1651,15 +1416,15 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
defp format_offset(total, hour, minute, :extended) do
|
||||
[sign(total), zero_pad(hour, 2), ?: | zero_pad(minute, 2)]
|
||||
sign(total) <> zero_pad(hour, 2) <> ":" <> zero_pad(minute, 2)
|
||||
end
|
||||
|
||||
defp format_offset(total, hour, minute, :basic) do
|
||||
[sign(total), zero_pad(hour, 2) | zero_pad(minute, 2)]
|
||||
sign(total) <> zero_pad(hour, 2) <> zero_pad(minute, 2)
|
||||
end
|
||||
|
||||
defp zone_to_iodata(_, _, _, "Etc/UTC"), do: []
|
||||
defp zone_to_iodata(_, _, abbr, zone), do: [?\s, abbr, ?\s | zone]
|
||||
defp zone_to_string(_, _, _, "Etc/UTC"), do: ""
|
||||
defp zone_to_string(_, _, abbr, zone), do: " " <> abbr <> " " <> zone
|
||||
|
||||
@doc """
|
||||
Determines if the date given is valid according to the proleptic Gregorian calendar.
|
||||
@@ -1720,24 +1485,16 @@ defmodule Calendar.ISO do
|
||||
{0, 1}
|
||||
end
|
||||
|
||||
defp sign(total) when total < 0, do: ?-
|
||||
defp sign(_), do: ?+
|
||||
defp sign(total) when total < 0, do: "-"
|
||||
defp sign(_), do: "+"
|
||||
|
||||
defp zero_pad(val, count) when val >= 0 and count <= 6 do
|
||||
defp zero_pad(val, count) when val >= 0 do
|
||||
num = Integer.to_string(val)
|
||||
|
||||
case max(count - byte_size(num), 0) do
|
||||
0 -> num
|
||||
1 -> ["0" | num]
|
||||
2 -> ["00" | num]
|
||||
3 -> ["000" | num]
|
||||
4 -> ["0000" | num]
|
||||
5 -> ["00000" | num]
|
||||
end
|
||||
:binary.copy("0", max(count - byte_size(num), 0)) <> num
|
||||
end
|
||||
|
||||
defp zero_pad(val, count) do
|
||||
[?- | zero_pad(-val, count)]
|
||||
"-" <> zero_pad(-val, count)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1745,11 +1502,11 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({0, {0, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({0, {0, 86400000000}})
|
||||
{0, {0, 86400000000}}
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({730_485, {43_200_000_000, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({730485, {43200000000, 86400000000}})
|
||||
{730485, {0, 86400000000}}
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({730_485, {46_800_000_000, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_beginning_of_day({730485, {46800000000, 86400000000}})
|
||||
{730485, {0, 86400000000}}
|
||||
|
||||
"""
|
||||
@@ -1765,11 +1522,11 @@ defmodule Calendar.ISO do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({0, {0, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({0, {0, 86400000000}})
|
||||
{0, {86399999999, 86400000000}}
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({730_485, {43_200_000_000, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({730485, {43200000000, 86400000000}})
|
||||
{730485, {86399999999, 86400000000}}
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({730_485, {46_800_000_000, 86_400_000_000}})
|
||||
iex> Calendar.ISO.iso_days_to_end_of_day({730485, {46800000000, 86400000000}})
|
||||
{730485, {86399999999, 86400000000}}
|
||||
|
||||
"""
|
||||
@@ -1878,7 +1635,7 @@ defmodule Calendar.ISO do
|
||||
@doc false
|
||||
def shift_days({year, month, day}, days) do
|
||||
{year, month, day} =
|
||||
valid_date_to_iso_days(year, month, day)
|
||||
date_to_iso_days(year, month, day)
|
||||
|> Kernel.+(days)
|
||||
|> date_from_iso_days()
|
||||
|
||||
@@ -1889,7 +1646,7 @@ defmodule Calendar.ISO do
|
||||
months_in_year = 12
|
||||
total_months = year * months_in_year + month + months - 1
|
||||
|
||||
new_year = floor_div_positive_divisor(total_months, months_in_year)
|
||||
new_year = Integer.floor_div(total_months, months_in_year)
|
||||
|
||||
new_month =
|
||||
case rem(total_months, months_in_year) + 1 do
|
||||
@@ -1929,7 +1686,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
def shift_time_unit({_days, _day_fraction} = iso_days, value, unit)
|
||||
when unit in [:second, :millisecond, :microsecond, :nanosecond] or is_integer(unit) do
|
||||
ppd = System.convert_time_unit(86_400, :second, unit)
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
add_day_fraction_to_iso_days(iso_days, value, ppd)
|
||||
end
|
||||
|
||||
@@ -1978,7 +1735,7 @@ defmodule Calendar.ISO do
|
||||
}) do
|
||||
[
|
||||
month: year * 12 + month,
|
||||
second: week * 7 * 86_400 + day * 86_400 + hour * 3600 + minute * 60 + second,
|
||||
second: week * 7 * 86400 + day * 86400 + hour * 3600 + minute * 60 + second,
|
||||
microsecond: microsecond
|
||||
]
|
||||
end
|
||||
@@ -2012,7 +1769,7 @@ defmodule Calendar.ISO do
|
||||
|
||||
if total in @unix_range_microseconds do
|
||||
microseconds = Integer.mod(total, @microseconds_per_second)
|
||||
seconds = @unix_epoch + floor_div_positive_divisor(total, @microseconds_per_second)
|
||||
seconds = @unix_epoch + Integer.floor_div(total, @microseconds_per_second)
|
||||
precision = precision_for_unit(unit)
|
||||
{date, time} = iso_seconds_to_datetime(seconds)
|
||||
{:ok, date, time, {microseconds, precision}}
|
||||
@@ -2034,13 +1791,16 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
|
||||
defp parse_microsecond("." <> rest) do
|
||||
case parse_microsecond(rest, 0, []) do
|
||||
{[], 0, _} ->
|
||||
case parse_microsecond(rest, 0, "") do
|
||||
{"", 0, _} ->
|
||||
:error
|
||||
|
||||
{microsecond, precision, rest} ->
|
||||
scale = scale_factor(precision)
|
||||
{{:erlang.list_to_integer(microsecond) * scale, precision}, rest}
|
||||
{microsecond, precision, rest} when precision in 1..6 ->
|
||||
pad = String.duplicate("0", 6 - byte_size(microsecond))
|
||||
{{String.to_integer(microsecond <> pad), precision}, rest}
|
||||
|
||||
{microsecond, _precision, rest} ->
|
||||
{{String.to_integer(binary_part(microsecond, 0, 6)), 6}, rest}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2052,42 +1812,34 @@ defmodule Calendar.ISO do
|
||||
{{0, 0}, rest}
|
||||
end
|
||||
|
||||
defp parse_microsecond(<<head, tail::binary>>, 6, acc) when head in ?0..?9,
|
||||
do: parse_microsecond(tail, 6, acc)
|
||||
|
||||
defp parse_microsecond(<<head, tail::binary>>, precision, acc) when head in ?0..?9,
|
||||
do: parse_microsecond(tail, precision + 1, [head | acc])
|
||||
do: parse_microsecond(tail, precision + 1, <<acc::binary, head>>)
|
||||
|
||||
defp parse_microsecond(rest, precision, acc) do
|
||||
{:lists.reverse(acc), precision, rest}
|
||||
end
|
||||
defp parse_microsecond(rest, precision, acc), do: {acc, precision, rest}
|
||||
|
||||
defp parse_offset(""), do: {nil, ""}
|
||||
defp parse_offset("Z"), do: {0, ""}
|
||||
defp parse_offset("-00:00"), do: :error
|
||||
|
||||
defp parse_offset(<<?+, h1, h2, ?:, m1, m2, rest::binary>>),
|
||||
do: parse_offset(1, h1, h2, m1, m2, rest)
|
||||
defp parse_offset(<<?+, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(1, hour, min, rest)
|
||||
|
||||
defp parse_offset(<<?-, h1, h2, ?:, m1, m2, rest::binary>>),
|
||||
do: parse_offset(-1, h1, h2, m1, m2, rest)
|
||||
defp parse_offset(<<?-, hour::2-bytes, ?:, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(-1, hour, min, rest)
|
||||
|
||||
defp parse_offset(<<?+, h1, h2, m1, m2, rest::binary>>),
|
||||
do: parse_offset(1, h1, h2, m1, m2, rest)
|
||||
defp parse_offset(<<?+, hour::2-bytes, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(1, hour, min, rest)
|
||||
|
||||
defp parse_offset(<<?-, h1, h2, m1, m2, rest::binary>>),
|
||||
do: parse_offset(-1, h1, h2, m1, m2, rest)
|
||||
defp parse_offset(<<?-, hour::2-bytes, min::2-bytes, rest::binary>>),
|
||||
do: parse_offset(-1, hour, min, rest)
|
||||
|
||||
defp parse_offset(<<?+, h1, h2, rest::binary>>), do: parse_offset(1, h1, h2, ?0, ?0, rest)
|
||||
defp parse_offset(<<?-, h1, h2, rest::binary>>), do: parse_offset(-1, h1, h2, ?0, ?0, rest)
|
||||
defp parse_offset(<<?+, hour::2-bytes, rest::binary>>), do: parse_offset(1, hour, "00", rest)
|
||||
defp parse_offset(<<?-, hour::2-bytes, rest::binary>>), do: parse_offset(-1, hour, "00", rest)
|
||||
defp parse_offset(_), do: :error
|
||||
|
||||
defp parse_offset(sign, h1, h2, m1, m2, rest) do
|
||||
with true <- h1 in ?0..?2 and h2 in ?0..?9,
|
||||
true <- m1 in ?0..?5 and m2 in ?0..?9,
|
||||
hour = (h1 - ?0) * 10 + h2 - ?0,
|
||||
min = (m1 - ?0) * 10 + m2 - ?0,
|
||||
true <- hour < 24 do
|
||||
defp parse_offset(sign, hour, min, rest) do
|
||||
with {hour, ""} when hour < 24 <- Integer.parse(hour),
|
||||
{min, ""} when min < 60 <- Integer.parse(min) do
|
||||
{(hour * 60 + min) * 60 * sign, rest}
|
||||
else
|
||||
_ -> :error
|
||||
@@ -2134,12 +1886,9 @@ defmodule Calendar.ISO do
|
||||
end
|
||||
end
|
||||
|
||||
defp leap_day_offset(_year, month) when month < 3, do: 0
|
||||
|
||||
defp leap_day_offset(year, _month) do
|
||||
if leap_year?(year), do: 1, else: 0
|
||||
end
|
||||
|
||||
# Note that this function does not add the extra leap day for a leap year.
|
||||
# If you want to add that leap day when appropriate,
|
||||
# add the result of leap_day_offset/2 to the result of days_before_month/1.
|
||||
defp days_before_month(1), do: 0
|
||||
defp days_before_month(2), do: 31
|
||||
defp days_before_month(3), do: 59
|
||||
@@ -2153,6 +1902,120 @@ defmodule Calendar.ISO do
|
||||
defp days_before_month(11), do: 304
|
||||
defp days_before_month(12), do: 334
|
||||
|
||||
defp leap_day_offset(_year, month) when month < 3, do: 0
|
||||
|
||||
defp leap_day_offset(year, _month) do
|
||||
if leap_year?(year), do: 1, else: 0
|
||||
end
|
||||
|
||||
defp days_to_year(days) when days < 0 do
|
||||
year_estimate = -div(-days, @days_per_nonleap_year) - 1
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_to_end_of_epoch(year_estimate))
|
||||
|
||||
leap_year_pad = if leap_year?(year), do: 1, else: 0
|
||||
{year, leap_year_pad + @days_per_nonleap_year + days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(days) do
|
||||
year_estimate = div(days, @days_per_nonleap_year)
|
||||
|
||||
{year, days_before_year} =
|
||||
days_to_year(year_estimate, days, days_in_previous_years(year_estimate))
|
||||
|
||||
{year, days - days_before_year}
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when year < 0 and days1 >= days2 do
|
||||
days_to_year(year + 1, days1, days_to_end_of_epoch(year + 1))
|
||||
end
|
||||
|
||||
defp days_to_year(year, days1, days2) when year >= 0 and days1 < days2 do
|
||||
days_to_year(year - 1, days1, days_in_previous_years(year - 1))
|
||||
end
|
||||
|
||||
defp days_to_year(year, _days1, days2) do
|
||||
{year, days2}
|
||||
end
|
||||
|
||||
defp days_to_end_of_epoch(year) when year < 0 do
|
||||
previous_year = year + 1
|
||||
|
||||
div(previous_year, 4) - div(previous_year, 100) + div(previous_year, 400) +
|
||||
previous_year * @days_per_nonleap_year
|
||||
end
|
||||
|
||||
defp days_in_previous_years(0), do: 0
|
||||
|
||||
# A concise version of the algorithm would use floor_div instead of div.
|
||||
# However, floor_div would check the operands on every operation.
|
||||
# We optimize this by providing a positive and negative version of each algorithm.
|
||||
defp days_in_previous_years(year) when year > 0 do
|
||||
previous_year = year - 1
|
||||
|
||||
div(previous_year, 4) - div(previous_year, 100) +
|
||||
div(previous_year, 400) + previous_year * @days_per_nonleap_year +
|
||||
@days_per_leap_year
|
||||
end
|
||||
|
||||
defp days_in_previous_years(year) when year < 0 do
|
||||
previous_year = year - 1
|
||||
|
||||
div(year, 4) - div(year, 100) +
|
||||
div(year, 400) - 1 + previous_year * @days_per_nonleap_year +
|
||||
@days_per_leap_year
|
||||
end
|
||||
|
||||
# Note that 0 is the first day of the month.
|
||||
defp year_day_to_year_date(_extra_day, day_of_year) when day_of_year < 31 do
|
||||
{1, day_of_year}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 59 + extra_day do
|
||||
{2, day_of_year - 31}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 90 + extra_day do
|
||||
{3, day_of_year - (59 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 120 + extra_day do
|
||||
{4, day_of_year - (90 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 151 + extra_day do
|
||||
{5, day_of_year - (120 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 181 + extra_day do
|
||||
{6, day_of_year - (151 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 212 + extra_day do
|
||||
{7, day_of_year - (181 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 243 + extra_day do
|
||||
{8, day_of_year - (212 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 273 + extra_day do
|
||||
{9, day_of_year - (243 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 304 + extra_day do
|
||||
{10, day_of_year - (273 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) when day_of_year < 334 + extra_day do
|
||||
{11, day_of_year - (304 + extra_day)}
|
||||
end
|
||||
|
||||
defp year_day_to_year_date(extra_day, day_of_year) do
|
||||
{12, day_of_year - (334 + extra_day)}
|
||||
end
|
||||
|
||||
defp iso_seconds_to_datetime(seconds) do
|
||||
{days, rest_seconds} = div_rem(seconds, @seconds_per_day)
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule NaiveDateTime do
|
||||
@moduledoc """
|
||||
A NaiveDateTime struct (without a time zone) and functions.
|
||||
@@ -40,10 +36,10 @@ defmodule NaiveDateTime do
|
||||
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `NaiveDateTime` struct fields. For proper comparison
|
||||
between naive datetimes, use the `compare/2`, `after?/2` and `before?/2` functions.
|
||||
The existence of the `compare/2` function in this module also allows
|
||||
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
|
||||
maximum naive datetime of an `Enum`. For example:
|
||||
between naive datetimes, use the `compare/2` function. The existence of the
|
||||
`compare/2` function in this module also allows using `Enum.min/2` and
|
||||
`Enum.max/2` functions to get the minimum and maximum naive datetime of an
|
||||
`Enum`. For example:
|
||||
|
||||
iex> Enum.min([~N[2020-01-01 23:00:07], ~N[2000-01-01 23:00:07]], NaiveDateTime)
|
||||
~N[2000-01-01 23:00:07]
|
||||
@@ -391,20 +387,13 @@ defmodule NaiveDateTime do
|
||||
@doc """
|
||||
Adds a specified amount of time to a `NaiveDateTime`.
|
||||
|
||||
> #### Prefer `shift/2` {: .info}
|
||||
>
|
||||
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
|
||||
>
|
||||
> `add/3` provides a lower-level API which only supports fixed units
|
||||
> such as `:hour` and `:second`, but not `:month` (as the exact length
|
||||
> of a month depends on the current month). `add/3` always considers
|
||||
> the unit to be computed according to the `Calendar.ISO`.
|
||||
|
||||
Accepts an `amount_to_add` in any `unit`. `unit` can be `:day`,
|
||||
`:hour`, `:minute`, `:second` or any subsecond precision from
|
||||
`t:System.time_unit/0` for convenience but ultimately they are
|
||||
all converted to microseconds. Negative values will move backwards
|
||||
in time and the default precision is `:second`.
|
||||
`t:System.time_unit/0`. It defaults to `:second`. Negative values
|
||||
will move backwards in time.
|
||||
|
||||
This function always consider the unit to be computed according
|
||||
to the `Calendar.ISO`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -454,6 +443,8 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.add(dt, 21, :second)
|
||||
~N[2000-02-29 23:00:28]
|
||||
|
||||
To shift a naive datetime by a `Duration` and according to its underlying calendar, use `NaiveDateTime.shift/2`.
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec add(Calendar.naive_datetime(), integer, :day | :hour | :minute | System.time_unit()) :: t
|
||||
@@ -715,18 +706,16 @@ defmodule NaiveDateTime do
|
||||
|
||||
"""
|
||||
@spec to_date(Calendar.naive_datetime()) :: Date.t()
|
||||
def to_date(
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
calendar: calendar,
|
||||
hour: _,
|
||||
minute: _,
|
||||
second: _,
|
||||
microsecond: _
|
||||
} = _naive_datetime
|
||||
) do
|
||||
def to_date(%{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
calendar: calendar,
|
||||
hour: _,
|
||||
minute: _,
|
||||
second: _,
|
||||
microsecond: _
|
||||
}) do
|
||||
%Date{year: year, month: month, day: day, calendar: calendar}
|
||||
end
|
||||
|
||||
@@ -743,18 +732,16 @@ defmodule NaiveDateTime do
|
||||
|
||||
"""
|
||||
@spec to_time(Calendar.naive_datetime()) :: Time.t()
|
||||
def to_time(
|
||||
%{
|
||||
year: _,
|
||||
month: _,
|
||||
day: _,
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
} = _naive_datetime
|
||||
) do
|
||||
def to_time(%{
|
||||
year: _,
|
||||
month: _,
|
||||
day: _,
|
||||
calendar: calendar,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond
|
||||
}) do
|
||||
%Time{
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
@@ -767,10 +754,10 @@ defmodule NaiveDateTime do
|
||||
@doc """
|
||||
Converts the given naive datetime to a string according to its calendar.
|
||||
|
||||
For readability, this function follows the RFC3339 suggestion of removing
|
||||
For redability, this function follows the RFC3339 suggestion of removing
|
||||
the "T" separator between the date and time components.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> NaiveDateTime.to_string(~N[2000-02-28 23:00:13])
|
||||
"2000-02-28 23:00:13"
|
||||
@@ -917,7 +904,7 @@ defmodule NaiveDateTime do
|
||||
Only supports converting naive datetimes which are in the ISO calendar,
|
||||
attempting to convert naive datetimes from other calendars will raise.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> NaiveDateTime.to_iso8601(~N[2000-02-28 23:00:13])
|
||||
"2000-02-28T23:00:13"
|
||||
@@ -943,19 +930,6 @@ defmodule NaiveDateTime do
|
||||
|
||||
def to_iso8601(%{calendar: Calendar.ISO} = naive_datetime, format)
|
||||
when format in [:basic, :extended] do
|
||||
naive_datetime
|
||||
|> to_iso8601_iodata(format)
|
||||
|> IO.iodata_to_binary()
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = naive_datetime, format)
|
||||
when format in [:basic, :extended] do
|
||||
naive_datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
defp to_iso8601_iodata(naive_datetime, format) do
|
||||
%{
|
||||
year: year,
|
||||
month: month,
|
||||
@@ -966,11 +940,14 @@ defmodule NaiveDateTime do
|
||||
microsecond: microsecond
|
||||
} = naive_datetime
|
||||
|
||||
[
|
||||
Calendar.ISO.date_to_iodata(year, month, day, format),
|
||||
?T,
|
||||
Calendar.ISO.time_to_iodata(hour, minute, second, microsecond, format)
|
||||
]
|
||||
Calendar.ISO.date_to_string(year, month, day, format) <>
|
||||
"T" <> Calendar.ISO.time_to_string(hour, minute, second, microsecond, format)
|
||||
end
|
||||
|
||||
def to_iso8601(%{calendar: _} = naive_datetime, format) when format in [:basic, :extended] do
|
||||
naive_datetime
|
||||
|> convert!(Calendar.ISO)
|
||||
|> to_iso8601(format)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1150,18 +1127,16 @@ defmodule NaiveDateTime do
|
||||
"""
|
||||
@doc since: "1.11.0"
|
||||
@spec to_gregorian_seconds(Calendar.naive_datetime()) :: {integer(), non_neg_integer()}
|
||||
def to_gregorian_seconds(
|
||||
%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision}
|
||||
} = _naive_datetime
|
||||
) do
|
||||
def to_gregorian_seconds(%{
|
||||
calendar: calendar,
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: {microsecond, precision}
|
||||
}) do
|
||||
{days, day_fraction} =
|
||||
calendar.naive_datetime_to_iso_days(
|
||||
year,
|
||||
@@ -1272,7 +1247,7 @@ defmodule NaiveDateTime do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> NaiveDateTime.convert(~N[2000-01-01 13:30:15], Calendar.Holocene)
|
||||
@@ -1320,7 +1295,7 @@ defmodule NaiveDateTime do
|
||||
if Calendar.compatible_calendars?(ndt_calendar, calendar) do
|
||||
result_naive_datetime =
|
||||
naive_datetime
|
||||
|> to_iso_days()
|
||||
|> to_iso_days
|
||||
|> from_iso_days(calendar, precision)
|
||||
|
||||
{:ok, result_naive_datetime}
|
||||
@@ -1338,7 +1313,7 @@ defmodule NaiveDateTime do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> NaiveDateTime.convert!(~N[2000-01-01 13:30:15], Calendar.Holocene)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Time do
|
||||
@moduledoc """
|
||||
A Time struct and functions.
|
||||
@@ -35,10 +31,9 @@ defmodule Time do
|
||||
|
||||
Comparisons in Elixir using `==/2`, `>/2`, `</2` and similar are structural
|
||||
and based on the `Time` struct fields. For proper comparison between
|
||||
times, use the `compare/2`, `after?/2` and `before?/2` functions.
|
||||
The existence of the `compare/2` function in this module also allows
|
||||
using `Enum.min/2` and `Enum.max/2` functions to get the minimum and
|
||||
maximum time of an `Enum`. For example:
|
||||
times, use the `compare/2` function. The existence of the `compare/2`
|
||||
function in this module also allows using `Enum.min/2` and `Enum.max/2`
|
||||
functions to get the minimum and maximum time of an `Enum`. For example:
|
||||
|
||||
iex> Enum.min([~T[23:00:07.001], ~T[10:00:07.001]], Time)
|
||||
~T[10:00:07.001]
|
||||
@@ -60,52 +55,17 @@ defmodule Time do
|
||||
@doc """
|
||||
Returns the current time in UTC.
|
||||
|
||||
You can pass a time unit to automatically truncate the resulting time.
|
||||
|
||||
The default unit if none gets passed is `:native` which results on a default resolution of microseconds.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> time = Time.utc_now()
|
||||
iex> time.hour >= 0
|
||||
true
|
||||
|
||||
iex> time = Time.utc_now(:second)
|
||||
iex> time.microsecond
|
||||
{0, 0}
|
||||
|
||||
"""
|
||||
@doc since: "1.4.0"
|
||||
@spec utc_now(Calendar.calendar() | :native | :microsecond | :millisecond | :second) :: t
|
||||
def utc_now(calendar_or_time_unit \\ Calendar.ISO) do
|
||||
case calendar_or_time_unit do
|
||||
unit when unit in [:native, :microsecond, :millisecond, :second] ->
|
||||
utc_now(unit, Calendar.ISO)
|
||||
|
||||
calendar ->
|
||||
utc_now(:native, calendar)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the current time in UTC, supporting a precision and a specific calendar.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> time = Time.utc_now(:microsecond, Calendar.ISO)
|
||||
iex> time.hour >= 0
|
||||
true
|
||||
|
||||
iex> time = Time.utc_now(:second, Calendar.ISO)
|
||||
iex> time.microsecond
|
||||
{0, 0}
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec utc_now(:native | :microsecond | :millisecond | :second, Calendar.calendar()) :: t
|
||||
def utc_now(time_unit, calendar)
|
||||
when time_unit in [:native, :microsecond, :millisecond, :second] do
|
||||
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(System.os_time(time_unit), time_unit)
|
||||
@spec utc_now(Calendar.calendar()) :: t
|
||||
def utc_now(calendar \\ Calendar.ISO) do
|
||||
{:ok, _, time, microsecond} = Calendar.ISO.from_unix(:os.system_time(), :native)
|
||||
{hour, minute, second} = time
|
||||
|
||||
iso_time = %Time{
|
||||
@@ -225,7 +185,7 @@ defmodule Time do
|
||||
@doc """
|
||||
Converts the given `time` to a string.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> Time.to_string(~T[23:00:00])
|
||||
"23:00:00"
|
||||
@@ -334,7 +294,7 @@ defmodule Time do
|
||||
format, for human readability. It also supports the "basic" format through
|
||||
passing the `:basic` option.
|
||||
|
||||
## Examples
|
||||
### Examples
|
||||
|
||||
iex> Time.to_iso8601(~T[23:00:13])
|
||||
"23:00:13"
|
||||
@@ -505,18 +465,13 @@ defmodule Time do
|
||||
@doc """
|
||||
Adds the `amount_to_add` of `unit`s to the given `time`.
|
||||
|
||||
> #### Prefer `shift/2` {: .info}
|
||||
>
|
||||
> Prefer `shift/2` over `add/3`, as it offers a more ergonomic API.
|
||||
>
|
||||
> `add/3` always considers the unit to be computed according to
|
||||
> the `Calendar.ISO`.
|
||||
|
||||
Accepts an `amount_to_add` in any `unit`. `unit` can be
|
||||
`:hour`, `:minute`, `:second` or any subsecond precision from
|
||||
`t:System.time_unit/0` for convenience but ultimately they are
|
||||
all converted to microseconds. Negative values will move backwards
|
||||
in time and the default precision is `:second`.
|
||||
`t:System.time_unit/0`. It defaults to `:second`. Negative values
|
||||
will move backwards in time.
|
||||
|
||||
This function always consider the unit to be computed according
|
||||
to the `Calendar.ISO`.
|
||||
|
||||
Note the result value represents the time of day, meaning that it is cyclic,
|
||||
for instance, it will never go over 24 hours for the ISO calendar.
|
||||
@@ -554,6 +509,8 @@ defmodule Time do
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
To shift a time by a `Duration` and according to its underlying calendar, use `Time.shift/2`.
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
|
||||
@@ -784,7 +741,7 @@ defmodule Time do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> Time.convert(~T[13:30:15], Calendar.Holocene)
|
||||
@@ -840,7 +797,7 @@ defmodule Time do
|
||||
## Examples
|
||||
|
||||
Imagine someone implements `Calendar.Holocene`, a calendar based on the
|
||||
Gregorian calendar that adds exactly 10 000 years to the current Gregorian
|
||||
Gregorian calendar that adds exactly 10,000 years to the current Gregorian
|
||||
year:
|
||||
|
||||
iex> Time.convert!(~T[13:30:15], Calendar.Holocene)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Calendar.TimeZoneDatabase do
|
||||
@moduledoc """
|
||||
This module defines a behaviour for providing time zone data.
|
||||
|
||||
+120
-291
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Code do
|
||||
@moduledoc ~S"""
|
||||
Utilities for managing code compilation, code evaluation, and code loading.
|
||||
@@ -248,82 +244,19 @@ defmodule Code do
|
||||
"""
|
||||
@type position() :: line() | {line :: pos_integer(), column :: pos_integer()}
|
||||
|
||||
@typedoc """
|
||||
Options for code formatting functions.
|
||||
"""
|
||||
@type format_opt ::
|
||||
{:file, binary()}
|
||||
| {:line, pos_integer()}
|
||||
| {:line_length, pos_integer()}
|
||||
| {:locals_without_parens, keyword()}
|
||||
| {:force_do_end_blocks, boolean()}
|
||||
| {:migrate, boolean()}
|
||||
| {:migrate_bitstring_modifiers, boolean()}
|
||||
| {:migrate_call_parens_on_pipe, boolean()}
|
||||
| {:migrate_charlists_as_sigils, boolean()}
|
||||
| {:migrate_unless, boolean()}
|
||||
| {atom(), term()}
|
||||
|
||||
@typedoc """
|
||||
Options for `quoted_to_algebra/2`.
|
||||
"""
|
||||
@type quoted_to_algebra_opt ::
|
||||
{:line, pos_integer() | nil}
|
||||
| {:escape, boolean()}
|
||||
| {:locals_without_parens, keyword()}
|
||||
| {:comments, [term()]}
|
||||
|
||||
@typedoc """
|
||||
Options for parsing functions that convert strings to quoted expressions.
|
||||
"""
|
||||
@type parser_opts :: [
|
||||
file: binary(),
|
||||
line: pos_integer(),
|
||||
column: pos_integer(),
|
||||
indentation: non_neg_integer(),
|
||||
columns: boolean(),
|
||||
unescape: boolean(),
|
||||
existing_atoms_only: boolean(),
|
||||
token_metadata: boolean(),
|
||||
literal_encoder: (term(), Macro.metadata() -> term()),
|
||||
static_atoms_encoder: (binary(), Macro.metadata() -> {:ok, term()} | {:error, binary()}),
|
||||
emit_warnings: boolean()
|
||||
]
|
||||
|
||||
@typedoc """
|
||||
Options for evaluation environment, accepted by `env_for_eval/1`.
|
||||
"""
|
||||
@type env_eval_opt ::
|
||||
{:file, binary()}
|
||||
| {:line, pos_integer()}
|
||||
| {:module, module()}
|
||||
|
||||
@typedoc """
|
||||
Options for evaluation functions like `eval_string/3`, `eval_quoted/3`
|
||||
and `eval_quoted_with_env/4`.
|
||||
"""
|
||||
@type eval_opt ::
|
||||
{:prune_binding, boolean()}
|
||||
| {:dbg_callback, {module(), atom(), list()}}
|
||||
|
||||
@boolean_compiler_options [
|
||||
:docs,
|
||||
:debug_info,
|
||||
:ignore_already_consolidated,
|
||||
:ignore_module_conflict,
|
||||
:infer_signatures,
|
||||
:relative_paths
|
||||
]
|
||||
|
||||
@list_compiler_options [:tracers, :parser_options]
|
||||
@list_compiler_options [:no_warn_undefined, :tracers, :parser_options]
|
||||
|
||||
@available_compiler_options @boolean_compiler_options ++
|
||||
@list_compiler_options ++
|
||||
[
|
||||
:on_undefined_variable,
|
||||
:infer_signatures,
|
||||
:no_warn_undefined,
|
||||
:module_definition
|
||||
]
|
||||
@list_compiler_options ++ [:on_undefined_variable]
|
||||
|
||||
@doc """
|
||||
Lists all required files.
|
||||
@@ -410,7 +343,7 @@ defmodule Code do
|
||||
|
||||
* `:cache` - (since v1.15.0) when true, the code path is cached
|
||||
the first time it is traversed in order to reduce file system
|
||||
operations.
|
||||
operations. It requires Erlang/OTP 26, otherwise it is a no-op.
|
||||
|
||||
"""
|
||||
@spec append_path(Path.t(), cache: boolean()) :: true | false
|
||||
@@ -441,7 +374,7 @@ defmodule Code do
|
||||
|
||||
* `:cache` - (since v1.15.0) when true, the code path is cached
|
||||
the first time it is traversed in order to reduce file system
|
||||
operations.
|
||||
operations. It requires Erlang/OTP 26, otherwise it is a no-op.
|
||||
|
||||
"""
|
||||
@spec prepend_path(Path.t(), cache: boolean()) :: boolean()
|
||||
@@ -470,7 +403,7 @@ defmodule Code do
|
||||
|
||||
* `:cache` - when true, the code path is cached the first time
|
||||
it is traversed in order to reduce file system operations.
|
||||
|
||||
It requires Erlang/OTP 26, otherwise it is a no-op.
|
||||
"""
|
||||
@doc since: "1.15.0"
|
||||
@spec prepend_paths([Path.t()], cache: boolean()) :: :ok
|
||||
@@ -499,7 +432,7 @@ defmodule Code do
|
||||
|
||||
* `:cache` - when true, the code path is cached the first time
|
||||
it is traversed in order to reduce file system operations.
|
||||
|
||||
It requires Erlang/OTP 26, otherwise it is a no-op.
|
||||
"""
|
||||
@doc since: "1.15.0"
|
||||
@spec append_paths([Path.t()], cache: boolean()) :: :ok
|
||||
@@ -579,11 +512,9 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
It accepts the same options as both `env_for_eval/1` and
|
||||
`eval_quoted_with_env/4`. Additionally, you may also pass an environment
|
||||
as third argument, so the evaluation happens within that environment.
|
||||
|
||||
## Return
|
||||
It accepts the same options as `env_for_eval/1`. Additionally, you may
|
||||
also pass an environment as second argument, so the evaluation happens
|
||||
within that environment.
|
||||
|
||||
Returns a tuple of the form `{value, binding}`, where `value` is the value
|
||||
returned from evaluating `string`. If an error occurs while evaluating
|
||||
@@ -613,40 +544,32 @@ defmodule Code do
|
||||
iex> Enum.sort(binding)
|
||||
[a: 3, b: 2]
|
||||
|
||||
For convenience, you can pass `__ENV__/0` as the `opts_or_env` argument and
|
||||
For convenience, you can pass `__ENV__/0` as the `opts` argument and
|
||||
all imports, requires and aliases defined in the current environment
|
||||
will be automatically carried over:
|
||||
|
||||
iex> require Integer, warn: false
|
||||
iex> {result, binding} = Code.eval_string("if Integer.is_odd(a), do: a + b", [a: 1, b: 2], __ENV__)
|
||||
iex> {result, binding} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
|
||||
iex> result
|
||||
3
|
||||
iex> Enum.sort(binding)
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec eval_string(List.Chars.t(), binding, Macro.Env.t() | [eval_opt | env_eval_opt]) ::
|
||||
{term, binding}
|
||||
def eval_string(string, binding \\ [], opts_or_env \\ [])
|
||||
@spec eval_string(List.Chars.t(), binding, Macro.Env.t() | keyword) :: {term, binding}
|
||||
def eval_string(string, binding \\ [], opts \\ [])
|
||||
|
||||
def eval_string(string, binding, %Macro.Env{} = env) do
|
||||
validated_eval_string(string, validate_binding(binding), env_for_eval(env), [])
|
||||
validated_eval_string(string, binding, env)
|
||||
end
|
||||
|
||||
def eval_string(string, binding, opts) when is_list(opts) do
|
||||
validated_eval_string(string, validate_binding(binding), env_for_eval(opts), opts)
|
||||
validated_eval_string(string, binding, opts)
|
||||
end
|
||||
|
||||
defp validate_binding(binding) when is_list(binding), do: binding
|
||||
|
||||
defp validate_binding(binding) do
|
||||
raise ArgumentError, "binding must be a list, got: #{inspect(binding)}"
|
||||
end
|
||||
|
||||
defp validated_eval_string(string, binding, env, opts) do
|
||||
%{line: line, file: file} = env
|
||||
defp validated_eval_string(string, binding, opts_or_env) do
|
||||
%{line: line, file: file} = env = env_for_eval(opts_or_env)
|
||||
forms = :elixir.string_to_quoted!(to_charlist(string), line, 1, file, [])
|
||||
{value, binding, _env} = eval_verify(:eval_forms, [forms, binding, env, opts])
|
||||
{value, binding, _env} = eval_verify(:eval_forms, [forms, binding, env])
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@@ -687,8 +610,7 @@ defmodule Code do
|
||||
|
||||
"""
|
||||
@doc since: "1.15.0"
|
||||
@spec with_diagnostics([log: boolean()], (-> result)) ::
|
||||
{result, [diagnostic(:warning | :error)]}
|
||||
@spec with_diagnostics(keyword(), (-> result)) :: {result, [diagnostic(:warning | :error)]}
|
||||
when result: term()
|
||||
def with_diagnostics(opts \\ [], fun) do
|
||||
value = :erlang.get(:elixir_code_diagnostics)
|
||||
@@ -721,7 +643,7 @@ defmodule Code do
|
||||
Defaults to `true`.
|
||||
"""
|
||||
@doc since: "1.15.0"
|
||||
@spec print_diagnostic(diagnostic(:warning | :error), snippet: boolean()) :: :ok
|
||||
@spec print_diagnostic(diagnostic(:warning | :error), keyword()) :: :ok
|
||||
def print_diagnostic(diagnostic, opts \\ []) do
|
||||
read_snippet? = Keyword.get(opts, :snippet, true)
|
||||
:elixir_errors.print_diagnostic(diagnostic, read_snippet?)
|
||||
@@ -745,7 +667,7 @@ defmodule Code do
|
||||
* `:line` - the line the string starts, used for error reporting
|
||||
|
||||
* `:line_length` - the line length to aim for when formatting
|
||||
the document. Defaults to `98`. This value indicates when an expression
|
||||
the document. Defaults to 98. This value indicates when an expression
|
||||
should be broken over multiple lines but it is not guaranteed
|
||||
to do so. See the "Line length" section below for more information
|
||||
|
||||
@@ -774,14 +696,6 @@ defmodule Code do
|
||||
modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
|
||||
Defaults to the value of the `:migrate` option. This option changes the AST.
|
||||
|
||||
* `:migrate_call_parens_on_pipe` (since v1.19.0) - when `true`,
|
||||
formats calls on the right-hand side of the pipe operator to always include
|
||||
parentheses, for example `foo |> bar` becomes `foo |> bar()` and
|
||||
`foo |> mod.fun` becomes `foo |> mod.fun()`.
|
||||
Parentheses are always added for qualified calls like `foo |> Bar.bar` even
|
||||
when this option is `false`.
|
||||
Defaults to the value of the `:migrate` option. This option changes the AST.
|
||||
|
||||
* `:migrate_charlists_as_sigils` (since v1.18.0) - when `true`,
|
||||
formats charlists as [`~c`](`Kernel.sigil_c/2`) sigils, for example
|
||||
`'foo'` becomes `~c"foo"`.
|
||||
@@ -1049,18 +963,12 @@ defmodule Code do
|
||||
the code representation (AST). While the formatter can preserve code
|
||||
comments between expressions and function arguments, the formatter
|
||||
cannot currently preserve them around operators. For example, the following
|
||||
code:
|
||||
code will move the code comments to before the operator usage:
|
||||
|
||||
foo() ||
|
||||
# also check for bar
|
||||
bar()
|
||||
|
||||
will move the code comments to before the operator usage:
|
||||
|
||||
# also check for bar
|
||||
foo() ||
|
||||
bar()
|
||||
|
||||
In some situations, code comments can be seen as ambiguous by the formatter.
|
||||
For example, the comment in the anonymous function below
|
||||
|
||||
@@ -1108,9 +1016,9 @@ defmodule Code do
|
||||
address the deprecation warnings.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec format_string!(binary, [format_opt]) :: iodata
|
||||
@spec format_string!(binary, keyword) :: iodata
|
||||
def format_string!(string, opts \\ []) when is_binary(string) and is_list(opts) do
|
||||
{line_length, opts} = Keyword.pop(opts, :line_length, 98)
|
||||
line_length = Keyword.get(opts, :line_length, 98)
|
||||
|
||||
to_quoted_opts =
|
||||
[
|
||||
@@ -1133,7 +1041,7 @@ defmodule Code do
|
||||
available options.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec format_file!(binary, [format_opt]) :: iodata
|
||||
@spec format_file!(binary, keyword) :: iodata
|
||||
def format_file!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
string = File.read!(file)
|
||||
formatted = format_string!(string, [file: file, line: 1] ++ opts)
|
||||
@@ -1148,9 +1056,7 @@ defmodule Code do
|
||||
Macro arguments are typically transformed by unquoting them into the
|
||||
returned quoted expressions (instead of evaluated).
|
||||
|
||||
See `eval_string/3` for a description of arguments and return types.
|
||||
It accepts the same options as both `env_for_eval/1` and
|
||||
`eval_quoted_with_env/4`.
|
||||
See `eval_string/3` for a description of `binding` and `opts`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1172,20 +1078,11 @@ defmodule Code do
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | [eval_opt | env_eval_opt]) ::
|
||||
{term, binding}
|
||||
def eval_quoted(quoted, binding \\ [], env_or_opts \\ [])
|
||||
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | keyword) :: {term, binding}
|
||||
def eval_quoted(quoted, binding \\ [], env_or_opts \\ []) do
|
||||
{value, binding, _env} =
|
||||
eval_verify(:eval_quoted, [quoted, binding, env_for_eval(env_or_opts)])
|
||||
|
||||
def eval_quoted(quoted, binding, %Macro.Env{} = env) do
|
||||
eval_quoted(quoted, validate_binding(binding), env_for_eval(env), [])
|
||||
end
|
||||
|
||||
def eval_quoted(quoted, binding, opts) when is_list(opts) do
|
||||
eval_quoted(quoted, validate_binding(binding), env_for_eval(opts), opts)
|
||||
end
|
||||
|
||||
defp eval_quoted(quoted, binding, env, opts) do
|
||||
{value, binding, _env} = eval_verify(:eval_quoted, [quoted, binding, env, opts])
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@@ -1212,10 +1109,8 @@ defmodule Code do
|
||||
* `:line` - the line on which the script starts
|
||||
|
||||
* `:module` - the module to run the environment on
|
||||
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec env_for_eval(Macro.Env.t() | [env_eval_opt]) :: Macro.Env.t()
|
||||
def env_for_eval(env_or_opts), do: :elixir.env_for_eval(env_or_opts)
|
||||
|
||||
@doc """
|
||||
@@ -1235,13 +1130,9 @@ defmodule Code do
|
||||
by the modules. You can submit to the `:on_module` tracer event
|
||||
and access the variables used by the module from its environment.
|
||||
|
||||
* `:dbg_callback` - (since v1.20.0) overrides the behaviour of `dbg/2`
|
||||
used in the evaluated code. It must be a `{module, function, args}`
|
||||
tuple, see `dbg/2` for more details.
|
||||
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), [eval_opt]) ::
|
||||
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), keyword) ::
|
||||
{term, binding, Macro.Env.t()}
|
||||
def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ [])
|
||||
when is_list(binding) do
|
||||
@@ -1260,14 +1151,10 @@ defmodule Code do
|
||||
Defaults to `"nofile"`.
|
||||
|
||||
* `:line` - the starting line of the string being parsed.
|
||||
Defaults to `1`.
|
||||
Defaults to 1.
|
||||
|
||||
* `:column` - (since v1.11.0) the starting column of the string being parsed.
|
||||
Defaults to `1`.
|
||||
|
||||
* `:indentation` - (since v1.19.0) the indentation for the string being parsed.
|
||||
This is useful when the code parsed is embedded within another document.
|
||||
Defaults to `0`.
|
||||
Defaults to 1.
|
||||
|
||||
* `:columns` - when `true`, attach a `:column` key to the quoted
|
||||
metadata. Defaults to `false`.
|
||||
@@ -1319,7 +1206,7 @@ defmodule Code do
|
||||
and keyword lists.
|
||||
|
||||
The encoder function will receive the atom name (as a binary) and a
|
||||
keyword list with the current line and column. It must return
|
||||
keyword list with the current file, line and column. It must return
|
||||
`{:ok, token :: term} | {:error, reason :: binary}`.
|
||||
|
||||
The encoder function is supposed to create an atom from the given
|
||||
@@ -1343,22 +1230,21 @@ defmodule Code do
|
||||
* atoms used to represent single-letter sigils like `:sigil_X`
|
||||
(but multi-letter sigils like `:sigil_XYZ` are encoded).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.string_to_quoted("1 + 3")
|
||||
{:ok, {:+, [line: 1], [1, 3]}}
|
||||
|
||||
iex> Code.string_to_quoted("1 \ 3")
|
||||
{:error, {[line: 1, column: 4], "syntax error before: ", "\"3\""}}
|
||||
|
||||
"""
|
||||
@spec string_to_quoted(List.Chars.t(), parser_opts) ::
|
||||
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
||||
{:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}}
|
||||
def string_to_quoted(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
column = Keyword.get(opts, :column, 1)
|
||||
:elixir.string_to_quoted(to_charlist(string), line, column, file, opts)
|
||||
|
||||
case :elixir.string_to_tokens(to_charlist(string), line, column, file, opts) do
|
||||
{:ok, tokens} ->
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
|
||||
{:error, _error_msg} = error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1372,7 +1258,7 @@ defmodule Code do
|
||||
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
@spec string_to_quoted!(List.Chars.t(), parser_opts) :: Macro.t()
|
||||
@spec string_to_quoted!(List.Chars.t(), keyword) :: Macro.t()
|
||||
def string_to_quoted!(string, opts \\ []) when is_list(opts) do
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
@@ -1423,7 +1309,7 @@ defmodule Code do
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec string_to_quoted_with_comments(List.Chars.t(), parser_opts) ::
|
||||
@spec string_to_quoted_with_comments(List.Chars.t(), keyword) ::
|
||||
{:ok, Macro.t(), list(map())} | {:error, {location :: keyword, term, term}}
|
||||
def string_to_quoted_with_comments(string, opts \\ []) when is_list(opts) do
|
||||
charlist = to_charlist(string)
|
||||
@@ -1434,7 +1320,8 @@ defmodule Code do
|
||||
Process.put(:code_formatter_comments, [])
|
||||
opts = [preserve_comments: &preserve_comments/5] ++ opts
|
||||
|
||||
with {:ok, forms} <- :elixir.string_to_quoted(charlist, line, column, file, opts) do
|
||||
with {:ok, tokens} <- :elixir.string_to_tokens(charlist, line, column, file, opts),
|
||||
{:ok, forms} <- :elixir.tokens_to_quoted(tokens, file, opts) do
|
||||
comments = Enum.reverse(Process.get(:code_formatter_comments))
|
||||
{:ok, forms, comments}
|
||||
end
|
||||
@@ -1452,7 +1339,7 @@ defmodule Code do
|
||||
Check `string_to_quoted/2` for options information.
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec string_to_quoted_with_comments!(List.Chars.t(), parser_opts) :: {Macro.t(), list(map())}
|
||||
@spec string_to_quoted_with_comments!(List.Chars.t(), keyword) :: {Macro.t(), list(map())}
|
||||
def string_to_quoted_with_comments!(string, opts \\ []) do
|
||||
charlist = to_charlist(string)
|
||||
|
||||
@@ -1461,11 +1348,13 @@ defmodule Code do
|
||||
{forms, comments}
|
||||
|
||||
{:error, {location, error, token}} ->
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
column = Keyword.get(opts, :column, 1)
|
||||
input = {charlist, line, column, Keyword.get(opts, :indentation, 0)}
|
||||
:elixir_errors.parse_error(location, file, error, token, input)
|
||||
:elixir_errors.parse_error(
|
||||
location,
|
||||
Keyword.get(opts, :file, "nofile"),
|
||||
error,
|
||||
token,
|
||||
{charlist, Keyword.get(opts, :line, 1), Keyword.get(opts, :column, 1)}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1475,7 +1364,7 @@ defmodule Code do
|
||||
comment = %{
|
||||
line: line,
|
||||
column: column,
|
||||
previous_eol_count: min(previous_eol_count(tokens), last_comment_distance(comments, line)),
|
||||
previous_eol_count: previous_eol_count(tokens),
|
||||
next_eol_count: next_eol_count(rest, 0),
|
||||
text: List.to_string(comment)
|
||||
}
|
||||
@@ -1489,9 +1378,6 @@ defmodule Code do
|
||||
defp next_eol_count([?\r, ?\n | rest], count), do: next_eol_count(rest, count + 1)
|
||||
defp next_eol_count(_, count), do: count
|
||||
|
||||
defp last_comment_distance([%{line: last_line} | _], line), do: line - last_line
|
||||
defp last_comment_distance([], _line), do: :infinity
|
||||
|
||||
defp previous_eol_count([{token, {_, _, count}} | _])
|
||||
when token in [:eol, :",", :";"] and count > 0 do
|
||||
count
|
||||
@@ -1537,9 +1423,6 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts all options supported by `format_string!/2` for controlling
|
||||
code formatting, plus these additional options:
|
||||
|
||||
* `:comments` - the list of comments associated with the quoted expression.
|
||||
Defaults to `[]`. It is recommended that both `:token_metadata` and
|
||||
`:literal_encoder` options are given to `string_to_quoted_with_comments/2`
|
||||
@@ -1550,13 +1433,17 @@ defmodule Code do
|
||||
`string_to_quoted/2`, setting this option to `false` will prevent it from
|
||||
escaping the sequences twice. Defaults to `true`.
|
||||
|
||||
See `format_string!/2` for the full list of formatting options including
|
||||
`:file`, `:line`, `:line_length`, `:locals_without_parens`, `:force_do_end_blocks`,
|
||||
`:syntax_colors`, and all migration options like `:migrate_charlists_as_sigils`.
|
||||
* `:locals_without_parens` - a keyword list of name and arity
|
||||
pairs that should be kept without parens whenever possible.
|
||||
The arity may be the atom `:*`, which implies all arities of
|
||||
that name. The formatter already includes a list of functions
|
||||
and this option augments this list.
|
||||
|
||||
* `:syntax_colors` - a keyword list of colors the output is colorized.
|
||||
See `Inspect.Opts` for more information.
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec quoted_to_algebra(Macro.t(), [format_opt() | quoted_to_algebra_opt()]) ::
|
||||
Inspect.Algebra.t()
|
||||
@spec quoted_to_algebra(Macro.t(), keyword) :: Inspect.Algebra.t()
|
||||
def quoted_to_algebra(quoted, opts \\ []) do
|
||||
quoted
|
||||
|> Code.Normalizer.normalize(opts)
|
||||
@@ -1634,19 +1521,13 @@ defmodule Code do
|
||||
nil
|
||||
|
||||
:proceed ->
|
||||
try do
|
||||
loaded =
|
||||
Module.ParallelChecker.verify(fn ->
|
||||
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
|
||||
end)
|
||||
loaded =
|
||||
Module.ParallelChecker.verify(fn ->
|
||||
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
|
||||
end)
|
||||
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
catch
|
||||
kind, reason ->
|
||||
:elixir_code_server.call({:release, file})
|
||||
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||
end
|
||||
:elixir_code_server.cast({:required, file})
|
||||
loaded
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1682,7 +1563,7 @@ defmodule Code do
|
||||
## Examples
|
||||
|
||||
Code.compiler_options(infer_signatures: false)
|
||||
#=> %{infer_signatures: [:elixir]}
|
||||
#=> %{infer_signatures: true}
|
||||
|
||||
"""
|
||||
@spec compiler_options(Enumerable.t({atom, term})) :: %{optional(atom) => term}
|
||||
@@ -1741,6 +1622,9 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retains documentation in the compiled module.
|
||||
Defaults to `true`.
|
||||
|
||||
* `:debug_info` - when `true`, retains debug information in the compiled
|
||||
module. This option can also be overridden per module using the `@compile`
|
||||
directive. Defaults to `true`.
|
||||
@@ -1756,9 +1640,6 @@ defmodule Code do
|
||||
via the `:test_elixirc_options` project configuration, as there is
|
||||
typically no need to store debug chunks for test files.
|
||||
|
||||
* `:docs` - when `true`, retains documentation in the compiled module.
|
||||
Defaults to `true`.
|
||||
|
||||
* `:ignore_already_consolidated` (since v1.10.0) - when `true`, does not warn
|
||||
when a protocol has already been consolidated and a new implementation is added.
|
||||
Defaults to `false`.
|
||||
@@ -1766,41 +1647,26 @@ defmodule Code do
|
||||
* `:ignore_module_conflict` - when `true`, does not warn when a module has
|
||||
already been defined. Defaults to `false`.
|
||||
|
||||
* `:infer_signatures` (since v1.18.0) - a list of applications of which modules
|
||||
should be using during type inference. When `false`, it disables module-local
|
||||
* `:infer_signatures` (since v1.18.0) - when `false`, it disables module-local
|
||||
signature inference used when type checking remote calls to the compiled
|
||||
module. Type checking will be executed regardless of the value of this option.
|
||||
Mix projects will set this option to your dependencies list in dev/prod, and
|
||||
it will disable this option during test (as there is typically no need to infer
|
||||
signature for test files). Outside of Mix projects, it defaults to `[:elixir]`.
|
||||
Defaults to `true`.
|
||||
|
||||
* `:module_definition` (since v1.20.0) - stores if the module definition should
|
||||
be `:compiled` (the default) or `:interpreted`. Note this does not affect the
|
||||
`.beam` file written to disk, only how the contents inside `defmodule` are
|
||||
executed. Using the `:interpreted` mode may offer better compilation times for
|
||||
large projects, especially on machines with high core count, however, it comes
|
||||
with some downsides:
|
||||
`mix test` automatically disables this option via the `:test_elixirc_options`
|
||||
project configuration, as there is typically no need to store infer signatures
|
||||
for test files.
|
||||
|
||||
* Errors during compilation may have less precise stacktraces
|
||||
|
||||
* Anonymous functions within `defmodule` can have only up to 20 arguments.
|
||||
If this is an issue, you can use maps or tuples to group the data.
|
||||
Note the functions themselves inside `defmodule`, such as the ones defined
|
||||
inside `def` and friends, can still have up to 255 arguments
|
||||
* `:relative_paths` - when `true`, uses relative paths in quoted nodes,
|
||||
warnings, and errors generated by the compiler. Note disabling this option
|
||||
won't affect runtime warnings and errors. Defaults to `true`.
|
||||
|
||||
* `:no_warn_undefined` (since v1.10.0) - list of modules and `{Mod, fun, arity}`
|
||||
tuples that will not emit warnings that the module or function does not exist
|
||||
at compilation time. Pass atom `:all` to skip warning for all undefined
|
||||
functions. This can be useful when doing dynamic compilation. Defaults to `[]`.
|
||||
|
||||
* `:on_undefined_variable` (since v1.15.0) - either `:raise` or `:warn`.
|
||||
When `:raise` (the default), undefined variables will trigger a compilation
|
||||
error. You may be set it to `:warn` if you want undefined variables to
|
||||
emit a warning and expand as to a local call to the zero-arity function
|
||||
of the same name (for example, `node` would be expanded as `node()`).
|
||||
This `:warn` behavior only exists for compatibility reasons when working
|
||||
with old dependencies, its usage is discouraged and it will be removed
|
||||
in future releases.
|
||||
* `:tracers` (since v1.10.0) - a list of tracers (modules) to be used during
|
||||
compilation. See the module docs for more information. Defaults to `[]`.
|
||||
|
||||
* `:parser_options` (since v1.10.0) - a keyword list of options to be given
|
||||
to the parser when compiling files. It accepts the same options as
|
||||
@@ -1811,12 +1677,14 @@ defmodule Code do
|
||||
and `compile_file/2` but not `string_to_quoted/2` and friends, as the
|
||||
latter is used for other purposes beyond compilation.
|
||||
|
||||
* `:relative_paths` - when `true`, uses relative paths in quoted nodes,
|
||||
warnings, and errors generated by the compiler. Note disabling this option
|
||||
won't affect runtime warnings and errors. Defaults to `true`.
|
||||
|
||||
* `:tracers` (since v1.10.0) - a list of tracers (modules) to be used during
|
||||
compilation. See the module docs for more information. Defaults to `[]`.
|
||||
* `:on_undefined_variable` (since v1.15.0) - either `:raise` or `:warn`.
|
||||
When `:raise` (the default), undefined variables will trigger a compilation
|
||||
error. You may be set it to `:warn` if you want undefined variables to
|
||||
emit a warning and expand as to a local call to the zero-arity function
|
||||
of the same name (for example, `node` would be expanded as `node()`).
|
||||
This `:warn` behavior only exists for compatibility reasons when working
|
||||
with old dependencies, its usage is discouraged and it will be removed
|
||||
in future releases.
|
||||
|
||||
It always returns `:ok`. Raises an error for invalid options.
|
||||
|
||||
@@ -1837,6 +1705,28 @@ defmodule Code do
|
||||
:ok
|
||||
end
|
||||
|
||||
# TODO: Remove me in Elixir v2.0
|
||||
def put_compiler_option(:warnings_as_errors, _value) do
|
||||
IO.warn(
|
||||
":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
|
||||
"instead you must pass it as a --warnings-as-errors flag. " <>
|
||||
"If you need to set it as a default in a mix task, you can also set it under aliases: " <>
|
||||
"[compile: \"compile --warnings-as-errors\"]"
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def put_compiler_option(:no_warn_undefined, value) do
|
||||
if value != :all and not is_list(value) do
|
||||
raise "compiler option :no_warn_undefined should be a list or the atom :all, " <>
|
||||
"got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
:elixir_config.put(:no_warn_undefined, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
def put_compiler_option(key, value) when key in @list_compiler_options do
|
||||
if not is_list(value) do
|
||||
raise "compiler option #{inspect(key)} should be a list, got: #{inspect(value)}"
|
||||
@@ -1856,70 +1746,9 @@ defmodule Code do
|
||||
:ok
|
||||
end
|
||||
|
||||
def put_compiler_option(:module_definition, value) do
|
||||
if value not in [:interpreted, :compiled] do
|
||||
raise "compiler option :module_definition should be either :interpreted or :compiled, got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
:elixir_config.put(:module_definition, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
def put_compiler_option(:infer_signatures, value) do
|
||||
value =
|
||||
cond do
|
||||
value == false ->
|
||||
false
|
||||
|
||||
value == true ->
|
||||
[:elixir]
|
||||
|
||||
is_list(value) and Enum.all?(value, &is_atom/1) ->
|
||||
value
|
||||
|
||||
true ->
|
||||
raise "compiler option :infer_signatures should be a boolean or a list of applications, got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
:elixir_config.put(:infer_signatures, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
def put_compiler_option(:no_warn_undefined, value) do
|
||||
if value != :all and not is_list(value) do
|
||||
raise "compiler option :no_warn_undefined should be a list or the atom :all, " <>
|
||||
"got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
:elixir_config.put(:no_warn_undefined, value)
|
||||
:ok
|
||||
end
|
||||
|
||||
# TODO: Remove me in Elixir v2.0
|
||||
def put_compiler_option(:warnings_as_errors, _value) do
|
||||
IO.warn(
|
||||
":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
|
||||
"instead you must pass it as a --warnings-as-errors flag. " <>
|
||||
"If you need to set it as a default in a mix task, you can also set it under aliases: " <>
|
||||
"[compile: \"compile --warnings-as-errors\"]"
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
# TODO: Remove me in Elixir v2.0
|
||||
# TODO: Remove this option on Elixir v2.0
|
||||
# TODO: Warn if mode is :warn on Elixir v1.19
|
||||
def put_compiler_option(:on_undefined_variable, value) when value in [:raise, :warn] do
|
||||
if value == :warn do
|
||||
IO.warn_once(
|
||||
{__MODULE__, :on_undefined_variable},
|
||||
fn ->
|
||||
"setting :on_undefined_variable to :warn is deprecated. " <>
|
||||
"The warning behaviour will be removed in future releases"
|
||||
end,
|
||||
3
|
||||
)
|
||||
end
|
||||
|
||||
:elixir_config.put(:on_undefined_variable, value)
|
||||
:ok
|
||||
end
|
||||
@@ -2184,7 +2013,7 @@ defmodule Code do
|
||||
case :code.ensure_loaded(module) do
|
||||
{:error, :nofile} = error ->
|
||||
if can_await_module_compilation?() do
|
||||
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode, nil) do
|
||||
case Kernel.ErrorHandler.ensure_compiled(module, :module, mode) do
|
||||
:found -> {:module, module}
|
||||
:deadlock -> {:error, :unavailable}
|
||||
:not_found -> {:error, :nofile}
|
||||
@@ -2206,7 +2035,7 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.loaded?(String)
|
||||
iex> Code.loaded?(Atom)
|
||||
true
|
||||
|
||||
iex> Code.loaded?(NotYetLoaded)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Code.Formatter do
|
||||
@moduledoc false
|
||||
import Inspect.Algebra, except: [format: 2, surround: 3, surround: 4]
|
||||
@@ -158,7 +154,6 @@ defmodule Code.Formatter do
|
||||
@doc """
|
||||
Converts the quoted expression into an algebra document.
|
||||
"""
|
||||
@spec to_algebra(Macro.t(), keyword()) :: Inspect.Algebra.t()
|
||||
def to_algebra(quoted, opts \\ []) do
|
||||
comments = Keyword.get(opts, :comments, [])
|
||||
|
||||
@@ -196,7 +191,6 @@ defmodule Code.Formatter do
|
||||
sigils = Keyword.get(opts, :sigils, [])
|
||||
migrate = Keyword.get(opts, :migrate, false)
|
||||
migrate_bitstring_modifiers = Keyword.get(opts, :migrate_bitstring_modifiers, migrate)
|
||||
migrate_call_parens_on_pipe = Keyword.get(opts, :migrate_call_parens_on_pipe, migrate)
|
||||
migrate_charlists_as_sigils = Keyword.get(opts, :migrate_charlists_as_sigils, migrate)
|
||||
migrate_unless = Keyword.get(opts, :migrate_unless, migrate)
|
||||
syntax_colors = Keyword.get(opts, :syntax_colors, [])
|
||||
@@ -224,7 +218,6 @@ defmodule Code.Formatter do
|
||||
sigils: sigils,
|
||||
file: file,
|
||||
migrate_bitstring_modifiers: migrate_bitstring_modifiers,
|
||||
migrate_call_parens_on_pipe: migrate_call_parens_on_pipe,
|
||||
migrate_charlists_as_sigils: migrate_charlists_as_sigils,
|
||||
migrate_unless: migrate_unless,
|
||||
inspect_opts: %Inspect.Opts{syntax_colors: syntax_colors}
|
||||
@@ -494,16 +487,7 @@ defmodule Code.Formatter do
|
||||
binary_op_to_algebra(:in, "not in", meta, left, right, context, state)
|
||||
end
|
||||
|
||||
# disable migrate_call_parens_on_pipe within defmacro
|
||||
defp quoted_to_algebra(
|
||||
{atom, _, [{:|>, _, _}, _]} = ast,
|
||||
context,
|
||||
%{migrate_call_parens_on_pipe: true} = state
|
||||
)
|
||||
when atom in [:defmacro, :defmacrop] do
|
||||
quoted_to_algebra(ast, context, %{state | migrate_call_parens_on_pipe: false})
|
||||
end
|
||||
|
||||
# disable migrate_unless within defmacro
|
||||
defp quoted_to_algebra(
|
||||
{atom, _, [{:unless, _, _}, _]} = ast,
|
||||
context,
|
||||
@@ -618,12 +602,9 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
doc =
|
||||
concat(
|
||||
group(left),
|
||||
with_next_break_fits(next_break_fits?(right_arg, state), right, fn right ->
|
||||
nest(glue(op, right), 2, :break)
|
||||
end)
|
||||
)
|
||||
with_next_break_fits(next_break_fits?(right_arg, state), right, fn right ->
|
||||
concat(group(left), group(nest(glue(op, group(right)), 2, :break)))
|
||||
end)
|
||||
|
||||
{doc, state}
|
||||
end
|
||||
@@ -806,13 +787,15 @@ defmodule Code.Formatter do
|
||||
{right, state} =
|
||||
binary_operand_to_algebra(right_arg, right_context, state, op, op_info, :right, 0)
|
||||
|
||||
{op_string, right} =
|
||||
doc =
|
||||
cond do
|
||||
op in @no_space_binary_operators ->
|
||||
{op_string, group(right)}
|
||||
op_doc = color_doc(op_string, :operator, state.inspect_opts)
|
||||
concat(concat(group(left), op_doc), group(right))
|
||||
|
||||
op in @no_newline_binary_operators ->
|
||||
{" " <> op_string <> " ", group(right)}
|
||||
op_doc = color_doc(" " <> op_string <> " ", :operator, state.inspect_opts)
|
||||
concat(concat(group(left), op_doc), group(right))
|
||||
|
||||
true ->
|
||||
eol? = eol?(meta, state)
|
||||
@@ -820,15 +803,14 @@ defmodule Code.Formatter do
|
||||
next_break_fits? =
|
||||
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and not eol?
|
||||
|
||||
{" " <> op_string,
|
||||
with_next_break_fits(next_break_fits?, right, fn right ->
|
||||
right = nest(concat(break(), right), nesting, :break)
|
||||
if eol?, do: force_unfit(right), else: right
|
||||
end)}
|
||||
with_next_break_fits(next_break_fits?, right, fn right ->
|
||||
op_doc = color_doc(" " <> op_string, :operator, state.inspect_opts)
|
||||
right = nest(glue(op_doc, group(right)), nesting, :break)
|
||||
right = if eol?, do: force_unfit(right), else: right
|
||||
concat(group(left), group(right))
|
||||
end)
|
||||
end
|
||||
|
||||
op_doc = color_doc(op_string, :operator, state.inspect_opts)
|
||||
doc = concat(concat(group(left), op_doc), group(right))
|
||||
{doc, state}
|
||||
end
|
||||
|
||||
@@ -850,38 +832,6 @@ defmodule Code.Formatter do
|
||||
{wrap_in_parens(doc), state}
|
||||
end
|
||||
|
||||
# |> var
|
||||
# |> var()
|
||||
defp binary_operand_to_algebra(
|
||||
{var, meta, var_context},
|
||||
context,
|
||||
%{migrate_call_parens_on_pipe: true} = state,
|
||||
:|>,
|
||||
_parent_info,
|
||||
:right,
|
||||
_nesting
|
||||
)
|
||||
when is_atom(var) and is_atom(var_context) do
|
||||
operand = {var, meta, []}
|
||||
quoted_to_algebra(operand, context, state)
|
||||
end
|
||||
|
||||
# |> var.fun
|
||||
# |> var.fun()
|
||||
defp binary_operand_to_algebra(
|
||||
{{:., _, [_, fun]} = call, meta, []},
|
||||
context,
|
||||
%{migrate_call_parens_on_pipe: true} = state,
|
||||
:|>,
|
||||
_parent_info,
|
||||
:right,
|
||||
_nesting
|
||||
)
|
||||
when is_atom(fun) do
|
||||
meta = Keyword.put_new_lazy(meta, :closing, fn -> [line: meta[:line]] end)
|
||||
quoted_to_algebra({call, meta, []}, context, state)
|
||||
end
|
||||
|
||||
defp binary_operand_to_algebra(operand, context, state, parent_op, parent_info, side, nesting) do
|
||||
{parent_assoc, parent_prec} = parent_info
|
||||
|
||||
@@ -1265,7 +1215,7 @@ defmodule Code.Formatter do
|
||||
args_doc =
|
||||
if skip_parens? do
|
||||
left_doc
|
||||
|> concat(group(right_doc, :optimistic))
|
||||
|> concat(next_break_fits(group(right_doc, :inherit), :enabled))
|
||||
|> nest(:cursor, :break)
|
||||
else
|
||||
right_doc =
|
||||
@@ -1273,7 +1223,8 @@ defmodule Code.Formatter do
|
||||
|> nest(2, :break)
|
||||
|> concat(break(""))
|
||||
|> concat(")")
|
||||
|> group(:optimistic)
|
||||
|> group(:inherit)
|
||||
|> next_break_fits(:enabled)
|
||||
|
||||
concat(nest(left_doc, 2, :break), right_doc)
|
||||
end
|
||||
@@ -1316,11 +1267,13 @@ defmodule Code.Formatter do
|
||||
|> concat(args_doc)
|
||||
|> nest(2)
|
||||
|> concat(extra)
|
||||
|> group()
|
||||
|
||||
skip_parens? ->
|
||||
" "
|
||||
|> concat(args_doc)
|
||||
|> concat(extra)
|
||||
|> group()
|
||||
|
||||
true ->
|
||||
"("
|
||||
@@ -1328,12 +1281,13 @@ defmodule Code.Formatter do
|
||||
|> nest(2, :break)
|
||||
|> concat(args_doc)
|
||||
|> concat(extra)
|
||||
|> group()
|
||||
end
|
||||
|
||||
if next_break_fits? do
|
||||
{group(doc, :pessimistic), state}
|
||||
{next_break_fits(doc, :disabled), state}
|
||||
else
|
||||
{group(doc), state}
|
||||
{doc, state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1799,17 +1753,10 @@ defmodule Code.Formatter do
|
||||
|
||||
doc =
|
||||
case args do
|
||||
[_ | _] ->
|
||||
concat_to_last_group(doc, ",")
|
||||
|
||||
[] when last_arg_mode == :force_comma ->
|
||||
concat_to_last_group(doc, ",")
|
||||
|
||||
[] when last_arg_mode == :next_break_fits ->
|
||||
doc |> ungroup_if_group() |> group(:optimistic)
|
||||
|
||||
[] when last_arg_mode == :none ->
|
||||
doc
|
||||
[_ | _] -> concat_to_last_group(doc, ",")
|
||||
[] when last_arg_mode == :force_comma -> concat_to_last_group(doc, ",")
|
||||
[] when last_arg_mode == :next_break_fits -> next_break_fits(doc, :enabled)
|
||||
[] when last_arg_mode == :none -> doc
|
||||
end
|
||||
|
||||
{{doc, @empty, 1}, state}
|
||||
@@ -2327,14 +2274,11 @@ defmodule Code.Formatter do
|
||||
defp with_next_break_fits(condition, doc, fun) do
|
||||
if condition do
|
||||
doc
|
||||
|> group(:optimistic)
|
||||
|> next_break_fits(:enabled)
|
||||
|> fun.()
|
||||
|> group(:pessimistic)
|
||||
|> next_break_fits(:disabled)
|
||||
else
|
||||
doc
|
||||
|> group()
|
||||
|> fun.()
|
||||
|> group()
|
||||
fun.(doc)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2501,16 +2445,16 @@ defmodule Code.Formatter do
|
||||
|
||||
# Relying on the inner document is brittle and error prone.
|
||||
# It would be best if we had a mechanism to apply this.
|
||||
defp concat_to_last_group([left | right], concat) do
|
||||
[left | concat_to_last_group(right, concat)]
|
||||
defp concat_to_last_group({:doc_cons, left, right}, concat) do
|
||||
{:doc_cons, left, concat_to_last_group(right, concat)}
|
||||
end
|
||||
|
||||
defp concat_to_last_group({:doc_group, group, mode}, concat) do
|
||||
{:doc_group, concat(group, concat), mode}
|
||||
{:doc_group, {:doc_cons, group, concat}, mode}
|
||||
end
|
||||
|
||||
defp concat_to_last_group(other, concat) do
|
||||
concat(other, concat)
|
||||
{:doc_cons, other, concat}
|
||||
end
|
||||
|
||||
defp ungroup_if_group({:doc_group, group, _mode}), do: group
|
||||
|
||||
+23
-160
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
defmodule Code.Fragment do
|
||||
@moduledoc """
|
||||
This module provides conveniences for analyzing fragments of
|
||||
@@ -11,59 +8,6 @@ defmodule Code.Fragment do
|
||||
|
||||
@type position :: {line :: pos_integer(), column :: pos_integer()}
|
||||
|
||||
@typedoc """
|
||||
Options for cursor context functions.
|
||||
|
||||
Currently, these options are not used but reserved for future extensibility.
|
||||
"""
|
||||
@type cursor_opts :: []
|
||||
|
||||
@typedoc """
|
||||
Options for converting code fragments to quoted expressions.
|
||||
"""
|
||||
@type container_cursor_to_quoted_opts :: [
|
||||
file: String.t(),
|
||||
line: pos_integer(),
|
||||
column: pos_integer(),
|
||||
columns: boolean(),
|
||||
token_metadata: boolean(),
|
||||
literal_encoder: (term(), Macro.metadata() -> term()),
|
||||
trailing_fragment: String.t()
|
||||
]
|
||||
|
||||
@doc ~S"""
|
||||
Returns the list of lines in the given string, preserving their line endings.
|
||||
|
||||
Only the line endings recognized by the Elixir compiler are
|
||||
considered, namely `\r\n` and `\n`. If you would like the retrieve
|
||||
lines without their line endings, use `String.split(string, ["\r\n", "\n"])`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Code.Fragment.lines("foo\r\nbar\r\nbaz")
|
||||
["foo\r\n", "bar\r\n", "baz"]
|
||||
|
||||
iex> Code.Fragment.lines("foo\nbar\nbaz")
|
||||
["foo\n", "bar\n", "baz"]
|
||||
|
||||
iex> Code.Fragment.lines("")
|
||||
[""]
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
def lines(string) do
|
||||
lines(string, <<>>)
|
||||
end
|
||||
|
||||
defp lines(<<?\n, rest::binary>>, acc),
|
||||
do: [<<acc::binary, ?\n>> | lines(rest, <<>>)]
|
||||
|
||||
defp lines(<<char, rest::binary>>, acc),
|
||||
do: lines(rest, <<acc::binary, char>>)
|
||||
|
||||
defp lines(<<>>, acc),
|
||||
do: [acc]
|
||||
|
||||
@doc """
|
||||
Receives a string and returns the cursor context.
|
||||
|
||||
@@ -99,9 +43,6 @@ defmodule Code.Fragment do
|
||||
or `{:local_or_var, charlist}` and `charlist` is a static part
|
||||
Examples are `__MODULE__.Submodule` or `@hello.Submodule`
|
||||
|
||||
* `{:block_keyword_or_binary_operator, charlist}` - may be a block keyword (do, end, after,
|
||||
catch, else, rescue) or a binary operator
|
||||
|
||||
* `{:dot, inside_dot, charlist}` - the context is a dot
|
||||
where `inside_dot` is either a `{:var, charlist}`, `{:alias, charlist}`,
|
||||
`{:module_attribute, charlist}`, `{:unquoted_atom, charlist}` or a `dot`
|
||||
@@ -192,10 +133,9 @@ defmodule Code.Fragment do
|
||||
references, and more.
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec cursor_context(List.Chars.t(), cursor_opts()) ::
|
||||
@spec cursor_context(List.Chars.t(), keyword()) ::
|
||||
{:alias, charlist}
|
||||
| {:alias, inside_alias, charlist}
|
||||
| {:block_keyword_or_binary_operator, charlist}
|
||||
| {:dot, inside_dot, charlist}
|
||||
| {:dot_arity, inside_dot, charlist}
|
||||
| {:dot_call, inside_dot, charlist}
|
||||
@@ -204,7 +144,6 @@ defmodule Code.Fragment do
|
||||
| {:local_arity, charlist}
|
||||
| {:local_call, charlist}
|
||||
| {:anonymous_call, inside_caller}
|
||||
| {:capture_arg, charlist}
|
||||
| {:module_attribute, charlist}
|
||||
| {:operator, charlist}
|
||||
| {:operator_arity, charlist}
|
||||
@@ -246,15 +185,15 @@ defmodule Code.Fragment do
|
||||
cursor_context(to_charlist(other), opts)
|
||||
end
|
||||
|
||||
@operators ~c"\\<>+-*/:=|&~^%!$"
|
||||
@starting_punctuation ~c",([{;"
|
||||
@closing_punctuation ~c")]}\"'"
|
||||
@operators ~c"\\<>+-*/:=|&~^%!"
|
||||
@starter_punctuation ~c",([{;"
|
||||
@non_starter_punctuation ~c")]}\"'.$"
|
||||
@space ~c"\t\s"
|
||||
@trailing_identifier ~c"?!"
|
||||
@tilde_op_prefix ~c"<=~"
|
||||
|
||||
@non_identifier @trailing_identifier ++
|
||||
@operators ++ @starting_punctuation ++ @closing_punctuation ++ @space ++ [?.]
|
||||
@operators ++ @starter_punctuation ++ @non_starter_punctuation ++ @space
|
||||
|
||||
@textual_operators ~w(when not and or in)c
|
||||
@keywords ~w(do end after else catch rescue fn true false nil)c
|
||||
@@ -284,11 +223,11 @@ defmodule Code.Fragment do
|
||||
# A local arity definition
|
||||
[?/ | rest] -> arity_to_cursor_context(strip_spaces(rest, spaces + 1))
|
||||
# Starting a new expression
|
||||
[h | _] when h in @starting_punctuation -> {:expr, 0}
|
||||
# It is keyword, binary operator, a local or remote call without parens
|
||||
rest when spaces > 0 -> closing_or_call_to_cursor_context({rest, spaces})
|
||||
[h | _] when h in @starter_punctuation -> {:expr, 0}
|
||||
# It is a local or remote call without parens
|
||||
rest when spaces > 0 -> call_to_cursor_context({rest, spaces})
|
||||
# It is an identifier
|
||||
_ -> identifier_to_cursor_context(reverse, spaces, false)
|
||||
_ -> identifier_to_cursor_context(reverse, 0, false)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -303,8 +242,7 @@ defmodule Code.Fragment do
|
||||
{{:local_or_var, acc}, count} -> {{:local_arity, acc}, count}
|
||||
{{:dot, base, acc}, count} -> {{:dot_arity, base, acc}, count}
|
||||
{{:operator, acc}, count} -> {{:operator_arity, acc}, count}
|
||||
{{:sigil, _}, _} -> {:none, 0}
|
||||
{_, _} -> {{:operator, ~c"/"}, 1}
|
||||
{_, _} -> {:none, 0}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -328,16 +266,8 @@ defmodule Code.Fragment do
|
||||
end
|
||||
end
|
||||
|
||||
defp closing_or_call_to_cursor_context({reverse, spaces}) do
|
||||
if closing?(reverse) do
|
||||
{{:block_keyword_or_binary_operator, ~c""}, 0}
|
||||
else
|
||||
call_to_cursor_context({reverse, spaces})
|
||||
end
|
||||
end
|
||||
|
||||
defp identifier_to_cursor_context([?., ?., ?: | _], n, _), do: {{:unquoted_atom, ~c".."}, n + 3}
|
||||
defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:operator, ~c"..."}, n + 3}
|
||||
defp identifier_to_cursor_context([?., ?., ?. | _], n, _), do: {{:local_or_var, ~c"..."}, n + 3}
|
||||
defp identifier_to_cursor_context([?., ?: | _], n, _), do: {{:unquoted_atom, ~c"."}, n + 2}
|
||||
defp identifier_to_cursor_context([?., ?. | _], n, _), do: {{:operator, ~c".."}, n + 2}
|
||||
|
||||
@@ -387,13 +317,8 @@ defmodule Code.Fragment do
|
||||
{~c"." ++ rest, count} when rest == [] or hd(rest) != ?. ->
|
||||
dot(rest, count + 1, acc)
|
||||
|
||||
{rest, rest_count} ->
|
||||
response =
|
||||
if rest_count > count and closing?(rest),
|
||||
do: :block_keyword_or_binary_operator,
|
||||
else: :local_or_var
|
||||
|
||||
{{response, acc}, count}
|
||||
_ ->
|
||||
{{:local_or_var, acc}, count}
|
||||
end
|
||||
|
||||
{:capture_arg, acc, count} ->
|
||||
@@ -401,28 +326,6 @@ defmodule Code.Fragment do
|
||||
end
|
||||
end
|
||||
|
||||
# If it is a closing punctuation
|
||||
defp closing?([h | _]) when h in @closing_punctuation, do: true
|
||||
# Closing bitstring (but deal with operators)
|
||||
defp closing?([?>, ?> | rest]), do: rest == [] or hd(rest) not in [?>, ?~]
|
||||
# Keywords
|
||||
defp closing?(rest) do
|
||||
case split_non_identifier(rest, []) do
|
||||
{~c"nil", _} -> true
|
||||
{~c"true", _} -> true
|
||||
{~c"false", _} -> true
|
||||
{[digit | _], _} when digit in ?0..?9 -> true
|
||||
{[upper | _], _} when upper in ?A..?Z -> true
|
||||
{[_ | _], [?: | rest]} -> rest == [] or hd(rest) != ?:
|
||||
{_, _} -> false
|
||||
end
|
||||
end
|
||||
|
||||
defp split_non_identifier([h | t], acc) when h not in @non_identifier,
|
||||
do: split_non_identifier(t, [h | acc])
|
||||
|
||||
defp split_non_identifier(rest, acc), do: {acc, rest}
|
||||
|
||||
defp identifier([?? | rest], count), do: check_identifier(rest, count + 1, [??])
|
||||
defp identifier([?! | rest], count), do: check_identifier(rest, count + 1, [?!])
|
||||
defp identifier(rest, count), do: check_identifier(rest, count, [])
|
||||
@@ -684,7 +587,7 @@ defmodule Code.Fragment do
|
||||
of examples and their return values.
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec surround_context(List.Chars.t(), position(), cursor_opts()) ::
|
||||
@spec surround_context(List.Chars.t(), position(), keyword()) ::
|
||||
%{begin: position, end: position, context: context} | :none
|
||||
when context:
|
||||
{:alias, charlist}
|
||||
@@ -793,12 +696,6 @@ defmodule Code.Fragment do
|
||||
{{:local_or_var, acc}, offset} ->
|
||||
build_surround({:local_or_var, acc}, reversed, line, offset)
|
||||
|
||||
{{:block_keyword_or_binary_operator, acc}, offset} when acc in @textual_operators ->
|
||||
build_surround({:operator, acc}, reversed, line, offset)
|
||||
|
||||
{{:block_keyword_or_binary_operator, acc}, offset} when acc in @keywords ->
|
||||
build_surround({:keyword, acc}, reversed, line, offset)
|
||||
|
||||
{{:module_attribute, ~c""}, offset} ->
|
||||
build_surround({:operator, ~c"@"}, reversed, line, offset)
|
||||
|
||||
@@ -1215,10 +1112,10 @@ defmodule Code.Fragment do
|
||||
Defaults to `"nofile"`.
|
||||
|
||||
* `:line` - the starting line of the string being parsed.
|
||||
Defaults to `1`.
|
||||
Defaults to 1.
|
||||
|
||||
* `:column` - the starting column of the string being parsed.
|
||||
Defaults to `1`.
|
||||
Defaults to 1.
|
||||
|
||||
* `:columns` - when `true`, attach a `:column` key to the quoted
|
||||
metadata. Defaults to `false`.
|
||||
@@ -1235,43 +1132,14 @@ defmodule Code.Fragment do
|
||||
the cursor. This is necessary to correctly complete anonymous functions
|
||||
and the left-hand side of `->`
|
||||
|
||||
* `:preserve_sigils` (since v1.20.0) - preserve sigil cursor location
|
||||
(see "Tracking sigils" section below)
|
||||
|
||||
## Tracking sigils
|
||||
|
||||
The `:preserve_sigils` option can be used to track cursor positions inside
|
||||
a sigil.
|
||||
|
||||
If the sigil is terminated abruptly, the `sigil_*` call will have the cursor
|
||||
as the second argument:
|
||||
|
||||
iex> Code.Fragment.container_cursor_to_quoted("~r/foo", preserve_sigils: true)
|
||||
{:ok,
|
||||
{:sigil_r, [delimiter: "/", line: 1],
|
||||
[{:<<>>, [line: 1], ["foo"]}, {:__cursor__, [line: 1, column: 7], []}]}}
|
||||
|
||||
In case the sigil is completed and has zero or more modifiers, the cursor will
|
||||
be nested in the list, with all previous delimiters specified:
|
||||
|
||||
iex> Code.Fragment.container_cursor_to_quoted("~r/foo/i", preserve_sigils: true)
|
||||
{:ok,
|
||||
{:sigil_r, [delimiter: "/", line: 1],
|
||||
[{:<<>>, [line: 1], ["foo"]}, [105, {:__cursor__, [line: 1, column: 9], []}]]}}
|
||||
|
||||
If the cursor is after the sigil, then it is discarded as everything else:
|
||||
|
||||
iex> Code.Fragment.container_cursor_to_quoted("~r/foo/i ", preserve_sigils: true)
|
||||
{:ok, {:__cursor__, [line: 1], []}}
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec container_cursor_to_quoted(List.Chars.t(), container_cursor_to_quoted_opts()) ::
|
||||
@spec container_cursor_to_quoted(List.Chars.t(), keyword()) ::
|
||||
{:ok, Macro.t()} | {:error, {location :: keyword, binary | {binary, binary}, binary}}
|
||||
def container_cursor_to_quoted(fragment, opts \\ []) do
|
||||
{trailing_fragment, opts} = Keyword.pop(opts, :trailing_fragment)
|
||||
{preserve_sigils?, opts} = Keyword.pop(opts, :preserve_sigils, false)
|
||||
opts = Keyword.take(opts, [:columns, :token_metadata, :literal_encoder])
|
||||
opts = [check_terminators: {:cursor, preserve_sigils?, []}] ++ opts
|
||||
opts = [check_terminators: {:cursor, []}, emit_warnings: false] ++ opts
|
||||
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
@@ -1291,10 +1159,7 @@ defmodule Code.Fragment do
|
||||
end
|
||||
|
||||
tokens = reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
|
||||
with {:ok, forms, _warnings} <- :elixir.tokens_to_quoted(tokens, file, opts) do
|
||||
{:ok, forms}
|
||||
end
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
|
||||
{:ok, line, column, _warnings, rev_tokens, rev_terminators} ->
|
||||
tokens =
|
||||
@@ -1302,7 +1167,7 @@ defmodule Code.Fragment do
|
||||
Enum.split_while(rev_terminators, &(elem(&1, 0) not in [:do, :fn])),
|
||||
true <- maybe_missing_stab?(rev_tokens, true),
|
||||
opts =
|
||||
Keyword.put(opts, :check_terminators, {:cursor, false, before_start}),
|
||||
Keyword.put(opts, :check_terminators, {:cursor, before_start}),
|
||||
{:error, {meta, _, ~c"end"}, _rest, _warnings, trailing_rev_tokens} <-
|
||||
:elixir_tokenizer.tokenize(to_charlist(trailing_fragment), line, column, opts) do
|
||||
trailing_tokens =
|
||||
@@ -1321,12 +1186,10 @@ defmodule Code.Fragment do
|
||||
_ -> reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
end
|
||||
|
||||
with {:ok, forms, _warnings} <- :elixir.tokens_to_quoted(tokens, file, opts) do
|
||||
{:ok, forms}
|
||||
end
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
|
||||
{:error, info, _rest, _warnings, _so_far} ->
|
||||
{:error, :elixir_tokenizer.format_error(info)}
|
||||
{:error, :elixir.format_token_error(info)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1367,7 +1230,7 @@ defmodule Code.Fragment do
|
||||
defp drop_tokens([{:do, _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
|
||||
defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter)
|
||||
defp drop_tokens([], _counter), do: []
|
||||
defp drop_tokens([], 0), do: []
|
||||
|
||||
defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Code.Identifier do
|
||||
@moduledoc false
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
defmodule Code.Normalizer do
|
||||
@moduledoc false
|
||||
|
||||
@@ -14,7 +11,6 @@ defmodule Code.Normalizer do
|
||||
Wraps literals in the quoted expression to conform to the AST format expected
|
||||
by the formatter.
|
||||
"""
|
||||
@spec normalize(Macro.t(), keyword()) :: Macro.t()
|
||||
def normalize(quoted, opts \\ []) do
|
||||
line = Keyword.get(opts, :line, nil)
|
||||
escape = Keyword.get(opts, :escape, true)
|
||||
@@ -68,7 +64,7 @@ defmodule Code.Normalizer do
|
||||
|
||||
# Bit containers
|
||||
defp do_normalize({:<<>>, _, args} = quoted, state) when is_list(args) do
|
||||
normalize_bitstring(quoted, state, false)
|
||||
normalize_bitstring(quoted, state)
|
||||
end
|
||||
|
||||
# Atoms with interpolations
|
||||
@@ -89,7 +85,13 @@ defmodule Code.Normalizer do
|
||||
normalize_literal(:utf8, [], state)
|
||||
end
|
||||
|
||||
string = normalize_bitstring(string, state, state.escape)
|
||||
string =
|
||||
if state.escape do
|
||||
normalize_bitstring(string, state, true)
|
||||
else
|
||||
normalize_bitstring(string, state)
|
||||
end
|
||||
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta, [string, utf8]}
|
||||
end
|
||||
|
||||
@@ -112,7 +114,6 @@ defmodule Code.Normalizer do
|
||||
end
|
||||
end)
|
||||
|
||||
parts = maybe_add_trailing_newline(call_meta, parts, state)
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
else
|
||||
normalize_call(quoted, state)
|
||||
@@ -400,8 +401,7 @@ defmodule Code.Normalizer do
|
||||
defp allow_keyword?(:{}, _), do: false
|
||||
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
|
||||
|
||||
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation) do
|
||||
parts = maybe_add_trailing_newline(meta, parts, state)
|
||||
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation \\ false) do
|
||||
meta = patch_meta_line(meta, state.parent_meta)
|
||||
|
||||
parts =
|
||||
@@ -423,17 +423,6 @@ defmodule Code.Normalizer do
|
||||
{:<<>>, meta, parts}
|
||||
end
|
||||
|
||||
defp maybe_add_trailing_newline(meta, parts, state) do
|
||||
with true <- state.escape and Keyword.get(meta, :delimiter) in ["\"\"\"", "'''"],
|
||||
last = List.last(parts),
|
||||
true <- is_binary(last) and not String.ends_with?(last, "\n") do
|
||||
[_last | rest] = Enum.reverse(parts)
|
||||
Enum.reverse([last <> "\n" | rest])
|
||||
else
|
||||
_ -> parts
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_interpolation_parts(parts, state, escape_interpolation) do
|
||||
Enum.map(parts, fn
|
||||
{:"::", interpolation_meta,
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Code.Typespec do
|
||||
@moduledoc false
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defprotocol Collectable do
|
||||
@moduledoc """
|
||||
A protocol to traverse data structures.
|
||||
@@ -69,22 +65,6 @@ defprotocol Collectable do
|
||||
iex> Enum.into([1, 2, 3], MapSet.new())
|
||||
MapSet.new([1, 2, 3])
|
||||
|
||||
## Halting
|
||||
|
||||
The `:halt` flag will be given whenever the collection won't
|
||||
terminate correctly and must be used to clean up existing resources
|
||||
(such as sockets, file handles, etc).
|
||||
|
||||
Note it is not guaranteed that the accumulator given to halt will
|
||||
be the latest version of the accumulator returned by a previous call
|
||||
with `{:cont, elem}`. Therefore, you must track the collected results
|
||||
within the resource you intend to halt.
|
||||
|
||||
This is by design: ensuring halt is always called with the latest
|
||||
accumulator would make pure collectables (the ones that do not implement
|
||||
halt) expensive. However, given the collectables that must implement halt
|
||||
already need to track state, the burden of tracking the accumulator
|
||||
across invocations is put on them.
|
||||
"""
|
||||
|
||||
@type command :: {:cont, term} | :done | :halt
|
||||
|
||||
+11
-27
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Config do
|
||||
@moduledoc ~S"""
|
||||
A simple keyword-based configuration API.
|
||||
@@ -98,12 +94,6 @@ defmodule Config do
|
||||
(assembled with `mix release`).
|
||||
"""
|
||||
|
||||
@type config_opts :: [
|
||||
imports: [Path.t()] | :disabled,
|
||||
env: atom(),
|
||||
target: atom()
|
||||
]
|
||||
|
||||
@opts_key {__MODULE__, :opts}
|
||||
@config_key {__MODULE__, :config}
|
||||
@imports_key {__MODULE__, :imports}
|
||||
@@ -141,6 +131,7 @@ defmodule Config do
|
||||
|
||||
config :logger,
|
||||
level: :warn,
|
||||
backends: [:console]
|
||||
|
||||
config :logger,
|
||||
level: :info,
|
||||
@@ -148,7 +139,7 @@ defmodule Config do
|
||||
|
||||
will have a final configuration for `:logger` of:
|
||||
|
||||
[level: :info, truncate: 1024]
|
||||
[level: :info, backends: [:console], truncate: 1024]
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@@ -233,8 +224,7 @@ defmodule Config do
|
||||
Returns the environment this configuration file is executed on.
|
||||
|
||||
In Mix projects this function returns the environment this configuration
|
||||
file is executed on.
|
||||
In releases, returns the `MIX_ENV` specified when running `mix release`.
|
||||
file is executed on. In releases, the environment when `mix release` ran.
|
||||
|
||||
This is most often used to execute conditional code:
|
||||
|
||||
@@ -312,7 +302,7 @@ defmodule Config do
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec __eval__!(Path.t(), binary(), config_opts) :: {keyword, [Path.t()] | :disabled}
|
||||
@spec __eval__!(Path.t(), binary(), keyword) :: {keyword, [Path.t()] | :disabled}
|
||||
def __eval__!(file, content, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
env = Keyword.get(opts, :env)
|
||||
target = Keyword.get(opts, :target)
|
||||
@@ -377,27 +367,21 @@ defmodule Config do
|
||||
end
|
||||
end
|
||||
|
||||
defp validate!(config, file) when is_list(config) do
|
||||
Enum.each(config, fn
|
||||
defp validate!(config, file) do
|
||||
Enum.all?(config, fn
|
||||
{app, value} when is_atom(app) ->
|
||||
if not Keyword.keyword?(value) do
|
||||
if Keyword.keyword?(value) do
|
||||
true
|
||||
else
|
||||
raise ArgumentError,
|
||||
"expected config for app #{inspect(app)} in #{Path.relative_to_cwd(file)} " <>
|
||||
"to return keyword list, got: #{inspect(value)}"
|
||||
end
|
||||
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected config in #{Path.relative_to_cwd(file)} to be a keyword list " <>
|
||||
"of {atom, keyword} pairs, got entry: #{inspect(other)}"
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
defp validate!(config, file) do
|
||||
raise ArgumentError,
|
||||
"expected config in #{Path.relative_to_cwd(file)} to be a keyword list " <>
|
||||
"of {atom, keyword} pairs, got: #{inspect(config)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Config.Provider do
|
||||
@moduledoc """
|
||||
Specifies a provider API that loads configuration during boot.
|
||||
@@ -111,16 +107,6 @@ defmodule Config.Provider do
|
||||
"""
|
||||
@type config_path :: {:system, binary(), binary()} | binary()
|
||||
|
||||
@typedoc """
|
||||
Options for `init/3`.
|
||||
"""
|
||||
@type init_opts :: [
|
||||
extra_config: config(),
|
||||
prune_runtime_sys_config_after_boot: boolean(),
|
||||
reboot_system_after_config: boolean(),
|
||||
validate_compile_env: [{atom(), [atom()], term()}]
|
||||
]
|
||||
|
||||
@doc """
|
||||
Invoked when initializing a config provider.
|
||||
|
||||
@@ -206,7 +192,6 @@ defmodule Config.Provider do
|
||||
@reboot_mode_key :config_provider_reboot_mode
|
||||
|
||||
@doc false
|
||||
@spec init([{module(), term()}], config_path(), init_opts()) :: config()
|
||||
def init(providers, config_path, opts \\ []) when is_list(providers) and is_list(opts) do
|
||||
validate_config_path!(config_path)
|
||||
providers = for {provider, init} <- providers, do: {provider, provider.init(init)}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Config.Reader do
|
||||
@moduledoc """
|
||||
API for reading config files defined with `Config`.
|
||||
@@ -46,12 +42,6 @@ defmodule Config.Reader do
|
||||
|
||||
@behaviour Config.Provider
|
||||
|
||||
@type config_opts :: [
|
||||
imports: [Path.t()] | :disabled,
|
||||
env: atom(),
|
||||
target: atom()
|
||||
]
|
||||
|
||||
@impl true
|
||||
def init(opts) when is_list(opts) do
|
||||
{path, opts} = Keyword.pop!(opts, :path)
|
||||
@@ -74,7 +64,7 @@ defmodule Config.Reader do
|
||||
Accepts the same options as `read!/2`.
|
||||
"""
|
||||
@doc since: "1.11.0"
|
||||
@spec eval!(Path.t(), binary, config_opts) :: keyword
|
||||
@spec eval!(Path.t(), binary, keyword) :: keyword
|
||||
def eval!(file, contents, opts \\ [])
|
||||
when is_binary(file) and is_binary(contents) and is_list(opts) do
|
||||
Config.__eval__!(Path.expand(file), contents, opts) |> elem(0)
|
||||
@@ -96,7 +86,7 @@ defmodule Config.Reader do
|
||||
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec read!(Path.t(), config_opts) :: keyword
|
||||
@spec read!(Path.t(), keyword) :: keyword
|
||||
def read!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
file = Path.expand(file)
|
||||
Config.__eval__!(file, File.read!(file), opts) |> elem(0)
|
||||
@@ -110,7 +100,7 @@ defmodule Config.Reader do
|
||||
option cannot be disabled in `read_imports!/2`.
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec read_imports!(Path.t(), config_opts) :: {keyword, [Path.t()]}
|
||||
@spec read_imports!(Path.t(), keyword) :: {keyword, [Path.t()]}
|
||||
def read_imports!(file, opts \\ []) when is_binary(file) and is_list(opts) do
|
||||
if opts[:imports] == :disabled do
|
||||
raise ArgumentError, ":imports must be a list of paths"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Dict do
|
||||
@moduledoc ~S"""
|
||||
Generic API for dictionaries.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule DynamicSupervisor do
|
||||
@moduledoc ~S"""
|
||||
A supervisor optimized to only start children dynamically.
|
||||
@@ -16,7 +12,7 @@ defmodule DynamicSupervisor do
|
||||
|
||||
## Examples
|
||||
|
||||
A dynamic supervisor is started with no children and often with a name:
|
||||
A dynamic supervisor is started with no children and often a name:
|
||||
|
||||
children = [
|
||||
{DynamicSupervisor, name: MyApp.DynamicSupervisor, strategy: :one_for_one}
|
||||
@@ -137,6 +133,67 @@ defmodule DynamicSupervisor do
|
||||
|
||||
A supervisor is bound to the same name registration rules as a `GenServer`.
|
||||
Read more about these rules in the documentation for `GenServer`.
|
||||
|
||||
## Migrating from Supervisor's :simple_one_for_one
|
||||
|
||||
In case you were using the deprecated `:simple_one_for_one` strategy from
|
||||
the `Supervisor` module, you can migrate to the `DynamicSupervisor` in
|
||||
few steps.
|
||||
|
||||
Imagine the given "old" code:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use Supervisor
|
||||
|
||||
def start_link(init_arg) do
|
||||
Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_child(foo, bar, baz) do
|
||||
# This will start child by calling MyWorker.start_link(init_arg, foo, bar, baz)
|
||||
Supervisor.start_child(__MODULE__, [foo, bar, baz])
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(init_arg) do
|
||||
children = [
|
||||
# Or the deprecated: worker(MyWorker, [init_arg])
|
||||
%{id: MyWorker, start: {MyWorker, :start_link, [init_arg]}}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :simple_one_for_one)
|
||||
end
|
||||
end
|
||||
|
||||
It can be upgraded to the DynamicSupervisor like this:
|
||||
|
||||
defmodule MySupervisor do
|
||||
use DynamicSupervisor
|
||||
|
||||
def start_link(init_arg) do
|
||||
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
|
||||
end
|
||||
|
||||
def start_child(foo, bar, baz) do
|
||||
# If MyWorker is not using the new child specs, we need to pass a map:
|
||||
# spec = %{id: MyWorker, start: {MyWorker, :start_link, [foo, bar, baz]}}
|
||||
spec = {MyWorker, foo: foo, bar: bar, baz: baz}
|
||||
DynamicSupervisor.start_child(__MODULE__, spec)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def init(init_arg) do
|
||||
DynamicSupervisor.init(
|
||||
strategy: :one_for_one,
|
||||
extra_arguments: [init_arg]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
The difference is that the `DynamicSupervisor` expects the child specification
|
||||
at the moment `start_child/2` is called, and no longer on the init callback.
|
||||
If there are any initial arguments given on initialization, such as `[initial_arg]`,
|
||||
it can be given in the `:extra_arguments` flag on `DynamicSupervisor.init/1`.
|
||||
"""
|
||||
|
||||
@behaviour GenServer
|
||||
@@ -169,15 +226,7 @@ defmodule DynamicSupervisor do
|
||||
@typedoc "Supported strategies"
|
||||
@type strategy :: :one_for_one
|
||||
|
||||
@typedoc """
|
||||
Return values of `start_child` functions.
|
||||
|
||||
Unlike `Supervisor`, this module ignores the child spec ids,
|
||||
so `{:error, {:already_started, pid}}` is not returned for child specs
|
||||
given with the same id. `{:error, {:already_started, pid}}` is returned
|
||||
however if a duplicate name is used when using
|
||||
[name registration](`m:GenServer#module-name-registration`).
|
||||
"""
|
||||
@typedoc "Return values of `start_child` functions"
|
||||
@type on_start_child ::
|
||||
{:ok, pid}
|
||||
| {:ok, pid, info :: term}
|
||||
@@ -206,7 +255,6 @@ defmodule DynamicSupervisor do
|
||||
See `Supervisor` for more information about child specifications.
|
||||
"""
|
||||
@doc since: "1.6.1"
|
||||
@spec child_spec([init_option() | GenServer.option()]) :: Supervisor.child_spec()
|
||||
def child_spec(options) when is_list(options) do
|
||||
id =
|
||||
case Keyword.get(options, :name, DynamicSupervisor) do
|
||||
@@ -348,17 +396,11 @@ defmodule DynamicSupervisor do
|
||||
@doc """
|
||||
Dynamically adds a child specification to `supervisor` and starts that child.
|
||||
|
||||
`child_spec` should be a valid [child specification](`m:Supervisor#module-child-specification`).
|
||||
The child process will be started as defined in the child specification. Note that while
|
||||
`child_spec` should be a valid child specification as detailed in the
|
||||
"Child specification" section of the documentation for `Supervisor`. The child
|
||||
process will be started as defined in the child specification. Note that while
|
||||
the `:id` field is still required in the spec, the value is ignored and
|
||||
therefore does not need to be unique. Unlike `Supervisor`, this module does not
|
||||
return `{:error, {:already_started, pid}}` for child specs given with the same id.
|
||||
`{:error, {:already_started, pid}}` is returned however if a duplicate name is
|
||||
used when using [name registration](`m:GenServer#module-name-registration`).
|
||||
|
||||
This function will block the `DynamicSupervisor` until the child initializes.
|
||||
When starting too many processes dynamically, you may want to use a
|
||||
`PartitionSupervisor` to split the work across multiple processes.
|
||||
therefore does not need to be unique.
|
||||
|
||||
If the child process start function returns `{:ok, child}` or `{:ok, child,
|
||||
info}`, then child specification and PID are added to the supervisor and
|
||||
@@ -463,14 +505,6 @@ defmodule DynamicSupervisor do
|
||||
@doc """
|
||||
Terminates the given child identified by `pid`.
|
||||
|
||||
This function will block the `DynamicSupervisor` until the child
|
||||
terminates, which may take an arbitrary amount of time if the child
|
||||
is trapping exits and implements its own terminate callback.
|
||||
For this reason, it is often better to ask the child process
|
||||
itself to terminate, often by declaring in its child spec it has
|
||||
a restart strategy of `:transient` (or `:temporary`) and then
|
||||
sending it a message to stop with reason `:shutdown`.
|
||||
|
||||
If successful, this function returns `:ok`. If there is no process with
|
||||
the given PID, this function returns `{:error, :not_found}`.
|
||||
"""
|
||||
@@ -481,11 +515,11 @@ defmodule DynamicSupervisor do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with information about all children of the given supervisor.
|
||||
Returns a list with information about all children.
|
||||
|
||||
Note that calling this function when supervising a large number
|
||||
of children under low memory conditions can bring the system down due to an
|
||||
out of memory error.
|
||||
of children under low memory conditions can cause an out of memory
|
||||
exception.
|
||||
|
||||
This function returns a list of tuples containing:
|
||||
|
||||
|
||||
+106
-341
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defprotocol Enumerable do
|
||||
@moduledoc """
|
||||
Enumerable protocol used by `Enum` and `Stream` modules.
|
||||
@@ -39,20 +35,6 @@ defprotocol Enumerable do
|
||||
`reduce/3` function. All other functions exist as optimizations paths
|
||||
for data structures that can implement certain properties in better
|
||||
than linear time.
|
||||
|
||||
## Default implementation for lists
|
||||
|
||||
Sometimes you may want to implement this protocol for a list contained
|
||||
in struct. This can be done by delegating to the `Enumerable.List` module
|
||||
in the `reduce/3` implementation and providing a straight-forward
|
||||
implementation for the remaining ones:
|
||||
|
||||
defimpl Enumerable, for: CustomStruct do
|
||||
def count(struct), do: {:ok, length(struct.items)}
|
||||
def member?(struct, value), do: {:ok, value in struct.items}
|
||||
def slice(struct), do: {:error, __MODULE__}
|
||||
def reduce(struct, acc, fun), do: Enumerable.List.reduce(struct.items, acc, fun)
|
||||
end
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
@@ -774,14 +756,26 @@ defmodule Enum do
|
||||
@doc since: "1.12.0"
|
||||
@spec count_until(t, pos_integer) :: non_neg_integer
|
||||
def count_until(enumerable, limit) when is_integer(limit) and limit > 0 do
|
||||
case enumerable do
|
||||
list when is_list(list) -> count_until_list(list, limit, 0)
|
||||
_ -> count_until_enum(enumerable, limit)
|
||||
end
|
||||
end
|
||||
stop_at = limit - 1
|
||||
|
||||
def count_until(_enumerable, limit) when is_integer(limit) do
|
||||
raise ArgumentError, "expected limit to be greater than 0, got: #{limit}"
|
||||
case Enumerable.count(enumerable) do
|
||||
{:ok, value} ->
|
||||
Kernel.min(value, limit)
|
||||
|
||||
{:error, module} ->
|
||||
enumerable
|
||||
|> module.reduce(
|
||||
{:cont, 0},
|
||||
fn
|
||||
_, ^stop_at ->
|
||||
{:halt, limit}
|
||||
|
||||
_, acc ->
|
||||
{:cont, acc + 1}
|
||||
end
|
||||
)
|
||||
|> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -799,14 +793,24 @@ defmodule Enum do
|
||||
@doc since: "1.12.0"
|
||||
@spec count_until(t, (element -> as_boolean(term)), pos_integer) :: non_neg_integer
|
||||
def count_until(enumerable, fun, limit) when is_integer(limit) and limit > 0 do
|
||||
case enumerable do
|
||||
list when is_list(list) -> count_until_list(list, fun, limit, 0)
|
||||
_ -> count_until_enum(enumerable, fun, limit)
|
||||
end
|
||||
end
|
||||
stop_at = limit - 1
|
||||
|
||||
def count_until(_enumerable, _fun, limit) when is_integer(limit) do
|
||||
raise ArgumentError, "expected limit to be greater than 0, got: #{limit}"
|
||||
Enumerable.reduce(enumerable, {:cont, 0}, fn
|
||||
entry, ^stop_at ->
|
||||
if fun.(entry) do
|
||||
{:halt, limit}
|
||||
else
|
||||
{:cont, stop_at}
|
||||
end
|
||||
|
||||
entry, acc ->
|
||||
if fun.(entry) do
|
||||
{:cont, acc + 1}
|
||||
else
|
||||
{:cont, acc}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -829,7 +833,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec dedup(t) :: list
|
||||
def dedup(enumerable) when is_list(enumerable) do
|
||||
dedup_list(enumerable)
|
||||
dedup_list(enumerable, []) |> :lists.reverse()
|
||||
end
|
||||
|
||||
def dedup(enumerable) do
|
||||
@@ -973,8 +977,8 @@ defmodule Enum do
|
||||
## Examples
|
||||
|
||||
Enum.each(["some", "example"], fn x -> IO.puts(x) end)
|
||||
some
|
||||
example
|
||||
"some"
|
||||
"example"
|
||||
#=> :ok
|
||||
|
||||
"""
|
||||
@@ -1070,14 +1074,14 @@ defmodule Enum do
|
||||
6
|
||||
|
||||
iex> Enum.fetch!([2, 4, 6], 4)
|
||||
** (Enum.OutOfBoundsError) out of bounds error at position 4 when traversing enumerable [2, 4, 6]
|
||||
** (Enum.OutOfBoundsError) out of bounds error
|
||||
|
||||
"""
|
||||
@spec fetch!(t, index) :: element
|
||||
def fetch!(enumerable, index) when is_integer(index) do
|
||||
case slice_forward(enumerable, index, 1, 1) do
|
||||
[value] -> value
|
||||
[] -> raise Enum.OutOfBoundsError, index: index, enumerable: enumerable
|
||||
[] -> raise Enum.OutOfBoundsError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1236,7 +1240,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps the given `fun` over `enumerable` and flattens the result only one level deep.
|
||||
Maps the given `fun` over `enumerable` and flattens the result.
|
||||
|
||||
This function returns a new enumerable built by appending the result of invoking `fun`
|
||||
on each element of `enumerable` together; conceptually, this is similar to a
|
||||
@@ -1253,7 +1257,7 @@ defmodule Enum do
|
||||
iex> Enum.flat_map([:a, :b, :c], fn x -> [[x]] end)
|
||||
[[:a], [:b], [:c]]
|
||||
|
||||
This is frequently used to transform and filter in one pass, returning empty
|
||||
This is frequently used to to transform and filter in one pass, returning empty
|
||||
lists to exclude results:
|
||||
|
||||
iex> Enum.flat_map([4, 0, 2, 0], fn x ->
|
||||
@@ -1284,16 +1288,13 @@ defmodule Enum do
|
||||
defp flat_reverse([], acc), do: acc
|
||||
|
||||
@doc """
|
||||
Maps and reduces an `enumerable`, flattening the results only one level deep.
|
||||
Maps and reduces an `enumerable`, flattening the given results (only one level deep).
|
||||
|
||||
It expects an accumulator and a function that receives each enumerable
|
||||
element, and must return a tuple containing a new enumerable (often a list)
|
||||
with the new accumulator or a tuple with `:halt` as first element and
|
||||
the accumulator as second.
|
||||
|
||||
Returns a 2-element tuple where the first element is the results flattened one level deep and
|
||||
the second element is the last accumulator.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> enumerable = 1..100
|
||||
@@ -1518,14 +1519,6 @@ defmodule Enum do
|
||||
to_list(enumerable)
|
||||
end
|
||||
|
||||
def into(enumerable, collectable) when is_struct(collectable, MapSet) do
|
||||
if MapSet.size(collectable) == 0 do
|
||||
MapSet.new(enumerable)
|
||||
else
|
||||
MapSet.new(enumerable) |> MapSet.union(collectable)
|
||||
end
|
||||
end
|
||||
|
||||
def into(%_{} = enumerable, collectable) do
|
||||
into_protocol(enumerable, collectable)
|
||||
end
|
||||
@@ -1602,12 +1595,8 @@ defmodule Enum do
|
||||
map(enumerable, transform)
|
||||
end
|
||||
|
||||
def into(enumerable, collectable, transform) when is_struct(collectable, MapSet) do
|
||||
if MapSet.size(collectable) == 0 do
|
||||
MapSet.new(enumerable, transform)
|
||||
else
|
||||
MapSet.new(enumerable, transform) |> MapSet.union(collectable)
|
||||
end
|
||||
def into(%_{} = enumerable, collectable, transform) do
|
||||
into_protocol(enumerable, collectable, transform)
|
||||
end
|
||||
|
||||
def into(enumerable, %_{} = collectable, transform) do
|
||||
@@ -1879,7 +1868,7 @@ defmodule Enum do
|
||||
Returns the maximal element in the `enumerable` according
|
||||
to Erlang's term ordering.
|
||||
|
||||
By default, the comparison is done with the [`>=`](`>=/2`) sorter function.
|
||||
By default, the comparison is done with the `>=` sorter function.
|
||||
If multiple elements are considered maximal, the first one that
|
||||
was found is returned. If you want the last element considered
|
||||
maximal to be returned, the sorter function should not return true
|
||||
@@ -1946,7 +1935,7 @@ defmodule Enum do
|
||||
Returns the maximal element in the `enumerable` as calculated
|
||||
by the given `fun`.
|
||||
|
||||
By default, the comparison is done with the [`>=`](`>=/2`) sorter function.
|
||||
By default, the comparison is done with the `>=` sorter function.
|
||||
If multiple elements are considered maximal, the first one that
|
||||
was found is returned. If you want the last element considered
|
||||
maximal to be returned, the sorter function should not return true
|
||||
@@ -2023,16 +2012,11 @@ defmodule Enum do
|
||||
operators work by using this function.
|
||||
"""
|
||||
@spec member?(t, element) :: boolean
|
||||
def member?(enumerable, element) do
|
||||
__in__(element, enumerable)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __in__(element, enumerable) when is_list(enumerable) do
|
||||
def member?(enumerable, element) when is_list(enumerable) do
|
||||
:lists.member(element, enumerable)
|
||||
end
|
||||
|
||||
def __in__(element, enumerable) do
|
||||
def member?(enumerable, element) do
|
||||
case Enumerable.member?(enumerable, element) do
|
||||
{:ok, element} when is_boolean(element) ->
|
||||
element
|
||||
@@ -2064,7 +2048,7 @@ defmodule Enum do
|
||||
Returns the minimal element in the `enumerable` according
|
||||
to Erlang's term ordering.
|
||||
|
||||
By default, the comparison is done with the [`<=`](`<=/2`) sorter function.
|
||||
By default, the comparison is done with the `<=` sorter function.
|
||||
If multiple elements are considered minimal, the first one that
|
||||
was found is returned. If you want the last element considered
|
||||
minimal to be returned, the sorter function should not return true
|
||||
@@ -2131,7 +2115,7 @@ defmodule Enum do
|
||||
Returns the minimal element in the `enumerable` as calculated
|
||||
by the given `fun`.
|
||||
|
||||
By default, the comparison is done with the [`<=`](`<=/2`) sorter function.
|
||||
By default, the comparison is done with the `<=` sorter function.
|
||||
If multiple elements are considered minimal, the first one that
|
||||
was found is returned. If you want the last element considered
|
||||
minimal to be returned, the sorter function should not return true
|
||||
@@ -2185,60 +2169,28 @@ defmodule Enum do
|
||||
|
||||
@doc """
|
||||
Returns a tuple with the minimal and the maximal elements in the
|
||||
enumerable.
|
||||
enumerable according to Erlang's term ordering.
|
||||
|
||||
By default, the comparison is done with the [`<`](`</2`) sorter function,
|
||||
as the function must not return true for equal elements.
|
||||
If multiple elements are considered maximal or minimal, the first one
|
||||
that was found is returned.
|
||||
|
||||
Calls the provided `empty_fallback` function and returns its value if
|
||||
`enumerable` is empty. The default `empty_fallback` raises `Enum.EmptyError`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.min_max([2, 3, 1])
|
||||
{1, 3}
|
||||
|
||||
iex> Enum.min_max(["foo", "bar", "baz"])
|
||||
{"bar", "foo"}
|
||||
|
||||
iex> Enum.min_max([], fn -> {nil, nil} end)
|
||||
{nil, nil}
|
||||
|
||||
The fact this function uses Erlang's term ordering means that the
|
||||
comparison is structural and not semantic. Therefore, if you want
|
||||
to compare structs, most structs provide a "compare" function, such as
|
||||
`Date.compare/2`, which receives two structs and returns `:lt` (less-than),
|
||||
`:eq` (equal to), and `:gt` (greater-than). If you pass a module as the
|
||||
sorting function, Elixir will automatically use the `compare/2` function
|
||||
of said module:
|
||||
|
||||
iex> dates = [
|
||||
...> ~D[2019-01-01],
|
||||
...> ~D[2020-01-01],
|
||||
...> ~D[2018-01-01]
|
||||
...> ]
|
||||
iex> Enum.min_max(dates, Date)
|
||||
{~D[2018-01-01], ~D[2020-01-01]}
|
||||
|
||||
You can also pass a custom sorting function:
|
||||
|
||||
iex> Enum.min_max([2, 3, 1], &>/2)
|
||||
{3, 1}
|
||||
|
||||
Finally, if you don't want to raise on empty enumerables, you can pass
|
||||
the empty fallback:
|
||||
|
||||
iex> Enum.min_max([], fn -> nil end)
|
||||
nil
|
||||
|
||||
"""
|
||||
@spec min_max(t, (element, element -> boolean) | module()) :: {element, element}
|
||||
@spec min_max(t, (-> empty_result)) :: {element, element} | empty_result when empty_result: any
|
||||
@spec min_max(t, (element, element -> boolean) | module(), (-> empty_result)) ::
|
||||
{element, element} | empty_result
|
||||
@spec min_max(t, (-> empty_result)) :: {element, element} | empty_result
|
||||
when empty_result: any
|
||||
def min_max(enumerable, empty_fallback \\ fn -> raise Enum.EmptyError end)
|
||||
|
||||
def min_max(enumerable, sorter_or_empty_fallback \\ fn -> raise Enum.EmptyError end)
|
||||
|
||||
def min_max(first..last//step = range, empty_fallback)
|
||||
when is_function(empty_fallback, 0) do
|
||||
def min_max(first..last//step = range, empty_fallback) when is_function(empty_fallback, 0) do
|
||||
case Range.size(range) do
|
||||
0 ->
|
||||
empty_fallback.()
|
||||
@@ -2249,39 +2201,11 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
def min_max(enumerable, empty_fallback)
|
||||
when is_function(empty_fallback, 0) do
|
||||
min_max(enumerable, &</2, empty_fallback)
|
||||
end
|
||||
|
||||
def min_max(enumerable, sorter) when is_atom(sorter) do
|
||||
min_max(enumerable, min_max_sort_fun(sorter))
|
||||
end
|
||||
|
||||
def min_max(enumerable, sorter) when is_function(sorter, 2) do
|
||||
min_max(enumerable, sorter, fn -> raise Enum.EmptyError end)
|
||||
end
|
||||
|
||||
def min_max(enumerable, sorter, empty_fallback)
|
||||
when is_atom(sorter) and is_function(empty_fallback, 0) do
|
||||
min_max(enumerable, min_max_sort_fun(sorter), empty_fallback)
|
||||
end
|
||||
|
||||
def min_max(enumerable, sorter, empty_fallback)
|
||||
when is_function(sorter, 2) and is_function(empty_fallback, 0) do
|
||||
def min_max(enumerable, empty_fallback) when is_function(empty_fallback, 0) do
|
||||
first_fun = &[&1 | &1]
|
||||
|
||||
reduce_fun = fn entry, [min | max] = acc ->
|
||||
cond do
|
||||
sorter.(entry, min) ->
|
||||
[entry | max]
|
||||
|
||||
sorter.(max, entry) ->
|
||||
[min | entry]
|
||||
|
||||
true ->
|
||||
acc
|
||||
end
|
||||
reduce_fun = fn entry, [min | max] ->
|
||||
[Kernel.min(min, entry) | Kernel.max(max, entry)]
|
||||
end
|
||||
|
||||
case reduce_by(enumerable, first_fun, reduce_fun) do
|
||||
@@ -2302,8 +2226,8 @@ defmodule Enum do
|
||||
Returns a tuple with the minimal and the maximal elements in the
|
||||
enumerable as calculated by the given function.
|
||||
|
||||
By default, the comparison is done with the [`<`](`</2`) sorter function,
|
||||
as the function must not return `true` for equal elements.
|
||||
If multiple elements are considered maximal or minimal, the first one
|
||||
that was found is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -2361,7 +2285,7 @@ defmodule Enum do
|
||||
|
||||
def min_max_by(enumerable, fun, sorter, empty_fallback)
|
||||
when is_function(fun, 1) and is_atom(sorter) and is_function(empty_fallback, 0) do
|
||||
min_max_by(enumerable, fun, min_max_sort_fun(sorter), empty_fallback)
|
||||
min_max_by(enumerable, fun, min_max_by_sort_fun(sorter), empty_fallback)
|
||||
end
|
||||
|
||||
def min_max_by(enumerable, fun, sorter, empty_fallback)
|
||||
@@ -2392,7 +2316,7 @@ defmodule Enum do
|
||||
end
|
||||
end
|
||||
|
||||
defp min_max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) == :lt)
|
||||
defp min_max_by_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) == :lt)
|
||||
|
||||
@doc """
|
||||
Splits the `enumerable` in two lists according to the given function `fun`.
|
||||
@@ -2506,7 +2430,7 @@ defmodule Enum do
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
fun.(random_count(count), 1, 1)
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
# TODO: Remove deprecation on Elixir v1.20.
|
||||
{:ok, count, fun} when is_function(fun, 2) ->
|
||||
IO.warn(
|
||||
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
|
||||
@@ -2936,19 +2860,14 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Passes each element from `enumerable` to the `fun` as the first argument,
|
||||
stores the `fun` result in a list and passes the result as the second argument
|
||||
for the next computation.
|
||||
|
||||
The `fun` isn't applied for the first element of the `enumerable`,
|
||||
the element is taken as it is.
|
||||
Applies the given function to each element in the `enumerable`,
|
||||
storing the result in a list and passing it as the accumulator
|
||||
for the next computation. Uses the first element in the `enumerable`
|
||||
as the starting value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.scan(["a", "b", "c", "d", "e"], fn element, acc -> element <> String.first(acc) end)
|
||||
["a", "ba", "cb", "dc", "ed"]
|
||||
|
||||
iex> Enum.scan(1..5, fn element, acc -> element + acc end)
|
||||
iex> Enum.scan(1..5, &(&1 + &2))
|
||||
[1, 3, 6, 10, 15]
|
||||
|
||||
"""
|
||||
@@ -2968,18 +2887,13 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Passes each element from `enumerable` to the `fun` as the first argument,
|
||||
stores the `fun` result in a list and passes the result as the second argument
|
||||
for the next computation.
|
||||
|
||||
Passes the given `acc` as the second argument for the `fun` with the first element.
|
||||
Applies the given function to each element in the `enumerable`,
|
||||
storing the result in a list and passing it as the accumulator
|
||||
for the next computation. Uses the given `acc` as the starting value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.scan(["a", "b", "c", "d", "e"], "_", fn element, acc -> element <> String.first(acc) end)
|
||||
["a_", "ba", "cb", "dc", "ed"]
|
||||
|
||||
iex> Enum.scan(1..5, 0, fn element, acc -> element + acc end)
|
||||
iex> Enum.scan(1..5, 0, &(&1 + &2))
|
||||
[1, 3, 6, 10, 15]
|
||||
|
||||
"""
|
||||
@@ -3713,14 +3627,9 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def take(enumerable, amount) when is_integer(amount) and amount < 0 do
|
||||
case slice_count_and_fun(enumerable, 1) do
|
||||
{0, _fun} ->
|
||||
[]
|
||||
|
||||
{count, fun} ->
|
||||
first = Kernel.max(amount + count, 0)
|
||||
fun.(first, count - first, 1)
|
||||
end
|
||||
{count, fun} = slice_count_and_fun(enumerable, 1)
|
||||
first = Kernel.max(amount + count, 0)
|
||||
fun.(first, count - first, 1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -3997,7 +3906,6 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec unzip(t) :: {[element], [element]}
|
||||
def unzip(enumerable)
|
||||
|
||||
def unzip([_ | _] = list) do
|
||||
:lists.reverse(list) |> unzip([], [])
|
||||
@@ -4031,9 +3939,8 @@ defmodule Enum do
|
||||
If an integer offset is given as `fun_or_offset`, it will index from the given
|
||||
offset instead of from zero.
|
||||
|
||||
If a 2-arity function is given as `fun_or_offset`, the function will be invoked
|
||||
for each element in `enumerable` as the first argument and with a zero-based
|
||||
index as the second. `with_index/2` returns a list with the result of each invocation.
|
||||
If a function is given as `fun_or_offset`, it will index by invoking the function
|
||||
for each element and index (zero-based) of the enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -4144,10 +4051,10 @@ defmodule Enum do
|
||||
key in the left map and the matching key in the right map, but there is no such
|
||||
guarantee because map keys are not ordered! Consider the following:
|
||||
|
||||
left = %{:a => 1, 1 => 3}
|
||||
left = %{:a => 1, 1 => 3}
|
||||
right = %{:a => 1, :b => :c}
|
||||
Enum.zip(left, right)
|
||||
#=> [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
|
||||
# [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
|
||||
|
||||
As you can see `:a` does not get paired with `:a`. If this is what you want,
|
||||
you should use `Map.merge/3`.
|
||||
@@ -4196,11 +4103,6 @@ defmodule Enum do
|
||||
iex> Enum.zip_with([[1, 2], [3, 4]], fn [x, y] -> x + y end)
|
||||
[4, 6]
|
||||
|
||||
`zip_with/2` can be used to transpose lists of lists:
|
||||
|
||||
iex> Enum.zip_with([[1, 2,], [3, 4]], & &1)
|
||||
[[1, 3], [2, 4]]
|
||||
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
@spec zip_with(t, ([term] -> term)) :: [term]
|
||||
@@ -4225,11 +4127,8 @@ defmodule Enum do
|
||||
iex> Enum.zip_reduce([1, 2], [3, 4], 0, fn x, y, acc -> x + y + acc end)
|
||||
10
|
||||
|
||||
If one of the lists has more entries than the others,
|
||||
those entries are discarded:
|
||||
|
||||
iex> Enum.zip_reduce([1, 2, 3], [4, 5], [], fn x, y, acc -> [x + y | acc] end)
|
||||
[7, 5]
|
||||
iex> Enum.zip_reduce([1, 2], [3, 4], [], fn x, y, acc -> [x + y | acc] end)
|
||||
[6, 4]
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
@spec zip_reduce(t, t, acc, (enum1_elem :: term, enum2_elem :: term, acc -> acc)) :: acc
|
||||
@@ -4265,10 +4164,7 @@ defmodule Enum do
|
||||
...> end)
|
||||
[{1, 2, 3}, {1, 2, 3}]
|
||||
|
||||
If one of the lists has more entries than the others,
|
||||
those entries are discarded:
|
||||
|
||||
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6, 7]]
|
||||
iex> enums = [[1, 2], [a: 3, b: 4], [5, 6]]
|
||||
...> Enum.zip_reduce(enums, [], fn elements, acc ->
|
||||
...> [List.to_tuple(elements) | acc]
|
||||
...> end)
|
||||
@@ -4278,8 +4174,8 @@ defmodule Enum do
|
||||
@spec zip_reduce(t, acc, ([term], acc -> acc)) :: acc when acc: term
|
||||
def zip_reduce([], acc, reducer) when is_function(reducer, 2), do: acc
|
||||
|
||||
def zip_reduce(enumerables, acc, reducer) when is_function(reducer, 2) do
|
||||
R.zip_with(enumerables, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
|
||||
def zip_reduce(enums, acc, reducer) when is_function(reducer, 2) do
|
||||
R.zip_with(enums, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -4452,69 +4348,21 @@ defmodule Enum do
|
||||
enum |> reduce([], &reduce(&1, &2, fun)) |> :lists.reverse()
|
||||
end
|
||||
|
||||
# count_until
|
||||
|
||||
@compile {:inline, count_until_list: 3}
|
||||
|
||||
defp count_until_list([], _limit, acc), do: acc
|
||||
|
||||
defp count_until_list([_head | tail], limit, acc) do
|
||||
case acc + 1 do
|
||||
^limit -> limit
|
||||
acc -> count_until_list(tail, limit, acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp count_until_enum(enumerable, limit) do
|
||||
case Enumerable.count(enumerable) do
|
||||
{:ok, value} ->
|
||||
Kernel.min(value, limit)
|
||||
|
||||
{:error, module} ->
|
||||
module.reduce(enumerable, {:cont, 0}, fn _entry, acc ->
|
||||
case acc + 1 do
|
||||
^limit -> {:halt, limit}
|
||||
acc -> {:cont, acc}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, count_until_list: 4}
|
||||
|
||||
defp count_until_list([], _fun, _limit, acc), do: acc
|
||||
|
||||
defp count_until_list([head | tail], fun, limit, acc) do
|
||||
if fun.(head) do
|
||||
case acc + 1 do
|
||||
^limit -> limit
|
||||
acc -> count_until_list(tail, fun, limit, acc)
|
||||
end
|
||||
else
|
||||
count_until_list(tail, fun, limit, acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp count_until_enum(enumerable, fun, limit) do
|
||||
Enumerable.reduce(enumerable, {:cont, 0}, fn entry, acc ->
|
||||
if fun.(entry) do
|
||||
case acc + 1 do
|
||||
^limit -> {:halt, limit}
|
||||
acc -> {:cont, acc}
|
||||
end
|
||||
else
|
||||
{:cont, acc}
|
||||
end
|
||||
end)
|
||||
|> elem(1)
|
||||
end
|
||||
|
||||
# dedup
|
||||
|
||||
defp dedup_list([value | [value | _] = tail]), do: dedup_list(tail)
|
||||
defp dedup_list([value | tail]), do: [value | dedup_list(tail)]
|
||||
defp dedup_list([]), do: []
|
||||
defp dedup_list([value | tail], acc) do
|
||||
acc =
|
||||
case acc do
|
||||
[^value | _] -> acc
|
||||
_ -> [value | acc]
|
||||
end
|
||||
|
||||
dedup_list(tail, acc)
|
||||
end
|
||||
|
||||
defp dedup_list([], acc) do
|
||||
acc
|
||||
end
|
||||
|
||||
## drop
|
||||
|
||||
@@ -4740,7 +4588,7 @@ defmodule Enum do
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
fun.(start, amount, step)
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
# TODO: Remove me on v2.0.
|
||||
{:ok, count, fun} when is_function(fun, 2) ->
|
||||
IO.warn(
|
||||
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
|
||||
@@ -5108,7 +4956,8 @@ end
|
||||
defimpl Enumerable, for: List do
|
||||
def count(list), do: {:ok, length(list)}
|
||||
|
||||
def member?(list, value), do: {:ok, :lists.member(value, list)}
|
||||
def member?([], _value), do: {:ok, false}
|
||||
def member?(_list, _value), do: {:error, __MODULE__}
|
||||
|
||||
def slice([]), do: {:ok, 0, fn _, _, _ -> [] end}
|
||||
def slice(_list), do: {:error, __MODULE__}
|
||||
@@ -5156,87 +5005,3 @@ defimpl Enumerable, for: Function do
|
||||
description: "only anonymous functions of arity 2 are enumerable"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: Range do
|
||||
def reduce(first..last//step, acc, fun) do
|
||||
reduce(first, last, acc, fun, step)
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
reduce =
|
||||
quote generated: true do
|
||||
reduce(
|
||||
%{__struct__: Range, first: var!(first), last: var!(last)} = var!(range),
|
||||
var!(acc),
|
||||
var!(fun)
|
||||
)
|
||||
end
|
||||
|
||||
def unquote(reduce) do
|
||||
step = if first <= last, do: 1, else: -1
|
||||
reduce(Map.put(range, :step, step), acc, fun)
|
||||
end
|
||||
|
||||
defp reduce(_first, _last, {:halt, acc}, _fun, _step) do
|
||||
{:halted, acc}
|
||||
end
|
||||
|
||||
defp reduce(first, last, {:suspend, acc}, fun, step) do
|
||||
{:suspended, acc, &reduce(first, last, &1, fun, step)}
|
||||
end
|
||||
|
||||
defp reduce(first, last, {:cont, acc}, fun, step)
|
||||
when step > 0 and first <= last
|
||||
when step < 0 and first >= last do
|
||||
reduce(first + step, last, fun.(first, acc), fun, step)
|
||||
end
|
||||
|
||||
defp reduce(_, _, {:cont, acc}, _fun, _up) do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first..last//step, value) when is_integer(value) and step > 0 do
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
end
|
||||
|
||||
def member?(first..last//step, value) when is_integer(value) and step < 0 do
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
def member?(%{__struct__: Range, first: first, last: last} = range, value)
|
||||
when is_integer(value) do
|
||||
step = if first <= last, do: 1, else: -1
|
||||
member?(Map.put(range, :step, step), value)
|
||||
end
|
||||
|
||||
def member?(_, _value) do
|
||||
{:ok, false}
|
||||
end
|
||||
|
||||
def count(range) do
|
||||
{:ok, Range.size(range)}
|
||||
end
|
||||
|
||||
def slice(first.._//step = range) do
|
||||
{:ok, Range.size(range), &slice(first + &1 * step, step * &3, &2)}
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
|
||||
slice =
|
||||
quote generated: true do
|
||||
slice(%{__struct__: Range, first: var!(first), last: var!(last)} = var!(range))
|
||||
end
|
||||
|
||||
def unquote(slice) do
|
||||
step = if first <= last, do: 1, else: -1
|
||||
slice(Map.put(range, :step, step))
|
||||
end
|
||||
|
||||
defp slice(current, _step, 1), do: [current]
|
||||
|
||||
defp slice(current, step, remaining) when remaining > 1 do
|
||||
[current | slice(current + step, step, remaining - 1)]
|
||||
end
|
||||
end
|
||||
|
||||
+185
-298
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Exception do
|
||||
@moduledoc """
|
||||
Functions for dealing with throw/catch/exit and exceptions.
|
||||
@@ -26,7 +22,7 @@ defmodule Exception do
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => term,
|
||||
required(:__exception__) => true,
|
||||
optional(atom) => any
|
||||
}
|
||||
|
||||
@@ -77,19 +73,14 @@ defmodule Exception do
|
||||
@doc false
|
||||
@deprecated "Use Kernel.is_exception/1 instead"
|
||||
def exception?(term)
|
||||
def exception?(%_{__exception__: _}), do: true
|
||||
def exception?(%_{__exception__: true}), do: true
|
||||
def exception?(_), do: false
|
||||
|
||||
@doc """
|
||||
Gets the message for an `exception`.
|
||||
|
||||
This function will invoke the `c:message/1` callback on the exception
|
||||
module to retrieve the message. If the callback raises an exception or
|
||||
returns a non-binary value, this function will rescue the error and
|
||||
return a descriptive error message instead.
|
||||
"""
|
||||
@spec message(t) :: String.t()
|
||||
def message(%module{__exception__: _} = exception) do
|
||||
def message(%module{__exception__: true} = exception) do
|
||||
try do
|
||||
module.message(exception)
|
||||
rescue
|
||||
@@ -123,7 +114,7 @@ defmodule Exception do
|
||||
@spec normalize(:error, any, stacktrace) :: t
|
||||
@spec normalize(non_error_kind, payload, stacktrace) :: payload when payload: var
|
||||
def normalize(kind, payload, stacktrace \\ [])
|
||||
def normalize(:error, %_{__exception__: _} = payload, _stacktrace), do: payload
|
||||
def normalize(:error, %_{__exception__: true} = payload, _stacktrace), do: payload
|
||||
def normalize(:error, payload, stacktrace), do: ErlangError.normalize(payload, stacktrace)
|
||||
def normalize(_kind, payload, _stacktrace), do: payload
|
||||
|
||||
@@ -158,7 +149,7 @@ defmodule Exception do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalizes and formats throws/errors/exits and stacktraces.
|
||||
Normalizes and formats throw/errors/exits and stacktraces.
|
||||
|
||||
It relies on `format_banner/3` and `format_stacktrace/1`
|
||||
to generate the final format.
|
||||
@@ -182,33 +173,15 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __format_message_with_term__(message, term) do
|
||||
inspected =
|
||||
term
|
||||
|> inspect(pretty: true)
|
||||
|> String.split("\n")
|
||||
|> Enum.map_intersperse("\n", fn
|
||||
"" -> ""
|
||||
line -> " " <> line
|
||||
end)
|
||||
|
||||
IO.iodata_to_binary([message, "\n\n", inspected, "\n"])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Attaches information to throws/errors/exits for extra debugging.
|
||||
Attaches information to exceptions for extra debugging.
|
||||
|
||||
This operation is potentially expensive, as it reads data
|
||||
from the file system, parses beam files, evaluates code and
|
||||
so on.
|
||||
|
||||
If `kind` argument is `:error` and the `error` is an Erlang exception, this function will
|
||||
normalize it. If the `error` argument is an Elixir exception, this function will invoke
|
||||
the optional `c:blame/2` callback on the exception module if it is implemented.
|
||||
Unlike `message/1`, this function will not rescue errors - if the callback raises an exception,
|
||||
the error will propagate to the caller. It is your choice if you want to rescue and return
|
||||
the original exception, return a different exception, or let it cascade.
|
||||
If the exception module implements the optional `c:blame/2`
|
||||
callback, it will be invoked to perform the computation.
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec blame(:error, any, stacktrace) :: {t, stacktrace}
|
||||
@@ -287,10 +260,10 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp map_node?({:is_map, _, [_]}), do: true
|
||||
defp map_node?(_), do: false
|
||||
defp map_key_node?({:is_map_key, _, [_, _]}), do: true
|
||||
defp map_key_node?(_), do: false
|
||||
defp is_map_node?({:is_map, _, [_]}), do: true
|
||||
defp is_map_node?(_), do: false
|
||||
defp is_map_key_node?({:is_map_key, _, [_, _]}), do: true
|
||||
defp is_map_key_node?(_), do: false
|
||||
|
||||
defp struct_validation_node?(
|
||||
{:is_atom, _, [{{:., [], [:erlang, :map_get]}, _, [:__struct__, _]}]}
|
||||
@@ -304,16 +277,16 @@ defmodule Exception do
|
||||
|
||||
defp struct_validation_node?(_), do: false
|
||||
|
||||
defp struct_macro?(
|
||||
defp is_struct_macro?(
|
||||
{:and, _,
|
||||
[
|
||||
{:and, _, [%{node: node_1 = {_, _, [arg]}}, %{node: node_2 = {_, _, [arg, _]}}]},
|
||||
%{node: node_3 = {_, _, [{_, _, [_, arg]}]}}
|
||||
]}
|
||||
),
|
||||
do: map_node?(node_1) and map_key_node?(node_2) and struct_validation_node?(node_3)
|
||||
do: is_map_node?(node_1) and is_map_key_node?(node_2) and struct_validation_node?(node_3)
|
||||
|
||||
defp struct_macro?(
|
||||
defp is_struct_macro?(
|
||||
{:and, _,
|
||||
[
|
||||
{:and, _,
|
||||
@@ -328,12 +301,12 @@ defmodule Exception do
|
||||
%{node: node_3 = {_, _, [{_, _, [_, arg]}, _]}}
|
||||
]}
|
||||
),
|
||||
do: map_node?(node_1) and map_key_node?(node_2) and struct_validation_node?(node_3)
|
||||
do: is_map_node?(node_1) and is_map_key_node?(node_2) and struct_validation_node?(node_3)
|
||||
|
||||
defp struct_macro?(_), do: false
|
||||
defp is_struct_macro?(_), do: false
|
||||
|
||||
defp translate_guard(guard) do
|
||||
if struct_macro?(guard) do
|
||||
if is_struct_macro?(guard) do
|
||||
undo_is_struct_guard(guard)
|
||||
else
|
||||
guard
|
||||
@@ -1067,12 +1040,7 @@ defmodule ArgumentError do
|
||||
An exception raised when an argument to a function is invalid.
|
||||
|
||||
You can raise this exception when you want to signal that an argument to
|
||||
a function is invalid. For example, this exception is raised when calling
|
||||
`Integer.to_string/1` with an invalid argument:
|
||||
|
||||
iex> Integer.to_string(1.0)
|
||||
** (ArgumentError) errors were found at the given arguments:
|
||||
...
|
||||
a function is invalid.
|
||||
|
||||
`ArgumentError` exceptions have a single field, `:message` (a `t:String.t/0`),
|
||||
which is public and can be accessed freely when reading or creating `ArgumentError`
|
||||
@@ -1089,7 +1057,8 @@ defmodule ArithmeticError do
|
||||
For example, this exception is raised if you divide by `0`:
|
||||
|
||||
iex> 1 / 0
|
||||
** (ArithmeticError) bad argument in arithmetic expression
|
||||
** (ArithmeticError) bad argument in arithmetic expression: 1 / 0
|
||||
|
||||
"""
|
||||
|
||||
defexception message: "bad argument in arithmetic expression"
|
||||
@@ -1135,10 +1104,7 @@ defmodule SystemLimitError do
|
||||
@moduledoc """
|
||||
An exception raised when a system limit has been reached.
|
||||
|
||||
For example, this can happen if you try to create an atom that is too large:
|
||||
|
||||
iex> String.to_atom(String.duplicate("a", 100_000))
|
||||
** (SystemLimitError) a system limit has been reached
|
||||
For example, this can happen if you try to create an atom that is too large.
|
||||
"""
|
||||
|
||||
defexception message: "a system limit has been reached"
|
||||
@@ -1150,9 +1116,8 @@ defmodule MismatchedDelimiterError do
|
||||
|
||||
For example:
|
||||
|
||||
iex> Code.eval_string("[1, 2, 3}")
|
||||
** (MismatchedDelimiterError) mismatched delimiter found on nofile:1:9:
|
||||
...
|
||||
* `[1, 2, 3}`
|
||||
* `fn a -> )`
|
||||
|
||||
The following fields of this exceptions are public and can be accessed freely:
|
||||
|
||||
@@ -1166,6 +1131,7 @@ defmodule MismatchedDelimiterError do
|
||||
* `:closing_delimiter` - an atom representing the mismatched closing delimiter
|
||||
* `:expected_delimiter` - an atom representing the closing delimiter
|
||||
* `:description` - a description of the mismatched delimiter error
|
||||
|
||||
"""
|
||||
|
||||
defexception [
|
||||
@@ -1224,12 +1190,6 @@ defmodule SyntaxError do
|
||||
@moduledoc """
|
||||
An exception raised when there's a syntax error when parsing code.
|
||||
|
||||
For example:
|
||||
|
||||
iex> Code.eval_string("5 + 5h")
|
||||
** (SyntaxError) invalid syntax found on nofile:1:5:
|
||||
...
|
||||
|
||||
The following fields of this exceptions are public and can be accessed freely:
|
||||
|
||||
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
|
||||
@@ -1237,6 +1197,7 @@ defmodule SyntaxError do
|
||||
* `:line` - the line where the error occurred
|
||||
* `:column` - the column where the error occurred
|
||||
* `:description` - a description of the syntax error
|
||||
|
||||
"""
|
||||
|
||||
defexception [:file, :line, :column, :snippet, description: "syntax error"]
|
||||
@@ -1280,12 +1241,6 @@ defmodule TokenMissingError do
|
||||
@moduledoc """
|
||||
An exception raised when a token is missing when parsing code.
|
||||
|
||||
For example:
|
||||
|
||||
iex> Code.eval_string("[1, 2, 3")
|
||||
** (TokenMissingError) token missing on nofile:1:9:
|
||||
...
|
||||
|
||||
The following fields of this exceptions are public and can be accessed freely:
|
||||
|
||||
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
|
||||
@@ -1297,8 +1252,6 @@ defmodule TokenMissingError do
|
||||
* `:opening_delimiter` - an atom representing the opening delimiter
|
||||
* `:expected_delimiter` - an atom representing the expected delimiter
|
||||
* `:description` - a description of the missing token error
|
||||
|
||||
This is mostly raised by Elixir tooling when compiling and evaluating code.
|
||||
"""
|
||||
|
||||
defexception [
|
||||
@@ -1378,19 +1331,12 @@ defmodule CompileError do
|
||||
@moduledoc """
|
||||
An exception raised when there's an error when compiling code.
|
||||
|
||||
For example:
|
||||
|
||||
1 = y
|
||||
** (CompileError) iex:1: undefined variable "y"
|
||||
|
||||
The following fields of this exceptions are public and can be accessed freely:
|
||||
|
||||
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
|
||||
the error occurred in code that did not come from a file
|
||||
* `:line` (`t:non_neg_integer/0`) - the line where the error occurred
|
||||
* `:description` (`t:String.t/0`) - a description of the compile error
|
||||
|
||||
This is mostly raised by Elixir tooling when compiling and evaluating code.
|
||||
"""
|
||||
|
||||
defexception [:file, :line, description: "compile error"]
|
||||
@@ -1408,19 +1354,12 @@ defmodule Kernel.TypespecError do
|
||||
@moduledoc """
|
||||
An exception raised when there's an error in a typespec.
|
||||
|
||||
For example, if your typespec definition points to an invalid type, you get an exception:
|
||||
|
||||
@type my_type :: intger()
|
||||
|
||||
will raise:
|
||||
|
||||
** (Kernel.TypespecError) type intger/0 undefined
|
||||
|
||||
The following fields of this exceptions are public and can be accessed freely:
|
||||
|
||||
* `:file` (`t:Path.t/0` or `nil`) - the file where the error occurred, or `nil` if
|
||||
the error occurred in code that did not come from a file
|
||||
* `:line` (`t:non_neg_integer/0`) - the line where the error occurred
|
||||
|
||||
"""
|
||||
|
||||
defexception [:file, :line, :description]
|
||||
@@ -1435,16 +1374,6 @@ defmodule Kernel.TypespecError do
|
||||
end
|
||||
|
||||
defmodule BadFunctionError do
|
||||
@moduledoc """
|
||||
An exception raised when a function is expected, but something else was given.
|
||||
|
||||
For example:
|
||||
|
||||
iex> value = "hello"
|
||||
iex> value.()
|
||||
** (BadFunctionError) expected a function, got: "hello"
|
||||
"""
|
||||
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
@@ -1457,48 +1386,38 @@ defmodule BadFunctionError do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadStructError do
|
||||
defexception [:struct, :term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
"expected a struct named #{inspect(exception.struct)}, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadMapError do
|
||||
@moduledoc """
|
||||
An exception raised when a map is expected, but something else was given.
|
||||
|
||||
For example:
|
||||
|
||||
iex> value = "hello"
|
||||
iex> %{value | key: "value"}
|
||||
** (BadMapError) expected a map, got:
|
||||
...
|
||||
An exception raised when something expected a map, but received something else.
|
||||
"""
|
||||
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"expected a map, got:",
|
||||
exception.term
|
||||
)
|
||||
"expected a map, got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadBooleanError do
|
||||
@moduledoc """
|
||||
An exception raised when a boolean is expected, but something else was given.
|
||||
|
||||
This exception is raised by `and` and `or` when the first argument is not a boolean:
|
||||
|
||||
iex> 123 and true
|
||||
** (BadBooleanError) expected a boolean on left-side of "and", got:
|
||||
...
|
||||
An exception raised when an operator expected a boolean, but received something else.
|
||||
"""
|
||||
|
||||
defexception [:term, :operator]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"expected a boolean on left-side of \"#{exception.operator}\", got:",
|
||||
exception.term
|
||||
)
|
||||
"expected a boolean on left-side of \"#{exception.operator}\", got: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1506,26 +1425,21 @@ defmodule MatchError do
|
||||
@moduledoc """
|
||||
An exception raised when a pattern match (`=/2`) fails.
|
||||
|
||||
For example:
|
||||
|
||||
iex> [_ | _] = []
|
||||
** (MatchError) no match of right hand side value:
|
||||
...
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:term` (`t:term/0`) - the term that did not match the pattern
|
||||
|
||||
For example, this exception gets raised for code like this:
|
||||
|
||||
[_ | _] = []
|
||||
|
||||
"""
|
||||
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"no match of right hand side value:",
|
||||
exception.term
|
||||
)
|
||||
"no match of right hand side value: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1534,28 +1448,24 @@ defmodule CaseClauseError do
|
||||
An exception raised when a term in a `case/2` expression
|
||||
does not match any of the defined `->` clauses.
|
||||
|
||||
For example:
|
||||
|
||||
iex> case System.unique_integer() do
|
||||
...> bin when is_binary(bin) -> :oops
|
||||
...> :ok -> :neither_this_one
|
||||
...> end
|
||||
** (CaseClauseError) no case clause matching:
|
||||
...
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:term` (`t:term/0`) - the term that did not match any of the clauses
|
||||
|
||||
For example, this exception gets raised for a `case/2` like the following:
|
||||
|
||||
case System.unique_integer() do
|
||||
bin when is_binary(bin) -> :oops
|
||||
:ok -> :neither_this_one
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"no case clause matching:",
|
||||
exception.term
|
||||
)
|
||||
"no case clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1564,32 +1474,28 @@ defmodule WithClauseError do
|
||||
An exception raised when a term in a `with/1` expression
|
||||
does not match any of the defined `->` clauses in its `else`.
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:term` (`t:term/0`) - the term that did not match any of the clauses
|
||||
|
||||
For example, this exception gets raised for a `with/1` like the following, because
|
||||
the `{:ok, 2}` term does not match the `:error` or `{:error, _}` clauses in the
|
||||
`else`:
|
||||
|
||||
iex> with {:ok, 1} <- {:ok, 2} do
|
||||
...> :woah
|
||||
...> else
|
||||
...> :error -> :error
|
||||
...> {:error, _} -> :error
|
||||
...> end
|
||||
** (WithClauseError) no with clause matching:
|
||||
...
|
||||
with {:ok, 1} <- {:ok, 2} do
|
||||
:woah
|
||||
else
|
||||
:error -> :error
|
||||
{:error, _} -> :error
|
||||
end
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:term` (`t:term/0`) - the term that did not match any of the clauses
|
||||
"""
|
||||
|
||||
defexception [:term]
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"no with clause matching:",
|
||||
exception.term
|
||||
)
|
||||
"no with clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1599,11 +1505,11 @@ defmodule CondClauseError do
|
||||
|
||||
For example, this exception gets raised for a `cond/1` like the following:
|
||||
|
||||
iex> cond do
|
||||
...> 1 + 1 == 3 -> :woah
|
||||
...> nil -> "yeah this won't happen"
|
||||
...> end
|
||||
** (CondClauseError) no cond clause evaluated to a truthy value
|
||||
cond do
|
||||
1 + 1 == 3 -> :woah
|
||||
nil -> "yeah this won't happen
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
defexception []
|
||||
@@ -1616,20 +1522,8 @@ end
|
||||
|
||||
defmodule TryClauseError do
|
||||
@moduledoc """
|
||||
An exception raised when none of the `else` clauses in a `try/1` match.
|
||||
|
||||
For example:
|
||||
|
||||
iex> try do
|
||||
...> :ok
|
||||
...> rescue
|
||||
...> e -> e
|
||||
...> else
|
||||
...> # :ok -> :ok is missing
|
||||
...> :not_ok -> :not_ok
|
||||
...> end
|
||||
** (TryClauseError) no try clause matching:
|
||||
...
|
||||
An exception raised when a term in a `try/1` expression
|
||||
does not match any of the defined `->` clauses in its `else`.
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
@@ -1640,22 +1534,13 @@ defmodule TryClauseError do
|
||||
|
||||
@impl true
|
||||
def message(exception) do
|
||||
Exception.__format_message_with_term__(
|
||||
"no try clause matching:",
|
||||
exception.term
|
||||
)
|
||||
"no try clause matching: #{inspect(exception.term)}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule BadArityError do
|
||||
@moduledoc """
|
||||
An exception raised when a function is called with the wrong number of arguments.
|
||||
|
||||
For example:
|
||||
|
||||
my_function = fn x, y -> x + y end
|
||||
my_function.(42)
|
||||
** (BadArityError) #Function<41.39164016/2 in :erl_eval.expr/6> with arity 2 called with 1 argument (42)
|
||||
"""
|
||||
|
||||
defexception [:function, :args]
|
||||
@@ -1678,17 +1563,22 @@ defmodule UndefinedFunctionError do
|
||||
@moduledoc """
|
||||
An exception raised when a function is invoked that is not defined.
|
||||
|
||||
For example:
|
||||
|
||||
# Let's use apply/3 as otherwise Elixir emits a compile-time warning
|
||||
iex> apply(String, :non_existing_fun, ["hello"])
|
||||
** (UndefinedFunctionError) function String.non_existing_fun/1 is undefined or private
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:module` (`t:module/0`) - the module name
|
||||
* `:function` (`t:atom/0`) - the function name
|
||||
* `:arity` (`t:non_neg_integer/0`) - the arity of the function
|
||||
|
||||
For example, if you try to call `MyMod.non_existing_fun("hello", 1)`,
|
||||
the error would look like:
|
||||
|
||||
%UndefinedFunctionError{
|
||||
module: MyMod,
|
||||
function: :non_existing_fun,
|
||||
arity: 2,
|
||||
# Other private fields...
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
@function_threshold 0.77
|
||||
@@ -1898,7 +1788,7 @@ defmodule UndefinedFunctionError do
|
||||
end
|
||||
|
||||
defp format_fa({_dist, fun, arity}) do
|
||||
[" * ", Macro.inspect_atom(:remote_call, fun), ?/, Integer.to_string(arity), ?\n]
|
||||
[" * ", Macro.inspect_atom(:remote_call, fun), ?/, Integer.to_string(arity), ?\n]
|
||||
end
|
||||
|
||||
defp exports_for(module) do
|
||||
@@ -1928,16 +1818,22 @@ defmodule FunctionClauseError do
|
||||
@moduledoc """
|
||||
An exception raised when a function call doesn't match any defined clause.
|
||||
|
||||
For example:
|
||||
|
||||
iex> List.duplicate(:ok, -3)
|
||||
** (FunctionClauseError) no function clause matching in List.duplicate/2
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:module` (`t:module/0`) - the module name
|
||||
* `:function` (`t:atom/0`) - the function name
|
||||
* `:arity` (`t:non_neg_integer/0`) - the arity of the function
|
||||
|
||||
For example, if you try to call a function such as `URI.parse/1` with something
|
||||
other than a string, the error would look like:
|
||||
|
||||
%FunctionClauseError{
|
||||
module: URI,
|
||||
function: :parse,
|
||||
arity: 1,
|
||||
# Other private fields...
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
defexception [:module, :function, :arity, :kind, :args, :clauses]
|
||||
@@ -2057,15 +1953,11 @@ defmodule Code.LoadError do
|
||||
@moduledoc """
|
||||
An exception raised when a file cannot be loaded.
|
||||
|
||||
This is typically raised by functions in the `Code` module, for example:
|
||||
|
||||
Code.require_file("missing_file.exs")
|
||||
** (Code.LoadError) could not load missing_file.exs. Reason: enoent
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:file` (`t:String.t/0`) - the file name
|
||||
* `:reason` (`t:term/0`) - the reason why the file could not be loaded
|
||||
|
||||
"""
|
||||
|
||||
defexception [:file, :message, :reason]
|
||||
@@ -2082,16 +1974,23 @@ defmodule Protocol.UndefinedError do
|
||||
@moduledoc """
|
||||
An exception raised when a protocol is not implemented for a given value.
|
||||
|
||||
For example:
|
||||
|
||||
iex> Enum.at("A string!", 0)
|
||||
** (Protocol.UndefinedError) protocol Enumerable not implemented for BitString
|
||||
...
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:protocol` (`t:module/0`) - the protocol that is not implemented
|
||||
* `:value` (`t:term/0`) - the value that does not implement the protocol
|
||||
|
||||
For example, this code:
|
||||
|
||||
Enum.at("A string!", 0)
|
||||
|
||||
would raise the following exception:
|
||||
|
||||
%Protocol.UndefinedError{
|
||||
protocol: Enumerable,
|
||||
value: "A string!",
|
||||
# ...
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
defexception [:protocol, :value, description: ""]
|
||||
@@ -2104,7 +2003,7 @@ defmodule Protocol.UndefinedError do
|
||||
# Indent only lines with contents on them
|
||||
|> String.replace(~r/^(?=.+)/m, " ")
|
||||
|
||||
"protocol #{inspect(protocol)} not implemented for " <>
|
||||
"protocol #{inspect(protocol)} not implemented for type " <>
|
||||
value_type(value) <>
|
||||
maybe_description(description) <>
|
||||
maybe_available(protocol) <>
|
||||
@@ -2139,7 +2038,7 @@ defmodule Protocol.UndefinedError do
|
||||
". There are no implementations for this protocol."
|
||||
|
||||
{:consolidated, types} ->
|
||||
". This protocol is implemented for: " <>
|
||||
". This protocol is implemented for the following type(s): " <>
|
||||
Enum.map_join(types, ", ", &inspect/1)
|
||||
|
||||
:not_consolidated ->
|
||||
@@ -2153,17 +2052,13 @@ defmodule KeyError do
|
||||
An exception raised when a key is not found in a data structure.
|
||||
|
||||
For example, this is raised by `Map.fetch!/2` when the given key
|
||||
cannot be found in the given map:
|
||||
|
||||
iex> map = %{name: "Alice", age: 25}
|
||||
iex> Map.fetch!(map, :first_name)
|
||||
** (KeyError) key :first_name not found in:
|
||||
...
|
||||
cannot be found in the given map.
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:term` (`t:term/0`) - the data structure that was searched
|
||||
* `:key` (`t:term/0`) - the key that was not found
|
||||
|
||||
"""
|
||||
|
||||
defexception [:key, :term, :message]
|
||||
@@ -2185,10 +2080,7 @@ defmodule KeyError do
|
||||
"make sure to add parentheses after the function name)"
|
||||
|
||||
true ->
|
||||
Exception.__format_message_with_term__(
|
||||
message <> " in:",
|
||||
term
|
||||
)
|
||||
message <> " in: #{inspect(term, pretty: true, limit: :infinity)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2230,7 +2122,7 @@ defmodule KeyError do
|
||||
|
||||
case suggestions do
|
||||
[] -> []
|
||||
suggestions -> ["\nDid you mean:\n\n" | format_suggestions(suggestions)]
|
||||
suggestions -> [". Did you mean:\n\n" | format_suggestions(suggestions)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2239,20 +2131,11 @@ defmodule KeyError do
|
||||
|> Enum.sort(&(elem(&1, 0) >= elem(&2, 0)))
|
||||
|> Enum.take(@max_suggestions)
|
||||
|> Enum.sort(&(elem(&1, 1) <= elem(&2, 1)))
|
||||
|> Enum.map(fn {_, key} -> [" * ", inspect(key), ?\n] end)
|
||||
|> Enum.map(fn {_, key} -> [" * ", inspect(key), ?\n] end)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule UnicodeConversionError do
|
||||
@moduledoc """
|
||||
An exception raised when converting data to or from Unicode.
|
||||
|
||||
For example:
|
||||
|
||||
iex> String.to_charlist(<<0xFF>>)
|
||||
** (UnicodeConversionError) invalid encoding starting at <<255>>
|
||||
|
||||
"""
|
||||
defexception [:encoded, :message]
|
||||
|
||||
def exception(opts) do
|
||||
@@ -2330,31 +2213,10 @@ defmodule Enum.OutOfBoundsError do
|
||||
An exception that is raised when a function expects an enumerable to have
|
||||
a certain size but finds that it is too small.
|
||||
|
||||
For example:
|
||||
|
||||
iex> Enum.fetch!([1, 2, 3], 5)
|
||||
** (Enum.OutOfBoundsError) out of bounds error at position 5 when traversing enumerable [1, 2, 3]
|
||||
For example, this is raised by `Access.at!/1`.
|
||||
"""
|
||||
|
||||
defexception [:enumerable, :index, :message]
|
||||
|
||||
@impl true
|
||||
def message(exception = %{message: nil}), do: message(exception.index, exception.enumerable)
|
||||
def message(%{message: message}), do: message
|
||||
|
||||
def message(index, enumerable) do
|
||||
"out of bounds error" <>
|
||||
if index do
|
||||
" at position #{index}"
|
||||
else
|
||||
""
|
||||
end <>
|
||||
if enumerable do
|
||||
" when traversing enumerable #{inspect(enumerable)}"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
defexception message: "out of bounds error"
|
||||
end
|
||||
|
||||
defmodule Enum.EmptyError do
|
||||
@@ -2362,11 +2224,7 @@ defmodule Enum.EmptyError do
|
||||
An exception that is raised when something expects a non-empty enumerable
|
||||
but finds an empty one.
|
||||
|
||||
For example:
|
||||
|
||||
iex> Enum.min([])
|
||||
** (Enum.EmptyError) empty error
|
||||
|
||||
For example, this is raised by `Enum.min/3`.
|
||||
"""
|
||||
|
||||
defexception message: "empty error"
|
||||
@@ -2376,11 +2234,6 @@ defmodule File.Error do
|
||||
@moduledoc """
|
||||
An exception that is raised when a file operation fails.
|
||||
|
||||
For example, this exception is raised, when trying to read a non existent file:
|
||||
|
||||
iex> File.read!("nonexistent_file.txt")
|
||||
** (File.Error) could not read file "nonexistent_file.txt": no such file or directory
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:path` (`t:Path.t/0`) - the path of the file that caused the error
|
||||
@@ -2409,11 +2262,6 @@ defmodule File.CopyError do
|
||||
@moduledoc """
|
||||
An exception that is raised when copying a file fails.
|
||||
|
||||
For example, this exception is raised when trying to copy to file or directory that isn't present:
|
||||
|
||||
iex> File.cp_r!("non_existent", "source_dir/subdir")
|
||||
** (File.CopyError) could not copy recursively from "non_existent" to "source_dir/subdir". non_existent: no such file or directory
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:source` (`t:Path.t/0`) - the source path
|
||||
@@ -2443,11 +2291,6 @@ defmodule File.RenameError do
|
||||
@moduledoc """
|
||||
An exception that is raised when renaming a file fails.
|
||||
|
||||
For example, this exception is raised when trying to rename a file that isn't present:
|
||||
|
||||
iex> File.rename!("source.txt", "target.txt")
|
||||
** (File.RenameError) could not rename from "source.txt" to "target.txt": no such file or directory
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:source` (`t:Path.t/0`) - the source path
|
||||
@@ -2477,11 +2320,6 @@ defmodule File.LinkError do
|
||||
@moduledoc """
|
||||
An exception that is raised when linking a file fails.
|
||||
|
||||
For example, this exception is raised when trying to link to file that isn't present:
|
||||
|
||||
iex> File.ln!("existing.txt", "link.txt")
|
||||
** (File.LinkError) could not create hard link from "link.txt" to "existing.txt": no such file or directory
|
||||
|
||||
The following fields of this exception are public and can be accessed freely:
|
||||
|
||||
* `:existing` (`t:Path.t/0`) - the existing file to link
|
||||
@@ -2496,25 +2334,12 @@ defmodule File.LinkError do
|
||||
def message(exception) do
|
||||
formatted = IO.iodata_to_binary(:file.format_error(exception.reason))
|
||||
|
||||
"could not #{exception.action} from #{inspect(exception.new)} to " <>
|
||||
"#{inspect(exception.existing)}: #{formatted}"
|
||||
"could not #{exception.action} from #{inspect(exception.existing)} to " <>
|
||||
"#{inspect(exception.new)}: #{formatted}"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ErlangError do
|
||||
@moduledoc """
|
||||
An exception raised when invoking an Erlang code that errors
|
||||
with a value not handled by Elixir.
|
||||
|
||||
Most common error reasons, such as `:badarg` are automatically
|
||||
converted into exceptions by Elixir. However, you may invoke some
|
||||
code that emits a custom error reason and those get wrapped into
|
||||
`ErlangError`:
|
||||
|
||||
iex> :erlang.error(:some_invalid_error)
|
||||
** (ErlangError) Erlang error: :some_invalid_error
|
||||
"""
|
||||
|
||||
defexception [:original, :reason]
|
||||
|
||||
@impl true
|
||||
@@ -2537,7 +2362,7 @@ defmodule ErlangError do
|
||||
is_map(module) and is_atom(function) and is_map_key(module, function) ->
|
||||
"you attempted to apply a function named #{inspect(function)} on a map/struct. " <>
|
||||
"If you are using Kernel.apply/3, make sure the module is an atom. " <>
|
||||
if is_function(Map.get(module, function)) do
|
||||
if is_function(module[function]) do
|
||||
"If you are trying to invoke an anonymous function in a map/struct, " <>
|
||||
"add a dot between the function name and the parenthesis: map.#{function}.()"
|
||||
else
|
||||
@@ -2591,6 +2416,10 @@ defmodule ErlangError do
|
||||
%BadFunctionError{term: term}
|
||||
end
|
||||
|
||||
def normalize({:badstruct, struct, term}, _stacktrace) do
|
||||
%BadStructError{struct: struct, term: term}
|
||||
end
|
||||
|
||||
def normalize({:badmatch, term}, _stacktrace) do
|
||||
%MatchError{term: term}
|
||||
end
|
||||
@@ -2718,3 +2547,61 @@ defmodule ErlangError do
|
||||
defp nth(3), do: "3rd"
|
||||
defp nth(n), do: "#{n}th"
|
||||
end
|
||||
|
||||
defmodule Inspect.Error do
|
||||
@moduledoc """
|
||||
Raised when a struct cannot be inspected.
|
||||
"""
|
||||
@enforce_keys [:exception_module, :exception_message, :stacktrace, :inspected_struct]
|
||||
defexception @enforce_keys
|
||||
|
||||
@impl true
|
||||
def exception(arguments) when is_list(arguments) do
|
||||
exception = Keyword.fetch!(arguments, :exception)
|
||||
exception_module = exception.__struct__
|
||||
exception_message = Exception.message(exception) |> String.trim_trailing("\n")
|
||||
stacktrace = Keyword.fetch!(arguments, :stacktrace)
|
||||
inspected_struct = Keyword.fetch!(arguments, :inspected_struct)
|
||||
|
||||
%Inspect.Error{
|
||||
exception_module: exception_module,
|
||||
exception_message: exception_message,
|
||||
stacktrace: stacktrace,
|
||||
inspected_struct: inspected_struct
|
||||
}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def message(%__MODULE__{
|
||||
exception_module: exception_module,
|
||||
exception_message: exception_message,
|
||||
inspected_struct: inspected_struct
|
||||
}) do
|
||||
~s'''
|
||||
got #{inspect(exception_module)} with message:
|
||||
|
||||
"""
|
||||
#{pad(exception_message, 4)}
|
||||
"""
|
||||
|
||||
while inspecting:
|
||||
|
||||
#{pad(inspected_struct, 4)}
|
||||
'''
|
||||
end
|
||||
|
||||
@doc false
|
||||
def pad(message, padding_length)
|
||||
when is_binary(message) and is_integer(padding_length) and padding_length >= 0 do
|
||||
padding = String.duplicate(" ", padding_length)
|
||||
|
||||
message
|
||||
|> String.split("\n")
|
||||
|> Enum.map(fn
|
||||
"" -> "\n"
|
||||
line -> [padding, line, ?\n]
|
||||
end)
|
||||
|> IO.iodata_to_binary()
|
||||
|> String.trim_trailing("\n")
|
||||
end
|
||||
end
|
||||
|
||||
+77
-592
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
require Record
|
||||
|
||||
defmodule File.Stat do
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule File.Stream do
|
||||
@moduledoc """
|
||||
Defines a `File.Stream` struct returned by `File.stream!/3`.
|
||||
@@ -119,7 +115,7 @@ defmodule File.Stream do
|
||||
|
||||
counter = fn device ->
|
||||
device = skip_bom_and_offset(device, raw, modes)
|
||||
count_lines(device, path, pattern, read_function(stream), 0, :empty)
|
||||
count_lines(device, path, pattern, read_function(stream), 0)
|
||||
end
|
||||
|
||||
{:ok, open!(stream, modes, counter)}
|
||||
@@ -229,28 +225,21 @@ defmodule File.Stream do
|
||||
for mode <- modes, mode not in [:write, :append, :trim_bom], do: mode
|
||||
end
|
||||
|
||||
defp count_lines(device, path, pattern, read, count, last_byte) do
|
||||
defp count_lines(device, path, pattern, read, count) do
|
||||
case read.(device) do
|
||||
data when is_binary(data) and byte_size(data) > 0 ->
|
||||
newlines = length(:binary.matches(data, pattern))
|
||||
last = :binary.last(data)
|
||||
count_lines(device, path, pattern, read, count + newlines, last)
|
||||
|
||||
data when is_binary(data) ->
|
||||
count_lines(device, path, pattern, read, count, last_byte)
|
||||
count_lines(device, path, pattern, read, count + count_lines(data, pattern))
|
||||
|
||||
:eof ->
|
||||
case last_byte do
|
||||
:empty -> 0
|
||||
?\n -> count
|
||||
_ -> count + 1
|
||||
end
|
||||
count
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.Error, reason: reason, action: "stream", path: path
|
||||
end
|
||||
end
|
||||
|
||||
defp count_lines(data, pattern), do: length(:binary.matches(data, pattern))
|
||||
|
||||
defp read_function(%{raw: true}), do: &IO.binread(&1, @read_ahead_size)
|
||||
defp read_function(%{raw: false}), do: &IO.read(&1, @read_ahead_size)
|
||||
end
|
||||
|
||||
+121
-151
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
import Kernel, except: [round: 1]
|
||||
|
||||
defmodule Float do
|
||||
@@ -42,7 +38,7 @@ defmodule Float do
|
||||
|
||||
To learn more about floating-point arithmetic visit:
|
||||
|
||||
* [0.30000000000000004.com](https://0.30000000000000004.com/)
|
||||
* [0.30000000000000004.com](http://0.30000000000000004.com/)
|
||||
* [What Every Programmer Should Know About Floating-Point Arithmetic](https://floating-point-gui.de/)
|
||||
|
||||
"""
|
||||
@@ -168,39 +164,40 @@ defmodule Float do
|
||||
end
|
||||
|
||||
defp parse_unsigned(<<digit, rest::binary>>) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, false, false, [digit])
|
||||
do: parse_unsigned(rest, false, false, <<digit>>)
|
||||
|
||||
defp parse_unsigned(binary) when is_binary(binary), do: :error
|
||||
|
||||
defp parse_unsigned(<<digit, rest::binary>>, dot?, e?, acc) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, dot?, e?, [digit | acc])
|
||||
do: parse_unsigned(rest, dot?, e?, <<acc::binary, digit>>)
|
||||
|
||||
defp parse_unsigned(<<?., digit, rest::binary>>, false, false, acc) when digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, false, [digit, ?. | acc])
|
||||
do: parse_unsigned(rest, true, false, <<acc::binary, ?., digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, digit, rest::binary>>, dot?, false, acc)
|
||||
when exp_marker in ~c"eE" and digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, true, [digit, ?e | add_dot(acc, dot?)])
|
||||
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, digit>>)
|
||||
|
||||
defp parse_unsigned(<<exp_marker, sign, digit, rest::binary>>, dot?, false, acc)
|
||||
when exp_marker in ~c"eE" and sign in ~c"-+" and digit in ?0..?9,
|
||||
do: parse_unsigned(rest, true, true, [digit, sign, ?e | add_dot(acc, dot?)])
|
||||
do: parse_unsigned(rest, true, true, <<add_dot(acc, dot?)::binary, ?e, sign, digit>>)
|
||||
|
||||
# :erlang.binary_to_float/1 can raise an ArgumentError if the e exponent is too big. For example,
|
||||
# "1.0e400". Because of this, we rescue the ArgumentError here and return an error.
|
||||
defp parse_unsigned(rest, dot?, _, acc) do
|
||||
acc
|
||||
|> add_dot(dot?)
|
||||
|> :lists.reverse()
|
||||
|> :erlang.list_to_float()
|
||||
# When floats are expressed in scientific notation, :erlang.binary_to_float/1 can raise an
|
||||
# ArgumentError if the e exponent is too big. For example, "1.0e400". Because of this, we
|
||||
# rescue the ArgumentError here and return an error.
|
||||
defp parse_unsigned(rest, dot?, true = _e?, acc) do
|
||||
:erlang.binary_to_float(add_dot(acc, dot?))
|
||||
rescue
|
||||
ArgumentError -> :error
|
||||
else
|
||||
float -> {float, rest}
|
||||
end
|
||||
|
||||
defp parse_unsigned(rest, dot?, false = _e?, acc),
|
||||
do: {:erlang.binary_to_float(add_dot(acc, dot?)), rest}
|
||||
|
||||
defp add_dot(acc, true), do: acc
|
||||
defp add_dot(acc, false), do: [?0, ?. | acc]
|
||||
defp add_dot(acc, false), do: acc <> ".0"
|
||||
|
||||
@doc """
|
||||
Rounds a float to the largest float less than or equal to `number`.
|
||||
@@ -344,12 +341,15 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec round(float, precision_range) :: float
|
||||
# This implementation is slow since it relies on big integers.
|
||||
# Faster implementations are available on more recent papers
|
||||
# and could be implemented in the future.
|
||||
def round(float, precision \\ 0)
|
||||
|
||||
def round(float, 0) when float == 0.0, do: float
|
||||
|
||||
def round(float, 0) when is_float(float) do
|
||||
case :erlang.round(float) * 1.0 do
|
||||
case float |> :erlang.round() |> :erlang.float() do
|
||||
zero when zero == 0.0 and float < 0.0 -> -0.0
|
||||
rounded -> rounded
|
||||
end
|
||||
@@ -363,170 +363,140 @@ defmodule Float do
|
||||
raise ArgumentError, invalid_precision_message(precision)
|
||||
end
|
||||
|
||||
# Decimal-place rounding via exact rational scaling. This is the bignum
|
||||
# core used by reference implementations like David M. Gay's "Correctly
|
||||
# Rounded Binary-Decimal and Decimal-Binary Conversions" (cited in the
|
||||
# @doc above), Python's round(), and Java's BigDecimal.setScale.
|
||||
#
|
||||
# 1. Decompose float exactly: |float| = mantissa / 2^shift.
|
||||
# 2. Scale exactly: |float| * 10^precision = mantissa * 10^precision / 2^shift.
|
||||
# Because precision is bounded to 0..15, the product fits in ~103 bits
|
||||
# (53-bit mantissa + ~50-bit power of ten) and BEAM bignums handle it
|
||||
# directly without approximation.
|
||||
# 3. Round the exact rational to an integer per the requested mode
|
||||
# (half_up / floor / ceil) using quotient and remainder.
|
||||
# 4. Emit the float closest to rounded_int / 10^precision:
|
||||
# - fast path: when rounded_int < 2^53, both operands are exactly
|
||||
# representable as floats and IEEE division is correctly rounded.
|
||||
# - slow path: bignum alignment + manual mantissa extraction with
|
||||
# round-to-nearest-even for the trailing bit.
|
||||
#
|
||||
# The integer-rounding decision (step 3) and the binary-emission decision
|
||||
# (step 4) are deliberately independent: step 3 picks the exact rational
|
||||
# the user asked for, step 4 picks the closest float to that rational.
|
||||
# Conflating them is the classic source of double-rounding bugs.
|
||||
#
|
||||
# Faster algorithms exist (Cox 2026's table-based uscale; Ryū / Schubfach
|
||||
# for round-trip printing) but target different problems or assume
|
||||
# fixed-width machine arithmetic that BEAM doesn't expose efficiently.
|
||||
# At precision <= 15, the exact path is small, easy to audit, and fast
|
||||
# enough that a more complex algorithm has not been justified by benchmarks.
|
||||
defp round(num, _precision, _rounding) when is_float(num) and num == 0.0, do: num
|
||||
|
||||
defp round(float, precision, mode) do
|
||||
<<sign::1, exp::11, mantissa::52>> = <<float::float>>
|
||||
defp round(float, precision, rounding) do
|
||||
<<sign::1, exp::11, significant::52-bitstring>> = <<float::float>>
|
||||
{num, count} = decompose(significant, 1)
|
||||
count = count - exp + 1023
|
||||
|
||||
cond do
|
||||
# Subnormal — tiny but non-zero; treat per-mode (ceil(+) and floor(-) bump
|
||||
# to 10^-precision; everything else rounds to signed zero).
|
||||
exp == 0 ->
|
||||
tiny_round(sign, precision, mode)
|
||||
# Precision beyond 15 digits
|
||||
count >= 104 ->
|
||||
case rounding do
|
||||
:ceil when sign === 0 -> 1 / power_of_10(precision)
|
||||
:floor when sign === 1 -> -1 / power_of_10(precision)
|
||||
:ceil when sign === 1 -> minus_zero()
|
||||
:half_up when sign === 1 -> minus_zero()
|
||||
_ -> 0.0
|
||||
end
|
||||
|
||||
# |float| >= 2^52 — has no fractional bits, return unchanged.
|
||||
exp - 1075 >= 0 ->
|
||||
# We are asking more precision than we have
|
||||
count <= precision ->
|
||||
float
|
||||
|
||||
true ->
|
||||
mantissa = @power_of_2_to_52 ||| mantissa
|
||||
shift = 1075 - exp
|
||||
do_round(sign, mantissa, shift, precision, mode)
|
||||
# Difference in precision between float and asked precision
|
||||
# We subtract 1 because we need to calculate the remainder too
|
||||
diff = count - precision - 1
|
||||
|
||||
# Get up to latest so we calculate the remainder
|
||||
power_of_10 = power_of_10(diff)
|
||||
|
||||
# Convert the numerand to decimal base
|
||||
num = num * power_of_5(count)
|
||||
|
||||
# Move to the given precision - 1
|
||||
num = div(num, power_of_10)
|
||||
div = div(num, 10)
|
||||
num = rounding(rounding, sign, num, div)
|
||||
|
||||
# Convert back to float without loss
|
||||
# https://www.exploringbinary.com/correct-decimal-to-floating-point-using-big-integers/
|
||||
den = power_of_10(precision)
|
||||
boundary = den <<< 52
|
||||
|
||||
cond do
|
||||
num == 0 and sign == 1 ->
|
||||
minus_zero()
|
||||
|
||||
num == 0 ->
|
||||
0.0
|
||||
|
||||
num >= boundary ->
|
||||
{den, exp} = scale_down(num, boundary, 52)
|
||||
decimal_to_float(sign, num, den, exp)
|
||||
|
||||
true ->
|
||||
{num, exp} = scale_up(num, boundary, 52)
|
||||
decimal_to_float(sign, num, den, exp)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# |float * 10^precision| < 0.5 — integer round is 0; ceil/floor still bump per sign.
|
||||
defp do_round(sign, _mantissa, shift, precision, mode) when shift >= 104 do
|
||||
tiny_round(sign, precision, mode)
|
||||
# TODO remove once we require Erlang/OTP 27+
|
||||
# This function tricks the compiler to avoid this bug in previous versions:
|
||||
# https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/float.ex#L408-L412
|
||||
defp minus_zero, do: -0.0
|
||||
|
||||
defp decompose(significant, initial) do
|
||||
decompose(significant, 1, 0, initial)
|
||||
end
|
||||
|
||||
defp do_round(sign, mantissa, shift, precision, mode) do
|
||||
power = power_of_10(precision)
|
||||
product = mantissa * power
|
||||
half = 1 <<< (shift - 1)
|
||||
quotient = product >>> shift
|
||||
remainder = product - (quotient <<< shift)
|
||||
rounded_int = round_step(mode, sign, quotient, remainder, half)
|
||||
defp decompose(<<1::1, bits::bitstring>>, count, last_count, acc) do
|
||||
decompose(bits, count + 1, count, (acc <<< (count - last_count)) + 1)
|
||||
end
|
||||
|
||||
cond do
|
||||
rounded_int == 0 ->
|
||||
signed_zero(sign)
|
||||
defp decompose(<<0::1, bits::bitstring>>, count, last_count, acc) do
|
||||
decompose(bits, count + 1, last_count, acc)
|
||||
end
|
||||
|
||||
rounded_int < @power_of_2_to_52 <<< 1 ->
|
||||
# Both rounded_int and power fit in 53 bits, so IEEE float division
|
||||
# is correctly rounded.
|
||||
result = rounded_int / power
|
||||
if sign == 1, do: -result, else: result
|
||||
defp decompose(<<>>, _count, last_count, acc) do
|
||||
{acc, last_count}
|
||||
end
|
||||
|
||||
true ->
|
||||
bignum_to_float(sign, rounded_int, power)
|
||||
defp scale_up(num, boundary, exp) when num >= boundary, do: {num, exp}
|
||||
defp scale_up(num, boundary, exp), do: scale_up(num <<< 1, boundary, exp - 1)
|
||||
|
||||
defp scale_down(num, den, exp) do
|
||||
new_den = den <<< 1
|
||||
|
||||
if num < new_den do
|
||||
{den >>> 52, exp}
|
||||
else
|
||||
scale_down(num, new_den, exp + 1)
|
||||
end
|
||||
end
|
||||
|
||||
defp round_step(:half_up, _sign, quotient, remainder, half) do
|
||||
if remainder >= half, do: quotient + 1, else: quotient
|
||||
end
|
||||
defp decimal_to_float(sign, num, den, exp) do
|
||||
quo = div(num, den)
|
||||
rem = num - quo * den
|
||||
|
||||
defp round_step(:floor, 0, quotient, _remainder, _half), do: quotient
|
||||
defp round_step(:floor, 1, quotient, remainder, _half) when remainder > 0, do: quotient + 1
|
||||
defp round_step(:floor, 1, quotient, _remainder, _half), do: quotient
|
||||
|
||||
defp round_step(:ceil, 0, quotient, remainder, _half) when remainder > 0, do: quotient + 1
|
||||
defp round_step(:ceil, 0, quotient, _remainder, _half), do: quotient
|
||||
defp round_step(:ceil, 1, quotient, _remainder, _half), do: quotient
|
||||
|
||||
defp signed_zero(0), do: 0.0
|
||||
defp signed_zero(1), do: -0.0
|
||||
|
||||
# Result of rounding a non-zero float whose |float * 10^precision| < 0.5.
|
||||
# ceil(+) → +10^-precision, floor(-) → -10^-precision, others → signed 0.
|
||||
defp tiny_round(0, precision, :ceil), do: 1.0 / power_of_10(precision)
|
||||
defp tiny_round(1, precision, :floor), do: -1.0 / power_of_10(precision)
|
||||
defp tiny_round(sign, _precision, _mode), do: signed_zero(sign)
|
||||
|
||||
# Slow path: emit float closest to `sign * rounded_int / power` when
|
||||
# rounded_int >= 2^53. The binary emission step is always IEEE
|
||||
# round-to-nearest-even, regardless of the integer-rounding mode.
|
||||
defp bignum_to_float(sign, rounded_int, power) do
|
||||
shift_adjust = bit_length(rounded_int) - bit_length(power) - 53
|
||||
{numerator, denominator, exp} = align(rounded_int, power, shift_adjust)
|
||||
|
||||
quotient = div(numerator, denominator)
|
||||
remainder = numerator - quotient * denominator
|
||||
half = denominator >>> 1
|
||||
|
||||
mantissa =
|
||||
cond do
|
||||
remainder > half -> quotient + 1
|
||||
remainder < half -> quotient
|
||||
(quotient &&& 1) === 1 -> quotient + 1
|
||||
true -> quotient
|
||||
tmp =
|
||||
case den >>> 1 do
|
||||
den when rem > den -> quo + 1
|
||||
den when rem < den -> quo
|
||||
_ when (quo &&& 1) === 1 -> quo + 1
|
||||
_ -> quo
|
||||
end
|
||||
|
||||
# Carry-bit normalization: `mantissa` lives in [2^52, 2^53]. The upper
|
||||
# bound `2^53` is reachable when `align/3` returns an upper-bound quotient
|
||||
# or when rounding carries. Rebalance into the canonical [2^52, 2^53)
|
||||
# range so the 52-bit packing below doesn't silently truncate.
|
||||
{mantissa, exp} =
|
||||
if mantissa == @power_of_2_to_52 <<< 1,
|
||||
do: {@power_of_2_to_52, exp + 1},
|
||||
else: {mantissa, exp}
|
||||
|
||||
<<result::float>> = <<sign::1, exp + 1023::11, mantissa - @power_of_2_to_52::52>>
|
||||
result
|
||||
tmp = tmp - @power_of_2_to_52
|
||||
<<tmp::float>> = <<sign::1, exp + 1023::11, tmp::52>>
|
||||
tmp
|
||||
end
|
||||
|
||||
# Pick (numerator, denominator, exp) so that numerator/denominator ∈ [2^52, 2^53)
|
||||
# and the resulting float = numerator/denominator * 2^(exp-52).
|
||||
defp align(rounded_int, power, shift_adjust) when shift_adjust >= 0 do
|
||||
new_power = power <<< shift_adjust
|
||||
defp rounding(:floor, 1, _num, div), do: div + 1
|
||||
defp rounding(:ceil, 0, _num, div), do: div + 1
|
||||
|
||||
if rounded_int < new_power <<< 53,
|
||||
do: {rounded_int, new_power, 52 + shift_adjust},
|
||||
else: {rounded_int, new_power <<< 1, 53 + shift_adjust}
|
||||
end
|
||||
|
||||
defp align(rounded_int, power, shift_adjust) do
|
||||
shifted = rounded_int <<< -shift_adjust
|
||||
|
||||
cond do
|
||||
shifted >= power <<< 53 -> {shifted, power <<< 1, 53 + shift_adjust}
|
||||
shifted >= power <<< 52 -> {shifted, power, 52 + shift_adjust}
|
||||
true -> {shifted <<< 1, power, 51 + shift_adjust}
|
||||
defp rounding(:half_up, _sign, num, div) do
|
||||
case rem(num, 10) do
|
||||
rem when rem < 5 -> div
|
||||
rem when rem >= 5 -> div + 1
|
||||
end
|
||||
end
|
||||
|
||||
defp bit_length(0), do: 0
|
||||
defp bit_length(integer) when integer > 0, do: bit_length(integer, 0)
|
||||
defp bit_length(integer, acc) when integer >= 1 <<< 64, do: bit_length(integer >>> 64, acc + 64)
|
||||
defp bit_length(integer, acc) when integer >= 1 <<< 16, do: bit_length(integer >>> 16, acc + 16)
|
||||
defp bit_length(integer, acc) when integer >= 1 <<< 4, do: bit_length(integer >>> 4, acc + 4)
|
||||
defp bit_length(integer, acc) when integer >= 1, do: bit_length(integer >>> 1, acc + 1)
|
||||
defp bit_length(_integer, acc), do: acc
|
||||
defp rounding(_, _, _, div), do: div
|
||||
|
||||
Enum.reduce(0..15, 1, fn exponent, acc ->
|
||||
defp power_of_10(unquote(exponent)), do: unquote(acc)
|
||||
Enum.reduce(0..104, 1, fn x, acc ->
|
||||
defp power_of_10(unquote(x)), do: unquote(acc)
|
||||
acc * 10
|
||||
end)
|
||||
|
||||
Enum.reduce(0..104, 1, fn x, acc ->
|
||||
defp power_of_5(unquote(x)), do: unquote(acc)
|
||||
acc * 5
|
||||
end)
|
||||
|
||||
@doc """
|
||||
Returns a pair of integers whose ratio is exactly equal
|
||||
to the original float and with a positive denominator.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Function do
|
||||
@moduledoc """
|
||||
A set of functions for working with functions.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule GenEvent do
|
||||
# Functions from this module are deprecated in elixir_dispatch.
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule GenEvent.Stream do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "This functionality is no longer supported"
|
||||
defstruct manager: nil, timeout: :infinity
|
||||
|
||||
@type t :: %__MODULE__{manager: GenEvent.manager(), timeout: timeout}
|
||||
@@ -51,9 +46,6 @@ defmodule GenEvent.Stream do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: GenEvent.Stream do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "This functionality is no longer supported"
|
||||
|
||||
def reduce(stream, acc, fun) do
|
||||
start_fun = fn -> start(stream) end
|
||||
next_fun = &next(stream, &1)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule GenServer do
|
||||
@moduledoc """
|
||||
A behaviour module for implementing the server of a client-server relation.
|
||||
@@ -207,17 +203,14 @@ defmodule GenServer do
|
||||
The generated `child_spec/1` can be customized with the following options:
|
||||
|
||||
* `:id` - the child specification identifier, defaults to the current module
|
||||
* [`:restart`](`m:Supervisor#module-restart-values-restart`) - when the
|
||||
child should be restarted, defaults to `:permanent`
|
||||
* [`:shutdown`](`m:Supervisor#module-shutdown-values-shutdown`) - how to
|
||||
shut down the child, either immediately or by giving it time to shut down,
|
||||
defaults to `5_000`
|
||||
* `:restart` - when the child should be restarted, defaults to `:permanent`
|
||||
* `:shutdown` - how to shut down the child, either immediately or by giving it time to shut down
|
||||
|
||||
For example:
|
||||
|
||||
use GenServer, restart: :transient, shutdown: 10_000
|
||||
|
||||
See the ["Child specification"](`m:Supervisor#module-child-specification`) section in the `Supervisor` module for more
|
||||
See the "Child specification" section in the `Supervisor` module for more
|
||||
detailed information. The `@doc` annotation immediately preceding
|
||||
`use GenServer` will be attached to the generated `child_spec/1` function.
|
||||
|
||||
@@ -232,8 +225,6 @@ defmodule GenServer do
|
||||
a name on start via the `:name` option. Registered names are also
|
||||
automatically cleaned up on termination. The supported values are:
|
||||
|
||||
* `nil` (default) - the GenServer is not registered with a name.
|
||||
|
||||
* an atom - the GenServer is registered locally (to the current node)
|
||||
with the given name using `Process.register/2`.
|
||||
|
||||
@@ -274,14 +265,6 @@ defmodule GenServer do
|
||||
generated atoms won't be garbage-collected. For such cases, you can
|
||||
set up your own local registry by using the `Registry` module.
|
||||
|
||||
For example:
|
||||
|
||||
{:ok, _} = Registry.start_link(keys: :unique, name: :stacks)
|
||||
name = {:via, Registry, {:stacks, "stack 1"}}
|
||||
{:ok, _pid} = GenServer.start_link(Stack, "hello", name: name)
|
||||
GenServer.whereis(name)
|
||||
#=> #PID<0.150.0>
|
||||
|
||||
## Receiving "regular" messages
|
||||
|
||||
The goal of a `GenServer` is to abstract the "receive" loop for developers,
|
||||
@@ -352,41 +335,6 @@ defmodule GenServer do
|
||||
message arriving, `handle_info/2` is called with `:timeout` as the first
|
||||
argument.
|
||||
|
||||
For example:
|
||||
|
||||
defmodule Counter do
|
||||
use GenServer
|
||||
|
||||
@timeout to_timeout(second: 5)
|
||||
|
||||
@impl true
|
||||
def init(count) do
|
||||
{:ok, count, @timeout}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call(:increment, _from, count) do
|
||||
new_count = count + 1
|
||||
{:reply, new_count, new_count, @timeout}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:timeout, count) do
|
||||
{:stop, :normal, count}
|
||||
end
|
||||
end
|
||||
|
||||
A `Counter` server will exit with `:normal` if there are no messages in 5 seconds
|
||||
after the initialization or after the last `:increment` call:
|
||||
|
||||
{:ok, counter_pid} = GenServer.start(Counter, 50)
|
||||
GenServer.call(counter_pid, :increment)
|
||||
#=> 51
|
||||
|
||||
# After 5 seconds
|
||||
Process.alive?(counter_pid)
|
||||
#=> false
|
||||
|
||||
## When (not) to use a GenServer
|
||||
|
||||
So far, we have learned that a `GenServer` can be used as a supervised process
|
||||
@@ -526,7 +474,7 @@ defmodule GenServer do
|
||||
* [GenServer - Elixir's Getting Started Guide](genservers.md)
|
||||
* [`:gen_server` module documentation](`:gen_server`)
|
||||
* [gen_server Behaviour - OTP Design Principles](https://www.erlang.org/doc/design_principles/gen_server_concepts.html)
|
||||
* [Clients and Servers - Learn You Some Erlang for Great Good!](https://learnyousomeerlang.com/clients-and-servers)
|
||||
* [Clients and Servers - Learn You Some Erlang for Great Good!](http://learnyousomeerlang.com/clients-and-servers)
|
||||
|
||||
"""
|
||||
|
||||
@@ -567,12 +515,9 @@ defmodule GenServer do
|
||||
`Supervisor`. Likely this approach involves calling `Supervisor.restart_child/2`
|
||||
after a delay to attempt a restart.
|
||||
|
||||
Returning `{:error, reason}` will cause `start_link/3` to return
|
||||
`{:error, reason}`.
|
||||
|
||||
Returning `{:stop, reason}` will the process to exit with reason `reason`,
|
||||
without entering the loop or calling `c:terminate/2`. `start_link/3` will
|
||||
return `{:error, reason}`, but only if the caller is trapping exits.
|
||||
Returning `{:stop, reason}` will cause `start_link/3` to return
|
||||
`{:error, reason}` and the process to exit with reason `reason` without
|
||||
entering the loop or calling `c:terminate/2`.
|
||||
"""
|
||||
@callback init(init_arg :: term) ::
|
||||
{:ok, state}
|
||||
@@ -863,7 +808,7 @@ defmodule GenServer do
|
||||
@type on_start :: {:ok, pid} | :ignore | {:error, {:already_started, pid} | term}
|
||||
|
||||
@typedoc "The GenServer name"
|
||||
@type name :: nil | atom | {:global, term} | {:via, module, term}
|
||||
@type name :: atom | {:global, term} | {:via, module, term}
|
||||
|
||||
@typedoc "Options used by the `start*` functions"
|
||||
@type options :: [option]
|
||||
@@ -1153,8 +1098,8 @@ defmodule GenServer do
|
||||
arrives or a timeout occurs. `c:handle_call/3` will be called on the server
|
||||
to handle the request.
|
||||
|
||||
`server` can be a PID or any of the other values described in the
|
||||
"Name registration" section of the documentation for this module.
|
||||
`server` can be any of the values described in the "Name registration"
|
||||
section of the documentation for this module.
|
||||
|
||||
## Timeouts
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule HashDict do
|
||||
@moduledoc """
|
||||
Tuple-based HashDict implementation.
|
||||
@@ -260,9 +256,6 @@ defmodule HashDict do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashDict do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use Map instead"
|
||||
|
||||
def reduce(dict, acc, fun) do
|
||||
# Avoid warnings about HashDict being deprecated.
|
||||
module = String.to_atom("HashDict")
|
||||
@@ -291,9 +284,6 @@ defimpl Enumerable, for: HashDict do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashDict do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use Map instead"
|
||||
|
||||
def into(original) do
|
||||
# Avoid warnings about HashDict being deprecated.
|
||||
module = String.to_atom("HashDict")
|
||||
@@ -309,8 +299,6 @@ defimpl Collectable, for: HashDict do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashDict do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use Map instead"
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(dict, opts) do
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule HashSet do
|
||||
@moduledoc """
|
||||
Tuple-based HashSet implementation.
|
||||
@@ -274,9 +270,6 @@ defmodule HashSet do
|
||||
end
|
||||
|
||||
defimpl Enumerable, for: HashSet do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use MapSet instead"
|
||||
|
||||
def reduce(set, acc, fun) do
|
||||
# Avoid warnings about HashSet being deprecated.
|
||||
module = String.to_atom("HashSet")
|
||||
@@ -301,9 +294,6 @@ defimpl Enumerable, for: HashSet do
|
||||
end
|
||||
|
||||
defimpl Collectable, for: HashSet do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use MapSet instead"
|
||||
|
||||
def into(original) do
|
||||
# Avoid warnings about HashSet being deprecated.
|
||||
module = String.to_atom("HashSet")
|
||||
@@ -319,8 +309,6 @@ defimpl Collectable, for: HashSet do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: HashSet do
|
||||
@moduledoc false
|
||||
@moduledoc deprecated: "Use MapSet instead"
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(set, opts) do
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user