Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
372836d623 | ||
|
|
444cd7e6bd | ||
|
|
cacb175ab3 | ||
|
|
bfce3ed7a9 | ||
|
|
06811cbdc8 | ||
|
|
7a6b35422c | ||
|
|
78f63d0831 | ||
|
|
86ec46a5c4 | ||
|
|
c99631876c | ||
|
|
46815ef554 | ||
|
|
256a55a53e | ||
|
|
a78c92043f | ||
|
|
b3bfa519a7 | ||
|
|
b734da3f62 | ||
|
|
10f86595b5 | ||
|
|
1da385776f | ||
|
|
a45e08eeda | ||
|
|
bb61b953d7 | ||
|
|
1e126e88b7 | ||
|
|
47abe2d107 | ||
|
|
e254e7e68e | ||
|
|
d3c0b38c5d | ||
|
|
662dd6b8d9 | ||
|
|
cadd501cbd | ||
|
|
ddf84aba7e | ||
|
|
2d808ec652 | ||
|
|
6bfb95ab88 | ||
|
|
d53977002d | ||
|
|
776e5a34ea | ||
|
|
ae5707f90a | ||
|
|
67617e5d8c | ||
|
|
6f5fc94276 | ||
|
|
6c935ca6ec | ||
|
|
d1076e487c | ||
|
|
b9e447757d | ||
|
|
b7943e113d | ||
|
|
1a320bb60d | ||
|
|
64e63b1b9d | ||
|
|
5a8b28427c | ||
|
|
a1eb59d035 | ||
|
|
0b8623a032 | ||
|
|
1f187b529e | ||
|
|
bde5b2d2fc | ||
|
|
357ffc497d | ||
|
|
48338bc093 | ||
|
|
d547721c41 | ||
|
|
615bb55e81 | ||
|
|
278cf65bb1 | ||
|
|
125d55527d | ||
|
|
9ffe44758c | ||
|
|
e2032e8dc4 | ||
|
|
ac64fba4eb | ||
|
|
78b7239eb7 | ||
|
|
021a698d5d | ||
|
|
b3c3e1399d | ||
|
|
18200af534 | ||
|
|
b63312ac2b | ||
|
|
db89cbf7e6 | ||
|
|
53c93b91da | ||
|
|
a7bf120756 | ||
|
|
b56a907297 | ||
|
|
c7dfc33705 | ||
|
|
4b1b852880 | ||
|
|
f9939a7029 | ||
|
|
0d6ae23e39 | ||
|
|
cf84b7b042 | ||
|
|
b84dd54ca1 | ||
|
|
bb779221ff | ||
|
|
be6ed1709d | ||
|
|
79c19dcb04 | ||
|
|
772c7b0884 | ||
|
|
6250a17d8c | ||
|
|
73d7762563 | ||
|
|
ebe347410c | ||
|
|
d74e6314e4 | ||
|
|
c9a2afc0ce | ||
|
|
e4a455eedc | ||
|
|
a2600eace1 | ||
|
|
27b6ea82ae |
+2
-5
@@ -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}",
|
||||
@@ -21,5 +17,6 @@
|
||||
|
||||
# Float tests
|
||||
float_assert: 1
|
||||
]
|
||||
],
|
||||
normalize_bitstring_modifiers: false
|
||||
]
|
||||
|
||||
@@ -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,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
---
|
||||
name: Report an issue
|
||||
description:
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
name: builds.hex.pm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
concurrency: builds_txt
|
||||
|
||||
jobs:
|
||||
release_pre_built:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- otp: 25
|
||||
otp_version: "25.3"
|
||||
upload_generic_version: upload_generic_version
|
||||
- otp: 26
|
||||
otp_version: "26.0"
|
||||
- otp: 27
|
||||
otp_version: "27.0"
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- uses: ./.github/workflows/release_pre_built
|
||||
with:
|
||||
otp_version: ${{ matrix.otp_version }}
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
- name: Utils.sh
|
||||
run: |
|
||||
cat << 'EOF' > utils.sh
|
||||
function purge_key() {
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Fastly-Key: ${FASTLY_KEY}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Length: 0" \
|
||||
"https://api.fastly.com/service/$1/purge/$2"
|
||||
}
|
||||
function purge() {
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
}
|
||||
EOF
|
||||
chmod +x utils.sh
|
||||
- name: Upload Docs to S3
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
for f in doc/*; do
|
||||
if [ -d "$f" ]; then
|
||||
app=`echo $f | sed s/"doc\/"//`
|
||||
tarball="${app}-${version}.tar.gz"
|
||||
surrogate_key="docs/${app}-${version}"
|
||||
tar -czf "${tarball}" -C "doc/${app}" .
|
||||
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
purge "${surrogate_key}"
|
||||
fi
|
||||
done
|
||||
- name: Upload Precompiled to S3
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
aws s3 cp elixir-otp-${{ matrix.otp }}.zip "s3://${{ env.AWS_S3_BUCKET }}/builds/elixir/${{github.ref_name}}-otp-${{matrix.otp}}.zip" --cache-control "public,max-age=3600" --metadata "{\"surrogate-key\":\"builds builds/elixir builds/elixir/${{github.ref_name}}-otp-${{matrix.otp}}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
purge builds/elixir/${{github.ref_name}}-otp-${{matrix.otp}}
|
||||
if [ "${{matrix.upload_generic_version}}" == "upload_generic_version" ]; then
|
||||
aws s3 cp elixir-otp-${{ matrix.otp }}.zip "s3://${{ env.AWS_S3_BUCKET }}/builds/elixir/${{github.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\"}"
|
||||
purge builds/elixir/${{github.ref_name}}
|
||||
fi
|
||||
- name: Update builds txt
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
date=$(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
build_sha256=$(sha256sum elixir-otp-${{ matrix.otp }}.zip | cut -d ' ' -f 1)
|
||||
ref_name=${{ github.ref_name }}
|
||||
aws s3 cp s3://${{ env.AWS_S3_BUCKET }}/builds/elixir/builds.txt builds.txt || true
|
||||
touch builds.txt
|
||||
sed -i "/^${ref_name}-otp-${{ matrix.otp }} /d" builds.txt
|
||||
echo -e "${ref_name}-otp-${{ matrix.otp }} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
|
||||
if [ "${{matrix.upload_generic_version}}" == "upload_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
|
||||
sort -u -k1,1 -o builds.txt builds.txt
|
||||
aws s3 cp builds.txt s3://${{ env.AWS_S3_BUCKET }}/builds/elixir/builds.txt --cache-control "public,max-age=3600" --metadata '{"surrogate-key":"builds builds/elixir builds/elixir/txt","surrogate-control":"public,max-age=604800"}'
|
||||
purge builds/elixir/txt
|
||||
@@ -1,22 +1,16 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: CI for Markdown content
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- 'main'
|
||||
paths:
|
||||
- "lib/**/*.md"
|
||||
- 'lib/**/*.md'
|
||||
pull_request:
|
||||
paths:
|
||||
- "lib/**/*.md"
|
||||
- 'lib/**/*.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint Markdown content
|
||||
@@ -24,16 +18,16 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Run markdownlint
|
||||
uses: DavidAnson/markdownlint-cli2-action@992badcdf24e3b8eb7e87ff9287fe931bcb00c6e # v20.0.0
|
||||
uses: DavidAnson/markdownlint-cli2-action@v16.0.0
|
||||
with:
|
||||
globs: |
|
||||
lib/elixir/pages/**/*.md
|
||||
|
||||
+18
-72
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
@@ -14,6 +11,7 @@ on:
|
||||
env:
|
||||
ELIXIR_ASSERT_TIMEOUT: 2000
|
||||
ELIXIRC_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
permissions:
|
||||
@@ -21,44 +19,28 @@ permissions:
|
||||
|
||||
jobs:
|
||||
test_linux:
|
||||
name: Ubuntu 24.04, Erlang/OTP ${{ matrix.otp_version }}${{ matrix.deterministic && ' (deterministic)' || '' }}${{ matrix.coverage && ' (coverage)' || '' }}
|
||||
name: Ubuntu 20.04, Erlang/OTP ${{ matrix.otp_version }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- otp_version: "28.1"
|
||||
deterministic: true
|
||||
- otp_version: "28.1"
|
||||
erlc_opts: "warnings_as_errors"
|
||||
coverage: true
|
||||
- otp_version: "28.1"
|
||||
otp_latest: true
|
||||
erlc_opts: "warnings_as_errors"
|
||||
- otp_version: "27.3"
|
||||
erlc_opts: "warnings_as_errors"
|
||||
- otp_version: "27.0"
|
||||
erlc_opts: "warnings_as_errors"
|
||||
otp_latest: true
|
||||
- otp_version: "26.0"
|
||||
- otp_version: "25.3"
|
||||
- otp_version: "25.0"
|
||||
- otp_version: master
|
||||
development: true
|
||||
- otp_version: maint
|
||||
development: true
|
||||
runs-on: ubuntu-24.04
|
||||
# Earlier Erlang/OTP versions ignored compiler directives
|
||||
# when using warnings_as_errors. So we only set ERLC_OPTS
|
||||
# from Erlang/OTP 27+.
|
||||
env:
|
||||
ERLC_OPTS: ${{ matrix.erlc_opts || '' }}
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
|
||||
- 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
|
||||
@@ -73,11 +55,6 @@ jobs:
|
||||
- name: Elixir test suite
|
||||
run: make test_elixir
|
||||
continue-on-error: ${{ matrix.development }}
|
||||
env:
|
||||
COVER: "${{ matrix.coverage }}"
|
||||
- name: "Calculate Coverage"
|
||||
run: make cover | tee "$GITHUB_STEP_SUMMARY"
|
||||
if: "${{ matrix.coverage }}"
|
||||
- name: Build docs (ExDoc main)
|
||||
if: ${{ matrix.otp_latest }}
|
||||
run: |
|
||||
@@ -86,35 +63,28 @@ jobs:
|
||||
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
|
||||
make docs
|
||||
- name: Check reproducible builds
|
||||
if: ${{ matrix.deterministic }}
|
||||
run: |
|
||||
rm -rf .git
|
||||
# Recompile System without .git
|
||||
cd lib/elixir && ../../bin/elixirc -o ebin lib/system.ex && cd -
|
||||
taskset 1 make check_reproducible
|
||||
- name: "Upload Coverage Artifact"
|
||||
if: "${{ matrix.coverage }}"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: TestCoverage
|
||||
path: cover/*
|
||||
if: ${{ matrix.otp_latest }}
|
||||
|
||||
test_windows:
|
||||
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
otp_version: ["26.2", "27.3", "28.1"]
|
||||
runs-on: windows-2022
|
||||
otp_version: ["25.3", "26.0"]
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Configure Git
|
||||
run: git config --global core.autocrlf input
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ matrix.otp_version }}
|
||||
- name: Compile Elixir
|
||||
@@ -126,17 +96,17 @@ jobs:
|
||||
- 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
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Install Shellcheck
|
||||
@@ -148,27 +118,3 @@ jobs:
|
||||
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"
|
||||
|
||||
license_compliance:
|
||||
name: Check Licence 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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- 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,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,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Notify
|
||||
|
||||
on:
|
||||
@@ -13,16 +10,16 @@ permissions:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
name: Notify
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@8aa8a857c6be0daae6e97272bb299d5b942675a4 # v1.19.0
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: "27.3"
|
||||
elixir-version: "1.18.3"
|
||||
otp-version: '25.0'
|
||||
elixir-version: '1.14.0'
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
|
||||
@@ -1,109 +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"
|
||||
|
||||
- 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 }}"
|
||||
+20
-381
@@ -1,33 +1,25 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create_draft_release:
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Create draft release
|
||||
if: github.ref_type != 'branch'
|
||||
run: |
|
||||
gh release create \
|
||||
--repo ${{ github.repository }} \
|
||||
@@ -35,396 +27,43 @@ jobs:
|
||||
--notes '' \
|
||||
--draft \
|
||||
${{ github.ref_name }}
|
||||
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
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
|
||||
|
||||
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
|
||||
fi
|
||||
|
||||
git tag $ref_name --force
|
||||
git push origin $ref_name --force
|
||||
|
||||
build:
|
||||
name: "Build Elixir"
|
||||
|
||||
release_pre_built:
|
||||
needs: create_draft_release
|
||||
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
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 50
|
||||
|
||||
- name: "Build Release"
|
||||
uses: ./.github/workflows/release_pre_built
|
||||
- uses: ./.github/workflows/release_pre_built
|
||||
with:
|
||||
otp_version: ${{ matrix.otp_version }}
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
|
||||
- name: Create Docs Hashes
|
||||
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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: build-linux-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.zip
|
||||
|
||||
- name: "Upload windows release artifacts"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: build-windows-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.exe
|
||||
|
||||
- name: "Upload doc artifacts"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: matrix.build_docs
|
||||
with:
|
||||
name: Docs
|
||||
path: Docs.zip*
|
||||
|
||||
sign:
|
||||
needs: [build]
|
||||
environment: release
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
otp: [26, 27, 28]
|
||||
flavor: [windows, linux]
|
||||
|
||||
env:
|
||||
RELEASE_FILE: elixir-otp-${{ matrix.otp }}.${{ matrix.flavor == 'linux' && 'zip' || 'exe' }}
|
||||
|
||||
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: "Download build"
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
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@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@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }}
|
||||
files-folder: ${{ github.workspace }}
|
||||
files-folder-filter: exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sha1 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA1
|
||||
$sha1.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha1sum"
|
||||
|
||||
$sha256 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA256
|
||||
$sha256.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha256sum"
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'linux'
|
||||
shell: bash
|
||||
run: |
|
||||
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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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
|
||||
|
||||
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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: "Download Build Artifacts"
|
||||
id: download-build-artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
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@115c3be05ff3974bcbd596578934b3f9ce39bf68 # v2.2.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 "${{ steps.ort.outputs.results-sbom-cyclonedx-xml-path }}").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-cyclonedx-json-path }}").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-spdx-yml-path }}").sigstore"
|
||||
cp "$ATTESTATION" "attestations/$(basename "${{ steps.ort.outputs.results-sbom-spdx-json-path }}").sigstore"
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-sbom.outputs.bundle-path }}"
|
||||
|
||||
- name: "Assemble Release SBoM Artifacts"
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: "Attestations"
|
||||
path: "attestations/*.sigstore"
|
||||
|
||||
upload-release:
|
||||
needs: [create_draft_release, build, sign, sbom]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs,SBoM,Attestations}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Upload Pre-built
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" == "branch" ]; then
|
||||
tag=${{ github.ref_name }}-latest
|
||||
else
|
||||
tag="${{ github.ref_name }}"
|
||||
fi
|
||||
|
||||
gh release upload \
|
||||
--repo ${{ github.repository }} \
|
||||
--clobber \
|
||||
"$tag" \
|
||||
elixir-otp-*.zip \
|
||||
elixir-otp-*.zip.sha{1,256}sum \
|
||||
elixir-otp-*.zip.sigstore \
|
||||
elixir-otp-*.exe \
|
||||
elixir-otp-*.exe.sha{1,256}sum \
|
||||
elixir-otp-*.exe.sigstore \
|
||||
Docs.zip \
|
||||
Docs.zip.sha{1,256}sum \
|
||||
Docs.zip.sigstore \
|
||||
bom.*
|
||||
|
||||
upload-builds-hex-pm:
|
||||
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 }}
|
||||
steps:
|
||||
- name: "Check if variables are set up"
|
||||
if: "${{ ! vars.HEX_AWS_REGION }}"
|
||||
run: |
|
||||
echo "Required variables for uploading to hex.pm are not set up, skipping..."
|
||||
exit 1
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Init purge keys file
|
||||
run: |
|
||||
touch purge_keys.txt
|
||||
|
||||
- name: Upload Precompiled to S3
|
||||
run: |
|
||||
ref_name=${{ github.ref_name }}
|
||||
|
||||
oldest_otp=$(find . -type f -name 'elixir-otp-*.zip' | sed -r 's/^.*elixir-otp-([[:digit:]]+)\.zip$/\1/' | sort -n | head -n 1)
|
||||
|
||||
for zip in $(find . -type f -name 'elixir-otp-*.zip' | sed 's/^\.\///'); do
|
||||
dest=${zip/elixir/${ref_name}}
|
||||
surrogate_key=${dest/.zip$/}
|
||||
|
||||
aws s3 cp "${zip}" "s3://${AWS_S3_BUCKET}/builds/elixir/${dest}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--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/${ref_name}.zip" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--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')
|
||||
|
||||
unzip Docs.zip
|
||||
|
||||
for f in doc/*; do
|
||||
if [ -d "$f" ]; then
|
||||
app=$(echo "$f" | sed s/"doc\/"//)
|
||||
tarball="${app}-${version}.tar.gz"
|
||||
surrogate_key="docs/${app}-${version}"
|
||||
|
||||
tar -czf "${tarball}" -C "doc/${app}" .
|
||||
aws s3 cp "${tarball}" "s3://${AWS_S3_BUCKET}/docs/${tarball}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
echo "${surrogate_key}" >> ../purge_keys.txt
|
||||
fi
|
||||
done
|
||||
|
||||
- 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)
|
||||
|
||||
aws s3 cp "s3://${AWS_S3_BUCKET}/builds/elixir/builds.txt" builds.txt || true
|
||||
touch builds.txt
|
||||
|
||||
for sha256_file in $(find . -name 'elixir-otp-*.zip.sha256sum' | sed 's/^\.\///'); do
|
||||
otp_version=$(echo "${sha256_file}" | sed -r 's/^elixir-otp-([[:digit:]]+)\.zip\.sha256sum/otp-\1/')
|
||||
build_sha256=$(cut -d ' ' -f 1 "${sha256_file}")
|
||||
|
||||
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
|
||||
sed -i "/^${ref_name} /d" builds.txt
|
||||
echo -e "${ref_name} ${{ github.sha }} ${date} ${build_sha256} \n$(cat builds.txt)" > builds.txt
|
||||
fi
|
||||
done
|
||||
|
||||
sort -u -k1,1 -o builds.txt builds.txt
|
||||
aws s3 cp builds.txt "s3://${AWS_S3_BUCKET}/builds/elixir/builds.txt" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata '{"surrogate-key":"builds builds/elixir builds/elixir/txt","surrogate-control":"public,max-age=604800"}'
|
||||
|
||||
echo 'builds/elixir/txt' >> purge_keys.txt
|
||||
|
||||
- name: Flush cache
|
||||
if: github.repository == 'elixir-lang/elixir'
|
||||
run: |
|
||||
function purge_key() {
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Fastly-Key: ${FASTLY_KEY}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Length: 0" \
|
||||
"https://api.fastly.com/service/$1/purge/$2"
|
||||
}
|
||||
|
||||
function purge() {
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
}
|
||||
|
||||
for key in $(cat purge_keys.txt); do
|
||||
purge "${key}"
|
||||
done
|
||||
gh release upload --clobber "${{ github.ref_name }}" \
|
||||
elixir-otp-${{ matrix.otp }}.zip \
|
||||
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum \
|
||||
elixir-otp-${{ matrix.otp }}.exe \
|
||||
elixir-otp-${{ matrix.otp }}.exe.sha{1,256}sum
|
||||
- name: Upload Docs to GitHub
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release upload --clobber "${{ github.ref_name }}" \
|
||||
Docs.zip \
|
||||
Docs.zip.sha{1,256}sum
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
name: "Release pre built"
|
||||
description: "Builds elixir release, ExDoc and generates docs"
|
||||
inputs:
|
||||
@@ -13,7 +10,7 @@ inputs:
|
||||
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
|
||||
@@ -22,6 +19,8 @@ runs:
|
||||
run: |
|
||||
make Precompiled.zip
|
||||
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
- name: Install NSIS
|
||||
shell: bash
|
||||
@@ -35,6 +34,8 @@ runs:
|
||||
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-${{ inputs.otp }}.exe .
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
|
||||
- name: Get ExDoc ref
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
@@ -45,7 +46,7 @@ runs:
|
||||
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@v3
|
||||
if: ${{ inputs.build_docs }}
|
||||
with:
|
||||
repository: elixir-lang/ex_doc
|
||||
@@ -63,5 +64,6 @@ runs:
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch --tags
|
||||
make Docs.zip
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
|
||||
@@ -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/
|
||||
@@ -15,4 +11,3 @@
|
||||
/.eunit
|
||||
.elixir.plt
|
||||
erl_crash.dump
|
||||
/cover/
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
{
|
||||
// Consecutive header levels (h1 -> h2 -> h3). We don't care about this.
|
||||
"MD001": 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"
|
||||
+140
-421
@@ -1,514 +1,233 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
# Changelog for Elixir v1.17
|
||||
|
||||
# Changelog for Elixir v1.19
|
||||
This release includes type inference of patterns to provide warnings for an initial set of constructs (binaries, maps, and atoms) within the same function. It also includes a new Duration data type to interact with Calendar types, support for Erlang/OTP 27, and many other improvements.
|
||||
|
||||
## Type system improvements
|
||||
## Warnings from gradual set-theoretic types
|
||||
|
||||
### Type checking of protocol dispatch and implementations
|
||||
This release introduces gradual set-theoretic types to infer types from patterns and use them to type check programs, enabling the Elixir compiler to find faults and bugs in codebases without requiring changes to existing software. The underlying principles, theory, and roadmap of our work have been outlined in ["The Design Principles of the Elixir Type System" by Giuseppe Castagna, Guillaume Duboc, José Valim](https://arxiv.org/abs/2306.06391).
|
||||
|
||||
This release also adds type checking when dispatching and implementing protocols.
|
||||
At the moment, Elixir developers will interact with set-theoretic types only through warnings found by the type system. The current implementation models all data types in the language:
|
||||
|
||||
For example, string interpolation in Elixir uses the `String.Chars` protocol. If you pass a value that does not implement said protocol, Elixir will now emit a warning accordingly.
|
||||
* `binary()`, `integer()`, `float()`, `pid()`, `port()`, `reference()` - these types are indivisible. This means both `1` and `13` get the same `integer()` type.
|
||||
|
||||
Here is an example passing a range, which cannot be converted into a string, to an interpolation:
|
||||
* `atom()` - it represents all atoms and it is divisible. For instance, the atom `:foo` and `:hello_world` are also valid (distinct) types.
|
||||
|
||||
```elixir
|
||||
defmodule Example do
|
||||
def my_code(first..last//step = range) do
|
||||
"hello #{range}"
|
||||
end
|
||||
end
|
||||
```
|
||||
* `map()` and structs - maps can be "closed" or "open". Closed maps only allow the specified keys, such as `%{key: atom(), value: integer()}`. Open maps support any other keys in addition to the ones listed and their definition starts with `...`, such as `%{..., key: atom(), value: integer()}`. Structs are closed maps with the `__struct__` key.
|
||||
|
||||
the above emits the following warnings:
|
||||
* `tuple()`, `list()`, and `function()` - currently they are modelled as indivisible types. The next Elixir versions will also introduce fine-grained support to them.
|
||||
|
||||
```
|
||||
warning: incompatible value given to string interpolation:
|
||||
We focused on atoms and maps on this initial release as they are respectively the simplest and the most complex types representations, so we can stress the performance of the type system and quality of error messages. Modelling these types will also provide the most immediate benefits to Elixir developers. Assuming there is a variable named `user`, holding a `%User{}` struct with an `address` field, Elixir v1.17 will emit the following warnings at compile-time:
|
||||
|
||||
data
|
||||
* Pattern matching against a map or a struct that does not have the given key, such as `%{adress: ...} = user` (notice `address` vs `adress`)
|
||||
|
||||
it has type:
|
||||
* Accessing a key on a map or a struct that does not have the given key, such as `user.adress`
|
||||
|
||||
%Range{first: term(), last: term(), step: term()}
|
||||
* Invoking a function on non-modules, such as `user.address()`
|
||||
|
||||
but expected a type that implements the String.Chars protocol, it must be one of:
|
||||
* Capturing a function on non-modules, such as `&user.address/0`
|
||||
|
||||
dynamic(
|
||||
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
|
||||
%Version.Requirement{}
|
||||
) or atom() or binary() or float() or integer() or list(term())
|
||||
```
|
||||
* Attempting to invoke to call an anonymous function without an actual function, such as `user.()`
|
||||
|
||||
Warnings are also emitted if you pass a data type that does not implement the `Enumerable` protocol as a generator to for-comprehensions:
|
||||
* Performing structural comparisons with structs, such as `my_date < ~D[2010-04-17]`
|
||||
|
||||
```elixir
|
||||
defmodule Example do
|
||||
def my_code(%Date{} = date) do
|
||||
for(x <- date, do: x)
|
||||
end
|
||||
end
|
||||
```
|
||||
* Performing structural comparisons between non-overlapping types, such as `integer >= string`
|
||||
|
||||
will emit:
|
||||
* Building and pattern matching on binaries without the relevant specifiers, such as `<<name>>` (this warns because by default it expects an integer, it should have been `<<name::binary>>` instead)
|
||||
|
||||
```
|
||||
warning: incompatible value given to for-comprehension:
|
||||
* Attempting to rescue an undefined exception or a struct that is not an exception
|
||||
|
||||
x <- date
|
||||
* Accessing a field that is not defined in a rescued exception
|
||||
|
||||
it has type:
|
||||
|
||||
%Date{year: term(), month: term(), day: term(), calendar: term()}
|
||||
|
||||
but expected a type that implements the Enumerable protocol, it must be one of:
|
||||
|
||||
dynamic(
|
||||
%Date.Range{} or %File.Stream{} or %GenEvent.Stream{} or %HashDict{} or %HashSet{} or
|
||||
%IO.Stream{} or %MapSet{} or %Range{} or %Stream{}
|
||||
) or fun() or list(term()) or non_struct_map()
|
||||
```
|
||||
|
||||
### Type checking and inference of anonymous functions
|
||||
|
||||
Elixir v1.19 can now type infer and type check anonymous functions. Here is a trivial example:
|
||||
|
||||
```elixir
|
||||
defmodule Example do
|
||||
def run do
|
||||
fun = fn %{} -> :map end
|
||||
fun.("hello")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The example above has an obvious typing violation, as the anonymous function expects a map but a string is given. With Elixir v1.19, the following warning is now printed:
|
||||
|
||||
```
|
||||
warning: incompatible types given on function application:
|
||||
|
||||
fun.("hello")
|
||||
|
||||
given types:
|
||||
|
||||
binary()
|
||||
|
||||
but function has type:
|
||||
|
||||
(dynamic(map()) -> :map)
|
||||
|
||||
typing violation found at:
|
||||
│
|
||||
6 │ fun.("hello")
|
||||
│ ~
|
||||
│
|
||||
└─ mod.exs:6:8: Example.run/0
|
||||
```
|
||||
|
||||
Function captures, such as `&String.to_integer/1`, will also propagate the type as of Elixir v1.19, arising more opportunity for Elixir's type system to catch bugs in our programs.
|
||||
|
||||
### Acknowledgements
|
||||
These new warnings help Elixir developers find bugs earlier and give more confidence when refactoring code, especially around maps and structs. While some of these warnings were emitted in the past, they were discovered using syntax analysis. The new warnings are more reliable, precise, and with better error messages. Keep in mind, however, that the Elixir typechecker only infers types from patterns within the same function at the moment. Analysis from guards and across function boundaries will be added in future relases. For more details, see our new [reference document on gradual set-theoretic types](https://hexdocs.pm/elixir/gradual-set-theoretic-types.html).
|
||||
|
||||
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/), [Starfish*](https://starfish.team/), and [Dashbit](https://dashbit.co/).
|
||||
|
||||
## Faster compile times in large projects
|
||||
## Erlang/OTP support
|
||||
|
||||
This release includes two compiler improvements that can lead up to 4x faster builds in large codebases.
|
||||
This release adds support for Erlang/OTP 27 and drops support for Erlang/OTP 24. We recommend Elixir developers to migrate to Erlang/OTP 26 or later, especially on Windows. Support for WERL (a graphical user interface for the Erlang terminal on Windows) will be removed in Elixir v1.18.
|
||||
|
||||
While Elixir has always compiled the given files in project or a dependency in parallel, the compiler would sometimes be unable to use all of the machine resources efficiently. This release addresses two common limitations, delivering performance improvements that scale with codebase size and available CPU cores.
|
||||
## Adding `Duration` and `shift/2` functions
|
||||
|
||||
### Code loading bottlenecks
|
||||
|
||||
Prior to this release, Elixir would load modules as soon as they were defined. However, because the Erlang part of code loading happens within a single process (the code server), this would make it a bottleneck, reducing parallelization, especially on large projects.
|
||||
|
||||
This release makes it so modules are loaded lazily. This reduces the pressure on the code server and the amount of work during compilation, with reports of more than two times faster compilation for large projects. The benefits depend on the codebase size and the number of CPU cores available.
|
||||
|
||||
Implementation wise, [the parallel compiler already acts as a mechanism to resolve modules during compilation](https://elixir-lang.org/blog/2012/04/24/a-peek-inside-elixir-s-parallel-compiler/), so we built on that. By making sure the compiler controls both module compilation and module loading, it can also better guarantee deterministic builds.
|
||||
|
||||
There are two potential regressions with this approach. The first one happens if you spawn processes during compilation which invoke other modules defined within the same project. For example:
|
||||
Elixir introduces the `Duration` data type and APIs to shift dates, times, and date times by a given duration, considering different calendars and time zones.
|
||||
|
||||
```elixir
|
||||
defmodule MyLib.SomeModule do
|
||||
list = [...]
|
||||
|
||||
Task.async_stream(list, fn item ->
|
||||
MyLib.SomeOtherModule.do_something(item)
|
||||
end)
|
||||
end
|
||||
iex> Date.shift(~D[2016-01-31], month: 2)
|
||||
~D[2016-03-31]
|
||||
```
|
||||
|
||||
Because the spawned process is not visible to the compiler, it won't be able to load `MyLib.SomeOtherModule`. You have two options, either use `Kernel.ParallelCompiler.pmap/2` or explicitly call `Code.ensure_compiled!(MyLib.SomeOtherModule)` before spawning the process that uses said module.
|
||||
|
||||
The second one is related to `@on_load` callbacks (typically used for [NIFs](https://www.erlang.org/doc/system/nif.html)) that invoke other modules defined within the same project. For example:
|
||||
Note the operation is called `shift` (instead of `add`) since working with durations does not obey properties such as associativity. For instance, adding one month and then one month does not give the same result as adding two months:
|
||||
|
||||
```elixir
|
||||
defmodule MyLib.SomeModule do
|
||||
@on_load :init
|
||||
|
||||
def init do
|
||||
MyLib.AnotherModule.do_something()
|
||||
end
|
||||
|
||||
def something_else do
|
||||
...
|
||||
end
|
||||
end
|
||||
|
||||
MyLib.SomeModule.something_else()
|
||||
iex> ~D[2016-01-31] |> Date.shift(month: 1) |> Date.shift(month: 1)
|
||||
~D[2016-03-29]
|
||||
```
|
||||
|
||||
The reason this fails is because `@on_load` callbacks are invoked within the code server and therefore they have limited ability to load additional modules. It is generally advisable to limit invocation of external modules during `@on_load` callbacks but, in case it is strictly necessary, you can set `@compile {:autoload, true}` in the invoked module to address this issue in a forward and backwards compatible manner.
|
||||
Still, durations are essential for building intervals, recurring events, and modelling scheduling complexities found in the world around us. For `DateTime`s, Elixir will correctly deal with time zone changes (such as Daylight Saving Time), but provisions are also available in case you want to surface conflicts (for example, you shifted to a wall clock that does not exist, because the clock has been moved forward by one hour). See `DateTime.shift/2` for examples.
|
||||
|
||||
Both snippets above could actually lead to non-deterministic compilation failures in the past, and as a result of these changes, compiling these cases are now deterministic.
|
||||
|
||||
### Parallel compilation of dependencies
|
||||
|
||||
This release introduces a variable called `MIX_OS_DEPS_COMPILE_PARTITION_COUNT`, which instructs `mix deps.compile` to compile dependencies in parallel.
|
||||
|
||||
While fetching dependencies and compiling individual Elixir dependencies already happened in parallel, as outlined in the previous section, there were pathological cases where performance gains would be left on the table, such as when compiling dependencies with native code or dependencies where one or two large files would take most of the compilation time.
|
||||
|
||||
By setting `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` to a number greater than 1, Mix will now compile multiple dependencies at the same time, using separate OS processes. Empirical testing shows that setting it to half of the number of cores on your machine is enough to maximize resource usage. The exact speed up will depend on the number of dependencies and the number of machine cores, although some reports mention up to 4x faster compilation times. If you plan to enable it on CI or build servers, keep in mind it will most likely have a direct impact on memory usage too.
|
||||
|
||||
## Improved pretty printing algorithm
|
||||
|
||||
Elixir v1.19 ships with a new pretty printing implementation that tracks limits as a whole, instead of per depth. Previous versions would track limits per depth. For example, if you had a list of lists of 4 elements and a limit of 5, it would be pretty printed as follows:
|
||||
Finally, a new `Kernel.to_timeout/1` function has been added, which helps developers normalize durations and integers to a timeout used by Process APIs. For example, to send a message after one hour, one can now write:
|
||||
|
||||
```elixir
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, ...],
|
||||
[1, ...],
|
||||
[...],
|
||||
...
|
||||
]
|
||||
Process.send_after(pid, :wake_up, to_timeout(hour: 1))
|
||||
```
|
||||
|
||||
This allows for more information to be shown at different nesting levels, which is useful for complex data structures. But it led to some pathological cases where the `limit` option had little effect on filtering the amount of data shown. The new implementation decouples the limit handling from depth, decreasing it as it goes. Therefore, the list above with the same limit in Elixir v1.19 is now printed as:
|
||||
|
||||
```elixir
|
||||
[
|
||||
[1, 2, 3],
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
The outer list is the first element, the first nested list is the second, followed by three numbers, reaching the limit. This gives developers more precise control over pretty printing.
|
||||
|
||||
Given this may reduce the amount of data printed by default, the default limit has also been increased from 50 to 100. We may further increase it in upcoming releases based on community feedback.
|
||||
|
||||
## Erlang/OTP 28 support
|
||||
|
||||
Elixir v1.19 officially supports Erlang/OTP 28.1+ and later. In order to support the new Erlang/OTP 28 representation for regular expressions, structs can now control how they are escaped into abstract syntax trees by defining a `__escape__/1` callback.
|
||||
|
||||
On the other hand, the new representation for regular expressions implies they can no longer be used as default values for struct fields. Instead of this:
|
||||
|
||||
```elixir
|
||||
defmodule Foo do
|
||||
defstruct regex: ~r/foo/
|
||||
end
|
||||
```
|
||||
|
||||
You must do this:
|
||||
|
||||
```elixir
|
||||
defmodule Foo do
|
||||
defstruct [:regex]
|
||||
|
||||
def new do
|
||||
%Foo{regex: ~r/foo/}
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## OpenChain certification
|
||||
|
||||
Elixir v1.19 is also our first release following OpenChain compliance, [as previously announced](https://elixir-lang.org/blog/2025/02/26/elixir-openchain-certification/). In a nutshell:
|
||||
|
||||
* Elixir releases now include a Source SBoM in CycloneDX 1.6 or later and SPDX 2.3 or later formats.
|
||||
* Each release is attested along with the Source SBoM.
|
||||
|
||||
These additions offer greater transparency into the components and licenses of each release, supporting more rigorous supply chain requirements.
|
||||
|
||||
This work was performed by [Jonatan Männchen](https://maennchen.dev) and sponsored by the [Erlang Ecosystem Foundation](https://erlef.org).
|
||||
|
||||
## v1.19.6 (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)
|
||||
|
||||
## v1.19.5 (2026-01-09)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Protocol] Optimize protocol consolidation to no longer load structs
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix unnecessary recompilation when `dbg_callback` is modified at runtime
|
||||
* [Kernel] Fix parser crash on missing parentheses on expression following operator `not in`
|
||||
* [Kernel] Support fetching abstract code for modules compiled with Elixir v1.14 and earlier
|
||||
* [Protocol] Ensure protocol consolidation no longer stores outdated struct types. As a consequence, protocols types only track struct names at the moment
|
||||
* [Stream] Revert optimization which caused nested streams in `Stream.flat_map/2` to crash
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Fix usage of `#iex:break` as part of multi-line prompts
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger.Backends] Do not crash on invalid metadata
|
||||
|
||||
## v1.19.4 (2025-11-27)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix xref] Add `--min-cycle-label` to help projects adapt to the more precise `mix xref graph` reports in Elixir v1.19. In previous versions, Elixir would break a large compilation cycle into several smaller ones, and therefore developers would check for `--min-cycle-size` on CI. However, the issue is not the size of the cycle (it has no implication in the amount of compiled files), but how many compile-time dependencies (aka compile labels) in a cycle. The new option allows developers to filter on the label parameter
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [File] Ensure `File.cp_r/3` reports non-existing destination properly (instead of source)
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Fix formatter crash when diffing takes too long
|
||||
* [ExUnit] Ensure parallel matches in `assert` propagate type information
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Fix regression where formatter would crash when given chardata (the crash would happen when logging non-ASCII characters)
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix help] Ensure `app:APP` works when the project or its dependencies were not yet compiled
|
||||
* [mix escript.build] Ensure the `hex` application can be included in escripts
|
||||
|
||||
## v1.19.3 (2025-11-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Support /E modifier for regular expressions in config files
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Allow forcing specific compilers, such as `--force-elixir`, `--force-app`, etc
|
||||
* [mix help app:APP] Support showing helps for apps in Elixir and Erlang standard libraries
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [IO] Fix dialyzer warning on `IO.inspect :label`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Case] Fix crash when formatting errors caused by a linked/trapped exit during `setup_all`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.app] Ensure functions in the format `&Mod.fun/arity` can be written to .app files
|
||||
* [mix compile.app] Ensure strings with Unicode characters can be written to .app files
|
||||
|
||||
## v1.19.2 (2025-11-02)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Measure and optimize writing of `.beam` files in the compiler
|
||||
* [Kernel] Optimize rare scenarios where type checking took too long
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Add flag `--no-check-cwd` to skip compiler check to aid debugging
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [IO] Fix dialyzer warning on `IO.inspect :label`
|
||||
* [Kernel] Ensure we warn on deprecated `~~~` unary operator
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Reset ansi escapes before newlines in Logger
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Warn if `elixirc_paths` is not a list of string paths
|
||||
* [mix compile] Address regression where umbrella children were compiled too early and without respecting compilation flags
|
||||
* [mix deps.compile] Improve reliability of `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` across `mix escript.install`, `mix archive.install`, and others
|
||||
|
||||
## v1.19.1 (2025-10-20)
|
||||
## v1.17.3 (2024-09-18)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Address Dialyzer warnings when invoking `EEx.compile_string`
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Optimize how types are computed for pretty printing
|
||||
* [Kernel] Optimize how differences are computed in the type system
|
||||
* [Macro] Do not escape options given to `dbg/2`
|
||||
* [Protocol] Improve protocol violation warnings
|
||||
* [Duration] Fix parsing of fractional durations with non-positive seconds
|
||||
* [Kernel] Do not attempt to group module warnings when they have a large context
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Properly reconsolidate protocols on `recompile`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Do not attempt to touch deleted files when compilation fails and then resumed with missing files
|
||||
* [mix deps.compile] Do not spawn partitions when all dependencies are local and already compiled
|
||||
* [mix compile.elixir] Do not verify modules twice
|
||||
* [mix xref] Respect the `--label` option on stats and cycles
|
||||
|
||||
## v1.19.0 (2025-10-16)
|
||||
## v1.17.2 (2024-07-06)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger.Translator] Fix logger crash when `:gen_statem`'s `format_status/2` returns non-tuple
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.get] Fix regression when fetching a git repository with a `:ref`
|
||||
* [mix release] Validate `RELEASE_MODE` and set ERRORLEVEL on `.bat` scripts
|
||||
* [mix release] Fix invalid example in code comment inside the generated vm.args.eex
|
||||
|
||||
## v1.17.1 (2024-06-18)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Do not run fixpoint computation on runtime dependencies. This should considerably improve compilation times for large projects when changing only one or two files
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### EEx
|
||||
|
||||
* [EEx] Do not warn for assignment with blocks in EEx
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix bug when using pinned variables inside `with`'s `else` patterns
|
||||
* [Kernel] Fix Dialyzer error when with else clause is calling a `no_return` function
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Do not alternative sync/async suites on `--repeat-until-failure`
|
||||
|
||||
## v1.17.0 (2024-06-12)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Access] Add `Access.values/0` for traversing maps and keyword lists values
|
||||
* [Base] Add functions to verify if an encoding is valid, such as `valid16?`, `valid64?`, and so forth
|
||||
* [Calendar] Support 2-arity options for `Calendar.strftime/3` which receives the whole data type
|
||||
* [Code] Add `:migrate_call_parens_on_pipe` formatter option
|
||||
* [Code] Add `:indentation` option to `Code.string_to_quoted/2`
|
||||
* [Code.Fragment] Preserve more block content around cursor in `container_cursor_to_quoted`
|
||||
* [Code.Fragment] Add `:block_keyword_or_binary_operator` to `Code.Fragment` for more precise suggestions after operators and closing terminators
|
||||
* [Code.Fragment] Add `Code.Fragment.lines/1`
|
||||
* [Enum] Provide more information on `Enum.OutOfBoundsError`
|
||||
* [Inspect] Allow `optional: :all` when deriving Inspect
|
||||
* [Inspect.Algebra] Add optimistic/pessimistic groups as a simplified implementation of `next_break_fits`
|
||||
* [IO.ANSI] Add ANSI codes to turn off conceal and crossed_out
|
||||
* [Kernel] Raise when U+2028 and U+2029 characters are present in comments and strings to avoid line spoofing attacks
|
||||
* [Kernel] Include the line for the previous clause in errors/warnings related to conflicts between defaults on function definitions
|
||||
* [Kernel] Support `min/2` and `max/2` as guards
|
||||
* [Kernel.ParallelCompiler] Add `each_long_verification_threshold` which invokes a callback when type checking a module takes too long
|
||||
* [Kernel.ParallelCompiler] Include lines in `== Compilation error in file ... ==` slogans
|
||||
* [Macro] Print debugging results from `Macro.dbg/3` as they happen, instead of once at the end
|
||||
* [Macro] Add `__escape__/1` callback so structs can escape references and other runtime data types in `Macro.escape/1`
|
||||
* [Module] Do not automatically load modules after their compilation, guaranteeing a more consistent compile time experience and drastically improving compilation times
|
||||
* [OptionParser] Support the `:regex` type
|
||||
* [OptionParser] Enhance parsing error to display available options
|
||||
* [Protocol] Type checking of protocols dispatch and implementations
|
||||
* [Regex] Add `Regex.to_embed/2` which returns an embeddable representation of regex in another regex
|
||||
* [Regex] Raise error message when regexes are used as default values in struct fields for compatibility with Erlang/OTP 28
|
||||
* [Registry] Add key-based partitioning of duplicate registries
|
||||
* [String] Add `String.count/2` to count occurrences of a pattern
|
||||
* [String] Update to Unicode 17.0.0
|
||||
* [Access] Add `Access.find/1` that mirrors `Enum.find/2`
|
||||
* [Code] Support cursor inside fn/rescue/catch/else/after inside `Code.Fragment.container_cursor_to_quoted/2`
|
||||
* [Date] Add `Date.shift/2` to shift dates with duration and calendar-specific semantics
|
||||
* [Date] Allow `Date` to accept years outside of `-9999..9999` range
|
||||
* [DateTime] Add `DateTime.shift/2` to shift datetimes with duration and calendar-specific semantics
|
||||
* [Duration] Add a new `Duration` data type
|
||||
* [GenServer] Add `c:GenServer.format_status/1` callback
|
||||
* [Kernel] Add `Kernel.get_in/1` with safe nil-handling for access and structs
|
||||
* [Kernel] Add `Kernel.is_non_struct_map/1` guard
|
||||
* [Kernel] Add `Kernel.to_timeout/1`
|
||||
* [Kernel] Emit warnings for undefined functions from modules defined within the same context as the caller code
|
||||
* [Kernel] Support integers in uppercase sigils
|
||||
* [Keyword] Add `Keyword.intersect/2-3` to mirror the `Map` API
|
||||
* [Macro] Add `Macro.Env.define_alias/4`, `Macro.Env.define_import/4`, `Macro.Env.define_require/4`, `Macro.Env.expand_alias/4`, `Macro.Env.expand_import/5`, and `Macro.Env.expand_require/6` to aid the implementation of language servers and embedded languages
|
||||
* [NaiveDateTime] Add `NaiveDateTime.shift/2` to shift naive datetimes with duration and calendar-specific semantics
|
||||
* [Process] Add `Process.set_label/1`
|
||||
* [String] Add `String.byte_slice/3` to slice a string to a maximum number of bytes while keeping it UTF-8 encoded
|
||||
* [System] Support `use_stdio: false` in `System.cmd/3` and `System.shell/2`
|
||||
* [Time] Add `Time.shift/2` to shift times with duration and calendar-specific semantics
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Set a process label for each test
|
||||
* [ExUnit.CaptureLog] Parallelize log dispatch when multiple processes are capturing log
|
||||
* [ExUnit.Case] Add `:test_group` to the test context
|
||||
* [ExUnit.Doctest] Support ellipsis in doctest exceptions to match the remaining of the exception
|
||||
* [ExUnit.Doctest] Add `:inspect_opts` option for doctest
|
||||
* [ExUnit] Propagate the test process itself as a caller in `start_supervised`
|
||||
* [ExUnit] Include max cases in ExUnit reports
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Support multi-line prompts (due to this feature, `:continuation_prompt` and `:alive_continuation_prompt` are no longer supported as IEx configuration)
|
||||
* [IEx.Autocomplete] Functions annotated with `@doc group: "Name"` metadata will appear within their own groups in autocompletion
|
||||
* [IEx.Helpers] Warns if `recompile` was called and the current working directory changed
|
||||
* [IEx.Helpers] Add `c/0` as an alias to `continue/0`
|
||||
* [IEx.Pry] Add `IEx.Pry.annotate_quoted/3` to annotate a quoted expression with pry breakpoints
|
||||
|
||||
#### Logger
|
||||
|
||||
* [Logger] Accept any enumerable in `Logger.metadata/1`
|
||||
* [Logger] Format `:gen_statem` reports using Elixir data structures
|
||||
* [Logger] Include process label in logger events
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Add support for `MIX_PROFILE_FLAGS` to configure `MIX_PROFILE`
|
||||
* [mix compile] Debug the compiler and type checker PID when `MIX_DEBUG=1` and compilation/verification thresholds are met
|
||||
* [mix compile] Add `Mix.Tasks.Compiler.reenable/1`
|
||||
* [mix deps.compile] Support `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` for compiling deps concurrently across multiple operating system processes
|
||||
* [mix help] Add `mix help Mod`, `mix help :mod`, `mix help Mod.fun`, `mix help Mod.fun/arity`, and `mix help app:package`
|
||||
* [mix format] Add options to mix format to allow excluding of files
|
||||
* [mix test] Add `--name-pattern` option to `mix test`
|
||||
* [mix test] Allow to distinguish the exit status between warnings as errors and test failures
|
||||
* [mix xref graph] Add support for `--format json`
|
||||
* [mix xref graph] Emit a warning if `--source` is part of a cycle
|
||||
* [Mix] Support the `:compilers` option
|
||||
* [Mix.Task.Compiler] Add `Mix.Task.Compiler.run/2`
|
||||
* [mix deps] Add `:depth` option to `Mix.SCM.Git`, thus supporting shallow clones of Git dependencies
|
||||
* [mix deps] Warn if `:optional` is used in combination with `:in_umbrella`
|
||||
* [mix deps.get] Do not add optional dependency requirements if its parent dep was skipped
|
||||
* [mix deps.tree] Add `--umbrella-only` to `mix deps.tree`
|
||||
* [mix profile.tprof] Add a new profiler, available on Erlang/OTP 27+, which can measure count, time, and heap usage
|
||||
* [mix test] Add `mix test --breakpoints` that sets up a breakpoint before each test that will run
|
||||
* [mix test] Add `mix test --repeat-until-failure` to rerun tests until a failure occurs
|
||||
* [mix test] Add `mix test --slowest-modules` to print slowest modules based on all of the tests they hold
|
||||
* [mix test] Generate cover HTML files in parallel
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Return error on invalid unicode sequences in `Code.string_to_quoted/2` instead of raising
|
||||
* [Code] Properly handle column annotation for `in` in `not in` expressions
|
||||
* [DateTime] Do not truncate microseconds regardless of precision in `DateTime.diff/3`
|
||||
* [Enum] Fix infinite loop on `Enum.take/2` with negative index on empty enumerable
|
||||
* [File] Properly handle permissions errors cascading from parent in `File.mkdir_p/1`
|
||||
* [Inspect] Inspect ill-formed structs as maps
|
||||
* [Kernel] Properly increment metadata newline when `?` is followed by a literal newline character
|
||||
* [Kernel] `not_a_map.key` now raises `BadMapError` for consistency with other map operations
|
||||
* [Protocol] `defstruct/1` and `defexception/1` are now disabled inside `defprotocol` as to not allow defining structs/exceptions alongside a protocol
|
||||
* [Regex] Fix `Regex.split/2` returning too many results when the chunk being split on was empty (which can happen when using features such as `/K`)
|
||||
* [Stream] Ensure `Stream.transform/5` respects suspend command when its inner stream halts
|
||||
* [URI] Several fixes to `URI.merge/2` related to trailing slashes, trailing dots, and hostless base URIs
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Assertions] Fix order of pinned variables in failure reports
|
||||
* [ExUnit.Assertions] Raise if attempting to raise an assertion error with invalid message (not a binary)
|
||||
* [ExUnit.Case] Do not crash on empty test unit groups
|
||||
* [bin/elixir.bat] Improve handling of quotes and exclamation marks in flags
|
||||
* [Code] Address a bug where AST nodes for `(a -> b)` were not wrapped as part of the literal encoder
|
||||
* [Kernel] Resolve inconsistencies of how `..` and `...` are handled at the AST level
|
||||
* [Kernel] Fix parsing precedence of ambiguous operators followed by containers
|
||||
* [Kernel] Do not expand code in `quote bind_quoted: ...` twice
|
||||
* [Kernel] Respect `:line` property when `:file` is given as option to `quote`
|
||||
* [Kernel] Do not crash on `Macro.escape/2` when passing a quote triplet without valid meta
|
||||
* [Kernel] Avoid double tracing events when capturing a function
|
||||
* [Kernel] Fix a bug where captured arguments would conflict when a capture included a macro that also used captures
|
||||
* [Module] Return default value in `Module.get_attribute/3` for persisted attributes which have not yet been written to
|
||||
* [String] Properly handle transpositions in `jaro_distance`. This will correct the distance result in certain cases
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Abort pipelines when there is an error in any step along the way
|
||||
* [IEx.Helpers] Update the history size whenever history is pruned
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix cmd] Preserve argument quoting in subcommands by no longer performing shell expansion. To revert to the previous behaviour, pass `--shell` before the command name
|
||||
* [mix compile] Fix bug where reverting changes to an external resource (such as HEEx template) after a compilation error would make it so the source module would not be compiled
|
||||
* [mix compile] Avoid failures when locking compilation across different users
|
||||
* [mix compile] Fix race condition when renaming files used by the compilation lock
|
||||
* [mix format] Ensure the formatter does not go over the specified limit in certain corner cases
|
||||
* [mix release] Fix `RELEASE_SYS_CONFIG` for Windows 11
|
||||
* [mix test] Ensure modules are preloaded in `mix test --slowest-modules=N`
|
||||
* [mix xref graph] Provide more consistent output by considering strong connected components when computing graphs. This means that, if you were using `mix xref graph --format cycles`, you will now get fewer but larger cycles, as cycle between `A` and `B` and another between `A` and `C` is now considered a single cycle between `A`, `B`, and `C`. Note the cycles themselves are not problematic, unless they have compile-time dependencies in them
|
||||
* [mix deps] Fix error message for diverged SCM definition in sibling
|
||||
|
||||
### 3. Soft deprecations (no warnings emitted)
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect.Algebra] `next_break_fits` is deprecated in favor of `optimistic`/`pessimistic` groups
|
||||
* [Node] `Node.start/2-3` is deprecated in favor of `Node.start/2` with a keyword list
|
||||
* [GenServer] Deprecate `c:GenServer.format_status/2` callback to align with Erlang/OTP 25+
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] `--no-protocol-consolidation` is deprecated in favor of `--no-consolidate-protocols` for consistency with `mix.exs` configuration
|
||||
* [mix compile.protocols] Protocol consolidation is now part of `compile.elixir` and the task itself has no effect
|
||||
* [mix profile.cprof] Deprecated in favor of the new `mix profile.tprof`
|
||||
* [mix profile.eprof] Deprecated in favor of the new `mix profile.tprof`
|
||||
|
||||
### 4. Hard deprecations
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Warn if line-break characters outside of `\r` and `\r\n` are found in strings according to UX#55. This warning will be fast-tracked into an error for security reasons in Elixir v1.20, following a similar rule to bidirectional control characters. They will already raise if found in comments
|
||||
* [Code] The `on_undefined_variable: :warn` is deprecated. Relying on undefined variables becoming function calls will not be supported in the future
|
||||
* [File] Passing a callback as third argument to `File.cp/3` is deprecated, pass it as a `on_conflict: callback` option instead
|
||||
* [File] Passing a callback as third argument to `File.cp_r/3` is deprecated, pass it as a `on_conflict: callback` option instead
|
||||
* [Kernel] The struct update syntax, such as `%URI{uri | path: "/foo/bar"}`, now requires the given variable (or expression) to explicitly pattern match on the struct before it can be updated. This is because, thanks to the type system, pattern matching on structs can find more errors, more reliably, and we want to promote its usage. Once pattern matching is added, you may optionally convert the struct update syntax into the map update syntax `%{uri | path: "/foo/bar"}` with no less of typing guarantees
|
||||
* [Kernel.ParallelCompiler] Passing `return_diagnostics: true` as an option is required on `compile`, `compile_to_path` and `require`
|
||||
* [IO] Passing `:all` to `IO.read/2` and `IO.binread/2` is deprecated, pass `:eof` instead
|
||||
* [Kernel] Single-quote charlists are deprecated, use `~c` instead
|
||||
* [Kernel] Deprecate escaping closing delimiter in uppercase sigils
|
||||
* [Range] `left..right` without explicit steps inside patterns and guards is deprecated, write `left..right//step` instead
|
||||
* [Range] Decreasing ranges, such as `10..1` without an explicit step is deprecated, write `10..1//-1` instead
|
||||
|
||||
#### Logger
|
||||
#### ExUnit
|
||||
|
||||
* [Logger] The `:backends` configuration is deprecated, either set the `:default_handler` to false or start backends in your application start callback
|
||||
* [ExUnit.Case] `register_test/4` is deprecated in favor of `register_test/6` for performance reasons
|
||||
|
||||
#### Mix
|
||||
## v1.16
|
||||
|
||||
* [mix] The `:default_task`, `:preferred_cli_env`, and `:preferred_cli_target` configuration inside `def project` in your `mix.exs` has been deprecated in favor of `:default_task`, `:preferred_envs` and `:preferred_targets` inside the `def cli` function
|
||||
* [mix do] Using commas as task separator in `mix do` (such as `mix do foo, bar`) is deprecated, use `+` instead (as in `mix do foo + bar`)
|
||||
|
||||
## v1.18
|
||||
|
||||
The CHANGELOG for v1.18 releases can be found [in the v1.18 branch](https://github.com/elixir-lang/elixir/blob/v1.18/CHANGELOG.md).
|
||||
The CHANGELOG for v1.16 releases can be found [in the v1.16 branch](https://github.com/elixir-lang/elixir/blob/v1.16/CHANGELOG.md).
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
<!--
|
||||
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
|
||||
|
||||
-197
@@ -1,197 +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 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.
|
||||
|
||||
## 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.
|
||||
|
||||
```
|
||||
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 http://developercertificate.org/ for a copy of the Developer Certificate
|
||||
of Origin license.
|
||||
|
||||
## 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).
|
||||
@@ -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,16 +1,11 @@
|
||||
# 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_MAKE := if [ -n "$(ERLC_OPTS)" ]; then ERL_COMPILER_OPTIONS=$(ERLC_OPTS) erl -make; else erl -make; fi
|
||||
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
|
||||
GENERATE_APP := $(CURDIR)/lib/elixir/scripts/generate_app.escript
|
||||
VERSION := $(strip $(shell cat VERSION))
|
||||
@@ -22,19 +17,19 @@ INSTALL_DIR = $(INSTALL) -m755 -d
|
||||
INSTALL_DATA = $(INSTALL) -m644
|
||||
INSTALL_PROGRAM = $(INSTALL) -m755
|
||||
GIT_REVISION = $(strip $(shell git rev-parse HEAD 2> /dev/null ))
|
||||
GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | grep -v latest | tail -1))
|
||||
GIT_TAG = $(strip $(shell head="$(call GIT_REVISION)"; git tag --points-at $$head 2> /dev/null | tail -1) )
|
||||
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 compile erlang elixir unicode app build_plt clean_plt dialyze test check_reproducible clean clean_residual_files format install_man clean_man 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 >= 26)])' -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 26.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
|
||||
@@ -74,21 +65,17 @@ endef
|
||||
#==> Compilation tasks
|
||||
|
||||
APP := lib/elixir/ebin/elixir.app
|
||||
EEX := lib/eex/ebin/Elixir.EEx.beam
|
||||
ELIXIR := lib/elixir/ebin/elixir.beam
|
||||
PARSER := lib/elixir/src/elixir_parser.erl
|
||||
KERNEL := lib/elixir/ebin/Elixir.Kernel.beam
|
||||
UNICODE := lib/elixir/ebin/Elixir.String.Unicode.beam
|
||||
|
||||
default: compile
|
||||
|
||||
compile: erlang elixir
|
||||
compile: erlang $(APP) elixir
|
||||
|
||||
erlang: $(ELIXIR)
|
||||
$(ELIXIR): $(PARSER) lib/elixir/src/*
|
||||
erlang: $(PARSER)
|
||||
$(Q) if [ ! -f $(APP) ]; then $(call CHECK_ERLANG_RELEASE); fi
|
||||
$(Q) cd lib/elixir && mkdir -p ebin && $(ERL_MAKE)
|
||||
$(Q) $(GENERATE_APP) $(VERSION)
|
||||
|
||||
$(PARSER): lib/elixir/src/elixir_parser.yrl
|
||||
$(Q) erlc -o $@ +'{verbose,true}' +'{report,true}' $<
|
||||
@@ -96,27 +83,29 @@ $(PARSER): lib/elixir/src/elixir_parser.yrl
|
||||
# Since Mix depends on EEx and EEx depends on Mix,
|
||||
# we first compile EEx without the .app file,
|
||||
# then Mix, and then compile EEx fully
|
||||
elixir: stdlib $(EEX) mix ex_unit logger eex iex
|
||||
stdlib: $(KERNEL) $(UNICODE) $(APP)
|
||||
elixir: stdlib lib/eex/ebin/Elixir.EEx.beam mix ex_unit logger eex iex
|
||||
|
||||
$(KERNEL): lib/elixir/src/* lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex VERSION
|
||||
stdlib: $(KERNEL) VERSION
|
||||
$(KERNEL): lib/elixir/lib/*.ex lib/elixir/lib/*/*.ex lib/elixir/lib/*/*/*.ex
|
||||
$(Q) if [ ! -f $(KERNEL) ]; then \
|
||||
echo "==> bootstrap (compile)"; \
|
||||
$(ERL) -s elixir_compiler bootstrap -s erlang halt; \
|
||||
"$(MAKE)" unicode; \
|
||||
fi
|
||||
$(Q) "$(MAKE)" unicode
|
||||
@ echo "==> elixir (compile)";
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC_MIN_SIG) "lib/**/*.ex" -o ebin;
|
||||
$(Q) cd lib/elixir && ../../$(ELIXIRC) "lib/**/*.ex" -o ebin;
|
||||
$(Q) "$(MAKE)" app
|
||||
|
||||
app: $(APP)
|
||||
$(APP): lib/elixir/src/elixir.app.src lib/elixir/ebin VERSION $(GENERATE_APP)
|
||||
$(Q) $(GENERATE_APP) $(VERSION)
|
||||
$(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/security.ex -o lib/elixir/ebin;
|
||||
$(Q) $(ELIXIRC) lib/elixir/unicode/tokenizer.ex -o lib/elixir/ebin;
|
||||
|
||||
$(eval $(call APP_TEMPLATE,ex_unit,ExUnit))
|
||||
$(eval $(call APP_TEMPLATE,logger,Logger))
|
||||
@@ -155,7 +144,6 @@ check_reproducible: compile
|
||||
$(Q) mv lib/iex/ebin/* lib/iex/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/logger/ebin/* lib/logger/tmp/ebin_reproducible/
|
||||
$(Q) mv lib/mix/ebin/* lib/mix/tmp/ebin_reproducible/
|
||||
$(Q) rm -rf lib/*/ebin
|
||||
SOURCE_DATE_EPOCH=$(call READ_SOURCE_DATE_EPOCH) "$(MAKE)" compile
|
||||
$(Q) echo "Diffing..."
|
||||
$(Q) bin/elixir lib/elixir/scripts/diff.exs lib/elixir/ebin/ lib/elixir/tmp/ebin_reproducible/
|
||||
@@ -166,10 +154,16 @@ check_reproducible: compile
|
||||
$(Q) bin/elixir lib/elixir/scripts/diff.exs lib/mix/ebin/ lib/mix/tmp/ebin_reproducible/
|
||||
$(Q) echo "Builds are reproducible"
|
||||
|
||||
clean: clean_man
|
||||
clean:
|
||||
rm -rf ebin
|
||||
rm -rf lib/*/ebin
|
||||
rm -rf $(PARSER)
|
||||
$(Q) "$(MAKE)" clean_residual_files
|
||||
|
||||
clean_elixir:
|
||||
$(Q) rm -f lib/*/ebin/Elixir.*.beam
|
||||
|
||||
clean_residual_files:
|
||||
rm -rf lib/*/_build/
|
||||
rm -rf lib/*/tmp/
|
||||
rm -rf lib/elixir/test/ebin/
|
||||
@@ -179,15 +173,12 @@ 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
|
||||
$(Q) "$(MAKE)" clean_man
|
||||
|
||||
#==> 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
|
||||
@@ -229,19 +220,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
|
||||
@@ -292,15 +283,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
|
||||
@@ -323,15 +305,17 @@ dialyze: compile $(PLT)
|
||||
|
||||
build_man: man/iex.1 man/elixir.1
|
||||
|
||||
define BUILD_MANPAGES
|
||||
man/$(APP).1:
|
||||
$(Q) cp man/$(APP).1.in man/$(APP).1
|
||||
$(Q) sed -i.bak "/{COMMON}/r man/common" man/$(APP).1
|
||||
$(Q) sed -i.bak "/{COMMON}/d" man/$(APP).1
|
||||
$(Q) rm -f man/$(APP).1.bak
|
||||
endef
|
||||
man/iex.1:
|
||||
$(Q) cp man/iex.1.in man/iex.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r man/common" man/iex.1
|
||||
$(Q) sed -i.bak "/{COMMON}/d" man/iex.1
|
||||
$(Q) rm -f man/iex.1.bak
|
||||
|
||||
$(foreach APP, elixir iex, $(eval $(BUILD_MANPAGES)))
|
||||
man/elixir.1:
|
||||
$(Q) cp man/elixir.1.in man/elixir.1
|
||||
$(Q) sed -i.bak "/{COMMON}/r man/common" man/elixir.1
|
||||
$(Q) sed -i.bak "/{COMMON}/d" man/elixir.1
|
||||
$(Q) rm -f man/elixir.1.bak
|
||||
|
||||
clean_man:
|
||||
rm -f man/elixir.1
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
LEGAL NOTICE INFORMATION
|
||||
------------------------
|
||||
|
||||
All the files in this distribution are copyright to the terms below.
|
||||
|
||||
== lib/elixir/src/elixir_parser.erl (generated by build scripts)
|
||||
|
||||
Copyright Ericsson AB 1996-2015
|
||||
|
||||
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,168 +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 Elixir Projects
|
||||
|
||||
Any code contributed to Elixir repositories 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.
|
||||
|
||||
Every Elixir project within the organization will have an automated GitHub
|
||||
Action to enforce these rules. This mechanism aids in detecting non-compliant
|
||||
licenses or files early in the review process.
|
||||
|
||||
## 5. Preservation of Copyright and License Information
|
||||
|
||||
Any third-party code incorporated into Elixir projects 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-02-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 peak 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,
|
||||
@@ -113,18 +103,122 @@ on Windows](https://github.com/elixir-lang/elixir/wiki/Windows).
|
||||
In case you want to use this Elixir version as your system version,
|
||||
you need to add the `bin` directory to [your PATH environment variable](https://elixir-lang.org/install.html#setting-path-environment-variable).
|
||||
|
||||
When updating the repository, you may want to run `make clean` before
|
||||
recompiling. For deterministic builds, you should set the environment
|
||||
variable `ERL_COMPILER_OPTIONS=deterministic`.
|
||||
Additionally, you may choose to run the test suite with `make clean test`.
|
||||
|
||||
## 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 also include information on our review
|
||||
process, licensing requirements, and helpful tips to ensure a smooth
|
||||
contribution experience.
|
||||
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
|
||||
```
|
||||
|
||||
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 (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
|
||||
|
||||
@@ -147,7 +241,6 @@ contribution experience.
|
||||
[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
|
||||
|
||||
@@ -155,4 +248,4 @@ contribution experience.
|
||||
|
||||
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,14 +1,8 @@
|
||||
<!--
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
-->
|
||||
|
||||
# Release process
|
||||
|
||||
## Shipping a new version
|
||||
|
||||
1. Update version in /VERSION, bin/elixir, and bin/elixir.bat
|
||||
1. Update version in /VERSION, bin/elixir and bin/elixir.bat
|
||||
|
||||
2. Ensure /CHANGELOG.md is updated, versioned and add the current date
|
||||
|
||||
@@ -28,7 +22,7 @@
|
||||
|
||||
### 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, bin/elixir.bat, and bin/elixir.ps1
|
||||
1. Bump /VERSION file, bin/elixir and bin/elixir.bat
|
||||
|
||||
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
|
||||
|
||||
+3
-9
@@ -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,11 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
Elixir version | Support
|
||||
:------------- | :-----------------------------
|
||||
1.19 | Bug fixes and security patches
|
||||
1.18 | Security patches only
|
||||
1.17 | Security patches only
|
||||
1.17 | Bug fixes and security patches
|
||||
1.16 | Security patches only
|
||||
1.15 | Security patches only
|
||||
1.14 | Security patches only
|
||||
1.13 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
|
||||
+11
-8
@@ -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.19.6
|
||||
ELIXIR_VERSION=1.17.3
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -23,13 +18,13 @@ Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--color, --no-color Enables or disables ANSI coloring
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
--eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--short-version Prints Elixir version (standalone)
|
||||
--werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
|
||||
Options given after the .exs file or -- are passed down to the executed code.
|
||||
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS or --erl.
|
||||
@@ -117,7 +112,7 @@ while [ $I -le $LENGTH ]; do
|
||||
C=1
|
||||
MODE="iex"
|
||||
;;
|
||||
-v|--no-halt|--color|--no-color)
|
||||
-v|--no-halt)
|
||||
C=1
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
|
||||
@@ -187,6 +182,10 @@ while [ $I -le $LENGTH ]; do
|
||||
echo "--pipe-to : LOGDIR cannot be a switch" >&2 && exit 1
|
||||
fi
|
||||
;;
|
||||
--werl)
|
||||
S=1
|
||||
if [ "$OS" = "Windows_NT" ]; then ERL_EXEC="werl"; fi
|
||||
;;
|
||||
*)
|
||||
while [ $I -le $LENGTH ]; do
|
||||
I=$((I + 1))
|
||||
@@ -221,6 +220,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=
|
||||
|
||||
+17
-10
@@ -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.19.6
|
||||
set ELIXIR_VERSION=1.17.3
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
@@ -28,13 +24,13 @@ echo -pa "PATH" Prepends the given path to Erlang code path
|
||||
echo -pz "PATH" Appends the given path to Erlang code path (*)
|
||||
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
echo.
|
||||
echo --color, --no-color Enables or disables ANSI coloring
|
||||
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
echo --logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
echo --no-halt Does not halt the Erlang VM after execution
|
||||
echo --short-version Prints Elixir version (standalone)
|
||||
echo --werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
echo.
|
||||
echo Options given after the .exs file or -- are passed down to the executed code.
|
||||
echo Options can be passed to the Erlang runtime using $ELIXIR_ERL_OPTIONS or --erl.
|
||||
@@ -99,6 +95,7 @@ if "!par!"=="" (
|
||||
shift
|
||||
set par="!par:"=\"!"
|
||||
rem ******* EXECUTION OPTIONS **********************
|
||||
if !par!=="--werl" (set useWerl=1 && goto startloop)
|
||||
if !par!=="+iex" (set useIEx=1 && goto startloop)
|
||||
if !par!=="+elixirc" (goto startloop)
|
||||
rem ******* ELIXIR PARAMETERS **********************
|
||||
@@ -112,8 +109,6 @@ if ""==!par:-pz=! (shift && goto startloop)
|
||||
if ""==!par:-v=! (goto startloop)
|
||||
if ""==!par:--version=! (goto startloop)
|
||||
if ""==!par:--no-halt=! (goto startloop)
|
||||
if ""==!par:--color=! (goto startloop)
|
||||
if ""==!par:--no-color=! (goto startloop)
|
||||
if ""==!par:--remsh=! (shift && goto startloop)
|
||||
if ""==!par:--dot-iex=! (shift && goto startloop)
|
||||
if ""==!par:--dbg=! (shift && goto startloop)
|
||||
@@ -133,6 +128,10 @@ if ""==!par:--pipe-to=! (echo --pipe-to : Option is not supported on
|
||||
|
||||
:run
|
||||
setlocal disabledelayedexpansion
|
||||
reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>nul
|
||||
if %errorlevel% == 0 (
|
||||
set beforeExtra=-elixir ansi_enabled true %beforeExtra%
|
||||
)
|
||||
if not defined useIEx (
|
||||
set beforeExtra=-s elixir start_cli %beforeExtra%
|
||||
)
|
||||
@@ -140,9 +139,17 @@ if not defined useIEx (
|
||||
set beforeExtra=-noshell -elixir_root "%SCRIPT_PATH%..\lib" -pa "%SCRIPT_PATH%..\lib\elixir\ebin" %beforeExtra%
|
||||
|
||||
if defined ELIXIR_CLI_DRY_RUN (
|
||||
echo "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
if defined useWerl (
|
||||
echo start "" "%ERTS_BIN%werl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) else (
|
||||
echo "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
) else (
|
||||
"%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
if defined useWerl (
|
||||
start "" "%ERTS_BIN%werl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) else (
|
||||
"%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
)
|
||||
exit /B %ERRORLEVEL%
|
||||
:end
|
||||
|
||||
@@ -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
|
||||
|
||||
+4
-6
@@ -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
|
||||
@@ -21,11 +16,14 @@ echo --dot-iex "FILE" Evaluates FILE, line by line, to set up IEx' environm
|
||||
echo Defaults to evaluating .iex.exs or ~/.iex.exs, if any exists.
|
||||
echo If FILE is empty, then no file will be loaded.
|
||||
echo --remsh NAME Connects to a node using a remote shell
|
||||
echo --werl Uses Erlang's Windows shell GUI (Windows only)
|
||||
echo.
|
||||
echo Set the IEX_WITH_WERL environment variable to always use werl.
|
||||
echo It accepts all other options listed by "elixir --help".
|
||||
goto end
|
||||
|
||||
:run
|
||||
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %*
|
||||
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %__ELIXIR_IEX_FLAGS% %*
|
||||
:end
|
||||
endlocal
|
||||
|
||||
@@ -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" %*
|
||||
|
||||
Executable → Regular
+1
-5
@@ -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 = @()
|
||||
@@ -24,4 +20,4 @@ for ($i = 0; $i -lt $args.length; $i++)
|
||||
}
|
||||
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
& $mixBatPath $newArgs
|
||||
& $mixBatPath $newArgs
|
||||
|
||||
+6
-26
@@ -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]) :: String.t()
|
||||
@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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,14 +48,8 @@ defmodule EEx.Compiler do
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove me on Elixir v2.0
|
||||
# TODO: Deprecate this on Elixir v1.18
|
||||
defp tokenize(~c"<%#" ++ t, line, column, state, buffer, acc) do
|
||||
IO.warn("<%# is deprecated, use <%!-- or add a space between <% and # instead",
|
||||
line: line,
|
||||
column: column,
|
||||
file: state.file
|
||||
)
|
||||
|
||||
case expr(t, line, column + 3, state, []) do
|
||||
{:error, message} ->
|
||||
{:error, message, %{line: line, column: column}}
|
||||
@@ -304,19 +294,11 @@ defmodule EEx.Compiler do
|
||||
source: source,
|
||||
line: line,
|
||||
quoted: [],
|
||||
parser_options: [indentation: indentation] ++ parser_options,
|
||||
parser_options: parser_options,
|
||||
indentation: indentation
|
||||
}
|
||||
|
||||
init = state.engine.init(opts)
|
||||
|
||||
if function_exported?(state.engine, :handle_text, 2) and
|
||||
not function_exported?(state.engine, :handle_text, 3) do
|
||||
IO.warn(
|
||||
"#{inspect(state.engine)}.handle_text/2 is deprecated, implement handle_text/3 instead"
|
||||
)
|
||||
end
|
||||
|
||||
generate_buffer(tokens, init, [], state)
|
||||
end
|
||||
|
||||
@@ -334,7 +316,8 @@ defmodule EEx.Compiler do
|
||||
meta = [line: meta.line, column: meta.column]
|
||||
state.engine.handle_text(buffer, meta, IO.chardata_to_string(chars))
|
||||
else
|
||||
# TODO: Remove this on Elixir v2.0. The deprecation is on init.
|
||||
# TODO: Deprecate this branch on Elixir v1.18.
|
||||
# We should most likely move this check to init to emit the deprecation once.
|
||||
state.engine.handle_text(buffer, IO.chardata_to_string(chars))
|
||||
end
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -130,21 +126,51 @@ defmodule EEx.TokenizerTest do
|
||||
end
|
||||
|
||||
test "EEx comments" do
|
||||
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
exprs = [
|
||||
{:text, ~c"foo ", %{column: 1, line: 1}},
|
||||
{:eof, %{column: 16, line: 1}}
|
||||
]
|
||||
exprs = [
|
||||
{:text, ~c"foo ", %{column: 1, line: 1}},
|
||||
{:eof, %{column: 16, line: 1}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c"foo <%# true %>", @opts) == {:ok, exprs}
|
||||
assert EEx.tokenize(~c"foo <%# true %>", @opts) == {:ok, exprs}
|
||||
|
||||
exprs = [
|
||||
{:text, ~c"foo ", %{column: 1, line: 1}},
|
||||
{:eof, %{column: 8, line: 2}}
|
||||
]
|
||||
exprs = [
|
||||
{:text, ~c"foo ", %{column: 1, line: 1}},
|
||||
{:eof, %{column: 8, line: 2}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c"foo <%#\ntrue %>", @opts) == {:ok, exprs}
|
||||
end)
|
||||
assert EEx.tokenize(~c"foo <%#\ntrue %>", @opts) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "EEx comments with do-end" do
|
||||
exprs = [
|
||||
{:text, ~c"foo ", %{column: 1, line: 1}},
|
||||
{:text, ~c"bar", %{column: 19, line: 1}},
|
||||
{:eof, %{column: 32, line: 1}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c"foo <%# true do %>bar<%# end %>", @opts) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "EEx comments inside do-end" do
|
||||
exprs = [
|
||||
{:start_expr, ~c"", ~c" if true do ", %{column: 1, line: 1}},
|
||||
{:text, ~c"bar", %{column: 31, line: 1}},
|
||||
{:end_expr, [], ~c" end ", %{column: 34, line: 1}},
|
||||
{:eof, %{column: 43, line: 1}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c"<% if true do %><%# comment %>bar<% end %>", @opts) == {:ok, exprs}
|
||||
|
||||
exprs = [
|
||||
{:start_expr, [], ~c" case true do ", %{column: 1, line: 1}},
|
||||
{:middle_expr, ~c"", ~c" true -> ", %{column: 33, line: 1}},
|
||||
{:text, ~c"bar", %{column: 46, line: 1}},
|
||||
{:end_expr, [], ~c" end ", %{column: 49, line: 1}},
|
||||
{:eof, %{column: 58, line: 1}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c"<% case true do %><%# comment %><% true -> %>bar<% end %>", @opts) ==
|
||||
{:ok, exprs}
|
||||
end
|
||||
|
||||
test "EEx multi-line comments" do
|
||||
@@ -295,6 +321,15 @@ defmodule EEx.TokenizerTest do
|
||||
assert EEx.tokenize(template, [trim: true] ++ @opts) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "trim mode with comment" do
|
||||
exprs = [
|
||||
{:text, ~c"\n123", %{column: 19, line: 1}},
|
||||
{:eof, %{column: 4, line: 2}}
|
||||
]
|
||||
|
||||
assert EEx.tokenize(~c" <%# comment %> \n123", [trim: true] ++ @opts) == {:ok, exprs}
|
||||
end
|
||||
|
||||
test "trim mode with multi-line comment" do
|
||||
exprs = [
|
||||
{:comment, ~c" comment ", %{column: 3, line: 1}},
|
||||
@@ -349,6 +384,9 @@ defmodule EEx.TokenizerTest do
|
||||
assert EEx.tokenize(~c"foo <% :bar", @opts) ==
|
||||
{:error, message, %{column: 5, line: 1}}
|
||||
|
||||
assert EEx.tokenize(~c"<%# true ", @opts) ==
|
||||
{:error, "expected closing '%>' for EEx expression", %{column: 1, line: 1}}
|
||||
|
||||
message = """
|
||||
expected closing '--%>' for EEx expression
|
||||
|
|
||||
|
||||
+12
-66
@@ -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
|
||||
@@ -202,6 +198,15 @@ defmodule EExTest do
|
||||
assert_eval("foo baz", "foo <%= if false do %>bar<% else %>baz<% end %>")
|
||||
end
|
||||
|
||||
test "embedded code with comments in do end" do
|
||||
assert_eval("foo bar", "foo <%= case true do %><%# comment %><% true -> %>bar<% end %>")
|
||||
|
||||
assert_eval(
|
||||
"foo\n\nbar\n",
|
||||
"foo\n<%= case true do %>\n<%# comment %>\n<% true -> %>\nbar\n<% end %>"
|
||||
)
|
||||
end
|
||||
|
||||
test "embedded code with multi-line comments in do end" do
|
||||
assert_eval("foo bar", "foo <%= case true do %><%!-- comment --%><% true -> %>bar<% end %>")
|
||||
|
||||
@@ -502,59 +507,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",
|
||||
@@ -917,13 +869,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
|
||||
|
||||
@@ -1001,12 +953,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,13 +1,6 @@
|
||||
# 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")
|
||||
|
||||
ExUnit.start(
|
||||
trace: !!System.get_env("TRACE"),
|
||||
include: line_include,
|
||||
|
||||
@@ -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,
|
||||
@@ -13,8 +9,8 @@
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
warn_exported_vars,
|
||||
%% Enable this when we require Erlang/OTP 27+
|
||||
%% warnings_as_errors,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
debug_info,
|
||||
{outdir, "ebin/"}
|
||||
]}.
|
||||
|
||||
+14
-108
@@ -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.
|
||||
@@ -354,7 +350,7 @@ defmodule Access do
|
||||
Accessing a list by index is typically discouraged in Elixir, \
|
||||
instead we prefer to use the Enum module to manipulate lists \
|
||||
as a whole. If you really must access a list element by index, \
|
||||
you can use Enum.at/2 or the functions in the List module\
|
||||
you can Enum.at/1 or the functions in the List module\
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -422,8 +418,8 @@ defmodule Access do
|
||||
|
||||
Accessing a list by index is typically discouraged in Elixir, \
|
||||
instead we prefer to use the Enum module to manipulate lists \
|
||||
as a whole. If you really must modify a list element by index, \
|
||||
you can use Access.at/1 or the functions in the List module\
|
||||
as a whole. If you really must mostify a list element by index, \
|
||||
you can Access.at/1 or the functions in the List module\
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -891,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
|
||||
@@ -999,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)
|
||||
@@ -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
|
||||
@@ -1161,7 +1067,7 @@ defmodule Access do
|
||||
|
||||
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.
|
||||
@@ -211,7 +207,7 @@ defmodule Agent do
|
||||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
if not Module.has_attribute?(__MODULE__, :doc) do
|
||||
unless Module.has_attribute?(__MODULE__, :doc) do
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
@@ -288,7 +284,7 @@ defmodule Agent do
|
||||
instead of an anonymous function; `fun` in `module` will be called with the
|
||||
given arguments `args` to initialize the state.
|
||||
"""
|
||||
@spec start_link(module, atom, [term], GenServer.options()) :: on_start
|
||||
@spec start_link(module, atom, [any], GenServer.options()) :: on_start
|
||||
def start_link(module, fun, args, options \\ []) do
|
||||
GenServer.start_link(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
@@ -315,7 +311,7 @@ defmodule Agent do
|
||||
|
||||
See `start_link/4` for more information.
|
||||
"""
|
||||
@spec start(module, atom, [term], GenServer.options()) :: on_start
|
||||
@spec start(module, atom, [any], GenServer.options()) :: on_start
|
||||
def start(module, fun, args, options \\ []) do
|
||||
GenServer.start(Agent.Server, {module, fun, args}, options)
|
||||
end
|
||||
@@ -352,7 +348,7 @@ defmodule Agent do
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of arguments.
|
||||
"""
|
||||
@spec get(agent, module, atom, [term], timeout) :: term
|
||||
@spec get(agent, module, atom, [term], timeout) :: any
|
||||
def get(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get, {module, fun, args}}, timeout)
|
||||
end
|
||||
@@ -393,7 +389,7 @@ defmodule Agent do
|
||||
instead of an anonymous function. The state is added as first
|
||||
argument to the given list of arguments.
|
||||
"""
|
||||
@spec get_and_update(agent, module, atom, [term], timeout) :: term
|
||||
@spec get_and_update(agent, module, atom, [term], timeout) :: any
|
||||
def get_and_update(agent, module, fun, args, timeout \\ 5000) do
|
||||
GenServer.call(agent, {:get_and_update, {module, fun, args}}, timeout)
|
||||
end
|
||||
|
||||
@@ -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.
|
||||
@@ -58,12 +54,12 @@ defmodule Application do
|
||||
You can also change the application environment dynamically by using functions
|
||||
such as `put_env/3` and `delete_env/2`.
|
||||
|
||||
> #### Application environment in libraries {: .info}
|
||||
> #### Environment in libraries {: .tip}
|
||||
>
|
||||
> If you are writing a library to be used by other developers,
|
||||
> it is generally recommended to avoid the application environment, as the
|
||||
> application environment is effectively a global storage. For more information,
|
||||
> read about this [anti-pattern](design-anti-patterns.md#using-application-configuration-for-libraries).
|
||||
> The config files `config/config.exs` and `config/runtime.exs`
|
||||
> are rarely used by libraries. Libraries typically define their environment
|
||||
> in the `application/0` function of their `mix.exs`. Configuration files
|
||||
> are rather used by applications to configure their libraries.
|
||||
|
||||
> #### Reading the environment of other applications {: .warning}
|
||||
>
|
||||
@@ -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
|
||||
@@ -677,6 +673,13 @@ defmodule Application do
|
||||
> You must use this function to read only your own application
|
||||
> environment. Do not read the environment of other applications.
|
||||
|
||||
> #### Application environment in libraries {: .info}
|
||||
>
|
||||
> If you are writing a library to be used by other developers,
|
||||
> it is generally recommended to avoid the application environment, as the
|
||||
> application environment is effectively a global storage. For more information,
|
||||
> read our [library guidelines](library-guidelines.md).
|
||||
|
||||
## Examples
|
||||
|
||||
`get_env/3` is commonly used to read the configuration of your OTP applications.
|
||||
@@ -793,12 +796,6 @@ defmodule Application do
|
||||
@doc """
|
||||
Puts the `value` in `key` for the given `app`.
|
||||
|
||||
> #### Compile environment {: .warning}
|
||||
>
|
||||
> Do not use this function to change environment variables read
|
||||
> via `Application.compile_env/2`. The compile environment must
|
||||
> be exclusively set before compilation, in your config files.
|
||||
|
||||
## Options
|
||||
|
||||
* `:timeout` - the timeout for the change (defaults to `5_000` milliseconds)
|
||||
@@ -912,7 +909,8 @@ defmodule Application do
|
||||
`: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) ::
|
||||
@@ -933,7 +931,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 """
|
||||
|
||||
@@ -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
-583
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Base do
|
||||
import Bitwise
|
||||
|
||||
@@ -319,126 +315,58 @@ defmodule Base do
|
||||
"Double check your string for unwanted characters or pad it accordingly"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a string is a valid base 16 encoded string.
|
||||
|
||||
> #### When to use this {: .tip}
|
||||
>
|
||||
> Use this function when you just need to *validate* that a string is
|
||||
> valid base 16 data, without actually producing a decoded output string.
|
||||
> This function is both more performant and memory efficient than using
|
||||
> `decode16/2`, checking that the result is `{:ok, ...}`, and then
|
||||
> discarding the decoded binary.
|
||||
|
||||
## Options
|
||||
|
||||
Accepts the same options as `decode16/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.valid16?("666F6F626172")
|
||||
true
|
||||
|
||||
iex> Base.valid16?("666f6f626172", case: :lower)
|
||||
true
|
||||
|
||||
iex> Base.valid16?("666f6F626172", case: :mixed)
|
||||
true
|
||||
|
||||
iex> Base.valid16?("ff", case: :upper)
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec valid16?(binary, case: decode_case) :: boolean
|
||||
def valid16?(string, opts \\ [])
|
||||
|
||||
def valid16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
|
||||
case Keyword.get(opts, :case, :upper) do
|
||||
:upper -> validate16upper?(string)
|
||||
:lower -> validate16lower?(string)
|
||||
:mixed -> validate16mixed?(string)
|
||||
end
|
||||
end
|
||||
|
||||
def valid16?(string, _opts) when is_binary(string) do
|
||||
false
|
||||
end
|
||||
|
||||
upper = Enum.with_index(b16_alphabet)
|
||||
|
||||
for {base, alphabet} <- [upper: upper, lower: to_lower_dec.(upper), mixed: to_mixed_dec.(upper)] do
|
||||
decode_name = :"decode16#{base}!"
|
||||
validate_name = :"validate16#{base}?"
|
||||
valid_char_name = :"valid_char16#{base}?"
|
||||
|
||||
name = :"decode16#{base}!"
|
||||
{min, decoded} = to_decode_list.(alphabet)
|
||||
|
||||
defp unquote(validate_name)(<<>>), do: true
|
||||
|
||||
defp unquote(validate_name)(<<c1, c2, rest::binary>>) do
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(validate_name)(rest)
|
||||
end
|
||||
|
||||
defp unquote(validate_name)(<<_char, _rest::binary>>), do: false
|
||||
|
||||
@compile {:inline, [{valid_char_name, 1}]}
|
||||
defp unquote(valid_char_name)(char)
|
||||
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
|
||||
do: true
|
||||
|
||||
defp unquote(valid_char_name)(_char), do: false
|
||||
|
||||
defp unquote(decode_name)(char) do
|
||||
index = char - unquote(min)
|
||||
|
||||
cond do
|
||||
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
|
||||
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
|
||||
true -> bad_character!(char)
|
||||
defp unquote(name)(char) do
|
||||
try do
|
||||
elem({unquote_splicing(decoded)}, char - unquote(min))
|
||||
rescue
|
||||
_ -> bad_character!(char)
|
||||
else
|
||||
nil -> bad_character!(char)
|
||||
char -> char
|
||||
end
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<c1, c2, c3, c4, c5, c6, c7, c8, rest::binary>>, acc) do
|
||||
unquote(decode_name)(
|
||||
defp unquote(name)(<<c1, c2, c3, c4, c5, c6, c7, c8, rest::binary>>, acc) do
|
||||
unquote(name)(
|
||||
rest,
|
||||
<<
|
||||
acc::binary,
|
||||
unquote(decode_name)(c1)::4,
|
||||
unquote(decode_name)(c2)::4,
|
||||
unquote(decode_name)(c3)::4,
|
||||
unquote(decode_name)(c4)::4,
|
||||
unquote(decode_name)(c5)::4,
|
||||
unquote(decode_name)(c6)::4,
|
||||
unquote(decode_name)(c7)::4,
|
||||
unquote(decode_name)(c8)::4
|
||||
unquote(name)(c1)::4,
|
||||
unquote(name)(c2)::4,
|
||||
unquote(name)(c3)::4,
|
||||
unquote(name)(c4)::4,
|
||||
unquote(name)(c5)::4,
|
||||
unquote(name)(c6)::4,
|
||||
unquote(name)(c7)::4,
|
||||
unquote(name)(c8)::4
|
||||
>>
|
||||
)
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<c1, c2, c3, c4, rest::binary>>, acc) do
|
||||
unquote(decode_name)(
|
||||
defp unquote(name)(<<c1, c2, c3, c4, rest::binary>>, acc) do
|
||||
unquote(name)(
|
||||
rest,
|
||||
<<
|
||||
acc::binary,
|
||||
unquote(decode_name)(c1)::4,
|
||||
unquote(decode_name)(c2)::4,
|
||||
unquote(decode_name)(c3)::4,
|
||||
unquote(decode_name)(c4)::4
|
||||
unquote(name)(c1)::4,
|
||||
unquote(name)(c2)::4,
|
||||
unquote(name)(c3)::4,
|
||||
unquote(name)(c4)::4
|
||||
>>
|
||||
)
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<c1::8, c2::8, rest::binary>>, acc) do
|
||||
unquote(decode_name)(
|
||||
rest,
|
||||
<<acc::binary, unquote(decode_name)(c1)::4, unquote(decode_name)(c2)::4>>
|
||||
)
|
||||
defp unquote(name)(<<c1::8, c2::8, rest::binary>>, acc) do
|
||||
unquote(name)(rest, <<acc::binary, unquote(name)(c1)::4, unquote(name)(c2)::4>>)
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<>>, acc) do
|
||||
defp unquote(name)(<<>>, acc) do
|
||||
acc
|
||||
end
|
||||
end
|
||||
@@ -627,40 +555,6 @@ defmodule Base do
|
||||
string |> remove_ignored(opts[:ignore]) |> decode64base!(pad?)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates a base 64 encoded string.
|
||||
|
||||
> #### When to use this {: .tip}
|
||||
>
|
||||
> Use this function when you just need to *validate* that a string is
|
||||
> valid base 64 data, without actually producing a decoded output string.
|
||||
> This function is both more performant and memory efficient than using
|
||||
> `decode64/2`, checking that the result is `{:ok, ...}`, and then
|
||||
> discarding the decoded binary.
|
||||
|
||||
## Options
|
||||
|
||||
Accepts the same options as `decode64/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.valid64?("Zm9vYmFy")
|
||||
true
|
||||
|
||||
iex> Base.valid64?("Zm9vYmFy\\n", ignore: :whitespace)
|
||||
true
|
||||
|
||||
iex> Base.valid64?("Zm9vYg==")
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean
|
||||
def valid64?(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
string |> remove_ignored(opts[:ignore]) |> validate64base?(pad?)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 64 encoded string with URL and filename safe alphabet
|
||||
into a binary string.
|
||||
@@ -721,266 +615,120 @@ defmodule Base do
|
||||
string |> remove_ignored(opts[:ignore]) |> decode64url!(pad?)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Validates a base 64 encoded string with URL and filename safe alphabet.
|
||||
|
||||
> #### When to use this {: .tip}
|
||||
>
|
||||
> Use this function when you just need to *validate* that a string is
|
||||
> valid (URL-safe) base 64 data, without actually producing a decoded
|
||||
> output string. This function is both more performant and memory efficient
|
||||
> than using `url_decode64/2`, checking that the result is `{:ok, ...}`,
|
||||
> and then discarding the decoded binary.
|
||||
|
||||
## Options
|
||||
|
||||
Accepts the same options as `url_decode64/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.url_valid64?("_3_-_A==")
|
||||
true
|
||||
|
||||
iex> Base.url_valid64?("_3_-_A==\\n", ignore: :whitespace)
|
||||
true
|
||||
|
||||
iex> Base.url_valid64?("_3_-_A", padding: false)
|
||||
true
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec url_valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean
|
||||
def url_valid64?(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
string |> remove_ignored(opts[:ignore]) |> validate64url?(pad?)
|
||||
end
|
||||
|
||||
for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do
|
||||
decode_name = :"decode64#{base}!"
|
||||
|
||||
validate_name = :"validate64#{base}?"
|
||||
validate_main_name = :"validate_main64#{validate_name}?"
|
||||
valid_char_name = :"valid_char64#{base}?"
|
||||
name = :"decode64#{base}!"
|
||||
{min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.()
|
||||
|
||||
defp unquote(validate_main_name)(<<>>), do: true
|
||||
|
||||
defp unquote(validate_main_name)(
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, rest::binary>>
|
||||
) do
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7) and
|
||||
unquote(valid_char_name)(c8) and
|
||||
unquote(validate_main_name)(rest)
|
||||
end
|
||||
|
||||
defp unquote(validate_name)(<<>>, _pad?), do: true
|
||||
|
||||
defp unquote(validate_name)(string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
|
||||
main_valid? = unquote(validate_main_name)(main)
|
||||
|
||||
case rest do
|
||||
_ when not main_valid? ->
|
||||
false
|
||||
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2)
|
||||
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7) and
|
||||
unquote(valid_char_name)(c8)
|
||||
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2)
|
||||
|
||||
<<c1::8, c2::8, c3::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7)
|
||||
|
||||
_ ->
|
||||
false
|
||||
defp unquote(name)(char) do
|
||||
try do
|
||||
elem({unquote_splicing(decoded)}, char - unquote(min))
|
||||
rescue
|
||||
_ -> bad_character!(char)
|
||||
else
|
||||
nil -> bad_character!(char)
|
||||
char -> char
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, [{valid_char_name, 1}]}
|
||||
defp unquote(valid_char_name)(char)
|
||||
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
|
||||
do: true
|
||||
defp unquote(name)(<<>>, _pad?), do: <<>>
|
||||
|
||||
defp unquote(valid_char_name)(_char), do: false
|
||||
|
||||
defp unquote(decode_name)(char) do
|
||||
index = char - unquote(min)
|
||||
|
||||
cond do
|
||||
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
|
||||
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
|
||||
true -> bad_character!(char)
|
||||
end
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<>>, _pad?), do: <<>>
|
||||
|
||||
defp unquote(decode_name)(string, pad?) do
|
||||
defp unquote(name)(string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
unquote(decode_name)(c6)::6,
|
||||
unquote(decode_name)(c7)::6,
|
||||
unquote(decode_name)(c8)::6
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
unquote(name)(c6)::6,
|
||||
unquote(name)(c7)::6,
|
||||
unquote(name)(c8)::6
|
||||
>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=>> ->
|
||||
<<main::bits, unquote(decode_name)(c1)::6, bsr(unquote(decode_name)(c2), 4)::2>>
|
||||
<<main::bits, unquote(name)(c1)::6, bsr(unquote(name)(c2), 4)::2>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, ?=>> ->
|
||||
<<main::bits, unquote(decode_name)(c1)::6, unquote(decode_name)(c2)::6,
|
||||
bsr(unquote(decode_name)(c3), 2)::4>>
|
||||
<<main::bits, unquote(name)(c1)::6, unquote(name)(c2)::6, bsr(unquote(name)(c3), 2)::4>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
bsr(unquote(decode_name)(c6), 4)::2
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
bsr(unquote(name)(c6), 4)::2
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
unquote(decode_name)(c6)::6,
|
||||
bsr(unquote(decode_name)(c7), 2)::4
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
unquote(name)(c6)::6,
|
||||
bsr(unquote(name)(c7), 2)::4
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
unquote(decode_name)(c6)::6,
|
||||
unquote(decode_name)(c7)::6,
|
||||
unquote(decode_name)(c8)::6
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
unquote(name)(c6)::6,
|
||||
unquote(name)(c7)::6,
|
||||
unquote(name)(c8)::6
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
<<main::bits, unquote(decode_name)(c1)::6, bsr(unquote(decode_name)(c2), 4)::2>>
|
||||
<<main::bits, unquote(name)(c1)::6, bsr(unquote(name)(c2), 4)::2>>
|
||||
|
||||
<<c1::8, c2::8, c3::8>> when not pad? ->
|
||||
<<main::bits, unquote(decode_name)(c1)::6, unquote(decode_name)(c2)::6,
|
||||
bsr(unquote(decode_name)(c3), 2)::4>>
|
||||
<<main::bits, unquote(name)(c1)::6, unquote(name)(c2)::6, bsr(unquote(name)(c3), 2)::4>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
bsr(unquote(decode_name)(c6), 4)::2
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
bsr(unquote(name)(c6), 4)::2
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::6,
|
||||
unquote(decode_name)(c2)::6,
|
||||
unquote(decode_name)(c3)::6,
|
||||
unquote(decode_name)(c4)::6,
|
||||
unquote(decode_name)(c5)::6,
|
||||
unquote(decode_name)(c6)::6,
|
||||
bsr(unquote(decode_name)(c7), 2)::4
|
||||
unquote(name)(c1)::6,
|
||||
unquote(name)(c2)::6,
|
||||
unquote(name)(c3)::6,
|
||||
unquote(name)(c4)::6,
|
||||
unquote(name)(c5)::6,
|
||||
unquote(name)(c6)::6,
|
||||
bsr(unquote(name)(c7), 2)::4
|
||||
>>
|
||||
|
||||
_ ->
|
||||
@@ -1235,45 +983,6 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a base 32 encoded string is valid.
|
||||
|
||||
> #### When to use this {: .tip}
|
||||
>
|
||||
> Use this function when you just need to *validate* that a string is
|
||||
> valid base 32 data, without actually producing a decoded output string.
|
||||
> This function is both more performant and memory efficient than using
|
||||
> `decode32/2`, checking that the result is `{:ok, ...}`, and then
|
||||
> discarding the decoded binary.
|
||||
|
||||
## Options
|
||||
|
||||
Accepts the same options as `decode32/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.valid32?("MZXW6YTBOI======")
|
||||
true
|
||||
|
||||
iex> Base.valid32?("mzxw6ytboi======", case: :lower)
|
||||
true
|
||||
|
||||
iex> Base.valid32?("zzz")
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec valid32?(binary, case: decode_case, padding: boolean) :: boolean()
|
||||
def valid32?(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
|
||||
case Keyword.get(opts, :case, :upper) do
|
||||
:upper -> validate32upper?(string, pad?)
|
||||
:lower -> validate32lower?(string, pad?)
|
||||
:mixed -> validate32mixed?(string, pad?)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Decodes a base 32 encoded string with extended hexadecimal alphabet
|
||||
into a binary string.
|
||||
@@ -1369,45 +1078,6 @@ defmodule Base do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a base 32 encoded string with extended hexadecimal alphabet is valid.
|
||||
|
||||
> #### When to use this {: .tip}
|
||||
>
|
||||
> Use this function when you just need to *validate* that a string is
|
||||
> valid (extended hexadecimal) base 32 data, without actually producing
|
||||
> a decoded output string. This function is both more performant and
|
||||
> memory efficient than using `hex_decode32/2`, checking that the result
|
||||
> is `{:ok, ...}`, and then discarding the decoded binary.
|
||||
|
||||
## Options
|
||||
|
||||
Accepts the same options as `hex_decode32/2`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Base.hex_valid32?("CPNMUOJ1E8======")
|
||||
true
|
||||
|
||||
iex> Base.hex_valid32?("cpnmuoj1e8======", case: :lower)
|
||||
true
|
||||
|
||||
iex> Base.hex_valid32?("zzz", padding: false)
|
||||
false
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
@spec hex_valid32?(binary, case: decode_case, padding: boolean) :: boolean
|
||||
def hex_valid32?(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
|
||||
case Keyword.get(opts, :case, :upper) do
|
||||
:upper -> validate32hexupper?(string, pad?)
|
||||
:lower -> validate32hexlower?(string, pad?)
|
||||
:mixed -> validate32hexmixed?(string, pad?)
|
||||
end
|
||||
end
|
||||
|
||||
upper = Enum.with_index(b32_alphabet)
|
||||
hexupper = Enum.with_index(b32hex_alphabet)
|
||||
|
||||
@@ -1419,223 +1089,120 @@ defmodule Base do
|
||||
hexlower: to_lower_dec.(hexupper),
|
||||
hexmixed: to_mixed_dec.(hexupper)
|
||||
] do
|
||||
decode_name = :"decode32#{base}!"
|
||||
validate_name = :"validate32#{base}?"
|
||||
validate_main_name = :"validate_main32#{validate_name}?"
|
||||
valid_char_name = :"valid_char32#{base}?"
|
||||
name = :"decode32#{base}!"
|
||||
{min, decoded} = to_decode_list.(alphabet)
|
||||
|
||||
defp unquote(validate_main_name)(<<>>), do: true
|
||||
|
||||
defp unquote(validate_main_name)(
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, rest::binary>>
|
||||
) do
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7) and
|
||||
unquote(valid_char_name)(c8) and
|
||||
unquote(validate_main_name)(rest)
|
||||
end
|
||||
|
||||
defp unquote(validate_name)(<<>>, _pad?), do: true
|
||||
|
||||
defp unquote(validate_name)(string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
|
||||
main_valid? = unquote(validate_main_name)(main)
|
||||
|
||||
case rest do
|
||||
_ when not main_valid? ->
|
||||
false
|
||||
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7) and
|
||||
unquote(valid_char_name)(c8)
|
||||
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5)
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
unquote(valid_char_name)(c1) and
|
||||
unquote(valid_char_name)(c2) and
|
||||
unquote(valid_char_name)(c3) and
|
||||
unquote(valid_char_name)(c4) and
|
||||
unquote(valid_char_name)(c5) and
|
||||
unquote(valid_char_name)(c6) and
|
||||
unquote(valid_char_name)(c7)
|
||||
|
||||
_ ->
|
||||
false
|
||||
defp unquote(name)(char) do
|
||||
try do
|
||||
elem({unquote_splicing(decoded)}, char - unquote(min))
|
||||
rescue
|
||||
_ -> bad_character!(char)
|
||||
else
|
||||
nil -> bad_character!(char)
|
||||
char -> char
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:inline, [{valid_char_name, 1}]}
|
||||
defp unquote(valid_char_name)(char)
|
||||
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
|
||||
do: true
|
||||
defp unquote(name)(<<>>, _), do: <<>>
|
||||
|
||||
defp unquote(valid_char_name)(_char), do: false
|
||||
|
||||
defp unquote(decode_name)(char) do
|
||||
index = char - unquote(min)
|
||||
|
||||
cond do
|
||||
index not in 0..unquote(length(decoded) - 1) -> bad_character!(char)
|
||||
new_char = elem({unquote_splicing(decoded)}, index) -> new_char
|
||||
true -> bad_character!(char)
|
||||
end
|
||||
end
|
||||
|
||||
defp unquote(decode_name)(<<>>, _), do: <<>>
|
||||
|
||||
defp unquote(decode_name)(string, pad?) do
|
||||
defp unquote(name)(string, pad?) do
|
||||
segs = div(byte_size(string) + 7, 8) - 1
|
||||
<<main::size(^segs)-binary-unit(64), rest::binary>> = string
|
||||
|
||||
main =
|
||||
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
|
||||
<<
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
unquote(decode_name)(c5)::5,
|
||||
unquote(decode_name)(c6)::5,
|
||||
unquote(decode_name)(c7)::5,
|
||||
unquote(decode_name)(c8)::5
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
unquote(name)(c5)::5,
|
||||
unquote(name)(c6)::5,
|
||||
unquote(name)(c7)::5,
|
||||
unquote(name)(c8)::5
|
||||
>>
|
||||
end
|
||||
|
||||
case rest do
|
||||
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
|
||||
<<main::bits, unquote(decode_name)(c1)::5, bsr(unquote(decode_name)(c2), 2)::3>>
|
||||
<<main::bits, unquote(name)(c1)::5, bsr(unquote(name)(c2), 2)::3>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
bsr(unquote(decode_name)(c4), 4)::1
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
bsr(unquote(name)(c4), 4)::1
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
bsr(unquote(decode_name)(c5), 1)::4
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
bsr(unquote(name)(c5), 1)::4
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
unquote(decode_name)(c5)::5,
|
||||
unquote(decode_name)(c6)::5,
|
||||
bsr(unquote(decode_name)(c7), 3)::2
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
unquote(name)(c5)::5,
|
||||
unquote(name)(c6)::5,
|
||||
bsr(unquote(name)(c7), 3)::2
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
unquote(decode_name)(c5)::5,
|
||||
unquote(decode_name)(c6)::5,
|
||||
unquote(decode_name)(c7)::5,
|
||||
unquote(decode_name)(c8)::5
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
unquote(name)(c5)::5,
|
||||
unquote(name)(c6)::5,
|
||||
unquote(name)(c7)::5,
|
||||
unquote(name)(c8)::5
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8>> when not pad? ->
|
||||
<<main::bits, unquote(decode_name)(c1)::5, bsr(unquote(decode_name)(c2), 2)::3>>
|
||||
<<main::bits, unquote(name)(c1)::5, bsr(unquote(name)(c2), 2)::3>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
bsr(unquote(decode_name)(c4), 4)::1
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
bsr(unquote(name)(c4), 4)::1
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
bsr(unquote(decode_name)(c5), 1)::4
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
bsr(unquote(name)(c5), 1)::4
|
||||
>>
|
||||
|
||||
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
|
||||
<<
|
||||
main::bits,
|
||||
unquote(decode_name)(c1)::5,
|
||||
unquote(decode_name)(c2)::5,
|
||||
unquote(decode_name)(c3)::5,
|
||||
unquote(decode_name)(c4)::5,
|
||||
unquote(decode_name)(c5)::5,
|
||||
unquote(decode_name)(c6)::5,
|
||||
bsr(unquote(decode_name)(c7), 3)::2
|
||||
unquote(name)(c1)::5,
|
||||
unquote(name)(c2)::5,
|
||||
unquote(name)(c3)::5,
|
||||
unquote(name)(c4)::5,
|
||||
unquote(name)(c5)::5,
|
||||
unquote(name)(c6)::5,
|
||||
bsr(unquote(name)(c7), 3)::2
|
||||
>>
|
||||
|
||||
_ ->
|
||||
|
||||
@@ -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.
|
||||
@@ -13,8 +9,7 @@ defmodule Behaviour do
|
||||
attributes.
|
||||
|
||||
Instead of `MyModule.__behaviour__(:callbacks)`,
|
||||
`MyModule.behaviour_info(:callbacks)` can be used. `behaviour_info/1`
|
||||
is documented in `Module`.
|
||||
`MyModule.behaviour_info(:callbacks)` can be used.
|
||||
"""
|
||||
|
||||
@moduledoc deprecated: "Use @callback and @macrocallback attributes instead"
|
||||
|
||||
@@ -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.
|
||||
|
||||
+21
-102
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Calendar do
|
||||
@moduledoc """
|
||||
This module defines the responsibilities for working with
|
||||
@@ -58,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}
|
||||
|
||||
@@ -162,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.
|
||||
"""
|
||||
@@ -203,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(),
|
||||
@@ -492,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
|
||||
|
||||
@@ -549,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
|
||||
@@ -571,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:
|
||||
@@ -620,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(
|
||||
@@ -686,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
|
||||
@@ -712,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])
|
||||
@@ -723,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])
|
||||
@@ -733,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])
|
||||
@@ -741,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
|
||||
@@ -822,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
|
||||
@@ -835,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)
|
||||
|
||||
@@ -1001,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
|
||||
@@ -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)
|
||||
@@ -1092,7 +1073,13 @@ defmodule Date do
|
||||
def year_of_era(date)
|
||||
|
||||
def year_of_era(%{calendar: calendar, year: year, month: month, day: day}) do
|
||||
calendar.year_of_era(year, month, day)
|
||||
# TODO: Remove me on 1.17
|
||||
# The behaviour implementation already warns on missing callback.
|
||||
if function_exported?(calendar, :year_of_era, 3) do
|
||||
calendar.year_of_era(year, month, day)
|
||||
else
|
||||
calendar.year_of_era(year)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -1172,11 +1159,12 @@ defmodule Date do
|
||||
|
||||
defimpl Inspect do
|
||||
def inspect(%{calendar: calendar, year: year, month: month, day: day}, _)
|
||||
when calendar != Calendar.ISO or year in -9999..9999 do
|
||||
when year in -9999..9999 do
|
||||
"~D[" <> calendar.date_to_string(year, month, day) <> suffix(calendar) <> "]"
|
||||
end
|
||||
|
||||
def inspect(%{calendar: Calendar.ISO, year: year, month: month, day: day}, _) do
|
||||
def inspect(%{calendar: calendar, year: year, month: month, day: day}, _)
|
||||
when calendar == Calendar.ISO do
|
||||
"Date.new!(#{Integer.to_string(year)}, #{Integer.to_string(month)}, #{Integer.to_string(day)})"
|
||||
end
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -95,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)
|
||||
@@ -171,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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -32,12 +28,7 @@ defmodule DateTime do
|
||||
## Time zone database
|
||||
|
||||
Many functions in this module require a time zone database.
|
||||
A time zone database is a record of the UTC offsets that its locales have
|
||||
used at various times in the past, are using, and are expected to use in the
|
||||
future.
|
||||
Because those plans can change, it needs to be periodically updated.
|
||||
|
||||
By default, `DateTime` uses the default time zone database returned by
|
||||
By default, it uses the default time zone database returned by
|
||||
`Calendar.get_time_zone_database/0`, which defaults to
|
||||
`Calendar.UTCOnlyTimeZoneDatabase` which only handles "Etc/UTC"
|
||||
datetimes and returns `{:error, :utc_only_time_zone_database}`
|
||||
@@ -52,7 +43,7 @@ defmodule DateTime do
|
||||
* [`zoneinfo`](https://github.com/smartrent/zoneinfo) -
|
||||
recommended for embedded devices
|
||||
|
||||
To use one of them, first make sure it is added as a dependency in `mix.exs`.
|
||||
To use them, first make sure it is added as a dependency in `mix.exs`.
|
||||
It can then be configured either via configuration:
|
||||
|
||||
config :elixir, :time_zone_database, Tz.TimeZoneDatabase
|
||||
@@ -383,12 +374,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 +876,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
|
||||
|
||||
@@ -1035,18 +1025,15 @@ defmodule DateTime do
|
||||
By default, `DateTime.to_iso8601/2` returns datetimes formatted in the "extended"
|
||||
format, for human readability. It also supports the "basic" format through passing the `:basic` option.
|
||||
|
||||
Only supports converting datetimes which are in the ISO calendar,
|
||||
attempting to convert datetimes from other calendars will raise.
|
||||
You can also optionally specify an offset for the formatted string.
|
||||
If none is given, the one in the given `datetime` is used.
|
||||
|
||||
Only supports converting datetimes which are in the ISO calendar.
|
||||
If another calendar is given, it is automatically converted to ISO.
|
||||
It raises if not possible.
|
||||
|
||||
WARNING: the ISO 8601 datetime format does not contain the time zone nor
|
||||
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 +1083,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 +1098,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 +1139,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 """
|
||||
@@ -1385,12 +1341,7 @@ defmodule DateTime do
|
||||
@doc """
|
||||
Converts the given `datetime` to a string according to its calendar.
|
||||
|
||||
Unfortunately, there is no standard that specifies rendering of a
|
||||
datetime with its complete time zone information, so Elixir uses a
|
||||
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,56 +1550,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`. 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)
|
||||
@@ -1676,6 +1613,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(
|
||||
@@ -1749,7 +1688,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:
|
||||
|
||||
@@ -1761,22 +1700,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:
|
||||
|
||||
@@ -1942,7 +1871,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",
|
||||
@@ -1971,7 +1900,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}
|
||||
@@ -1989,7 +1918,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",
|
||||
@@ -2150,8 +2079,7 @@ defmodule DateTime do
|
||||
)
|
||||
|
||||
case datetime do
|
||||
%{utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC", year: year}
|
||||
when calendar != Calendar.ISO or year in -9999..9999 ->
|
||||
%{utc_offset: 0, std_offset: 0, time_zone: "Etc/UTC"} ->
|
||||
"~U[" <> formatted <> suffix(calendar) <> "]"
|
||||
|
||||
_ ->
|
||||
|
||||
@@ -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 """
|
||||
@@ -398,119 +355,11 @@ defmodule Duration do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given `duration` to a human readable representation.
|
||||
|
||||
## Options
|
||||
|
||||
* `:units` - the units to be used alongside each duration component.
|
||||
The default units follow the ISO 80000-3 standard:
|
||||
|
||||
[
|
||||
year: "a",
|
||||
month: "mo",
|
||||
week: "wk",
|
||||
day: "d",
|
||||
hour: "h",
|
||||
minute: "min",
|
||||
second: "s"
|
||||
]
|
||||
|
||||
* `:separator` - a string used to separate the distinct components. Defaults to `" "`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Duration.to_string(Duration.new!(second: 30))
|
||||
"30s"
|
||||
iex> Duration.to_string(Duration.new!(day: 40, hour: 12, minute: 42, second: 12))
|
||||
"40d 12h 42min 12s"
|
||||
|
||||
By default, this function uses ISO 80000-3 units, which uses "a" for years.
|
||||
But you can customize all units via the units option:
|
||||
|
||||
iex> Duration.to_string(Duration.new!(year: 3))
|
||||
"3a"
|
||||
iex> Duration.to_string(Duration.new!(year: 3), units: [year: "y"])
|
||||
"3y"
|
||||
|
||||
You may also choose the separator:
|
||||
|
||||
iex> Duration.to_string(Duration.new!(day: 40, hour: 12, minute: 42, second: 12), separator: ", ")
|
||||
"40d, 12h, 42min, 12s"
|
||||
|
||||
A duration without components is rendered as "0s":
|
||||
|
||||
iex> Duration.to_string(Duration.new!([]))
|
||||
"0s"
|
||||
|
||||
Microseconds are rendered as part of seconds with the appropriate precision:
|
||||
|
||||
iex> Duration.to_string(Duration.new!(second: 1, microsecond: {2_200, 3}))
|
||||
"1.002s"
|
||||
iex> Duration.to_string(Duration.new!(second: 1, microsecond: {-1_200_000, 4}))
|
||||
"-0.2000s"
|
||||
|
||||
"""
|
||||
@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, " ")
|
||||
|
||||
case to_string_year(duration, [], units) do
|
||||
[] ->
|
||||
"0" <> Keyword.get(units, :second, "s")
|
||||
|
||||
[part] ->
|
||||
IO.iodata_to_binary(part)
|
||||
|
||||
parts ->
|
||||
parts |> Enum.reduce(&[&1, separator | &2]) |> IO.iodata_to_binary()
|
||||
end
|
||||
end
|
||||
|
||||
defp to_string_part(0, _units, _key, _default, acc),
|
||||
do: acc
|
||||
|
||||
defp to_string_part(x, units, key, default, acc),
|
||||
do: [[Integer.to_string(x) | Keyword.get(units, key, default)] | acc]
|
||||
|
||||
defp to_string_year(%{year: year} = duration, acc, units) do
|
||||
to_string_month(duration, to_string_part(year, units, :year, "a", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_month(%{month: month} = duration, acc, units) do
|
||||
to_string_week(duration, to_string_part(month, units, :month, "mo", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_week(%{week: week} = duration, acc, units) do
|
||||
to_string_day(duration, to_string_part(week, units, :week, "wk", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_day(%{day: day} = duration, acc, units) do
|
||||
to_string_hour(duration, to_string_part(day, units, :day, "d", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_hour(%{hour: hour} = duration, acc, units) do
|
||||
to_string_minute(duration, to_string_part(hour, units, :hour, "h", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_minute(%{minute: minute} = duration, acc, units) do
|
||||
to_string_second(duration, to_string_part(minute, units, :minute, "min", acc), units)
|
||||
end
|
||||
|
||||
defp to_string_second(%{second: 0, microsecond: {0, _}}, acc, _units) do
|
||||
acc
|
||||
end
|
||||
|
||||
defp to_string_second(%{second: s, microsecond: {ms, p}}, acc, units) do
|
||||
[[second_component(s, ms, p) | Keyword.get(units, :second, "s")] | acc]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts the given `duration` to an [ISO 8601-2:2019](https://en.wikipedia.org/wiki/ISO_8601) formatted string.
|
||||
|
||||
This function implements the extension of ISO 8601:2019, allowing weeks to appear between months and days: `P3M3W3D`.
|
||||
Note this function implements the *extension* of ISO 8601:2019. This extensions allows weeks to
|
||||
appear between months and days: `P3M3W3D`, making it fully compatible with any `Duration` struct.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -558,15 +407,15 @@ defmodule Duration do
|
||||
[]
|
||||
end
|
||||
|
||||
defp second_component(%{second: 0, microsecond: {_, 0}}) do
|
||||
~c"0S"
|
||||
end
|
||||
|
||||
defp second_component(%{second: second, microsecond: {_, 0}}) do
|
||||
[Integer.to_string(second), ?S]
|
||||
end
|
||||
|
||||
defp second_component(%{second: second, microsecond: {ms, p}}) do
|
||||
[second_component(second, ms, p), ?S]
|
||||
end
|
||||
|
||||
defp second_component(second, _ms, 0) do
|
||||
Integer.to_string(second)
|
||||
end
|
||||
|
||||
defp second_component(second, ms, p) do
|
||||
total_ms = second * @microseconds_per_second + ms
|
||||
second = total_ms |> div(@microseconds_per_second) |> abs()
|
||||
ms = total_ms |> rem(@microseconds_per_second) |> abs()
|
||||
@@ -576,7 +425,8 @@ 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),
|
||||
?S
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
+57
-264
@@ -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.
|
||||
@@ -1224,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,
|
||||
@@ -1263,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 """
|
||||
@@ -1327,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 """
|
||||
@@ -1399,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 """
|
||||
@@ -1519,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)
|
||||
@@ -1610,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.
|
||||
@@ -1679,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 """
|
||||
@@ -1912,7 +1710,7 @@ defmodule Calendar.ISO do
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
microsecond: {0, _precision}
|
||||
microsecond: {0, 0}
|
||||
}) do
|
||||
[
|
||||
month: year * 12 + month,
|
||||
@@ -1993,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
|
||||
|
||||
@@ -2011,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
|
||||
|
||||
@@ -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]
|
||||
@@ -114,12 +110,30 @@ defmodule NaiveDateTime do
|
||||
@spec utc_now(Calendar.calendar() | :native | :microsecond | :millisecond | :second) :: t
|
||||
def utc_now(calendar_or_time_unit \\ Calendar.ISO)
|
||||
|
||||
def utc_now(Calendar.ISO) do
|
||||
{:ok, {year, month, day}, {hour, minute, second}, microsecond} =
|
||||
Calendar.ISO.from_unix(:os.system_time(), :native)
|
||||
|
||||
%NaiveDateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: Calendar.ISO
|
||||
}
|
||||
end
|
||||
|
||||
def utc_now(time_unit) when time_unit in [:microsecond, :millisecond, :second, :native] do
|
||||
utc_now(time_unit, Calendar.ISO)
|
||||
end
|
||||
|
||||
def utc_now(calendar) do
|
||||
utc_now(:native, calendar)
|
||||
calendar
|
||||
|> DateTime.utc_now()
|
||||
|> DateTime.to_naive()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -144,20 +158,7 @@ defmodule NaiveDateTime do
|
||||
@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, {year, month, day}, {hour, minute, second}, microsecond} =
|
||||
Calendar.ISO.from_unix(System.os_time(time_unit), time_unit)
|
||||
|
||||
%NaiveDateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
microsecond: microsecond,
|
||||
calendar: Calendar.ISO
|
||||
}
|
||||
|> convert!(calendar)
|
||||
DateTime.utc_now(time_unit, calendar) |> DateTime.to_naive()
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -251,7 +252,7 @@ defmodule NaiveDateTime do
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.microsecond() | non_neg_integer,
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
def new(year, month, day, hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
@@ -316,7 +317,7 @@ defmodule NaiveDateTime do
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.microsecond() | non_neg_integer,
|
||||
Calendar.calendar()
|
||||
) :: t
|
||||
def new!(
|
||||
@@ -391,20 +392,14 @@ 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`. 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
|
||||
|
||||
It uses seconds by default:
|
||||
@@ -453,6 +448,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
|
||||
@@ -762,10 +759,7 @@ 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
|
||||
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"
|
||||
@@ -912,7 +906,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"
|
||||
@@ -938,19 +932,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,
|
||||
@@ -961,11 +942,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 """
|
||||
@@ -1009,8 +993,6 @@ defmodule NaiveDateTime do
|
||||
|
||||
iex> NaiveDateTime.from_erl({{2000, 1, 1}, {13, 30, 15}})
|
||||
{:ok, ~N[2000-01-01 13:30:15]}
|
||||
iex> NaiveDateTime.from_erl({{2000, 1, 1}, {13, 30, 15}}, 5000)
|
||||
{:ok, ~N[2000-01-01 13:30:15.005000]}
|
||||
iex> NaiveDateTime.from_erl({{2000, 1, 1}, {13, 30, 15}}, {5000, 3})
|
||||
{:ok, ~N[2000-01-01 13:30:15.005]}
|
||||
iex> NaiveDateTime.from_erl({{2000, 13, 1}, {13, 30, 15}})
|
||||
@@ -1019,11 +1001,7 @@ defmodule NaiveDateTime do
|
||||
{:error, :invalid_date}
|
||||
|
||||
"""
|
||||
@spec from_erl(
|
||||
:calendar.datetime(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.calendar()
|
||||
) ::
|
||||
@spec from_erl(:calendar.datetime(), Calendar.microsecond(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
def from_erl(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
@@ -1042,19 +1020,13 @@ defmodule NaiveDateTime do
|
||||
|
||||
iex> NaiveDateTime.from_erl!({{2000, 1, 1}, {13, 30, 15}})
|
||||
~N[2000-01-01 13:30:15]
|
||||
iex> NaiveDateTime.from_erl!({{2000, 1, 1}, {13, 30, 15}}, 5000)
|
||||
~N[2000-01-01 13:30:15.005000]
|
||||
iex> NaiveDateTime.from_erl!({{2000, 1, 1}, {13, 30, 15}}, {5000, 3})
|
||||
~N[2000-01-01 13:30:15.005]
|
||||
iex> NaiveDateTime.from_erl!({{2000, 13, 1}, {13, 30, 15}})
|
||||
** (ArgumentError) cannot convert {{2000, 13, 1}, {13, 30, 15}} to naive datetime, reason: :invalid_date
|
||||
|
||||
"""
|
||||
@spec from_erl!(
|
||||
:calendar.datetime(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.calendar()
|
||||
) :: t
|
||||
@spec from_erl!(:calendar.datetime(), Calendar.microsecond(), Calendar.calendar()) :: t
|
||||
def from_erl!(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO) do
|
||||
case from_erl(tuple, microsecond, calendar) do
|
||||
{:ok, value} ->
|
||||
@@ -1265,7 +1237,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)
|
||||
@@ -1278,10 +1250,6 @@ defmodule NaiveDateTime do
|
||||
{:ok, t} | {:error, :incompatible_calendars}
|
||||
|
||||
# Keep it multiline for proper function clause errors.
|
||||
def convert(%NaiveDateTime{calendar: calendar} = ndt, calendar) do
|
||||
{:ok, ndt}
|
||||
end
|
||||
|
||||
def convert(
|
||||
%{
|
||||
calendar: calendar,
|
||||
@@ -1313,7 +1281,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}
|
||||
@@ -1331,7 +1299,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)
|
||||
@@ -1483,15 +1451,10 @@ defmodule NaiveDateTime do
|
||||
calendar: calendar
|
||||
} = naive_datetime
|
||||
|
||||
if calendar != Calendar.ISO or year in -9999..9999 do
|
||||
formatted =
|
||||
calendar.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
formatted =
|
||||
calendar.naive_datetime_to_string(year, month, day, hour, minute, second, microsecond)
|
||||
|
||||
"~N[" <> formatted <> suffix(calendar) <> "]"
|
||||
else
|
||||
"NaiveDateTime.new!(#{Integer.to_string(year)}, #{Integer.to_string(month)}, #{Integer.to_string(day)}, " <>
|
||||
"#{Integer.to_string(hour)}, #{Integer.to_string(minute)}, #{Integer.to_string(second)}, #{inspect(microsecond)})"
|
||||
end
|
||||
"~N[" <> formatted <> suffix(calendar) <> "]"
|
||||
end
|
||||
|
||||
defp suffix(Calendar.ISO), do: ""
|
||||
|
||||
@@ -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{
|
||||
@@ -155,7 +115,7 @@ defmodule Time do
|
||||
Calendar.hour(),
|
||||
Calendar.minute(),
|
||||
Calendar.second(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.microsecond() | non_neg_integer,
|
||||
Calendar.calendar()
|
||||
) :: {:ok, t} | {:error, atom}
|
||||
def new(hour, minute, second, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
@@ -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"
|
||||
@@ -395,21 +355,13 @@ defmodule Time do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Time.from_erl({23, 30, 15})
|
||||
{:ok, ~T[23:30:15]}
|
||||
iex> Time.from_erl({23, 30, 15}, 5000)
|
||||
{:ok, ~T[23:30:15.005000]}
|
||||
iex> Time.from_erl({23, 30, 15}, {5000, 3})
|
||||
{:ok, ~T[23:30:15.005]}
|
||||
iex> Time.from_erl({24, 30, 15})
|
||||
{:error, :invalid_time}
|
||||
|
||||
"""
|
||||
@spec from_erl(
|
||||
:calendar.time(),
|
||||
Calendar.microsecond() | non_neg_integer(),
|
||||
Calendar.calendar()
|
||||
) ::
|
||||
@spec from_erl(:calendar.time(), Calendar.microsecond(), Calendar.calendar()) ::
|
||||
{:ok, t} | {:error, atom}
|
||||
def from_erl(tuple, microsecond \\ {0, 0}, calendar \\ Calendar.ISO)
|
||||
|
||||
@@ -425,8 +377,6 @@ defmodule Time do
|
||||
|
||||
iex> Time.from_erl!({23, 30, 15})
|
||||
~T[23:30:15]
|
||||
iex> Time.from_erl!({23, 30, 15}, 5000)
|
||||
~T[23:30:15.005000]
|
||||
iex> Time.from_erl!({23, 30, 15}, {5000, 3})
|
||||
~T[23:30:15.005]
|
||||
iex> Time.from_erl!({24, 30, 15})
|
||||
@@ -505,18 +455,14 @@ 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`. 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.
|
||||
|
||||
@@ -553,6 +499,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
|
||||
@@ -573,7 +521,7 @@ defmodule Time do
|
||||
do: unit > 0,
|
||||
else: unit in ~w(second millisecond microsecond nanosecond)a
|
||||
|
||||
if not valid? do
|
||||
unless valid? do
|
||||
raise ArgumentError,
|
||||
"unsupported time unit. Expected :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
|
||||
end
|
||||
@@ -783,7 +731,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)
|
||||
@@ -839,7 +787,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)
|
||||
@@ -849,8 +797,15 @@ defmodule Time do
|
||||
@doc since: "1.5.0"
|
||||
@spec convert!(Calendar.time(), Calendar.calendar()) :: t
|
||||
def convert!(time, calendar) do
|
||||
{:ok, value} = convert(time, calendar)
|
||||
value
|
||||
case convert(time, calendar) do
|
||||
{:ok, value} ->
|
||||
value
|
||||
|
||||
{:error, reason} ->
|
||||
raise ArgumentError,
|
||||
"cannot convert #{inspect(time)} to target calendar #{inspect(calendar)}, " <>
|
||||
"reason: #{inspect(reason)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -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.
|
||||
|
||||
+96
-277
@@ -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.
|
||||
@@ -127,11 +123,6 @@ defmodule Code do
|
||||
of the imported function/macro. A :remote_function/:remote_macro event
|
||||
may still be emitted for the imported module/name/arity.
|
||||
|
||||
* `{:imported_quoted, meta, module, name, [arity]}` - traced whenever an
|
||||
imported function or macro is processed inside a `quote/2`. `meta` is the
|
||||
call AST metadata, `module` is the module the import is from, followed by
|
||||
the `name` and a list of `arities` of the imported function/macro.
|
||||
|
||||
* `{:alias, meta, alias, as, opts}` - traced whenever `alias` is aliased
|
||||
to `as`. `meta` is the alias AST metadata and `opts` are the alias options.
|
||||
|
||||
@@ -189,7 +180,7 @@ defmodule Code do
|
||||
|
||||
defmodule MyTracer do
|
||||
def trace({:remote_function, _meta, module, name, arity}, env) do
|
||||
IO.puts("#{env.file}:#{env.line} #{inspect(module)}.#{name}/#{arity}")
|
||||
IO.puts "#{env.file}:#{env.line} #{inspect(module)}.#{name}/#{arity}"
|
||||
:ok
|
||||
end
|
||||
|
||||
@@ -248,71 +239,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: (atom() -> term()),
|
||||
emit_warnings: boolean()
|
||||
]
|
||||
|
||||
@typedoc """
|
||||
Options for environment evaluation functions like eval_string/3 and eval_quoted/3.
|
||||
"""
|
||||
@type env_eval_opts :: [
|
||||
file: binary(),
|
||||
line: pos_integer(),
|
||||
module: module(),
|
||||
prune_binding: boolean()
|
||||
]
|
||||
|
||||
@boolean_compiler_options [
|
||||
:docs,
|
||||
:debug_info,
|
||||
:ignore_already_consolidated,
|
||||
:ignore_module_conflict,
|
||||
:relative_paths
|
||||
:relative_paths,
|
||||
:warnings_as_errors
|
||||
]
|
||||
|
||||
@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]
|
||||
@list_compiler_options ++ [:on_undefined_variable]
|
||||
|
||||
@doc """
|
||||
Lists all required files.
|
||||
@@ -399,7 +338,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
|
||||
@@ -430,7 +369,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()
|
||||
@@ -459,7 +398,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
|
||||
@@ -488,7 +427,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
|
||||
@@ -568,9 +507,14 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
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.
|
||||
Options can be:
|
||||
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
|
||||
* `:line` - the line on which the script starts
|
||||
|
||||
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
|
||||
@@ -604,15 +548,14 @@ defmodule Code do
|
||||
all imports, requires and aliases defined in the current environment
|
||||
will be automatically carried over:
|
||||
|
||||
iex> require Integer
|
||||
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() | env_eval_opts) :: {term, binding}
|
||||
@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
|
||||
@@ -667,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)
|
||||
@@ -701,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?)
|
||||
@@ -717,15 +659,13 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
Regular options (do not change the AST):
|
||||
|
||||
* `:file` - the file which contains the string, used for error
|
||||
reporting
|
||||
|
||||
* `: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
|
||||
|
||||
@@ -742,42 +682,28 @@ defmodule Code do
|
||||
If you set it to `false` later on, `do`-`end` blocks won't be
|
||||
converted back to keywords.
|
||||
|
||||
Migration options (change the AST), see the "Migration formatting" section below:
|
||||
|
||||
* `:migrate` (since v1.18.0) - when `true`, sets all other migration options
|
||||
to `true` by default. Defaults to `false`.
|
||||
|
||||
* `:migrate_bitstring_modifiers` (since v1.18.0) - when `true`,
|
||||
* `:normalize_bitstring_modifiers` (since v1.14.0) - when `true`,
|
||||
removes unnecessary parentheses in known bitstring
|
||||
[modifiers](`<<>>/1`), for example `<<foo::binary()>>`
|
||||
becomes `<<foo::binary>>`, or adds parentheses for custom
|
||||
modifiers, where `<<foo::custom_type>>` becomes `<<foo::custom_type()>>`.
|
||||
Defaults to the value of the `:migrate` option. This option changes the AST.
|
||||
Defaults to `true`. 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`,
|
||||
* `:normalize_charlists_as_sigils` (since v1.15.0) - when `true`,
|
||||
formats charlists as [`~c`](`Kernel.sigil_c/2`) sigils, for example
|
||||
`'foo'` becomes `~c"foo"`.
|
||||
Defaults to the value of the `:migrate` option. This option changes the AST.
|
||||
|
||||
* `:migrate_unless` (since v1.18.0) - when `true`,
|
||||
rewrites `unless` expressions using `if` with a negated condition, for example
|
||||
`unless foo, do:` becomes `if !foo, do:`.
|
||||
Defaults to the value of the `:migrate` option. This option changes the AST.
|
||||
Defaults to `true`. This option changes the AST.
|
||||
|
||||
## Design principles
|
||||
|
||||
The formatter was designed under three principles.
|
||||
|
||||
First, the formatter never changes the semantics of the code by default.
|
||||
First, the formatter never changes the semantics of the code.
|
||||
This means the input AST and the output AST are almost always equivalent.
|
||||
The only cases where the formatter will change the AST is when the input AST
|
||||
would cause *compiler warnings* and the output AST won't. The cases where
|
||||
the formatter changes the AST can be disabled through formatting options
|
||||
if desired.
|
||||
|
||||
The second principle is to provide as little configuration as possible.
|
||||
This eases the formatter adoption by removing contention points while
|
||||
@@ -1011,8 +937,9 @@ defmodule Code do
|
||||
|
||||
## Code comments
|
||||
|
||||
The formatter handles code comments and guarantees a space is always added
|
||||
between the beginning of the comment (#) and the next character.
|
||||
The formatter also handles code comments in a way to guarantee a space
|
||||
is always added between the beginning of the comment (#) and the next
|
||||
character.
|
||||
|
||||
The formatter also extracts all trailing comments to their previous line.
|
||||
For example, the code below
|
||||
@@ -1024,25 +951,9 @@ defmodule Code do
|
||||
# world
|
||||
hello
|
||||
|
||||
While the formatter attempts to preserve comments in most situations,
|
||||
that's not always possible, because code comments are handled apart from
|
||||
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:
|
||||
|
||||
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
|
||||
Because code comments are handled apart from the code representation (AST),
|
||||
there are some situations where code comments are seen as ambiguous by the
|
||||
code formatter. For example, the comment in the anonymous function below
|
||||
|
||||
fn
|
||||
arg1 ->
|
||||
@@ -1071,26 +982,11 @@ defmodule Code do
|
||||
## Newlines
|
||||
|
||||
The formatter converts all newlines in code from `\r\n` to `\n`.
|
||||
|
||||
## Migration formatting
|
||||
|
||||
As part of the Elixir release cycle, deprecations are being introduced,
|
||||
emitting warnings which might require existing code to be changed.
|
||||
In order to reduce the burden on developers when upgrading Elixir to the
|
||||
next version, the formatter exposes some options, disabled by default,
|
||||
in order to automate this process.
|
||||
|
||||
These options should address most of the typical use cases, but given they
|
||||
introduce changes to the AST, there is a non-zero risk for meta-programming
|
||||
heavy projects that relied on a specific AST, or projects that are
|
||||
re-defining functions from the `Kernel`. In such cases, migrations cannot
|
||||
be applied blindly and some extra changes might be needed in order to
|
||||
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 =
|
||||
[
|
||||
@@ -1113,7 +1009,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)
|
||||
@@ -1128,8 +1024,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.
|
||||
The options are described under `env_for_eval/1`.
|
||||
See `eval_string/3` for a description of `binding` and `opts`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1151,7 +1046,7 @@ defmodule Code do
|
||||
[a: 1, b: 2]
|
||||
|
||||
"""
|
||||
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | env_eval_opts) :: {term, binding}
|
||||
@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)])
|
||||
@@ -1180,17 +1075,8 @@ defmodule Code do
|
||||
* `:file` - the file to be considered in the evaluation
|
||||
|
||||
* `:line` - the line on which the script starts
|
||||
|
||||
* `:module` - the module to run the environment on
|
||||
|
||||
* `:prune_binding` - (since v1.14.2) prune binding to keep only
|
||||
variables read or written by the evaluated code. Note that
|
||||
variables used by modules are always pruned, even if later used
|
||||
by the modules. You can submit to the `:on_module` tracer event
|
||||
and access the variables used by the module from its environment.
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec env_for_eval(Macro.Env.t() | env_eval_opts) :: Macro.Env.t()
|
||||
def env_for_eval(env_or_opts), do: :elixir.env_for_eval(env_or_opts)
|
||||
|
||||
@doc """
|
||||
@@ -1204,11 +1090,15 @@ defmodule Code do
|
||||
|
||||
## Options
|
||||
|
||||
It accepts the same options as `env_for_eval/1`.
|
||||
* `:prune_binding` - (since v1.14.2) prune binding to keep only
|
||||
variables read or written by the evaluated code. Note that
|
||||
variables used by modules are always pruned, even if later used
|
||||
by the modules. You can submit to the `:on_module` tracer event
|
||||
and access the variables used by the module from its environment.
|
||||
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec eval_quoted_with_env(Macro.t(), binding, Macro.Env.t(), env_eval_opts) ::
|
||||
@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
|
||||
@@ -1227,14 +1117,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`.
|
||||
@@ -1310,16 +1196,8 @@ 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")
|
||||
@@ -1346,7 +1224,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)
|
||||
@@ -1397,7 +1275,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)
|
||||
@@ -1427,7 +1305,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)
|
||||
|
||||
@@ -1436,11 +1314,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
|
||||
|
||||
@@ -1450,7 +1330,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)
|
||||
}
|
||||
@@ -1464,9 +1344,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
|
||||
@@ -1512,9 +1389,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`
|
||||
@@ -1525,13 +1399,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)
|
||||
@@ -1650,8 +1528,8 @@ defmodule Code do
|
||||
|
||||
## Examples
|
||||
|
||||
Code.compiler_options(infer_signatures: false)
|
||||
#=> %{infer_signatures: [:elixir]}
|
||||
Code.compiler_options(warnings_as_errors: true)
|
||||
#=> %{warnings_as_errors: false}
|
||||
|
||||
"""
|
||||
@spec compiler_options(Enumerable.t({atom, term})) :: %{optional(atom) => term}
|
||||
@@ -1680,12 +1558,6 @@ defmodule Code do
|
||||
:elixir_config.get(key)
|
||||
end
|
||||
|
||||
# TODO: Remove me in Elixir v2.0
|
||||
def get_compiler_option(:warnings_as_errors) do
|
||||
IO.warn(":warnings_as_errors is deprecated as part of Code.get_compiler_option/1")
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list with all available compiler options.
|
||||
|
||||
@@ -1714,19 +1586,16 @@ defmodule Code do
|
||||
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`.
|
||||
|
||||
This enables tooling to partially reconstruct the original source code,
|
||||
for instance, to perform static analysis of code. Therefore, disabling
|
||||
module. Defaults to `true`.
|
||||
This enables static analysis tools as it allows developers to
|
||||
partially reconstruct the original source code. Therefore, disabling
|
||||
`:debug_info` is not recommended as it removes the ability of the
|
||||
Elixir compiler and other tools to provide feedback. If you want to
|
||||
remove the `:debug_info` while deploying, tools like `mix release`
|
||||
already do such by default.
|
||||
|
||||
Other environments, such as `mix test`, automatically disables this
|
||||
via the `:test_elixirc_options` project configuration, as there is
|
||||
typically no need to store debug chunks for test files.
|
||||
Additionally, `mix test` disables it via the `:test_elixirc_options`
|
||||
project configuration option.
|
||||
This option can also be overridden per module using the `@compile` directive.
|
||||
|
||||
* `: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.
|
||||
@@ -1735,21 +1604,13 @@ 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
|
||||
signature inference used when type checking remote calls to the compiled
|
||||
module. Type checking will be executed regardless of the value of this option.
|
||||
Defaults to `true`, which is equivalent to setting it to `[:elixir]` only.
|
||||
|
||||
When setting this option, we recommend running `mix clean` so the current module
|
||||
may be compiled from scratch. `mix test` automatically disables this option via
|
||||
the `:test_elixirc_options` project configuration, as there is typically no need
|
||||
to infer signatures for test files.
|
||||
|
||||
* `: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`.
|
||||
|
||||
* `:warnings_as_errors` - causes compilation to fail when warnings are
|
||||
generated. Defaults to `false`.
|
||||
|
||||
* `: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
|
||||
@@ -1795,6 +1656,16 @@ defmodule Code do
|
||||
: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)}"
|
||||
@@ -1814,61 +1685,9 @@ defmodule Code do
|
||||
: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: Make this option have no effect 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
|
||||
@@ -2133,7 +1952,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}
|
||||
@@ -2155,7 +1974,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]
|
||||
@@ -20,7 +16,7 @@ defmodule Code.Formatter do
|
||||
@ampersand_prec Code.Identifier.unary_op(:&) |> elem(1)
|
||||
|
||||
# Operators that are composed of multiple binary operators
|
||||
@multi_binary_operators [:..//]
|
||||
@multi_binary_operators [:"..//"]
|
||||
|
||||
# Operators that do not have space between operands
|
||||
@no_space_binary_operators [:.., :"//"]
|
||||
@@ -35,37 +31,30 @@ defmodule Code.Formatter do
|
||||
@right_new_line_before_binary_operators [:|, :when]
|
||||
|
||||
# Operators that are logical cannot be mixed without parens
|
||||
@required_parens_logical_binary_operands [:|||, :||, :or, :&&&, :&&, :and]
|
||||
@required_parens_logical_binary_operands [:||, :|||, :or, :&&, :&&&, :and]
|
||||
|
||||
# Operators with next break fits
|
||||
@next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :"::"]
|
||||
|
||||
# Operators that always require parens even
|
||||
# when they are their own parents as they are not semantically associative
|
||||
@required_parens_even_when_parent [:--, :---]
|
||||
|
||||
# Operators that always require parens on operands
|
||||
# when they are the parent of another operator with a difference precedence
|
||||
# Most operators are listed, except comparison, arithmetic, and low precedence
|
||||
# Operators that always require parens on operands when they are the parent
|
||||
@required_parens_on_binary_operands [
|
||||
:|>,
|
||||
:<<<,
|
||||
:>>>,
|
||||
:|>,
|
||||
:<~,
|
||||
:~>,
|
||||
:<<~,
|
||||
:~>>,
|
||||
:<~>,
|
||||
:"<|>",
|
||||
:in,
|
||||
:"^^^",
|
||||
:"//",
|
||||
:++,
|
||||
:--,
|
||||
:+++,
|
||||
:---,
|
||||
:<>,
|
||||
:..
|
||||
:in,
|
||||
:++,
|
||||
:--,
|
||||
:..,
|
||||
:<>
|
||||
]
|
||||
|
||||
@locals_without_parens [
|
||||
@@ -158,7 +147,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, [])
|
||||
|
||||
@@ -194,11 +182,8 @@ defmodule Code.Formatter do
|
||||
locals_without_parens = Keyword.get(opts, :locals_without_parens, [])
|
||||
file = Keyword.get(opts, :file, nil)
|
||||
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)
|
||||
normalize_bitstring_modifiers = Keyword.get(opts, :normalize_bitstring_modifiers, true)
|
||||
normalize_charlists_as_sigils = Keyword.get(opts, :normalize_charlists_as_sigils, true)
|
||||
syntax_colors = Keyword.get(opts, :syntax_colors, [])
|
||||
|
||||
sigils =
|
||||
@@ -223,10 +208,8 @@ defmodule Code.Formatter do
|
||||
comments: comments,
|
||||
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,
|
||||
normalize_bitstring_modifiers: normalize_bitstring_modifiers,
|
||||
normalize_charlists_as_sigils: normalize_charlists_as_sigils,
|
||||
inspect_opts: %Inspect.Opts{syntax_colors: syntax_colors}
|
||||
}
|
||||
end
|
||||
@@ -283,7 +266,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({var, _meta, var_context}, _context, state) when is_atom(var_context) do
|
||||
{var |> Atom.to_string() |> string() |> color_doc(:variable, state.inspect_opts), state}
|
||||
{var |> Atom.to_string() |> string() |> color(:variable, state.inspect_opts), state}
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:<<>>, meta, entries}, _context, state) do
|
||||
@@ -414,7 +397,7 @@ defmodule Code.Formatter do
|
||||
{@double_heredoc
|
||||
|> concat(string)
|
||||
|> concat(@double_heredoc)
|
||||
|> color_doc(:string, state.inspect_opts)
|
||||
|> color(:string, state.inspect_opts)
|
||||
|> force_unfit(), state}
|
||||
else
|
||||
string = escape_string(string, @double_quote)
|
||||
@@ -422,7 +405,7 @@ defmodule Code.Formatter do
|
||||
{@double_quote
|
||||
|> concat(string)
|
||||
|> concat(@double_quote)
|
||||
|> color_doc(:string, state.inspect_opts), state}
|
||||
|> color(:string, state.inspect_opts), state}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -454,7 +437,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:__block__, _meta, []}, _context, state) do
|
||||
{color_doc("nil", nil, state.inspect_opts), state}
|
||||
{color("nil", nil, state.inspect_opts), state}
|
||||
end
|
||||
|
||||
defp quoted_to_algebra({:__block__, meta, args} = block, _context, state) when is_list(args) do
|
||||
@@ -471,7 +454,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
{Enum.reduce(tail, doc, &concat(&2, "." <> Atom.to_string(&1)))
|
||||
|> color_doc(:atom, state.inspect_opts), state}
|
||||
|> color(:atom, state.inspect_opts), state}
|
||||
end
|
||||
|
||||
# &1
|
||||
@@ -494,62 +477,6 @@ 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
|
||||
|
||||
defp quoted_to_algebra(
|
||||
{atom, _, [{:unless, _, _}, _]} = ast,
|
||||
context,
|
||||
%{migrate_unless: true} = state
|
||||
)
|
||||
when atom in [:defmacro, :defmacrop] do
|
||||
quoted_to_algebra(ast, context, %{state | migrate_unless: false})
|
||||
end
|
||||
|
||||
# rewrite unless as if!
|
||||
defp quoted_to_algebra(
|
||||
{:unless, meta, [condition, block]},
|
||||
context,
|
||||
%{migrate_unless: true} = state
|
||||
) do
|
||||
quoted_to_algebra({:if, meta, [negate_condition(condition), block]}, context, state)
|
||||
end
|
||||
|
||||
# a |> b() |> unless(...) => a |> b() |> Kernel.!() |> unless(...)
|
||||
defp quoted_to_algebra(
|
||||
{:|>, meta1, [{:|>, _, _} = condition, {:unless, meta2, [block]}]},
|
||||
context,
|
||||
%{migrate_unless: true} = state
|
||||
) do
|
||||
negated_condition = {:|>, [], [condition, {{:., [], [Kernel, :!]}, [closing: []], []}]}
|
||||
|
||||
quoted_to_algebra(
|
||||
{:|>, meta1, [negated_condition, {:if, meta2, [block]}]},
|
||||
context,
|
||||
state
|
||||
)
|
||||
end
|
||||
|
||||
# condition |> unless(...) => negated(condition) |> unless(...)
|
||||
defp quoted_to_algebra(
|
||||
{:|>, meta1, [condition, {:unless, meta2, [block]}]},
|
||||
context,
|
||||
%{migrate_unless: true} = state
|
||||
) do
|
||||
quoted_to_algebra(
|
||||
{:|>, meta1, [negate_condition(condition), {:if, meta2, [block]}]},
|
||||
context,
|
||||
state
|
||||
)
|
||||
end
|
||||
|
||||
# ..
|
||||
defp quoted_to_algebra({:.., _meta, []}, context, state) do
|
||||
if context in [:no_parens_arg, :no_parens_one_arg] do
|
||||
@@ -565,7 +492,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
# 1..2//3
|
||||
defp quoted_to_algebra({:..//, meta, [left, middle, right]}, context, state) do
|
||||
defp quoted_to_algebra({:"..//", meta, [left, middle, right]}, context, state) do
|
||||
quoted_to_algebra({:"//", meta, [{:.., meta, [left, middle]}, right]}, context, state)
|
||||
end
|
||||
|
||||
@@ -597,12 +524,20 @@ defmodule Code.Formatter do
|
||||
if keyword_key?(left_arg) do
|
||||
{left, state} =
|
||||
case left_arg do
|
||||
{:__block__, _, [atom]} when is_atom(atom) ->
|
||||
formatted = Macro.inspect_atom(:key, atom, escape: &escape_atom/2)
|
||||
# TODO: Remove this clause in v1.18 when we no longer quote operator :..//
|
||||
{:__block__, _, [:"..//"]} ->
|
||||
{string(~S{"..//":}), state}
|
||||
|
||||
{formatted
|
||||
|> string()
|
||||
|> color_doc(:atom, state.inspect_opts), state}
|
||||
{:__block__, _, [atom]} when is_atom(atom) ->
|
||||
iodata =
|
||||
if Macro.classify_atom(atom) in [:identifier, :unquoted] do
|
||||
[Atom.to_string(atom), ?:]
|
||||
else
|
||||
[?", atom |> Atom.to_string() |> String.replace("\"", "\\\""), ?", ?:]
|
||||
end
|
||||
|
||||
{iodata |> IO.iodata_to_binary() |> string() |> color(:atom, state.inspect_opts),
|
||||
state}
|
||||
|
||||
{{:., _, [:erlang, :binary_to_atom]}, _, [{:<<>>, _, entries}, :utf8]} ->
|
||||
interpolation_to_algebra(entries, @double_quote, state, "\"", "\":")
|
||||
@@ -618,22 +553,16 @@ 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
|
||||
|
||||
# #PID's and #Ref's may appear on regular AST
|
||||
# Other foreign structures, such as maps and structs,
|
||||
# may appear from Macro.to_string, so we stick a limit,
|
||||
# although they won't be formatted accordingly.
|
||||
defp quoted_to_algebra(unknown, _context, state) do
|
||||
{inspect(unknown, printable_limit: :infinity), state}
|
||||
{inspect(unknown), state}
|
||||
end
|
||||
|
||||
## Blocks
|
||||
@@ -703,7 +632,7 @@ defmodule Code.Formatter do
|
||||
Atom.to_string(op)
|
||||
end
|
||||
|
||||
{color_doc(op_string, :operator, state.inspect_opts) |> concat(doc), state}
|
||||
{color(op_string, :operator, state.inspect_opts) |> concat(doc), state}
|
||||
end
|
||||
|
||||
defp maybe_binary_op_to_algebra(fun, meta, args, context, state) do
|
||||
@@ -806,13 +735,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(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(" " <> op_string <> " ", :operator, state.inspect_opts)
|
||||
concat(concat(group(left), op_doc), group(right))
|
||||
|
||||
true ->
|
||||
eol? = eol?(meta, state)
|
||||
@@ -820,15 +751,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(" " <> 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 +780,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
|
||||
|
||||
@@ -891,13 +789,14 @@ defmodule Code.Formatter do
|
||||
op_string = Atom.to_string(op)
|
||||
|
||||
cond do
|
||||
# If we have the same operator and it is in the correct side,
|
||||
# we don't add parens unless it is explicitly required.
|
||||
parent_assoc == side and op == parent_op and op not in @required_parens_even_when_parent ->
|
||||
# If the operator has the same precedence as the parent and is on
|
||||
# the correct side, we respect the nesting rule to avoid multiple
|
||||
# nestings. This only applies for left associativity or same operator.
|
||||
parent_prec == prec and parent_assoc == side and (side == :left or op == parent_op) ->
|
||||
binary_op_to_algebra(op, op_string, meta, left, right, context, state, nesting)
|
||||
|
||||
# If the operator requires parens (most of them do) or we are mixing logical operators
|
||||
# or the precedence is inverted or it is in the wrong side, then we *need* parenthesis.
|
||||
# If the parent requires parens or the precedence is inverted or
|
||||
# it is in the wrong side, then we *need* parenthesis.
|
||||
(parent_op in @required_parens_on_binary_operands and op not in @no_space_binary_operators) or
|
||||
(op in @required_parens_logical_binary_operands and
|
||||
parent_op in @required_parens_logical_binary_operands) or parent_prec > prec or
|
||||
@@ -1054,7 +953,7 @@ defmodule Code.Formatter do
|
||||
)
|
||||
when is_atom(fun) and is_integer(arity) do
|
||||
{target_doc, state} = remote_target_to_algebra(target, state)
|
||||
fun = Macro.inspect_atom(:remote_call, fun, escape: &escape_atom/2)
|
||||
fun = Macro.inspect_atom(:remote_call, fun)
|
||||
{target_doc |> nest(1) |> concat(string(".#{fun}/#{arity}")), state}
|
||||
end
|
||||
|
||||
@@ -1101,9 +1000,7 @@ defmodule Code.Formatter do
|
||||
{target_doc, state} = remote_target_to_algebra(target, state)
|
||||
|
||||
fun_doc =
|
||||
Macro.inspect_atom(:remote_call, fun, escape: &escape_atom/2)
|
||||
|> string()
|
||||
|> color_doc(:call, state.inspect_opts)
|
||||
Macro.inspect_atom(:remote_call, fun) |> string() |> color(:call, state.inspect_opts)
|
||||
|
||||
remote_doc = target_doc |> concat(".") |> concat(fun_doc)
|
||||
|
||||
@@ -1171,7 +1068,7 @@ defmodule Code.Formatter do
|
||||
fun
|
||||
|> Atom.to_string()
|
||||
|> string()
|
||||
|> color_doc(:call, state.inspect_opts)
|
||||
|> color(:call, state.inspect_opts)
|
||||
|> concat(call_doc)
|
||||
|
||||
doc = if wrap_in_parens?, do: wrap_in_parens(doc), else: doc
|
||||
@@ -1265,7 +1162,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 +1170,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 +1214,13 @@ defmodule Code.Formatter do
|
||||
|> concat(args_doc)
|
||||
|> nest(2)
|
||||
|> concat(extra)
|
||||
|> group()
|
||||
|
||||
skip_parens? ->
|
||||
" "
|
||||
|> concat(args_doc)
|
||||
|> concat(extra)
|
||||
|> group()
|
||||
|
||||
true ->
|
||||
"("
|
||||
@@ -1328,12 +1228,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
|
||||
|
||||
@@ -1530,7 +1431,7 @@ defmodule Code.Formatter do
|
||||
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
||||
|
||||
{spec, state} =
|
||||
bitstring_spec_to_algebra(spec, state, state.migrate_bitstring_modifiers, :"::")
|
||||
bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers, :"::")
|
||||
|
||||
spec = wrap_in_parens_if_inspected_atom(spec)
|
||||
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
|
||||
@@ -1608,8 +1509,8 @@ defmodule Code.Formatter do
|
||||
{args_doc, _join, state} =
|
||||
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
|
||||
|
||||
left_bracket = color_doc("[", :list, state.inspect_opts)
|
||||
right_bracket = color_doc("]", :list, state.inspect_opts)
|
||||
left_bracket = color("[", :list, state.inspect_opts)
|
||||
right_bracket = color("]", :list, state.inspect_opts)
|
||||
|
||||
{surround(left_bracket, args_doc, right_bracket), state}
|
||||
end
|
||||
@@ -1641,8 +1542,8 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp do_map_to_algebra(name_doc, args_doc, state) do
|
||||
name_doc = "%" |> concat(name_doc) |> concat("{") |> color_doc(:map, state.inspect_opts)
|
||||
{surround(name_doc, args_doc, color_doc("}", :map, state.inspect_opts)), state}
|
||||
name_doc = "%" |> concat(name_doc) |> concat("{") |> color(:map, state.inspect_opts)
|
||||
{surround(name_doc, args_doc, color("}", :map, state.inspect_opts)), state}
|
||||
end
|
||||
|
||||
defp tuple_to_algebra(meta, args, join, state) do
|
||||
@@ -1652,8 +1553,8 @@ defmodule Code.Formatter do
|
||||
{args_doc, join, state} =
|
||||
args_to_algebra_with_comments(args, meta, false, :none, join, state, fun)
|
||||
|
||||
left_bracket = color_doc("{", :tuple, state.inspect_opts)
|
||||
right_bracket = color_doc("}", :tuple, state.inspect_opts)
|
||||
left_bracket = color("{", :tuple, state.inspect_opts)
|
||||
right_bracket = color("}", :tuple, state.inspect_opts)
|
||||
|
||||
if join == :flex_break do
|
||||
{left_bracket |> concat(args_doc) |> nest(1) |> concat(right_bracket) |> group(), state}
|
||||
@@ -1663,11 +1564,16 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp atom_to_algebra(atom, _, inspect_opts) when atom in [true, false] do
|
||||
Atom.to_string(atom) |> color_doc(:boolean, inspect_opts)
|
||||
Atom.to_string(atom) |> color(:boolean, inspect_opts)
|
||||
end
|
||||
|
||||
defp atom_to_algebra(nil, _, inspect_opts) do
|
||||
Atom.to_string(nil) |> color_doc(nil, inspect_opts)
|
||||
Atom.to_string(nil) |> color(nil, inspect_opts)
|
||||
end
|
||||
|
||||
# TODO: Remove this clause in v1.18 when we no longer quote operator :..//
|
||||
defp atom_to_algebra(:"..//", _, inspect_opts) do
|
||||
string(":\"..//\"") |> color(:atom, inspect_opts)
|
||||
end
|
||||
|
||||
defp atom_to_algebra(:\\, meta, inspect_opts) do
|
||||
@@ -1680,7 +1586,7 @@ defmodule Code.Formatter do
|
||||
_ -> ":\\\\"
|
||||
end
|
||||
|
||||
string(string) |> color_doc(:atom, inspect_opts)
|
||||
string(string) |> color(:atom, inspect_opts)
|
||||
end
|
||||
|
||||
defp atom_to_algebra(atom, _, inspect_opts) do
|
||||
@@ -1693,7 +1599,7 @@ defmodule Code.Formatter do
|
||||
[?:, ?", String.replace(string, "\"", "\\\""), ?"]
|
||||
end
|
||||
|
||||
iodata |> IO.iodata_to_binary() |> string() |> color_doc(:atom, inspect_opts)
|
||||
iodata |> IO.iodata_to_binary() |> string() |> color(:atom, inspect_opts)
|
||||
end
|
||||
|
||||
defp integer_to_algebra(text, inspect_otps) do
|
||||
@@ -1710,7 +1616,7 @@ defmodule Code.Formatter do
|
||||
decimal ->
|
||||
insert_underscores(decimal)
|
||||
end
|
||||
|> color_doc(:number, inspect_otps)
|
||||
|> color(:number, inspect_otps)
|
||||
end
|
||||
|
||||
defp float_to_algebra(text, inspect_otps) do
|
||||
@@ -1718,7 +1624,7 @@ defmodule Code.Formatter do
|
||||
decimal_part = String.downcase(decimal_part)
|
||||
|
||||
string = insert_underscores(int_part) <> "." <> decimal_part
|
||||
color_doc(string, :number, inspect_otps)
|
||||
color(string, :number, inspect_otps)
|
||||
end
|
||||
|
||||
defp insert_underscores("-" <> digits) do
|
||||
@@ -1799,17 +1705,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 +2226,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
|
||||
|
||||
@@ -2493,24 +2389,20 @@ defmodule Code.Formatter do
|
||||
meta[:closing][:line] || @min_line
|
||||
end
|
||||
|
||||
defp escape_atom(string, char) do
|
||||
String.replace(string, <<char>>, <<?\\, char>>)
|
||||
end
|
||||
|
||||
## Algebra helpers
|
||||
|
||||
# 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
|
||||
@@ -2542,7 +2434,7 @@ defmodule Code.Formatter do
|
||||
end
|
||||
|
||||
defp get_charlist_quotes(:heredoc, state) do
|
||||
if state.migrate_charlists_as_sigils do
|
||||
if state.normalize_charlists_as_sigils do
|
||||
{@sigil_c_heredoc, @double_heredoc}
|
||||
else
|
||||
{@single_heredoc, @single_heredoc}
|
||||
@@ -2551,7 +2443,7 @@ defmodule Code.Formatter do
|
||||
|
||||
defp get_charlist_quotes({:regular, chunks}, state) do
|
||||
cond do
|
||||
!state.migrate_charlists_as_sigils -> {@single_quote, @single_quote}
|
||||
!state.normalize_charlists_as_sigils -> {@single_quote, @single_quote}
|
||||
Enum.any?(chunks, &has_double_quote?/1) -> {@sigil_c_single, @single_quote}
|
||||
true -> {@sigil_c_double, @double_quote}
|
||||
end
|
||||
@@ -2560,46 +2452,4 @@ defmodule Code.Formatter do
|
||||
defp has_double_quote?(chunk) do
|
||||
is_binary(chunk) and chunk =~ @double_quote
|
||||
end
|
||||
|
||||
# Migration rewrites
|
||||
|
||||
@bool_operators [
|
||||
:>,
|
||||
:>=,
|
||||
:<,
|
||||
:<=,
|
||||
:in
|
||||
]
|
||||
@guards [
|
||||
:is_atom,
|
||||
:is_boolean,
|
||||
:is_nil,
|
||||
:is_number,
|
||||
:is_integer,
|
||||
:is_float,
|
||||
:is_binary,
|
||||
:is_map,
|
||||
:is_struct,
|
||||
:is_non_struct_map,
|
||||
:is_exception,
|
||||
:is_list,
|
||||
:is_tuple,
|
||||
:is_function,
|
||||
:is_reference,
|
||||
:is_pid,
|
||||
:is_port
|
||||
]
|
||||
|
||||
defp negate_condition(condition) do
|
||||
case condition do
|
||||
{neg, _, [condition]} when neg in [:!, :not] -> condition
|
||||
{op, _, [_, _]} when op in @bool_operators -> {:not, [], [condition]}
|
||||
{guard, _, [_ | _]} when guard in @guards -> {:not, [], [condition]}
|
||||
{:==, meta, [left, right]} -> {:!=, meta, [left, right]}
|
||||
{:===, meta, [left, right]} -> {:!==, meta, [left, right]}
|
||||
{:!=, meta, [left, right]} -> {:==, meta, [left, right]}
|
||||
{:!==, meta, [left, right]} -> {:===, meta, [left, right]}
|
||||
_ -> {:!, [], [condition]}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+45
-263
@@ -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}
|
||||
@@ -245,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
|
||||
@@ -283,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
|
||||
|
||||
@@ -302,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
|
||||
|
||||
@@ -327,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}
|
||||
|
||||
@@ -386,42 +317,12 @@ 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} ->
|
||||
{{:capture_arg, acc}, count}
|
||||
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, [])
|
||||
@@ -461,14 +362,6 @@ defmodule Code.Fragment do
|
||||
:none
|
||||
end
|
||||
|
||||
defp rest_identifier([?& | tail] = rest, count, acc) when tail == [] or hd(tail) != ?& do
|
||||
if Enum.all?(acc, &(&1 in ?0..?9)) do
|
||||
{:capture_arg, [?& | acc], count + 1}
|
||||
else
|
||||
tokenize_identifier(rest, count, acc)
|
||||
end
|
||||
end
|
||||
|
||||
defp rest_identifier(rest, count, acc) do
|
||||
tokenize_identifier(rest, count, acc)
|
||||
end
|
||||
@@ -669,13 +562,13 @@ defmodule Code.Fragment do
|
||||
`local_call`/`local_arity` and `local_or_var`, since the latter can
|
||||
be a local or variable
|
||||
|
||||
* `@` when not followed by any identifier is returned as `{:operator, ~c"@"}`
|
||||
(in contrast to `{:module_attribute, ~c""}` in `cursor_context/2`
|
||||
* `@` when not followed by any identifier is returned as `{:operator, '@'}`
|
||||
(in contrast to `{:module_attribute, ''}` in `cursor_context/2`
|
||||
|
||||
* This function never returns empty sigils `{:sigil, ~c""}` or empty structs
|
||||
`{:struct, ~c""}` as context
|
||||
* This function never returns empty sigils `{:sigil, ''}` or empty structs
|
||||
`{:struct, ''}` as context
|
||||
|
||||
* This function returns keywords as `{:keyword, ~c"do"}`
|
||||
* This function returns keywords as `{:keyword, 'do'}`
|
||||
|
||||
* This function never returns `:expr`
|
||||
|
||||
@@ -683,7 +576,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}
|
||||
@@ -697,9 +590,7 @@ defmodule Code.Fragment do
|
||||
| {:sigil, charlist}
|
||||
| {:struct, inside_struct}
|
||||
| {:unquoted_atom, charlist}
|
||||
| {:keyword, charlist}
|
||||
| {:key, charlist}
|
||||
| {:capture_arg, charlist},
|
||||
| {:keyword, charlist},
|
||||
inside_dot:
|
||||
{:alias, charlist}
|
||||
| {:alias, inside_alias, charlist}
|
||||
@@ -749,16 +640,7 @@ defmodule Code.Fragment do
|
||||
maybe_operator(reversed_pre, post, line, opts)
|
||||
|
||||
{:identifier, reversed_post, rest} ->
|
||||
{keyword_key?, rest} =
|
||||
case rest do
|
||||
[?: | tail] when tail == [] or hd(tail) in @space ->
|
||||
{true, rest}
|
||||
|
||||
_ ->
|
||||
{rest, _} = strip_spaces(rest, 0)
|
||||
{false, rest}
|
||||
end
|
||||
|
||||
{rest, _} = strip_spaces(rest, 0)
|
||||
reversed = reversed_post ++ reversed_pre
|
||||
|
||||
case codepoint_cursor_context(reversed, opts) do
|
||||
@@ -774,9 +656,6 @@ defmodule Code.Fragment do
|
||||
{{:dot, _, [_ | _]} = dot, offset} ->
|
||||
build_surround(dot, reversed, line, offset)
|
||||
|
||||
{{:local_or_var, acc}, offset} when keyword_key? ->
|
||||
build_surround({:key, acc}, reversed, line, offset)
|
||||
|
||||
{{:local_or_var, acc}, offset} when hd(rest) == ?( ->
|
||||
build_surround({:local_call, acc}, reversed, line, offset)
|
||||
|
||||
@@ -792,12 +671,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)
|
||||
|
||||
@@ -810,9 +683,6 @@ defmodule Code.Fragment do
|
||||
{{:unquoted_atom, acc}, offset} ->
|
||||
build_surround({:unquoted_atom, acc}, reversed, line, offset)
|
||||
|
||||
{{:capture_arg, acc}, offset} ->
|
||||
build_surround({:capture_arg, acc}, reversed, line, offset)
|
||||
|
||||
_ ->
|
||||
maybe_operator(reversed_pre, post, line, opts)
|
||||
end
|
||||
@@ -845,16 +715,6 @@ defmodule Code.Fragment do
|
||||
reversed = reversed_post ++ reversed_pre
|
||||
|
||||
case codepoint_cursor_context(reversed, opts) do
|
||||
{{:operator, ~c"&"}, offset} when hd(rest) in ?0..?9 ->
|
||||
arg = Enum.take_while(rest, &(&1 in ?0..?9))
|
||||
|
||||
build_surround(
|
||||
{:capture_arg, ~c"&" ++ arg},
|
||||
:lists.reverse(arg, reversed),
|
||||
line,
|
||||
offset + length(arg)
|
||||
)
|
||||
|
||||
{{:operator, acc}, offset} ->
|
||||
build_surround({:operator, acc}, reversed, line, offset)
|
||||
|
||||
@@ -1201,23 +1061,16 @@ defmodule Code.Fragment do
|
||||
iex> Code.Fragment.container_cursor_to_quoted("foo +")
|
||||
{:ok, {:+, [line: 1], [{:foo, [line: 1], nil}, {:__cursor__, [line: 1], []}]}}
|
||||
|
||||
In order to parse the left-side of `->` properly, which appears both
|
||||
in anonymous functions and do-end blocks, the trailing fragment option
|
||||
must be given with the rest of the contents:
|
||||
|
||||
iex> Code.Fragment.container_cursor_to_quoted("fn x", trailing_fragment: " -> :ok end")
|
||||
{:ok, {:fn, [line: 1], [{:->, [line: 1], [[{:__cursor__, [line: 1], []}], :ok]}]}}
|
||||
|
||||
## Options
|
||||
|
||||
* `:file` - the filename to be reported in case of parsing errors.
|
||||
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`.
|
||||
@@ -1230,117 +1083,46 @@ defmodule Code.Fragment do
|
||||
* `:literal_encoder` - a function to encode literals in the AST.
|
||||
See the documentation for `Code.string_to_quoted/2` for more information.
|
||||
|
||||
* `:trailing_fragment` (since v1.18.0) - the rest of the contents after
|
||||
the cursor. This is necessary to correctly complete anonymous functions
|
||||
and the left-hand side of `->`
|
||||
|
||||
"""
|
||||
@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)
|
||||
opts = Keyword.take(opts, [:columns, :token_metadata, :literal_encoder])
|
||||
opts = [check_terminators: {:cursor, []}, emit_warnings: false] ++ opts
|
||||
opts = [cursor_completion: true, emit_warnings: false] ++ opts
|
||||
|
||||
file = Keyword.get(opts, :file, "nofile")
|
||||
line = Keyword.get(opts, :line, 1)
|
||||
column = Keyword.get(opts, :column, 1)
|
||||
|
||||
case :elixir_tokenizer.tokenize(to_charlist(fragment), line, column, opts) do
|
||||
{:ok, line, column, _warnings, rev_tokens, rev_terminators}
|
||||
when trailing_fragment == nil ->
|
||||
{rev_tokens, rev_terminators} =
|
||||
with [close, open, {_, _, :__cursor__} = cursor | rev_tokens] <- rev_tokens,
|
||||
{_, [_ | after_fn]} <- Enum.split_while(rev_terminators, &(elem(&1, 0) != :fn)),
|
||||
true <- maybe_missing_stab?(rev_tokens, false),
|
||||
[_ | rev_tokens] <- Enum.drop_while(rev_tokens, &(elem(&1, 0) != :fn)) do
|
||||
{[close, open, cursor | rev_tokens], after_fn}
|
||||
else
|
||||
_ -> {rev_tokens, rev_terminators}
|
||||
end
|
||||
|
||||
tokens = reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
|
||||
{:ok, line, column, _warnings, rev_tokens, rev_terminators} ->
|
||||
tokens =
|
||||
with {before_start, [_ | _] = after_start} <-
|
||||
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, before_start}),
|
||||
{:error, {meta, _, ~c"end"}, _rest, _warnings, trailing_rev_tokens} <-
|
||||
:elixir_tokenizer.tokenize(to_charlist(trailing_fragment), line, column, opts) do
|
||||
trailing_tokens =
|
||||
reverse_tokens(meta[:line], meta[:column], trailing_rev_tokens, after_start)
|
||||
tokens = :lists.reverse(rev_tokens, rev_terminators)
|
||||
|
||||
# If the cursor has its own line, then we do not trim new lines trailing tokens.
|
||||
# Otherwise we want to drop any newline so we drop the next tokens after eol.
|
||||
trailing_tokens =
|
||||
case rev_tokens do
|
||||
[_close, _open, {_, _, :__cursor__}, {:eol, _} | _] -> trailing_tokens
|
||||
_ -> Enum.drop_while(trailing_tokens, &match?({:eol, _}, &1))
|
||||
end
|
||||
case :elixir.tokens_to_quoted(tokens, file, opts) do
|
||||
{:ok, ast} ->
|
||||
{:ok, ast}
|
||||
|
||||
Enum.reverse(rev_tokens, drop_tokens(trailing_tokens, 0))
|
||||
else
|
||||
_ -> reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
end
|
||||
{:error, error} ->
|
||||
# In case parsing fails, we give it another shot but handling fn/do/else/catch/rescue/after.
|
||||
tokens =
|
||||
:lists.reverse(
|
||||
rev_tokens,
|
||||
[{:stab_op, {line, column, nil}, :->}, {nil, {line, column + 2, nil}}] ++
|
||||
Enum.map(rev_terminators, fn tuple ->
|
||||
{line, column, info} = elem(tuple, 1)
|
||||
put_elem(tuple, 1, {line, column + 5, info})
|
||||
end)
|
||||
)
|
||||
|
||||
:elixir.tokens_to_quoted(tokens, file, opts)
|
||||
case :elixir.tokens_to_quoted(tokens, file, opts) do
|
||||
{:ok, ast} -> {:ok, ast}
|
||||
{:error, _} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
{:error, info, _rest, _warnings, _so_far} ->
|
||||
{:error, :elixir.format_token_error(info)}
|
||||
end
|
||||
end
|
||||
|
||||
defp reverse_tokens(line, column, tokens, terminators) do
|
||||
{terminators, _} =
|
||||
Enum.map_reduce(terminators, column, fn {start, _, _}, column ->
|
||||
atom = :elixir_tokenizer.terminator(start)
|
||||
|
||||
{{atom, {line, column, nil}}, column + length(Atom.to_charlist(atom))}
|
||||
end)
|
||||
|
||||
Enum.reverse(tokens, terminators)
|
||||
end
|
||||
|
||||
# Otherwise we drop all tokens, trying to build a minimal AST
|
||||
# for cursor completion.
|
||||
defp drop_tokens([{:"}", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:"]", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:")", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:">>", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:end, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:",", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:";", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:eol, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:stab_op, _, :->} | _] = tokens, 0), do: tokens
|
||||
|
||||
defp drop_tokens([{:"}", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
defp drop_tokens([{:"]", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
defp drop_tokens([{:")", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
defp drop_tokens([{:">>", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
defp drop_tokens([{:end, _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
|
||||
defp drop_tokens([{:"{", _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
defp drop_tokens([{:"[", _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
defp drop_tokens([{:"(", _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
defp drop_tokens([{:"<<", _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
defp drop_tokens([{:fn, _} | tokens], counter), do: drop_tokens(tokens, counter + 1)
|
||||
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 maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:fn, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:else, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:catch, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:rescue, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:stab_op, _, :->} | _], stab_choice?), do: stab_choice?
|
||||
defp maybe_missing_stab?([_ | tail], stab_choice?), do: maybe_missing_stab?(tail, stab_choice?)
|
||||
defp maybe_missing_stab?([], _stab_choice?), do: false
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -75,11 +71,11 @@ defmodule Code.Identifier do
|
||||
Escapes the given identifier.
|
||||
"""
|
||||
@spec escape(binary(), char() | nil, :infinity | non_neg_integer, (char() -> iolist() | false)) ::
|
||||
{escaped :: binary(), remaining :: binary()}
|
||||
{escaped :: iolist(), remaining :: binary()}
|
||||
def escape(binary, char, limit \\ :infinity, fun \\ &escape_map/1)
|
||||
when (is_binary(binary) and ((char in 0..0x10FFFF or is_nil(char)) and limit == :infinity)) or
|
||||
when ((char in 0..0x10FFFF or is_nil(char)) and limit == :infinity) or
|
||||
(is_integer(limit) and limit >= 0) do
|
||||
escape(binary, char, limit, <<>>, fun)
|
||||
escape(binary, char, limit, [], fun)
|
||||
end
|
||||
|
||||
defp escape(<<_, _::binary>> = binary, _char, 0, acc, _fun) do
|
||||
@@ -87,96 +83,64 @@ defmodule Code.Identifier do
|
||||
end
|
||||
|
||||
defp escape(<<char, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), <<acc::binary, ?\\, char>>, fun)
|
||||
escape(t, char, decrement(count), [acc | [?\\, char]], fun)
|
||||
end
|
||||
|
||||
defp escape(<<?#, ?{, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), <<acc::binary, ?\\, ?#, ?{>>, fun)
|
||||
escape(t, char, decrement(count), [acc | [?\\, ?#, ?{]], fun)
|
||||
end
|
||||
|
||||
defp escape(<<h::utf8, t::binary>>, char, count, acc, fun) do
|
||||
if value = fun.(h) do
|
||||
value = IO.iodata_to_binary(value)
|
||||
escape(t, char, decrement(count), <<acc::binary, value::binary>>, fun)
|
||||
else
|
||||
escape(t, char, decrement(count), escape_char(h, acc), fun)
|
||||
end
|
||||
escaped = if value = fun.(h), do: value, else: escape_char(h)
|
||||
escape(t, char, decrement(count), [acc | escaped], fun)
|
||||
end
|
||||
|
||||
defp escape(<<a::4, b::4, t::binary>>, char, count, acc, fun) do
|
||||
escape(t, char, decrement(count), <<acc::binary, ?\\, ?x, to_hex(a), to_hex(b)>>, fun)
|
||||
escape(t, char, decrement(count), [acc | [?\\, ?x, to_hex(a), to_hex(b)]], fun)
|
||||
end
|
||||
|
||||
defp escape(<<>>, _char, _count, acc, _fun) do
|
||||
{acc, <<>>}
|
||||
end
|
||||
|
||||
defp escape_char(0, acc), do: <<acc::binary, ?\\, ?0>>
|
||||
defp escape_char(0), do: [?\\, ?0]
|
||||
|
||||
defp escape_char(char, acc)
|
||||
# Some characters that are confusing (zero-width / alternative spaces) are displayed
|
||||
# using their unicode representation:
|
||||
# https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Special-purpose_characters
|
||||
@escaped_bom :binary.bin_to_list("\\uFEFF")
|
||||
defp escape_char(65279), do: @escaped_bom
|
||||
|
||||
# BOM
|
||||
when char == 0xFEFF
|
||||
# Mathematical invisibles
|
||||
when char in 0x2061..0x2064
|
||||
# Bidirectional neutral
|
||||
when char in [0x061C, 0x200E, 0x200F]
|
||||
# Bidirectional general (source of vulnerabilities)
|
||||
when char in 0x202A..0x202E
|
||||
when char in 0x2066..0x2069
|
||||
# Interlinear annotations
|
||||
when char in 0xFFF9..0xFFFC
|
||||
# Zero-width joiners and non-joiners
|
||||
when char in [0x200C, 0x200D, 0x034F]
|
||||
# Non-break space / zero-width space
|
||||
when char in [0x00A0, 0x200B, 0x2060]
|
||||
# Line/paragraph separators
|
||||
when char in [0x2028, 0x2029]
|
||||
# Spaces
|
||||
when char in 0x2000..0x200A
|
||||
when char == 0x205F do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::16>>
|
||||
<<acc::binary, ?\\, ?u, to_hex(a), to_hex(b), to_hex(c), to_hex(d)>>
|
||||
end
|
||||
|
||||
defp escape_char(char, acc)
|
||||
defp escape_char(char)
|
||||
when char in 0x20..0x7E
|
||||
when char in 0xA0..0xD7FF
|
||||
when char in 0xE000..0xFFFD
|
||||
when char in 0x10000..0x10FFFF do
|
||||
<<acc::binary, char::utf8>>
|
||||
<<char::utf8>>
|
||||
end
|
||||
|
||||
defp escape_char(char, acc) when char < 0x100 do
|
||||
defp escape_char(char) when char < 0x100 do
|
||||
<<a::4, b::4>> = <<char::8>>
|
||||
<<acc::binary, ?\\, ?x, to_hex(a), to_hex(b)>>
|
||||
[?\\, ?x, to_hex(a), to_hex(b)]
|
||||
end
|
||||
|
||||
defp escape_char(char, acc) when char < 0x10000 do
|
||||
defp escape_char(char) when char < 0x10000 do
|
||||
<<a::4, b::4, c::4, d::4>> = <<char::16>>
|
||||
<<acc::binary, ?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}>>
|
||||
[?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), ?}]
|
||||
end
|
||||
|
||||
defp escape_char(char, acc) when char < 0x1000000 do
|
||||
defp escape_char(char) when char < 0x1000000 do
|
||||
<<a::4, b::4, c::4, d::4, e::4, f::4>> = <<char::24>>
|
||||
|
||||
<<acc::binary, ?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), to_hex(e), to_hex(f),
|
||||
?}>>
|
||||
[?\\, ?x, ?{, to_hex(a), to_hex(b), to_hex(c), to_hex(d), to_hex(e), to_hex(f), ?}]
|
||||
end
|
||||
|
||||
defp escape_map(?\a), do: "\\a"
|
||||
defp escape_map(?\b), do: "\\b"
|
||||
defp escape_map(?\d), do: "\\d"
|
||||
defp escape_map(?\e), do: "\\e"
|
||||
defp escape_map(?\f), do: "\\f"
|
||||
defp escape_map(?\n), do: "\\n"
|
||||
defp escape_map(?\r), do: "\\r"
|
||||
defp escape_map(?\t), do: "\\t"
|
||||
defp escape_map(?\v), do: "\\v"
|
||||
defp escape_map(?\\), do: "\\\\"
|
||||
defp escape_map(?\a), do: [?\\, ?a]
|
||||
defp escape_map(?\b), do: [?\\, ?b]
|
||||
defp escape_map(?\d), do: [?\\, ?d]
|
||||
defp escape_map(?\e), do: [?\\, ?e]
|
||||
defp escape_map(?\f), do: [?\\, ?f]
|
||||
defp escape_map(?\n), do: [?\\, ?n]
|
||||
defp escape_map(?\r), do: [?\\, ?r]
|
||||
defp escape_map(?\t), do: [?\\, ?t]
|
||||
defp escape_map(?\v), do: [?\\, ?v]
|
||||
defp escape_map(?\\), do: [?\\, ?\\]
|
||||
defp escape_map(_), do: false
|
||||
|
||||
@compile {:inline, to_hex: 1, decrement: 1}
|
||||
|
||||
@@ -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)
|
||||
@@ -62,7 +58,7 @@ defmodule Code.Normalizer do
|
||||
{:.., meta, [left, right]}
|
||||
else
|
||||
step = do_normalize(step, state)
|
||||
{:..//, meta, [left, right, step]}
|
||||
{:"..//", meta, [left, right, step]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -130,13 +126,13 @@ defmodule Code.Normalizer do
|
||||
{:., meta, [Access, :get]}
|
||||
end
|
||||
|
||||
# Only normalize the left side of the dot operator
|
||||
# The right hand side is an atom in the AST but it's not an atom literal, so
|
||||
# it should not be wrapped. However, it should be escaped if applicable.
|
||||
defp do_normalize({:., meta, [left, right]}, state) when is_atom(right) do
|
||||
# it should not be wrapped
|
||||
defp do_normalize({:., meta, [left, right]}, state) do
|
||||
meta = patch_meta_line(meta, state.parent_meta)
|
||||
|
||||
left = do_normalize(left, %{state | parent_meta: meta})
|
||||
right = maybe_escape_literal(right, state)
|
||||
|
||||
{:., meta, [left, right]}
|
||||
end
|
||||
@@ -293,18 +289,21 @@ defmodule Code.Normalizer do
|
||||
# Lists
|
||||
defp normalize_literal(list, meta, state) when is_list(list) do
|
||||
if list != [] and List.ascii_printable?(list) do
|
||||
# It's a charlist, we normalize it as a ~C sigil
|
||||
string =
|
||||
# It's a charlist
|
||||
list =
|
||||
if state.escape do
|
||||
{iolist, _} = Code.Identifier.escape(IO.chardata_to_string(list), nil)
|
||||
IO.iodata_to_binary(iolist)
|
||||
{string, _} = Code.Identifier.escape(IO.chardata_to_string(list), nil)
|
||||
IO.iodata_to_binary(string) |> to_charlist()
|
||||
else
|
||||
List.to_string(list)
|
||||
list
|
||||
end
|
||||
|
||||
meta = patch_meta_line([delimiter: "\""], state.parent_meta)
|
||||
meta =
|
||||
meta
|
||||
|> Keyword.put_new(:delimiter, "'")
|
||||
|> patch_meta_line(state.parent_meta)
|
||||
|
||||
{:sigil_c, meta, [{:<<>>, [], [string]}, []]}
|
||||
{:__block__, meta, [list]}
|
||||
else
|
||||
meta =
|
||||
if line = state.parent_meta[:line] do
|
||||
@@ -351,14 +350,14 @@ defmodule Code.Normalizer do
|
||||
args = normalize_args(args, %{state | parent_meta: meta})
|
||||
{form, meta, args}
|
||||
|
||||
Keyword.has_key?(meta, :do) ->
|
||||
Keyword.has_key?(meta, :do) or match?([{{:__block__, _, [:do]}, _} | _], last) ->
|
||||
# def foo do :ok end
|
||||
# def foo, do: :ok
|
||||
normalize_kw_blocks(form, meta, args, state)
|
||||
|
||||
match?([{:do, _} | _], last) and Keyword.keyword?(last) ->
|
||||
# Non normalized kw blocks
|
||||
line = state.parent_meta[:line] || meta[:line]
|
||||
line = state.parent_meta[:line]
|
||||
meta = meta ++ [do: [line: line], end: [line: line]]
|
||||
normalize_kw_blocks(form, meta, args, state)
|
||||
|
||||
@@ -368,20 +367,11 @@ defmodule Code.Normalizer do
|
||||
|
||||
last_args =
|
||||
case last_arg do
|
||||
{:__block__, _meta, [[{{:__block__, key_meta, _}, _} | _] = keyword]} ->
|
||||
cond do
|
||||
key_meta[:format] == :keyword ->
|
||||
[keyword]
|
||||
|
||||
block_keyword?(keyword) ->
|
||||
[
|
||||
Enum.map(keyword, fn {{:__block__, meta, args}, value} ->
|
||||
{{:__block__, [format: :keyword] ++ meta, args}, value}
|
||||
end)
|
||||
]
|
||||
|
||||
true ->
|
||||
[last_arg]
|
||||
{:__block__, _, [[{{:__block__, key_meta, _}, _} | _]] = last_args} ->
|
||||
if key_meta[:format] == :keyword do
|
||||
last_args
|
||||
else
|
||||
[last_arg]
|
||||
end
|
||||
|
||||
[] ->
|
||||
@@ -395,12 +385,6 @@ defmodule Code.Normalizer do
|
||||
end
|
||||
end
|
||||
|
||||
defp block_keyword?([{{:__block__, _, [key]}, _val} | tail]) when is_atom(key),
|
||||
do: block_keyword?(tail)
|
||||
|
||||
defp block_keyword?([]), do: true
|
||||
defp block_keyword?(_), do: false
|
||||
|
||||
defp allow_keyword?(:when, 2), do: true
|
||||
defp allow_keyword?(:{}, _), do: false
|
||||
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,6 +27,11 @@ defmodule Code.Typespec do
|
||||
end
|
||||
end
|
||||
|
||||
def spec_to_quoted(name, {:type, anno, :fun, []}) when is_atom(name) do
|
||||
meta = meta(anno)
|
||||
{:"::", meta, [{name, meta, []}, quote(do: term)]}
|
||||
end
|
||||
|
||||
def spec_to_quoted(name, {:type, anno, :bounded_fun, [type, constrs]}) when is_atom(name) do
|
||||
meta = meta(anno)
|
||||
{:type, _, :fun, [{:type, _, :product, args}, result]} = type
|
||||
@@ -287,6 +288,7 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, anno, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_quoted(arg)
|
||||
line = meta(anno)[:line]
|
||||
|
||||
case {typespec_to_quoted(arg1), typespec_to_quoted(arg2)} do
|
||||
@@ -315,6 +317,10 @@ defmodule Code.Typespec do
|
||||
[{:->, meta(anno), [[typespec_to_quoted(args)], typespec_to_quoted(result)]}]
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, anno, :fun, []}) do
|
||||
typespec_to_quoted({:type, anno, :fun, [{:type, anno, :any}, {:type, anno, :any, []}]})
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, anno, :range, [left, right]}) do
|
||||
{:.., meta(anno), [typespec_to_quoted(left), typespec_to_quoted(right)]}
|
||||
end
|
||||
@@ -332,14 +338,10 @@ defmodule Code.Typespec do
|
||||
{erl_to_ex_var(var), meta(anno), nil}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, anno, op, arg}) when op in [:+, :-] do
|
||||
defp typespec_to_quoted({:op, anno, op, arg}) do
|
||||
{op, meta(anno), [typespec_to_quoted(arg)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, anno, :*, arg1, arg2}) do
|
||||
{:*, meta(anno), [typespec_to_quoted(arg1), typespec_to_quoted(arg2)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:remote_type, anno, [mod, name, args]}) do
|
||||
remote_type(anno, mod, name, args)
|
||||
end
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -45,8 +41,7 @@ defmodule Config do
|
||||
> application environment is effectively a global storage. Also note that
|
||||
> the `config/config.exs` of a library is not evaluated when the library is
|
||||
> used as a dependency, as configuration is always meant to configure the
|
||||
> current project. For more information, see ["Using application configuration for
|
||||
> libraries"](design-anti-patterns.md#using-application-configuration-for-libraries).
|
||||
> current project. For more information, read our [library guidelines](library-guidelines.md).
|
||||
|
||||
## Migrating from `use Mix.Config`
|
||||
|
||||
@@ -98,12 +93,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 +130,7 @@ defmodule Config do
|
||||
|
||||
config :logger,
|
||||
level: :warn,
|
||||
backends: [:console]
|
||||
|
||||
config :logger,
|
||||
level: :info,
|
||||
@@ -148,12 +138,12 @@ 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"
|
||||
def config(root_key, opts) when is_atom(root_key) and is_list(opts) do
|
||||
if not Keyword.keyword?(opts) do
|
||||
unless Keyword.keyword?(opts) do
|
||||
raise ArgumentError, "config/2 expected a keyword list, got: #{inspect(opts)}"
|
||||
end
|
||||
|
||||
@@ -204,37 +194,11 @@ defmodule Config do
|
||||
|> put_config()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reads the configuration for the given root key.
|
||||
|
||||
This function only reads the configuration from a previous
|
||||
`config/2` or `config/3` call. If `root_key` points to an
|
||||
application, it does not read its actual application environment.
|
||||
Its main use case is to make it easier to access and share
|
||||
configuration values across files.
|
||||
|
||||
If the `root_key` was not configured, it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
# In config/config.exs
|
||||
config :my_app, foo: :bar
|
||||
|
||||
# In config/dev.exs
|
||||
config :another_app, foo: read_config(:my_app)[:foo] || raise "missing parent configuration"
|
||||
|
||||
"""
|
||||
@doc since: "1.18.0"
|
||||
def read_config(root_key) when is_atom(root_key) do
|
||||
get_config!()[root_key]
|
||||
end
|
||||
|
||||
@doc """
|
||||
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 +276,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)
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -158,7 +215,10 @@ defmodule DynamicSupervisor do
|
||||
extra_arguments: [term()]
|
||||
}
|
||||
|
||||
@typedoc "Options given to `start_link/1` and `init/1` functions"
|
||||
@typedoc "Options given to `start_link` functions"
|
||||
@type option :: GenServer.option()
|
||||
|
||||
@typedoc "Options given to `start_link` and `init/1` functions"
|
||||
@type init_option ::
|
||||
{:strategy, strategy()}
|
||||
| {:max_restarts, non_neg_integer()}
|
||||
@@ -169,15 +229,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}
|
||||
@@ -225,7 +277,7 @@ defmodule DynamicSupervisor do
|
||||
defmacro __using__(opts) do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour DynamicSupervisor
|
||||
if not Module.has_attribute?(__MODULE__, :doc) do
|
||||
unless Module.has_attribute?(__MODULE__, :doc) do
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
@@ -293,11 +345,9 @@ defmodule DynamicSupervisor do
|
||||
specified in the child spec given to `start_child/2`. Defaults to
|
||||
an empty list.
|
||||
|
||||
* Any of the standard [GenServer options](`t:GenServer.option/0`)
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec start_link([init_option | GenServer.option()]) :: Supervisor.on_start()
|
||||
@spec start_link([option | init_option]) :: Supervisor.on_start()
|
||||
def start_link(options) when is_list(options) do
|
||||
keys = [:extra_arguments, :max_children, :max_seconds, :max_restarts, :strategy]
|
||||
{sup_opts, start_opts} = Keyword.split(options, keys)
|
||||
@@ -331,15 +381,9 @@ defmodule DynamicSupervisor do
|
||||
Note that a supervisor started with this function is linked to the parent
|
||||
process and exits not only on crashes but also if the parent process exits
|
||||
with `:normal` reason.
|
||||
|
||||
## Options
|
||||
|
||||
This function accepts any regular [`GenServer` options](`t:GenServer.option/0`).
|
||||
Options specific to `DynamicSupervisor` must be returned from the `c:init/1`
|
||||
callback.
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec start_link(module, term, [GenServer.option()]) :: Supervisor.on_start()
|
||||
@spec start_link(module, term, [option]) :: Supervisor.on_start()
|
||||
def start_link(module, init_arg, opts \\ []) do
|
||||
GenServer.start_link(__MODULE__, {module, init_arg, opts[:name]}, opts)
|
||||
end
|
||||
@@ -347,17 +391,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
|
||||
@@ -462,14 +500,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}`.
|
||||
"""
|
||||
|
||||
+88
-322
@@ -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.
|
||||
@@ -760,9 +756,25 @@ 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)
|
||||
stop_at = limit - 1
|
||||
|
||||
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
|
||||
|
||||
@@ -781,10 +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
|
||||
stop_at = limit - 1
|
||||
|
||||
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 """
|
||||
@@ -811,7 +837,7 @@ defmodule Enum do
|
||||
end
|
||||
|
||||
def dedup(enumerable) do
|
||||
reduce(enumerable, [], fn x, acc ->
|
||||
Enum.reduce(enumerable, [], fn x, acc ->
|
||||
case acc do
|
||||
[^x | _] -> acc
|
||||
_ -> [x | acc]
|
||||
@@ -1048,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
|
||||
|
||||
@@ -1231,14 +1257,6 @@ defmodule Enum do
|
||||
iex> Enum.flat_map([:a, :b, :c], fn x -> [[x]] end)
|
||||
[[:a], [:b], [:c]]
|
||||
|
||||
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 ->
|
||||
...> if x != 0, do: [1 / x], else: []
|
||||
...> end)
|
||||
[0.25, 0.5]
|
||||
|
||||
"""
|
||||
@spec flat_map(t, (element -> t)) :: list
|
||||
def flat_map(enumerable, fun) when is_list(enumerable) do
|
||||
@@ -1248,7 +1266,6 @@ defmodule Enum do
|
||||
def flat_map(enumerable, fun) do
|
||||
reduce(enumerable, [], fn entry, acc ->
|
||||
case fun.(entry) do
|
||||
[] -> acc
|
||||
list when is_list(list) -> [list | acc]
|
||||
other -> [to_list(other) | acc]
|
||||
end
|
||||
@@ -1256,8 +1273,6 @@ defmodule Enum do
|
||||
|> flat_reverse([])
|
||||
end
|
||||
|
||||
# the first clause is an optimization
|
||||
defp flat_reverse([[elem] | t], acc), do: flat_reverse(t, [elem | acc])
|
||||
defp flat_reverse([h | t], acc), do: flat_reverse(t, h ++ acc)
|
||||
defp flat_reverse([], acc), do: acc
|
||||
|
||||
@@ -1523,7 +1538,7 @@ defmodule Enum do
|
||||
do: Map.merge(collectable, :maps.from_list(enumerable))
|
||||
|
||||
defp into_map(enumerable, collectable),
|
||||
do: reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)
|
||||
do: Enum.reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)
|
||||
|
||||
defp into_protocol(enumerable, collectable) do
|
||||
{initial, fun} = Collectable.into(collectable)
|
||||
@@ -1566,7 +1581,7 @@ defmodule Enum do
|
||||
"""
|
||||
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
|
||||
def into(enumerable, [], transform) do
|
||||
map(enumerable, transform)
|
||||
Enum.map(enumerable, transform)
|
||||
end
|
||||
|
||||
def into(%_{} = enumerable, collectable, transform) do
|
||||
@@ -1579,9 +1594,9 @@ defmodule Enum do
|
||||
|
||||
def into(enumerable, %{} = collectable, transform) do
|
||||
if map_size(collectable) == 0 do
|
||||
enumerable |> map(transform) |> :maps.from_list()
|
||||
enumerable |> Enum.map(transform) |> :maps.from_list()
|
||||
else
|
||||
reduce(enumerable, collectable, fn entry, acc ->
|
||||
Enum.reduce(enumerable, collectable, fn entry, acc ->
|
||||
{key, val} = transform.(entry)
|
||||
Map.put(acc, key, val)
|
||||
end)
|
||||
@@ -2401,17 +2416,13 @@ defmodule Enum do
|
||||
{:ok, count, fun} when is_function(fun, 1) ->
|
||||
slice_list(fun.(enumerable), random_count(count), 1, 1)
|
||||
|
||||
# TODO: Deprecate me in Elixir v1.18.
|
||||
{:ok, count, fun} when is_function(fun, 2) ->
|
||||
fun.(random_count(count), 1)
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
fun.(random_count(count), 1, 1)
|
||||
|
||||
# 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"
|
||||
)
|
||||
|
||||
fun.(random_count(count), 1)
|
||||
|
||||
{:error, _} ->
|
||||
take_random(enumerable, 1)
|
||||
end
|
||||
@@ -2691,11 +2702,11 @@ defmodule Enum do
|
||||
iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 5, 1)
|
||||
[:a, :f, :b, :c, :d, :e, :g]
|
||||
|
||||
# Slide a range of elements towards the head of the list
|
||||
# Slide a range of elements backward
|
||||
iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 3..5, 1)
|
||||
[:a, :d, :e, :f, :b, :c, :g]
|
||||
|
||||
# Slide a range of elements towards the tail of the list
|
||||
# Slide a range of elements forward
|
||||
iex> Enum.slide([:a, :b, :c, :d, :e, :f, :g], 1..3, 5)
|
||||
[:a, :e, :f, :b, :c, :d, :g]
|
||||
|
||||
@@ -2782,7 +2793,7 @@ defmodule Enum do
|
||||
# Then at the end, we're going to reassemble and reverse them, and end up with the
|
||||
# chunks in the correct order.
|
||||
{_size, pre, post} =
|
||||
reduce(enumerable, {0, [], []}, fn item, {index, pre, post} ->
|
||||
Enum.reduce(enumerable, {0, [], []}, fn item, {index, pre, post} ->
|
||||
{pre, post} =
|
||||
cond do
|
||||
index < start -> {[item | pre], post}
|
||||
@@ -2834,19 +2845,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]
|
||||
|
||||
"""
|
||||
@@ -2866,18 +2872,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]
|
||||
|
||||
"""
|
||||
@@ -3337,7 +3338,7 @@ defmodule Enum do
|
||||
|
||||
def sort_by(enumerable, mapper, :desc) when is_function(mapper, 1) do
|
||||
enumerable
|
||||
|> reduce([], &[{&1, mapper.(&1)} | &2])
|
||||
|> Enum.reduce([], &[{&1, mapper.(&1)} | &2])
|
||||
|> List.keysort(1, :asc)
|
||||
|> List.foldl([], &[elem(&1, 0) | &2])
|
||||
end
|
||||
@@ -3442,8 +3443,6 @@ defmodule Enum do
|
||||
|
||||
Raises `ArithmeticError` if `enumerable` contains a non-numeric value.
|
||||
|
||||
If you need to apply a transformation first, consider using `Enum.sum_by/2` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sum([1, 2, 3])
|
||||
@@ -3470,47 +3469,11 @@ defmodule Enum do
|
||||
reduce(enumerable, 0, &+/2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps and sums the given `enumerable` in one pass.
|
||||
|
||||
Raises `ArithmeticError` if `mapper` returns a non-numeric value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.sum_by([%{count: 1}, %{count: 2}, %{count: 3}], fn x -> x.count end)
|
||||
6
|
||||
|
||||
iex> Enum.sum_by(1..3, fn x -> x ** 2 end)
|
||||
14
|
||||
|
||||
iex> Enum.sum_by([], fn x -> x.count end)
|
||||
0
|
||||
|
||||
Filtering can be achieved by returning `0` to ignore elements:
|
||||
|
||||
iex> Enum.sum_by([1, -2, 3], fn x -> if x > 0, do: x, else: 0 end)
|
||||
4
|
||||
|
||||
"""
|
||||
@doc since: "1.18.0"
|
||||
@spec sum_by(t, (element -> number)) :: number
|
||||
def sum_by(enumerable, mapper)
|
||||
|
||||
def sum_by(list, mapper) when is_list(list) and is_function(mapper, 1) do
|
||||
sum_by_list(list, mapper, 0)
|
||||
end
|
||||
|
||||
def sum_by(enumerable, mapper) when is_function(mapper, 1) do
|
||||
reduce(enumerable, 0, fn x, acc -> acc + mapper.(x) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the product of all elements.
|
||||
|
||||
Raises `ArithmeticError` if `enumerable` contains a non-numeric value.
|
||||
|
||||
If you need to apply a transformation first, consider using `Enum.product_by/2` instead.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.product([])
|
||||
@@ -3527,40 +3490,6 @@ defmodule Enum do
|
||||
reduce(enumerable, 1, &*/2)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps and computes the product of the given `enumerable` in one pass.
|
||||
|
||||
Raises `ArithmeticError` if `mapper` returns a non-numeric value.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.product_by([%{count: 2}, %{count: 4}, %{count: 3}], fn x -> x.count end)
|
||||
24
|
||||
|
||||
iex> Enum.product_by(1..3, fn x -> x ** 2 end)
|
||||
36
|
||||
|
||||
iex> Enum.product_by([], fn x -> x.count end)
|
||||
1
|
||||
|
||||
Filtering can be achieved by returning `1` to ignore elements:
|
||||
|
||||
iex> Enum.product_by([2, -1, 3], fn x -> if x > 0, do: x, else: 1 end)
|
||||
6
|
||||
|
||||
"""
|
||||
@doc since: "1.18.0"
|
||||
@spec product_by(t, (element -> number)) :: number
|
||||
def product_by(enumerable, mapper)
|
||||
|
||||
def product_by(list, mapper) when is_list(list) and is_function(mapper, 1) do
|
||||
product_by_list(list, mapper, 1)
|
||||
end
|
||||
|
||||
def product_by(enumerable, mapper) when is_function(mapper, 1) do
|
||||
reduce(enumerable, 1, fn x, acc -> acc * mapper.(x) end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes an `amount` of elements from the beginning or the end of the `enumerable`.
|
||||
|
||||
@@ -3611,14 +3540,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 """
|
||||
@@ -3826,9 +3750,6 @@ defmodule Enum do
|
||||
@doc """
|
||||
Enumerates the `enumerable`, removing all duplicate elements.
|
||||
|
||||
The first occurrence of each element is kept and all following
|
||||
duplicates are removed. The overall order is preserved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.uniq([1, 2, 3, 3, 2, 1])
|
||||
@@ -3854,8 +3775,7 @@ defmodule Enum do
|
||||
considered duplicates if the return value of `fun` is equal for
|
||||
both of them.
|
||||
|
||||
The first occurrence of each element is kept and all following
|
||||
duplicates are removed. The overall order is preserved.
|
||||
The first occurrence of each element is kept.
|
||||
|
||||
## Example
|
||||
|
||||
@@ -3895,7 +3815,6 @@ defmodule Enum do
|
||||
|
||||
"""
|
||||
@spec unzip(t) :: {[element], [element]}
|
||||
def unzip(enumerable)
|
||||
|
||||
def unzip([_ | _] = list) do
|
||||
:lists.reverse(list) |> unzip([], [])
|
||||
@@ -3926,12 +3845,13 @@ defmodule Enum do
|
||||
Returns the `enumerable` with each element wrapped in a tuple
|
||||
alongside its index or according to a given function.
|
||||
|
||||
If an integer offset is given as `fun_or_offset`, it will index from the given
|
||||
offset instead of from zero.
|
||||
May receive a function or an integer offset.
|
||||
|
||||
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 an `offset` is given, it will index from the given offset instead of from
|
||||
zero.
|
||||
|
||||
If a `function` is given, it will index by invoking the function for each
|
||||
element and index (zero-based) of the enumerable.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -4042,10 +3962,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`.
|
||||
@@ -4118,11 +4038,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
|
||||
@@ -4134,7 +4051,7 @@ defmodule Enum do
|
||||
|
||||
def zip_reduce(left, right, acc, reducer) when is_function(reducer, 3) do
|
||||
reduce = fn [l, r], acc -> {:cont, reducer.(l, r, acc)} end
|
||||
R.zip_with([left, right], & &1).({:cont, acc}, reduce) |> elem(1)
|
||||
Stream.zip_with([left, right], & &1).({:cont, acc}, reduce) |> elem(1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -4158,10 +4075,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)
|
||||
@@ -4171,8 +4085,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
|
||||
Stream.zip_with(enums, & &1).({:cont, acc}, &{:cont, reducer.(&1, &2)}) |> elem(1)
|
||||
end
|
||||
|
||||
## Helpers
|
||||
@@ -4345,64 +4259,6 @@ 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 | tail], acc) do
|
||||
@@ -4495,9 +4351,6 @@ defmodule Enum do
|
||||
|
||||
defp flat_map_list([head | tail], fun) do
|
||||
case fun.(head) do
|
||||
# the two first clauses are an optimization
|
||||
[] -> flat_map_list(tail, fun)
|
||||
[elem] -> [elem | flat_map_list(tail, fun)]
|
||||
list when is_list(list) -> list ++ flat_map_list(tail, fun)
|
||||
other -> to_list(other) ++ flat_map_list(tail, fun)
|
||||
end
|
||||
@@ -4639,16 +4492,8 @@ defmodule Enum do
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
enumerable |> fun.() |> slice_exact(start, amount, step, count)
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
fun.(start, amount, step)
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
# TODO: Deprecate me in Elixir v1.18.
|
||||
{:ok, count, fun} when is_function(fun, 2) ->
|
||||
IO.warn(
|
||||
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
|
||||
)
|
||||
|
||||
amount = Kernel.min(amount, count - start)
|
||||
|
||||
if step == 1 do
|
||||
@@ -4658,6 +4503,10 @@ defmodule Enum do
|
||||
|> take_every_list(amount, step - 1)
|
||||
end
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||
fun.(start, amount, step)
|
||||
|
||||
{:error, module} ->
|
||||
slice_enum(enumerable, module, start, amount, step)
|
||||
end
|
||||
@@ -4716,18 +4565,11 @@ defmodule Enum do
|
||||
|
||||
defp slice_count_and_fun(enumerable, step) do
|
||||
case Enumerable.slice(enumerable) do
|
||||
{:ok, count, fun} when is_function(fun, 1) ->
|
||||
{count, &slice_exact(fun.(enumerable), &1, &2, &3, count)}
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 3) ->
|
||||
{count, fun}
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
# TODO: Deprecate me in Elixir v1.18.
|
||||
{:ok, count, fun} when is_function(fun, 2) ->
|
||||
IO.warn(
|
||||
"#{inspect(Enumerable.impl_for(enumerable))} must return a three arity function on slice/1"
|
||||
)
|
||||
|
||||
if step == 1 do
|
||||
{count, fn start, amount, 1 -> fun.(start, amount) end}
|
||||
else
|
||||
@@ -4738,6 +4580,9 @@ defmodule Enum do
|
||||
end}
|
||||
end
|
||||
|
||||
{:ok, count, fun} when is_function(fun, 1) ->
|
||||
{count, &slice_exact(fun.(enumerable), &1, &2, &3, count)}
|
||||
|
||||
{:error, module} ->
|
||||
{list, count} =
|
||||
enumerable
|
||||
@@ -4913,16 +4758,6 @@ defmodule Enum do
|
||||
{:lists.reverse(acc), []}
|
||||
end
|
||||
|
||||
## sum_by
|
||||
|
||||
defp sum_by_list([], _, acc), do: acc
|
||||
defp sum_by_list([h | t], mapper, acc), do: sum_by_list(t, mapper, acc + mapper.(h))
|
||||
|
||||
## product_by
|
||||
|
||||
defp product_by_list([], _, acc), do: acc
|
||||
defp product_by_list([h | t], mapper, acc), do: product_by_list(t, mapper, acc * mapper.(h))
|
||||
|
||||
## take
|
||||
|
||||
defp take_list(_list, 0), do: []
|
||||
@@ -5060,72 +4895,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
|
||||
def reduce(%{__struct__: Range, first: first, last: last} = range, acc, fun) 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) do
|
||||
if step > 0 do
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
else
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
end
|
||||
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 - 1, &2)}
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
def slice(%{__struct__: Range, first: first, last: last} = range) 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
|
||||
|
||||
+212
-419
File diff suppressed because it is too large
Load Diff
+55
-478
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule File do
|
||||
@moduledoc ~S"""
|
||||
This module contains functions to manipulate files.
|
||||
@@ -266,15 +262,8 @@ defmodule File do
|
||||
* `:enotdir` - a component of `path` is not a directory;
|
||||
on some platforms, `:enoent` is returned instead
|
||||
|
||||
## Examples
|
||||
|
||||
File.mkdir("test/unit")
|
||||
#=> :ok
|
||||
|
||||
File.mkdir("non/existing")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec mkdir(Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec mkdir(Path.t()) :: :ok | {:error, posix}
|
||||
def mkdir(path) do
|
||||
:file.make_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
@@ -282,14 +271,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `mkdir/1`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.mkdir!("test/unit")
|
||||
#=> :ok
|
||||
|
||||
File.mkdir!("non/existing")
|
||||
** (File.Error) could not make directory "non/existing": no such file or directory
|
||||
"""
|
||||
@spec mkdir!(Path.t()) :: :ok
|
||||
def mkdir!(path) do
|
||||
@@ -317,17 +298,9 @@ defmodule File do
|
||||
directories of `path`
|
||||
* `:enospc` - there is no space left on the device
|
||||
* `:enotdir` - a component of `path` is not a directory
|
||||
* `:eperm` - missed required permissions
|
||||
|
||||
## Examples
|
||||
|
||||
File.mkdir_p("non/existing/parents")
|
||||
#=> :ok
|
||||
|
||||
File.mkdir_p("/usr/sbin/temp")
|
||||
#=> {:error, :eperm}
|
||||
"""
|
||||
@spec mkdir_p(Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec mkdir_p(Path.t()) :: :ok | {:error, posix}
|
||||
def mkdir_p(path) do
|
||||
do_mkdir_p(IO.chardata_to_string(path))
|
||||
end
|
||||
@@ -337,23 +310,24 @@ defmodule File do
|
||||
end
|
||||
|
||||
defp do_mkdir_p(path) do
|
||||
parent = Path.dirname(path)
|
||||
|
||||
if parent == path do
|
||||
if dir?(path) do
|
||||
:ok
|
||||
else
|
||||
case do_mkdir_p(parent) do
|
||||
:ok ->
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} ->
|
||||
if dir?(path), do: :ok, else: {:error, :enotdir}
|
||||
parent = Path.dirname(path)
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
if parent == path do
|
||||
# Protect against infinite loop
|
||||
{:error, :einval}
|
||||
else
|
||||
_ = do_mkdir_p(parent)
|
||||
|
||||
e ->
|
||||
e
|
||||
case :file.make_dir(path) do
|
||||
{:error, :eexist} = error ->
|
||||
if dir?(path), do: :ok, else: error
|
||||
|
||||
other ->
|
||||
other
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -361,14 +335,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `mkdir_p/1`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.mkdir_p!("non/existing/parents")
|
||||
#=> :ok
|
||||
|
||||
File.mkdir_p!("/usr/sbin/temp")
|
||||
** (File.Error) could not make directory (with -p) "/usr/sbin/temp": not owner
|
||||
"""
|
||||
@spec mkdir_p!(Path.t()) :: :ok
|
||||
def mkdir_p!(path) do
|
||||
@@ -399,16 +365,8 @@ defmodule File do
|
||||
* `:enomem` - there is not enough memory for the contents of the file
|
||||
|
||||
You can use `:file.format_error/1` to get a descriptive string of the error.
|
||||
|
||||
## Examples
|
||||
|
||||
File.read("hello.txt")
|
||||
#=> {:ok, "world"}
|
||||
|
||||
File.read("non_existing.txt")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec read(Path.t()) :: {:ok, binary} | {:error, posix | :badarg | :terminated | :system_limit}
|
||||
@spec read(Path.t()) :: {:ok, binary} | {:error, posix}
|
||||
def read(path) do
|
||||
:file.read_file(IO.chardata_to_string(path))
|
||||
end
|
||||
@@ -416,14 +374,6 @@ defmodule File do
|
||||
@doc """
|
||||
Returns a binary with the contents of the given filename,
|
||||
or raises a `File.Error` exception if an error occurs.
|
||||
|
||||
## Examples
|
||||
|
||||
File.read!("hello.txt")
|
||||
#=> "world"
|
||||
|
||||
File.read!("non_existing.txt")
|
||||
** (File.Error) could not read file "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec read!(Path.t()) :: binary
|
||||
def read!(path) do
|
||||
@@ -457,16 +407,8 @@ defmodule File do
|
||||
|
||||
Note: Since file times are stored in POSIX time format on most operating systems,
|
||||
it is faster to retrieve file information with the `time: :posix` option.
|
||||
|
||||
## Examples
|
||||
|
||||
File.stat("hello.txt")
|
||||
#=> {:ok, %File.Stat{...}}
|
||||
|
||||
File.stat("non_existing.txt", time: :posix)
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix | :badarg}
|
||||
@spec stat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
|
||||
def stat(path, opts \\ []) do
|
||||
opts = Keyword.put_new(opts, :time, :universal)
|
||||
|
||||
@@ -482,14 +424,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `stat/2` but returns the `File.Stat` directly,
|
||||
or raises a `File.Error` exception if an error is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
File.stat!("hello.txt")
|
||||
#=> %File.Stat{...}
|
||||
|
||||
File.stat!("non_existing.txt", time: :posix)
|
||||
** (File.Error) could not read file stats "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec stat!(Path.t(), stat_options) :: File.Stat.t()
|
||||
def stat!(path, opts \\ []) do
|
||||
@@ -526,16 +460,8 @@ defmodule File do
|
||||
|
||||
Note: Since file times are stored in POSIX time format on most operating systems,
|
||||
it is faster to retrieve file information with the `time: :posix` option.
|
||||
|
||||
## Examples
|
||||
|
||||
File.lstat("link_to_hello")
|
||||
#=> {:ok, %File.Stat{type: :symlink, ...}}
|
||||
|
||||
File.lstat("non_existing.txt", time: :posix)
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix | :badarg}
|
||||
@spec lstat(Path.t(), stat_options) :: {:ok, File.Stat.t()} | {:error, posix}
|
||||
def lstat(path, opts \\ []) do
|
||||
opts = Keyword.put_new(opts, :time, :universal)
|
||||
|
||||
@@ -551,14 +477,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `lstat/2` but returns the `File.Stat` struct directly,
|
||||
or raises a `File.Error` exception if an error is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
File.lstat!("link_to_hello")
|
||||
#=> %File.Stat{type: :symlink, ...}
|
||||
|
||||
File.lstat!("non_existing.txt", time: :posix)
|
||||
** (File.Error) could not read file stats "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec lstat!(Path.t(), stat_options) :: File.Stat.t()
|
||||
def lstat!(path, opts \\ []) do
|
||||
@@ -588,16 +506,9 @@ defmodule File do
|
||||
* `:enoent` - path does not exist
|
||||
* `:enotsup` - symbolic links are not supported on the current platform
|
||||
|
||||
## Examples
|
||||
|
||||
File.read_link("link_to_hello")
|
||||
#=> {:ok, "hello.txt"}
|
||||
|
||||
File.read_link("hello.txt")
|
||||
#=> {:error, :einval}
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec read_link(Path.t()) :: {:ok, binary} | {:error, posix | :badarg}
|
||||
@spec read_link(Path.t()) :: {:ok, binary} | {:error, posix}
|
||||
def read_link(path) do
|
||||
case path |> IO.chardata_to_string() |> :file.read_link() do
|
||||
{:ok, target} -> {:ok, IO.chardata_to_string(target)}
|
||||
@@ -608,14 +519,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `read_link/1` but returns the target directly,
|
||||
or raises a `File.Error` exception if an error is returned.
|
||||
|
||||
## Examples
|
||||
|
||||
File.read_link!("link_to_hello")
|
||||
#=> "hello.txt"
|
||||
|
||||
File.read_link!("hello.txt")
|
||||
** (File.Error) could not read link "hello.txt": invalid argument
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec read_link!(Path.t()) :: binary
|
||||
@@ -632,16 +535,8 @@ defmodule File do
|
||||
@doc """
|
||||
Writes the given `File.Stat` back to the file system at the given
|
||||
path. Returns `:ok` or `{:error, reason}`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.write_stat("hello.txt", new_stat)
|
||||
#=> :ok
|
||||
|
||||
File.write_stat("non_existing.txt", new_stat)
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec write_stat(Path.t(), File.Stat.t(), stat_options) :: :ok | {:error, posix | :badarg}
|
||||
@spec write_stat(Path.t(), File.Stat.t(), stat_options) :: :ok | {:error, posix}
|
||||
def write_stat(path, stat, opts \\ []) do
|
||||
opts = Keyword.put_new(opts, :time, :universal)
|
||||
:file.write_file_info(IO.chardata_to_string(path), File.Stat.to_record(stat), opts)
|
||||
@@ -650,14 +545,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `write_stat/3` but raises a `File.Error` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.write_stat!("hello.txt", new_stat)
|
||||
#=> :ok
|
||||
|
||||
File.write_stat!("non_existing.txt", new_stat)
|
||||
** (File.Error) could not write file stats "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec write_stat!(Path.t(), File.Stat.t(), stat_options) :: :ok
|
||||
def write_stat!(path, stat, opts \\ []) do
|
||||
@@ -698,15 +585,14 @@ defmodule File do
|
||||
#=> :ok
|
||||
|
||||
"""
|
||||
@spec touch(Path.t(), erlang_time() | posix_time()) ::
|
||||
:ok | {:error, posix | :badarg | :terminated | :system_limit}
|
||||
@spec touch(Path.t(), erlang_time() | posix_time()) :: :ok | {:error, posix}
|
||||
def touch(path, time \\ System.os_time(:second))
|
||||
|
||||
def touch(path, time) when is_tuple(time) do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
with {:error, :enoent} <- :elixir_utils.change_universal_time(path, time),
|
||||
:ok <- write(path, "", [:raw, :append]),
|
||||
:ok <- write(path, "", [:append]),
|
||||
do: :elixir_utils.change_universal_time(path, time)
|
||||
end
|
||||
|
||||
@@ -714,7 +600,7 @@ defmodule File do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
with {:error, :enoent} <- :elixir_utils.change_posix_time(path, time),
|
||||
:ok <- write(path, "", [:raw, :append]),
|
||||
:ok <- write(path, "", [:append]),
|
||||
do: :elixir_utils.change_posix_time(path, time)
|
||||
end
|
||||
|
||||
@@ -753,17 +639,9 @@ defmodule File do
|
||||
Returns `:ok` if successful, `{:error, reason}` otherwise.
|
||||
If the operating system does not support hard links, returns
|
||||
`{:error, :enotsup}`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.ln("hello.txt", "hard_link_to_hello")
|
||||
#=> :ok
|
||||
|
||||
File.ln("non_existing.txt", "link")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec ln(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def ln(existing, new) do
|
||||
:file.make_link(IO.chardata_to_string(existing), IO.chardata_to_string(new))
|
||||
end
|
||||
@@ -771,14 +649,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `ln/2` but raises a `File.LinkError` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.ln!("hello.txt", "hard_link_to_hello")
|
||||
#=> :ok
|
||||
|
||||
File.ln!("non_existing.txt", "link")
|
||||
** (File.LinkError) could not create hard link from "non_existing.txt" to "link": no such file or directory
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln!(Path.t(), Path.t()) :: :ok
|
||||
@@ -802,23 +672,9 @@ defmodule File do
|
||||
Returns `:ok` if successful, `{:error, reason}` otherwise.
|
||||
If the operating system does not support symlinks, returns
|
||||
`{:error, :enotsup}`.
|
||||
|
||||
Creates a symlink even if the `existing` target actually doesn't exist
|
||||
|
||||
## Examples
|
||||
|
||||
File.ln_s("hello.txt", "link_to_hello")
|
||||
#=> :ok
|
||||
|
||||
File.ln_s("non_existing.txt", "link")
|
||||
#=> :ok
|
||||
|
||||
# Returns error if `new` file exists
|
||||
File.ln_s("non_existing.txt", "existed_link")
|
||||
#=> {:error, :eexist}
|
||||
"""
|
||||
@doc since: "1.5.0"
|
||||
@spec ln_s(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec ln_s(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def ln_s(existing, new) do
|
||||
:file.make_symlink(IO.chardata_to_string(existing), IO.chardata_to_string(new))
|
||||
end
|
||||
@@ -826,15 +682,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `ln_s/2` but raises a `File.LinkError` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.ln_s!("hello.txt", "link_to_hello")
|
||||
#=> :ok
|
||||
|
||||
# Raises if `new` file exists
|
||||
File.ln_s!("non_existing.txt", "existed_link")
|
||||
** (File.LinkError) could not create symlink from "non_existing.txt" to "existed_link": file already exists
|
||||
"""
|
||||
@spec ln_s!(Path.t(), Path.t()) :: :ok
|
||||
def ln_s!(existing, new) do
|
||||
@@ -872,17 +719,9 @@ defmodule File do
|
||||
|
||||
Typical error reasons are the same as in `open/2`,
|
||||
`read/1` and `write/3`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.copy("hello.txt", "hello_copy.txt")
|
||||
#=> {:ok, 6}
|
||||
|
||||
File.copy("non_existing.txt", "copy.txt")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec copy(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
|
||||
{:ok, non_neg_integer} | {:error, posix | :badarg | :terminated}
|
||||
{:ok, non_neg_integer} | {:error, posix}
|
||||
def copy(source, destination, bytes_count \\ :infinity) do
|
||||
source = normalize_path_or_io_device(source)
|
||||
destination = normalize_path_or_io_device(destination)
|
||||
@@ -893,14 +732,6 @@ defmodule File do
|
||||
@doc """
|
||||
The same as `copy/3` but raises a `File.CopyError` exception if it fails.
|
||||
Returns the `bytes_copied` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.copy!("hello.txt", "hello_copy.txt")
|
||||
#=> 6
|
||||
|
||||
File.copy!("non_existing.txt", "copy.txt")
|
||||
** (File.CopyError) could not copy from "non_existing.txt" to "copy.txt": no such file or directory
|
||||
"""
|
||||
@spec copy!(Path.t() | io_device, Path.t() | io_device, pos_integer | :infinity) ::
|
||||
non_neg_integer
|
||||
@@ -934,17 +765,13 @@ defmodule File do
|
||||
|
||||
# Rename file "a.txt" to "b.txt"
|
||||
File.rename("a.txt", "b.txt")
|
||||
#=> :ok
|
||||
|
||||
# Rename directory "samples" to "tmp"
|
||||
File.rename("samples", "tmp")
|
||||
#=> :ok
|
||||
|
||||
File.rename("non_existing.txt", "existing.txt")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@doc since: "1.1.0"
|
||||
@spec rename(Path.t(), Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec rename(Path.t(), Path.t()) :: :ok | {:error, posix}
|
||||
def rename(source, destination) do
|
||||
source = IO.chardata_to_string(source)
|
||||
destination = IO.chardata_to_string(destination)
|
||||
@@ -954,14 +781,6 @@ defmodule File do
|
||||
@doc """
|
||||
The same as `rename/2` but raises a `File.RenameError` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.rename!("samples", "tmp")
|
||||
#=> :ok
|
||||
|
||||
File.rename!("non_existing.txt", "existing.txt")
|
||||
** (File.RenameError) could not rename from "non_existing.txt" to "existing.txt": no such file or directory
|
||||
"""
|
||||
@doc since: "1.9.0"
|
||||
@spec rename!(Path.t(), Path.t()) :: :ok
|
||||
@@ -979,7 +798,7 @@ defmodule File do
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@doc """
|
||||
Copies the contents of `source_file` to `destination_file` preserving its modes.
|
||||
|
||||
`source_file` must be a file or a symbolic link to one. `destination_file` must
|
||||
@@ -1006,33 +825,12 @@ defmodule File do
|
||||
The default callback returns `true`. On earlier versions, this callback could be
|
||||
given as third argument, but such behavior is now deprecated.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cp("hello.txt", "hello_copy.txt")
|
||||
#=> :ok
|
||||
|
||||
File.cp("hello.txt", "hello_copy.txt", on_conflict: fn source, destination ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
|
||||
end)
|
||||
#=> :ok
|
||||
|
||||
File.cp("non_existing.txt", "copy.txt")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec cp(Path.t(), Path.t(), on_conflict: on_conflict_callback) ::
|
||||
:ok | {:error, posix | :badarg | :terminated}
|
||||
@spec cp(Path.t(), Path.t(), on_conflict: on_conflict_callback) :: :ok | {:error, posix}
|
||||
def cp(source_file, destination_file, options \\ [])
|
||||
|
||||
# TODO: Deprecate me on Elixir v1.19
|
||||
def cp(source_file, destination_file, callback) when is_function(callback, 2) do
|
||||
IO.warn_once(
|
||||
{__MODULE__, :cp},
|
||||
fn ->
|
||||
"passing a callback to File.cp/3 is deprecated, pass it as a on_conflict: callback option instead"
|
||||
end,
|
||||
3
|
||||
)
|
||||
|
||||
cp(source_file, destination_file, on_conflict: callback)
|
||||
end
|
||||
|
||||
@@ -1053,22 +851,9 @@ defmodule File do
|
||||
Path.expand(p1) !== Path.expand(p2)
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@doc """
|
||||
The same as `cp/3`, but raises a `File.CopyError` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cp!("hello.txt", "hello_copy.txt")
|
||||
#=> :ok
|
||||
|
||||
File.cp!("hello.txt", "hello_copy.txt", on_conflict: fn source, destination ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
|
||||
end)
|
||||
#=> :ok
|
||||
|
||||
File.cp!("non_existing.txt", "copy.txt")
|
||||
** (File.CopyError) could not copy from "non_existing.txt" to "copy.txt": no such file or directory
|
||||
"""
|
||||
@spec cp!(Path.t(), Path.t(), on_conflict: on_conflict_callback) :: :ok
|
||||
def cp!(source_file, destination_file, options \\ []) do
|
||||
@@ -1131,39 +916,26 @@ defmodule File do
|
||||
|
||||
# Copies file "a.txt" to "b.txt"
|
||||
File.cp_r("a.txt", "b.txt")
|
||||
#=> {:ok, ["b.txt"]}
|
||||
|
||||
# Copies all files in "samples" to "tmp"
|
||||
File.cp_r("samples", "tmp")
|
||||
#=> {:ok, ["z.txt", "y.txt", "x.txt]}
|
||||
|
||||
# Same as before, but asks the user how to proceed in case of conflicts
|
||||
File.cp_r("samples", "tmp", on_conflict: fn source, destination ->
|
||||
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n"
|
||||
end)
|
||||
#=> {:ok, ["z.txt", "y.txt", "x.txt]}
|
||||
|
||||
File.cp_r("non_existing.txt", "copy.txt")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec cp_r(Path.t(), Path.t(),
|
||||
on_conflict: on_conflict_callback,
|
||||
dereference_symlinks: boolean()
|
||||
) ::
|
||||
{:ok, [binary]} | {:error, posix | :badarg | :terminated, binary}
|
||||
{:ok, [binary]} | {:error, posix, binary}
|
||||
|
||||
def cp_r(source, destination, options \\ [])
|
||||
|
||||
# TODO: Deprecate me on Elixir v1.19
|
||||
def cp_r(source, destination, callback) when is_function(callback, 2) do
|
||||
IO.warn_once(
|
||||
{__MODULE__, :cp_r},
|
||||
fn ->
|
||||
"passing a callback to File.cp_r/3 is deprecated, pass it as a on_conflict: callback option instead"
|
||||
end,
|
||||
3
|
||||
)
|
||||
|
||||
cp_r(source, destination, on_conflict: callback)
|
||||
end
|
||||
|
||||
@@ -1190,17 +962,6 @@ defmodule File do
|
||||
@doc """
|
||||
The same as `cp_r/3`, but raises a `File.CopyError` exception if it fails.
|
||||
Returns the list of copied files otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cp_r!("a.txt", "b.txt")
|
||||
#=> ["b.txt"]
|
||||
|
||||
File.cp_r!("samples", "tmp")
|
||||
#=> ["z.txt", "y.txt", "x.txt]
|
||||
|
||||
File.cp_r!("non_existing.txt", "copy.txt")
|
||||
** (File.CopyError) could not copy recursively from "non_existing.txt" to "copy.txt". non_existing.txt: no such file or directory
|
||||
"""
|
||||
@spec cp_r!(Path.t(), Path.t(),
|
||||
on_conflict: on_conflict_callback,
|
||||
@@ -1224,13 +985,7 @@ defmodule File do
|
||||
defp do_cp_r(src, dest, on_conflict, dereference?, acc) when is_list(acc) do
|
||||
case :elixir_utils.read_link_type(src) do
|
||||
{:ok, :regular} ->
|
||||
case do_cp_file(src, dest, on_conflict, acc) do
|
||||
# we don't have a way to make a distinction between a non-existing src
|
||||
# or dest being a non-existing dir in the case of :enoent,
|
||||
# but we already know that src exists here.
|
||||
{:error, :enoent, _} -> {:error, :enoent, dest}
|
||||
other -> other
|
||||
end
|
||||
do_cp_file(src, dest, on_conflict, acc)
|
||||
|
||||
{:ok, :symlink} ->
|
||||
case :file.read_link(src) do
|
||||
@@ -1367,19 +1122,9 @@ defmodule File do
|
||||
the parent directories
|
||||
* `:eisdir` - the named file is a directory
|
||||
|
||||
Check `File.open/2` for the list of available `modes`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.write("hello.txt", "world!")
|
||||
#=> :ok
|
||||
|
||||
File.write("temp", "world!")
|
||||
#=> {:error, :eisdir}
|
||||
|
||||
Check `File.open/2` for other available options.
|
||||
"""
|
||||
@spec write(Path.t(), iodata, [mode]) ::
|
||||
:ok | {:error, posix | :badarg | :terminated | :system_limit}
|
||||
@spec write(Path.t(), iodata, [mode]) :: :ok | {:error, posix}
|
||||
def write(path, content, modes \\ []) do
|
||||
modes = normalize_modes(modes, false)
|
||||
:file.write_file(IO.chardata_to_string(path), content, modes)
|
||||
@@ -1388,14 +1133,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `write/3` but raises a `File.Error` exception if it fails.
|
||||
Returns `:ok` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.write!("hello.txt", "world!")
|
||||
#=> :ok
|
||||
|
||||
File.write!("temp", "world!")
|
||||
** (File.Error) could not write to file "temp": illegal operation on a directory
|
||||
"""
|
||||
@spec write!(Path.t(), iodata, [mode]) :: :ok
|
||||
def write!(path, content, modes \\ []) do
|
||||
@@ -1434,8 +1171,9 @@ defmodule File do
|
||||
|
||||
File.rm("tmp_dir/")
|
||||
#=> {:error, :eperm}
|
||||
|
||||
"""
|
||||
@spec rm(Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec rm(Path.t()) :: :ok | {:error, posix}
|
||||
def rm(path) do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
@@ -1473,14 +1211,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `rm/1`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.rm!("file.txt")
|
||||
#=> :ok
|
||||
|
||||
File.rm!("non_existing/")
|
||||
** (File.Error) could not remove file "non_existing/": no such file or directory
|
||||
"""
|
||||
@spec rm!(Path.t()) :: :ok
|
||||
def rm!(path) do
|
||||
@@ -1509,8 +1239,9 @@ defmodule File do
|
||||
|
||||
File.rmdir("file.txt")
|
||||
#=> {:error, :enotdir}
|
||||
|
||||
"""
|
||||
@spec rmdir(Path.t()) :: :ok | {:error, posix | :badarg}
|
||||
@spec rmdir(Path.t()) :: :ok | {:error, posix}
|
||||
def rmdir(path) do
|
||||
:file.del_dir(IO.chardata_to_string(path))
|
||||
end
|
||||
@@ -1518,19 +1249,8 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `rmdir/1`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.rmdir!("tmp_dir")
|
||||
#=> :ok
|
||||
|
||||
File.rmdir!("non_empty_dir")
|
||||
** (File.Error) could not remove directory "non_empty_dir": directory is not empty
|
||||
|
||||
File.rmdir!("file.txt")
|
||||
** (File.Error) could not remove directory "file.txt": not a directory
|
||||
"""
|
||||
@spec rmdir!(Path.t()) :: :ok
|
||||
@spec rmdir!(Path.t()) :: :ok | {:error, posix}
|
||||
def rmdir!(path) do
|
||||
case rmdir(path) do
|
||||
:ok ->
|
||||
@@ -1561,10 +1281,8 @@ defmodule File do
|
||||
File.rm_rf("unknown")
|
||||
#=> {:ok, []}
|
||||
|
||||
File.rm_rf("/tmp")
|
||||
#=> {:error, :eperm, "/tmp"}
|
||||
"""
|
||||
@spec rm_rf(Path.t()) :: {:ok, [binary]} | {:error, posix | :badarg, binary}
|
||||
@spec rm_rf(Path.t()) :: {:ok, [binary]} | {:error, posix, binary}
|
||||
def rm_rf(path) do
|
||||
{major, _} = :os.type()
|
||||
|
||||
@@ -1658,18 +1376,7 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
Same as `rm_rf/1` but raises a `File.Error` exception in case of failures,
|
||||
otherwise returns the list of files or directories removed.
|
||||
|
||||
## Examples
|
||||
|
||||
File.rm_rf!("samples")
|
||||
#=> ["samples", "samples/1.txt"]
|
||||
|
||||
File.rm_rf!("unknown")
|
||||
#=> []
|
||||
|
||||
File.rm_rf!("/tmp")
|
||||
** (File.Error) could not remove files and directories recursively from "/tmp": not owner
|
||||
otherwise the list of files or directories removed.
|
||||
"""
|
||||
@spec rm_rf!(Path.t()) :: [binary]
|
||||
def rm_rf!(path) do
|
||||
@@ -1773,10 +1480,8 @@ defmodule File do
|
||||
File.close(file)
|
||||
|
||||
"""
|
||||
@spec open(Path.t(), [mode | :ram]) ::
|
||||
{:ok, io_device | file_descriptor} | {:error, posix | :badarg | :system_limit}
|
||||
@spec open(Path.t(), (io_device | file_descriptor -> res)) ::
|
||||
{:ok, res} | {:error, posix | :badarg | :system_limit}
|
||||
@spec open(Path.t(), [mode | :ram]) :: {:ok, io_device | file_descriptor} | {:error, posix}
|
||||
@spec open(Path.t(), (io_device | file_descriptor -> res)) :: {:ok, res} | {:error, posix}
|
||||
when res: var
|
||||
def open(path, modes_or_function \\ [])
|
||||
|
||||
@@ -1803,17 +1508,16 @@ defmodule File do
|
||||
is given. For this reason, we do not recommend passing
|
||||
`:delayed_write` to this function.
|
||||
|
||||
See `open/2` for the list of available `modes`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.open("file.txt", [:read, :write], fn file ->
|
||||
IO.read(file, :line)
|
||||
end)
|
||||
#=> {:ok, "file content"}
|
||||
|
||||
See `open/2` for the list of available `modes`.
|
||||
"""
|
||||
@spec open(Path.t(), [mode | :ram], (io_device | file_descriptor -> res)) ::
|
||||
{:ok, res} | {:error, posix | :badarg | :system_limit}
|
||||
{:ok, res} | {:error, posix}
|
||||
when res: var
|
||||
def open(path, modes, function) when is_list(modes) and is_function(function, 1) do
|
||||
case open(path, modes) do
|
||||
@@ -1834,13 +1538,6 @@ defmodule File do
|
||||
could not be opened. Returns the IO device otherwise.
|
||||
|
||||
See `open/2` for the list of available modes.
|
||||
|
||||
## Examples
|
||||
|
||||
File.open!("file.txt", fn file ->
|
||||
IO.read(file, :line)
|
||||
end)
|
||||
#=> "file content"
|
||||
"""
|
||||
@spec open!(Path.t(), [mode | :ram]) :: io_device | file_descriptor
|
||||
@spec open!(Path.t(), (io_device | file_descriptor -> res)) :: res when res: var
|
||||
@@ -1861,13 +1558,6 @@ defmodule File do
|
||||
If it succeeds opening the file, it returns the `function` result on the IO device.
|
||||
|
||||
See `open/2` for the list of available `modes`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.open!("file.txt", [:read, :write], fn file ->
|
||||
IO.read(file, :line)
|
||||
end)
|
||||
#=> "file content"
|
||||
"""
|
||||
@spec open!(Path.t(), [mode | :ram], (io_device | file_descriptor -> res)) :: res when res: var
|
||||
def open!(path, modes, function) do
|
||||
@@ -1887,17 +1577,8 @@ defmodule File do
|
||||
if read permissions do not exist for the parent directories of the
|
||||
current directory. For this reason, returns `{:ok, cwd}` in case
|
||||
of success, `{:error, reason}` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cwd()
|
||||
#=> {:ok, "/Users/user/elixir/elixir_lang"}
|
||||
|
||||
# Missing read permission for one of the parents of the current directory
|
||||
File.cwd()
|
||||
#=> {:error, :eacces}
|
||||
"""
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix | :badarg}
|
||||
@spec cwd() :: {:ok, binary} | {:error, posix}
|
||||
def cwd() do
|
||||
case :file.get_cwd() do
|
||||
{:ok, base} -> {:ok, IO.chardata_to_string(fix_drive_letter(base))}
|
||||
@@ -1916,11 +1597,6 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
The same as `cwd/0`, but raises a `File.Error` exception if it fails.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cwd!()
|
||||
#=> "/Users/user/elixir/elixir_lang"
|
||||
"""
|
||||
@spec cwd!() :: binary
|
||||
def cwd!() do
|
||||
@@ -1943,30 +1619,14 @@ defmodule File do
|
||||
of `System.cmd/3` and `Port.open/2`.
|
||||
|
||||
Returns `:ok` if successful, `{:error, reason}` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cd("bin")
|
||||
#=> :ok
|
||||
|
||||
File.cd("non_existing_dir")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec cd(Path.t()) :: :ok | {:error, posix | :badarg | :no_translation}
|
||||
@spec cd(Path.t()) :: :ok | {:error, posix}
|
||||
def cd(path) do
|
||||
:file.set_cwd(IO.chardata_to_string(path))
|
||||
end
|
||||
|
||||
@doc """
|
||||
The same as `cd/1`, but raises a `File.Error` exception if it fails.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cd!("bin")
|
||||
#=> :ok
|
||||
|
||||
File.cd!("non_existing_dir")
|
||||
** (File.Error) could not set current working directory to "non_existing_dir": no such file or directory
|
||||
"""
|
||||
@spec cd!(Path.t()) :: :ok
|
||||
def cd!(path) do
|
||||
@@ -1995,14 +1655,6 @@ defmodule File do
|
||||
|
||||
Raises an error if retrieving or changing the current
|
||||
directory fails.
|
||||
|
||||
## Examples
|
||||
|
||||
File.cd!("bin", fn -> do_something() end)
|
||||
#=> :result_of_do_something
|
||||
|
||||
File.cd!("non_existing_dir", fn -> do_something() end)
|
||||
** (File.Error) could not set current working directory to "non_existing_dir": no such file or directory
|
||||
"""
|
||||
@spec cd!(Path.t(), (-> res)) :: res when res: var
|
||||
def cd!(path, function) do
|
||||
@@ -2026,16 +1678,8 @@ defmodule File do
|
||||
|
||||
Returns `{:ok, files}` in case of success,
|
||||
`{:error, reason}` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
File.ls("bin")
|
||||
#=> {:ok, ["iex", "elixir"]}
|
||||
|
||||
File.ls("non_existing_dir")
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec ls(Path.t()) :: {:ok, [binary]} | {:error, posix | :badarg | {:no_translation, binary}}
|
||||
@spec ls(Path.t()) :: {:ok, [binary]} | {:error, posix}
|
||||
def ls(path \\ ".") do
|
||||
case :file.list_dir(IO.chardata_to_string(path)) do
|
||||
{:ok, file_list} -> {:ok, Enum.map(file_list, &IO.chardata_to_string/1)}
|
||||
@@ -2045,14 +1689,6 @@ defmodule File do
|
||||
|
||||
@doc """
|
||||
The same as `ls/1` but raises a `File.Error` exception in case of an error.
|
||||
|
||||
## Examples
|
||||
|
||||
File.ls!("bin")
|
||||
#=> ["iex", "elixir"]
|
||||
|
||||
File.ls!("non_existing_dir")
|
||||
** (File.Error) could not list directory "non_existing_dir": no such file or directory
|
||||
"""
|
||||
@spec ls!(Path.t()) :: [binary]
|
||||
def ls!(path \\ ".") do
|
||||
@@ -2075,15 +1711,6 @@ defmodule File do
|
||||
Note that if the option `:delayed_write` was used when opening the file,
|
||||
`close/1` might return an old write error and not even try to close the file.
|
||||
See `open/2` for more information.
|
||||
|
||||
## Examples
|
||||
|
||||
{:ok, file} = File.open("hello.txt")
|
||||
File.close(file)
|
||||
#=> :ok
|
||||
|
||||
File.close(:not_an_io_device)
|
||||
#=> {:error, :badarg}
|
||||
"""
|
||||
@spec close(io_device) :: :ok | {:error, posix | :badarg | :terminated}
|
||||
def close(io_device) do
|
||||
@@ -2149,17 +1776,15 @@ defmodule File do
|
||||
that is skipped whenever enumerating the stream (if both `:read_offset`
|
||||
and `:trim_bom` are given, the offset is skipped after the BOM).
|
||||
|
||||
See `Stream.run/1` for an example of streaming into a file.
|
||||
|
||||
## Examples
|
||||
|
||||
# Read a utf8 text file which may include BOM
|
||||
File.stream!("./test/test.txt", [:trim_bom, encoding: :utf8])
|
||||
#=> %File.Stream{path: "./test/test.txt", ...}
|
||||
|
||||
# Read in 2048 byte chunks rather than lines
|
||||
File.stream!("./test/test.data", 2048)
|
||||
#=> %File.Stream{path: "./test/test.data", ...}
|
||||
|
||||
See `Stream.run/1` for an example of streaming into a file.
|
||||
"""
|
||||
@spec stream!(Path.t(), :line | pos_integer, [stream_mode]) :: File.Stream.t()
|
||||
def stream!(path, line_or_bytes, modes)
|
||||
@@ -2199,16 +1824,8 @@ defmodule File do
|
||||
write, read and execute permission to the owner
|
||||
and both read and execute permission to group
|
||||
and others.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chmod("hello.txt", 0o755)
|
||||
#=> :ok
|
||||
|
||||
File.chmod("non_existing.txt", 0o755)
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec chmod(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
|
||||
@spec chmod(Path.t(), non_neg_integer) :: :ok | {:error, posix}
|
||||
def chmod(path, mode) do
|
||||
:file.change_mode(IO.chardata_to_string(path), mode)
|
||||
end
|
||||
@@ -2216,14 +1833,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chmod/2`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chmod!("hello.txt", 0o755)
|
||||
#=> :ok
|
||||
|
||||
File.chmod!("non_existing.txt", 0o755)
|
||||
** (File.Error) could not change mode for "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec chmod!(Path.t(), non_neg_integer) :: :ok
|
||||
def chmod!(path, mode) do
|
||||
@@ -2243,16 +1852,8 @@ defmodule File do
|
||||
Changes the group given by the group ID `gid`
|
||||
for a given `file`. Returns `:ok` on success, or
|
||||
`{:error, reason}` on failure.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chgrp("hello.txt", 10)
|
||||
#=> :ok
|
||||
|
||||
File.chgrp("non_existing.txt", 10)
|
||||
#=> {:error, :enoent}
|
||||
"""
|
||||
@spec chgrp(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
|
||||
@spec chgrp(Path.t(), non_neg_integer) :: :ok | {:error, posix}
|
||||
def chgrp(path, gid) do
|
||||
:file.change_group(IO.chardata_to_string(path), gid)
|
||||
end
|
||||
@@ -2260,14 +1861,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chgrp/2`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chgrp!("hello.txt", 10)
|
||||
#=> :ok
|
||||
|
||||
File.chgrp!("non_existing.txt", 10)
|
||||
** (File.Error) could not change group for "non_existing.txt": no such file or directory
|
||||
"""
|
||||
@spec chgrp!(Path.t(), non_neg_integer) :: :ok
|
||||
def chgrp!(path, gid) do
|
||||
@@ -2287,16 +1880,8 @@ defmodule File do
|
||||
Changes the owner given by the user ID `uid`
|
||||
for a given `file`. Returns `:ok` on success,
|
||||
or `{:error, reason}` on failure.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chown("hello.txt", 15)
|
||||
#=> :ok
|
||||
|
||||
File.chown("secret.txt", 15)
|
||||
#=> {:error, :eperm}
|
||||
"""
|
||||
@spec chown(Path.t(), non_neg_integer) :: :ok | {:error, posix | :badarg}
|
||||
@spec chown(Path.t(), non_neg_integer) :: :ok | {:error, posix}
|
||||
def chown(path, uid) do
|
||||
:file.change_owner(IO.chardata_to_string(path), uid)
|
||||
end
|
||||
@@ -2304,14 +1889,6 @@ defmodule File do
|
||||
@doc """
|
||||
Same as `chown/2`, but raises a `File.Error` exception in case of failure.
|
||||
Otherwise `:ok`.
|
||||
|
||||
## Examples
|
||||
|
||||
File.chown!("hello.txt", 15)
|
||||
#=> :ok
|
||||
|
||||
File.chown!("secret.txt", 15)
|
||||
** (File.Error) could not change owner for "secret.txt": not owner
|
||||
"""
|
||||
@spec chown!(Path.t(), non_neg_integer) :: :ok
|
||||
def chown!(path, uid) do
|
||||
|
||||
@@ -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`.
|
||||
|
||||
+20
-34
@@ -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
|
||||
@@ -143,8 +139,6 @@ defmodule Float do
|
||||
iex> Float.parse("56.5xyz")
|
||||
{56.5, "xyz"}
|
||||
|
||||
iex> Float.parse(".12")
|
||||
:error
|
||||
iex> Float.parse("pi")
|
||||
:error
|
||||
iex> Float.parse("1.7976931348623159e+308")
|
||||
@@ -168,50 +162,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>>)
|
||||
|
||||
# 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
|
||||
acc
|
||||
|> add_dot(dot?)
|
||||
|> :lists.reverse()
|
||||
|> :erlang.list_to_float()
|
||||
: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
|
||||
float =
|
||||
acc
|
||||
|> add_dot(dot?)
|
||||
|> :lists.reverse()
|
||||
|> :erlang.list_to_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`.
|
||||
@@ -591,14 +575,18 @@ defmodule Float do
|
||||
Returns a charlist which corresponds to the shortest text representation
|
||||
of the given float.
|
||||
|
||||
It uses the algorithm presented in "Ryū: fast float-to-string conversion"
|
||||
in Proceedings of the SIGPLAN '2018 Conference on Programming Language
|
||||
Design and Implementation.
|
||||
The underlying algorithm changes depending on the Erlang/OTP version:
|
||||
|
||||
* For OTP >= 24, it uses the algorithm presented in "Ryū: fast
|
||||
float-to-string conversion" in Proceedings of the SIGPLAN '2018
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
* For OTP < 24, it uses the algorithm presented in "Printing Floating-Point
|
||||
Numbers Quickly and Accurately" in Proceedings of the SIGPLAN '1996
|
||||
Conference on Programming Language Design and Implementation.
|
||||
|
||||
For a configurable representation, use `:erlang.float_to_list/2`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_charlist(7.0)
|
||||
@@ -606,7 +594,7 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec to_charlist(float) :: charlist
|
||||
def to_charlist(float) do
|
||||
def to_charlist(float) when is_float(float) do
|
||||
:erlang.float_to_list(float, [:short])
|
||||
end
|
||||
|
||||
@@ -626,8 +614,6 @@ defmodule Float do
|
||||
|
||||
For a configurable representation, use `:erlang.float_to_binary/2`.
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Float.to_string(7.0)
|
||||
@@ -635,7 +621,7 @@ defmodule Float do
|
||||
|
||||
"""
|
||||
@spec to_string(float) :: String.t()
|
||||
def to_string(float) do
|
||||
def to_string(float) when is_float(float) do
|
||||
:erlang.float_to_binary(float, [:short])
|
||||
end
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -50,8 +46,8 @@ defmodule GenEvent do
|
||||
@callback init(args :: term) ::
|
||||
{:ok, state}
|
||||
| {:ok, state, :hibernate}
|
||||
| {:error, reason :: term}
|
||||
when state: term
|
||||
| {:error, reason :: any}
|
||||
when state: any
|
||||
|
||||
@callback handle_event(event :: term, state :: term) ::
|
||||
{:ok, new_state}
|
||||
|
||||
@@ -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,16 +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
|
||||
* `: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_spec-1-function`) 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.
|
||||
|
||||
@@ -271,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,
|
||||
@@ -429,7 +415,7 @@ defmodule GenServer do
|
||||
Let's see how we could use those functions for debugging the stack server
|
||||
we defined earlier.
|
||||
|
||||
iex> {:ok, pid} = Stack.start_link("")
|
||||
iex> {:ok, pid} = Stack.start_link([])
|
||||
iex> :sys.statistics(pid, true) # turn on collecting process statistics
|
||||
iex> :sys.trace(pid, true) # turn on event printing
|
||||
iex> Stack.push(pid, 1)
|
||||
@@ -537,8 +523,8 @@ defmodule GenServer do
|
||||
{:ok, state}
|
||||
| {:ok, state, timeout | :hibernate | {:continue, continue_arg :: term}}
|
||||
| :ignore
|
||||
| {:stop, reason :: term}
|
||||
when state: term
|
||||
| {:stop, reason :: any}
|
||||
when state: any
|
||||
|
||||
@doc """
|
||||
Invoked to handle synchronous `call/3` messages. `call/3` will block until a
|
||||
@@ -859,7 +845,7 @@ defmodule GenServer do
|
||||
quote location: :keep, bind_quoted: [opts: opts] do
|
||||
@behaviour GenServer
|
||||
|
||||
if not Module.has_attribute?(__MODULE__, :doc) do
|
||||
unless Module.has_attribute?(__MODULE__, :doc) do
|
||||
@doc """
|
||||
Returns a specification to start this module under a supervisor.
|
||||
|
||||
@@ -959,7 +945,7 @@ defmodule GenServer do
|
||||
end
|
||||
|
||||
defmacro __before_compile__(env) do
|
||||
if not Module.defines?(env.module, {:init, 1}) do
|
||||
unless Module.defines?(env.module, {:init, 1}) do
|
||||
message = """
|
||||
function init/1 required by behaviour GenServer is not implemented \
|
||||
(in module #{inspect(env.module)}).
|
||||
@@ -1032,7 +1018,7 @@ defmodule GenServer do
|
||||
or `:ignore`, the process is terminated and this function returns
|
||||
`{:error, reason}` or `:ignore`, respectively.
|
||||
"""
|
||||
@spec start_link(module, term, options) :: on_start
|
||||
@spec start_link(module, any, options) :: on_start
|
||||
def start_link(module, init_arg, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:link, module, init_arg, options)
|
||||
end
|
||||
@@ -1042,7 +1028,7 @@ defmodule GenServer do
|
||||
|
||||
See `start_link/3` for more information.
|
||||
"""
|
||||
@spec start(module, term, options) :: on_start
|
||||
@spec start(module, any, options) :: on_start
|
||||
def start(module, init_arg, options \\ []) when is_atom(module) and is_list(options) do
|
||||
do_start(:nolink, module, init_arg, options)
|
||||
end
|
||||
@@ -1112,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
|
||||
|
||||
+171
-246
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
import Kernel, except: [inspect: 1]
|
||||
import Inspect.Algebra
|
||||
|
||||
@@ -64,11 +60,9 @@ defprotocol Inspect do
|
||||
|
||||
* `:except` - remove the given fields when inspecting.
|
||||
|
||||
* `:optional` - (since v1.14.0) a list of fields that should not be
|
||||
included when they match their default value. This can be used to
|
||||
simplify the struct representation at the cost of hiding
|
||||
information. Since v1.19.0, the `:all` atom can be passed to
|
||||
mark all fields as optional.
|
||||
* `:optional` - (since v1.14.0) do not include a field if it
|
||||
matches its default value. This can be used to simplify the
|
||||
struct representation at the cost of hiding information.
|
||||
|
||||
Whenever `:only` or `:except` are used to restrict fields,
|
||||
the struct will be printed using the `#User<...>` notation,
|
||||
@@ -84,25 +78,15 @@ defprotocol Inspect do
|
||||
#=> #User<id: 1, name: "Jane", ...>
|
||||
|
||||
If you use only the `:optional` option, the struct will still be
|
||||
printed as a valid struct.
|
||||
|
||||
defmodule Point do
|
||||
@derive {Inspect, optional: [:z]}
|
||||
defstruct [x: 0, y: 0, z: 0]
|
||||
end
|
||||
|
||||
inspect(%Point{x: 1})
|
||||
%Point{x: 1, y: 0}
|
||||
printed as `%User{...}`.
|
||||
|
||||
## Custom implementation
|
||||
|
||||
You can also define your custom protocol implementation by
|
||||
defining the `inspect/2` function. The function receives the
|
||||
entity to be inspected followed by the inspecting options,
|
||||
represented by the struct `Inspect.Opts` and it must return
|
||||
an algebra document alongside the updated options (or, optionally,
|
||||
just the algebra document). Building of the algebra document
|
||||
is done with `Inspect.Algebra`.
|
||||
represented by the struct `Inspect.Opts`. Building of the
|
||||
algebra document is done with `Inspect.Algebra`.
|
||||
|
||||
Many times, inspecting a structure can be implemented in function
|
||||
of existing entities. For example, here is `MapSet`'s `inspect/2`
|
||||
@@ -112,19 +96,16 @@ defprotocol Inspect do
|
||||
import Inspect.Algebra
|
||||
|
||||
def inspect(map_set, opts) do
|
||||
{doc, opts} = to_doc_with_opts(MapSet.to_list(map_set), opts)
|
||||
{concat(["MapSet.new(", doc, ")"]), opts}
|
||||
concat(["MapSet.new(", Inspect.List.inspect(MapSet.to_list(map_set), opts), ")"])
|
||||
end
|
||||
end
|
||||
|
||||
First [`to_doc_with_opts/2`](`Inspect.Algebra.to_doc_with_opts/2`) is
|
||||
used to convert another data structure into its algebra document and
|
||||
then [`concat/1`](`Inspect.Algebra.concat/1`) concatenates algebra
|
||||
documents together.
|
||||
|
||||
The [`concat/1`](`Inspect.Algebra.concat/1`) function comes from
|
||||
`Inspect.Algebra` and it concatenates algebra documents together.
|
||||
In the example above it is concatenating the string `"MapSet.new("`,
|
||||
the document returned by `to_doc_with_opts/2`, and the final string `")"`.
|
||||
Therefore, the MapSet with the numbers 1, 2, and 3 will be printed as:
|
||||
the document returned by `Inspect.Algebra.to_doc/2`, and the final
|
||||
string `")"`. Therefore, the MapSet with the numbers 1, 2, and 3
|
||||
will be printed as:
|
||||
|
||||
iex> MapSet.new([1, 2, 3], fn x -> x * 2 end)
|
||||
MapSet.new([2, 4, 6])
|
||||
@@ -153,114 +134,11 @@ defprotocol Inspect do
|
||||
|
||||
Inspect.MapSet.inspect(MapSet.new(), %Inspect.Opts{})
|
||||
|
||||
Note that, from Elixir v1.19, the inspect protocol was augmented to
|
||||
allow a two-element tuple with the document and the updated options
|
||||
to be returned from the protocol.
|
||||
"""
|
||||
|
||||
# Handle structs in Any
|
||||
@fallback_to_any true
|
||||
|
||||
@impl true
|
||||
defmacro __deriving__(module, options) do
|
||||
info = Macro.struct_info!(module, __CALLER__)
|
||||
fields = Enum.sort(Enum.map(info, & &1.field) -- [:__exception__, :__struct__])
|
||||
|
||||
only = Keyword.get(options, :only, fields)
|
||||
except = Keyword.get(options, :except, [])
|
||||
|
||||
:ok = validate_option(:only, only, fields, module)
|
||||
:ok = validate_option(:except, except, fields, module)
|
||||
|
||||
optional =
|
||||
case Keyword.get(options, :optional, []) do
|
||||
:all ->
|
||||
fields
|
||||
|
||||
optional ->
|
||||
:ok = validate_option(:optional, optional, fields, module)
|
||||
optional
|
||||
end
|
||||
|
||||
inspect_module =
|
||||
if fields == Enum.sort(only) and except == [] do
|
||||
Inspect.Map
|
||||
else
|
||||
Inspect.Any
|
||||
end
|
||||
|
||||
filtered_fields =
|
||||
fields
|
||||
|> Enum.reject(&(&1 in except))
|
||||
|> Enum.filter(&(&1 in only))
|
||||
|
||||
filtered_guard =
|
||||
quote do
|
||||
var!(field) in unquote(filtered_fields)
|
||||
end
|
||||
|
||||
field_guard =
|
||||
if optional == [] do
|
||||
filtered_guard
|
||||
else
|
||||
optional_map =
|
||||
for field <- optional, into: %{} do
|
||||
default = Enum.find(info, %{}, &(&1.field == field)) |> Map.get(:default, nil)
|
||||
{field, default}
|
||||
end
|
||||
|
||||
quote do
|
||||
unquote(filtered_guard) and
|
||||
not case unquote(Macro.escape(optional_map)) do
|
||||
%{^var!(field) => var!(default)} ->
|
||||
var!(default) == Map.get(var!(struct), var!(field))
|
||||
|
||||
%{} ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
defimpl Inspect, for: unquote(module) do
|
||||
def inspect(var!(struct), var!(opts)) do
|
||||
var!(infos) =
|
||||
for %{field: var!(field)} = var!(info) <- unquote(module).__info__(:struct),
|
||||
unquote(field_guard),
|
||||
do: var!(info)
|
||||
|
||||
var!(name) = Macro.inspect_atom(:literal, unquote(module))
|
||||
|
||||
unquote(inspect_module).inspect_as_struct(
|
||||
var!(struct),
|
||||
var!(name),
|
||||
var!(infos),
|
||||
var!(opts)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_option(option, option_list, fields, module) do
|
||||
if not is_list(option_list) do
|
||||
raise ArgumentError,
|
||||
"invalid value #{Kernel.inspect(option_list)} in #{Kernel.inspect(option)} " <>
|
||||
"when deriving the Inspect protocol for #{Kernel.inspect(module)} " <>
|
||||
"(expected a list)"
|
||||
end
|
||||
|
||||
case option_list -- fields do
|
||||
[] ->
|
||||
:ok
|
||||
|
||||
unknown_fields ->
|
||||
raise ArgumentError,
|
||||
"unknown fields #{Kernel.inspect(unknown_fields)} in #{Kernel.inspect(option)} " <>
|
||||
"when deriving the Inspect protocol for #{Kernel.inspect(module)}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts `term` into an algebra document.
|
||||
|
||||
@@ -269,8 +147,7 @@ defprotocol Inspect do
|
||||
`Inspect.Algebra.to_doc/2` should be preferred as it handles structs
|
||||
and exceptions.
|
||||
"""
|
||||
@spec inspect(t, Inspect.Opts.t()) ::
|
||||
Inspect.Algebra.t() | {Inspect.Algebra.t(), Inspect.Opts.t()}
|
||||
@spec inspect(t, Inspect.Opts.t()) :: Inspect.Algebra.t()
|
||||
def inspect(term, opts)
|
||||
end
|
||||
|
||||
@@ -278,7 +155,7 @@ defimpl Inspect, for: Atom do
|
||||
require Macro
|
||||
|
||||
def inspect(atom, opts) do
|
||||
color_doc(Macro.inspect_atom(:literal, atom), color_key(atom), opts)
|
||||
color(Macro.inspect_atom(:literal, atom), color_key(atom), opts)
|
||||
end
|
||||
|
||||
defp color_key(atom) when is_boolean(atom), do: :boolean
|
||||
@@ -298,7 +175,7 @@ defimpl Inspect, for: BitString do
|
||||
{escaped, _} -> [?", escaped, ?", " <> ..."]
|
||||
end
|
||||
|
||||
color_doc(IO.iodata_to_binary(inspected), :string, opts)
|
||||
color(IO.iodata_to_binary(inspected), :string, opts)
|
||||
else
|
||||
inspect_bitstring(term, opts)
|
||||
end
|
||||
@@ -309,16 +186,14 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defp inspect_bitstring("", opts) do
|
||||
color_doc("<<>>", :binary, opts)
|
||||
color("<<>>", :binary, opts)
|
||||
end
|
||||
|
||||
defp inspect_bitstring(bitstring, %{limit: limit} = opts) do
|
||||
left = color_doc("<<", :binary, opts)
|
||||
right = color_doc(">>", :binary, opts)
|
||||
inner = each_bit(bitstring, limit, opts)
|
||||
doc = group(concat(concat(left, nest(inner, 2)), right))
|
||||
new_limit = if limit == :infinity, do: limit, else: max(0, limit - byte_size(bitstring))
|
||||
{doc, %{opts | limit: new_limit}}
|
||||
defp inspect_bitstring(bitstring, opts) do
|
||||
left = color("<<", :binary, opts)
|
||||
right = color(">>", :binary, opts)
|
||||
inner = each_bit(bitstring, opts.limit, opts)
|
||||
group(concat(concat(left, nest(inner, 2)), right))
|
||||
end
|
||||
|
||||
defp each_bit(_, 0, _) do
|
||||
@@ -326,7 +201,7 @@ defimpl Inspect, for: BitString do
|
||||
end
|
||||
|
||||
defp each_bit(<<>>, _counter, _opts) do
|
||||
Inspect.Algebra.empty()
|
||||
:doc_nil
|
||||
end
|
||||
|
||||
defp each_bit(<<h::8>>, _counter, opts) do
|
||||
@@ -353,7 +228,7 @@ end
|
||||
|
||||
defimpl Inspect, for: List do
|
||||
def inspect([], opts) do
|
||||
color_doc("[]", :list, opts)
|
||||
color("[]", :list, opts)
|
||||
end
|
||||
|
||||
# TODO: Remove :char_list and :as_char_lists handling on v2.0
|
||||
@@ -384,56 +259,32 @@ defimpl Inspect, for: List do
|
||||
lists
|
||||
end
|
||||
|
||||
open = color_doc("[", :list, opts)
|
||||
sep = color_doc(",", :list, opts)
|
||||
close = color_doc("]", :list, opts)
|
||||
open = color("[", :list, opts)
|
||||
sep = color(",", :list, opts)
|
||||
close = color("]", :list, opts)
|
||||
|
||||
cond do
|
||||
(lists == :as_charlists and unicode_list?(term, printable_limit)) or
|
||||
(lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
{split, tail} =
|
||||
if is_integer(printable_limit) do
|
||||
case Enum.split(term, printable_limit) do
|
||||
{split, []} -> {split, []}
|
||||
{split, _} -> {split, " ++ ..."}
|
||||
end
|
||||
else
|
||||
{term, []}
|
||||
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
inspected =
|
||||
case Identifier.escape(IO.chardata_to_string(term), ?", printable_limit) do
|
||||
{escaped, ""} -> [?~, ?c, ?", escaped, ?"]
|
||||
{escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
|
||||
end
|
||||
|
||||
{escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
|
||||
inspected = [?~, ?c, ?", escaped, ?" | tail]
|
||||
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
|
||||
color(IO.iodata_to_binary(inspected), :charlist, opts)
|
||||
|
||||
keyword?(term) ->
|
||||
container_doc_with_opts(open, term, close, opts, &keyword/2,
|
||||
separator: sep,
|
||||
break: :strict
|
||||
)
|
||||
container_doc(open, term, close, opts, &keyword/2, separator: sep, break: :strict)
|
||||
|
||||
true ->
|
||||
container_doc_with_opts(open, term, close, opts, &to_doc_with_opts/2, separator: sep)
|
||||
container_doc(open, term, close, opts, &to_doc/2, separator: sep)
|
||||
end
|
||||
end
|
||||
|
||||
defp unicode_list?(_, 0), do: true
|
||||
|
||||
defp unicode_list?([char | rest], counter)
|
||||
when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
|
||||
do: unicode_list?(rest, decrement(counter))
|
||||
|
||||
defp unicode_list?([], _counter), do: true
|
||||
defp unicode_list?(_, _counter), do: false
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc false
|
||||
def keyword({key, value}, opts) do
|
||||
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
|
||||
{doc, opts} = to_doc_with_opts(value, opts)
|
||||
{concat(key, concat(" ", doc)), opts}
|
||||
key = color(Macro.inspect_atom(:key, key), :atom, opts)
|
||||
concat(key, concat(" ", to_doc(value, opts)))
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -450,28 +301,16 @@ end
|
||||
|
||||
defimpl Inspect, for: Tuple do
|
||||
def inspect(tuple, opts) do
|
||||
open = color_doc("{", :tuple, opts)
|
||||
sep = color_doc(",", :tuple, opts)
|
||||
close = color_doc("}", :tuple, opts)
|
||||
open = color("{", :tuple, opts)
|
||||
sep = color(",", :tuple, opts)
|
||||
close = color("}", :tuple, opts)
|
||||
container_opts = [separator: sep, break: :flex]
|
||||
|
||||
container_doc_with_opts(
|
||||
open,
|
||||
Tuple.to_list(tuple),
|
||||
close,
|
||||
opts,
|
||||
&to_doc_with_opts/2,
|
||||
container_opts
|
||||
)
|
||||
container_doc(open, Tuple.to_list(tuple), close, opts, &to_doc/2, container_opts)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
inspect_as_map(map, opts)
|
||||
end
|
||||
|
||||
def inspect_as_map(map, opts) do
|
||||
list =
|
||||
if Keyword.get(opts.custom_options, :sort_maps) do
|
||||
map |> Map.to_list() |> :lists.sort()
|
||||
@@ -483,36 +322,34 @@ defimpl Inspect, for: Map do
|
||||
if Inspect.List.keyword?(list) do
|
||||
&Inspect.List.keyword/2
|
||||
else
|
||||
sep = color_doc(" => ", :map, opts)
|
||||
sep = color(" => ", :map, opts)
|
||||
&to_assoc(&1, &2, sep)
|
||||
end
|
||||
|
||||
map_container_doc(list, "", opts, fun)
|
||||
end
|
||||
|
||||
def inspect_as_struct(map, name, infos, opts) do
|
||||
def inspect(map, name, infos, opts) do
|
||||
fun = fn %{field: field}, opts -> Inspect.List.keyword({field, Map.get(map, field)}, opts) end
|
||||
map_container_doc(infos, name, opts, fun)
|
||||
end
|
||||
|
||||
defp to_assoc({key, value}, opts, sep) do
|
||||
{key_doc, opts} = to_doc_with_opts(key, opts)
|
||||
{value_doc, opts} = to_doc_with_opts(value, opts)
|
||||
{concat(concat(key_doc, sep), value_doc), opts}
|
||||
concat(concat(to_doc(key, opts), sep), to_doc(value, opts))
|
||||
end
|
||||
|
||||
defp map_container_doc(list, name, opts, fun) do
|
||||
open = color_doc("%" <> name <> "{", :map, opts)
|
||||
sep = color_doc(",", :map, opts)
|
||||
close = color_doc("}", :map, opts)
|
||||
container_doc_with_opts(open, list, close, opts, fun, separator: sep, break: :strict)
|
||||
open = color("%" <> name <> "{", :map, opts)
|
||||
sep = color(",", :map, opts)
|
||||
close = color("}", :map, opts)
|
||||
container_doc(open, list, close, opts, fun, separator: sep, break: :strict)
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Integer do
|
||||
def inspect(term, %Inspect.Opts{base: base} = opts) do
|
||||
inspected = Integer.to_string(term, base_to_value(base)) |> prepend_prefix(base)
|
||||
color_doc(inspected, :number, opts)
|
||||
color(inspected, :number, opts)
|
||||
end
|
||||
|
||||
defp base_to_value(base) do
|
||||
@@ -550,10 +387,10 @@ defimpl Inspect, for: Float do
|
||||
if abs >= 1.0 and abs < 1.0e16 and trunc(float) == float do
|
||||
[Integer.to_string(trunc(float)), ?., ?0]
|
||||
else
|
||||
Float.to_charlist(float)
|
||||
:erlang.float_to_list(float, [:short])
|
||||
end
|
||||
|
||||
color_doc(IO.iodata_to_binary(formatted), :number, opts)
|
||||
color(IO.iodata_to_binary(formatted), :number, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -563,9 +400,9 @@ defimpl Inspect, for: Regex do
|
||||
:error ->
|
||||
concat([
|
||||
"Regex.compile!(",
|
||||
to_doc(regex.source, opts),
|
||||
Inspect.BitString.inspect(regex.source, opts),
|
||||
", ",
|
||||
to_doc(regex_opts, opts),
|
||||
Inspect.List.inspect(regex_opts, opts),
|
||||
")"
|
||||
])
|
||||
|
||||
@@ -576,7 +413,7 @@ defimpl Inspect, for: Regex do
|
||||
|> Identifier.escape(?/, :infinity, &escape_map/1)
|
||||
|
||||
source = IO.iodata_to_binary([?~, ?r, ?/, escaped, ?/, translated_opts])
|
||||
color_doc(source, :regex, opts)
|
||||
color(source, :regex, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -589,7 +426,6 @@ defimpl Inspect, for: Regex do
|
||||
defp translate_options([:firstline | t], acc), do: translate_options(t, [?f | acc])
|
||||
defp translate_options([:ungreedy | t], acc), do: translate_options(t, [?U | acc])
|
||||
defp translate_options([:multiline | t], acc), do: translate_options(t, [?m | acc])
|
||||
defp translate_options([:export | t], acc), do: translate_options(t, [?E | acc])
|
||||
defp translate_options([], acc), do: acc
|
||||
defp translate_options(_t, _acc), do: :error
|
||||
|
||||
@@ -662,6 +498,36 @@ defimpl Inspect, for: Function do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Inspect.Error do
|
||||
@impl true
|
||||
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
|
||||
message = Exception.message(inspect_error)
|
||||
format_output(message, stacktrace)
|
||||
end
|
||||
|
||||
defp format_output(message, [_ | _] = stacktrace) do
|
||||
stacktrace = Exception.format_stacktrace(stacktrace)
|
||||
|
||||
"""
|
||||
#Inspect.Error<
|
||||
#{Inspect.Error.pad(message, 2)}
|
||||
|
||||
Stacktrace:
|
||||
|
||||
#{stacktrace}
|
||||
>\
|
||||
"""
|
||||
end
|
||||
|
||||
defp format_output(message, []) do
|
||||
"""
|
||||
#Inspect.Error<
|
||||
#{Inspect.Error.pad(message, 2)}
|
||||
>\
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: PID do
|
||||
def inspect(pid, _opts) do
|
||||
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
|
||||
@@ -682,19 +548,97 @@ defimpl Inspect, for: Reference do
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Any do
|
||||
def inspect(%module{} = struct, opts) do
|
||||
info =
|
||||
for %{field: field} = map <- module.__info__(:struct),
|
||||
field != :__exception__,
|
||||
do: map
|
||||
defmacro __deriving__(module, struct, options) do
|
||||
fields = Enum.sort(Map.keys(struct) -- [:__exception__, :__struct__])
|
||||
|
||||
Inspect.Map.inspect_as_struct(struct, Macro.inspect_atom(:literal, module), info, opts)
|
||||
only = Keyword.get(options, :only, fields)
|
||||
except = Keyword.get(options, :except, [])
|
||||
optional = Keyword.get(options, :optional, [])
|
||||
|
||||
:ok = validate_option(:only, only, fields, module)
|
||||
:ok = validate_option(:except, except, fields, module)
|
||||
:ok = validate_option(:optional, optional, fields, module)
|
||||
|
||||
inspect_module =
|
||||
if fields == Enum.sort(only) and except == [] do
|
||||
Inspect.Map
|
||||
else
|
||||
Inspect.Any
|
||||
end
|
||||
|
||||
filtered_fields =
|
||||
fields
|
||||
|> Enum.reject(&(&1 in except))
|
||||
|> Enum.filter(&(&1 in only))
|
||||
|
||||
optional? =
|
||||
if optional == [] do
|
||||
false
|
||||
else
|
||||
optional_map = for field <- optional, into: %{}, do: {field, Map.fetch!(struct, field)}
|
||||
|
||||
quote do
|
||||
case unquote(Macro.escape(optional_map)) do
|
||||
%{^var!(field) => var!(default)} ->
|
||||
var!(default) == Map.get(var!(struct), var!(field))
|
||||
|
||||
%{} ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
quote do
|
||||
defimpl Inspect, for: unquote(module) do
|
||||
def inspect(var!(struct), var!(opts)) do
|
||||
var!(infos) =
|
||||
for %{field: var!(field)} = var!(info) <- unquote(module).__info__(:struct),
|
||||
var!(field) in unquote(filtered_fields) and not unquote(optional?),
|
||||
do: var!(info)
|
||||
|
||||
var!(name) = Macro.inspect_atom(:literal, unquote(module))
|
||||
unquote(inspect_module).inspect(var!(struct), var!(name), var!(infos), var!(opts))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inspect_as_struct(map, name, infos, opts) do
|
||||
open = color_doc("#" <> name <> "<", :map, opts)
|
||||
sep = color_doc(",", :map, opts)
|
||||
close = color_doc(">", :map, opts)
|
||||
defp validate_option(option, option_list, fields, module) do
|
||||
case option_list -- fields do
|
||||
[] ->
|
||||
:ok
|
||||
|
||||
unknown_fields ->
|
||||
raise ArgumentError,
|
||||
"unknown fields #{Kernel.inspect(unknown_fields)} in #{Kernel.inspect(option)} " <>
|
||||
"when deriving the Inspect protocol for #{Kernel.inspect(module)}"
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(%module{} = struct, opts) do
|
||||
try do
|
||||
{module.__struct__(), module.__info__(:struct)}
|
||||
rescue
|
||||
_ -> Inspect.Map.inspect(struct, opts)
|
||||
else
|
||||
{dunder, fields} ->
|
||||
if Map.keys(dunder) == Map.keys(struct) do
|
||||
infos =
|
||||
for %{field: field} = info <- fields,
|
||||
field not in [:__struct__, :__exception__],
|
||||
do: info
|
||||
|
||||
Inspect.Map.inspect(struct, Macro.inspect_atom(:literal, module), infos, opts)
|
||||
else
|
||||
Inspect.Map.inspect(struct, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def inspect(map, name, infos, opts) do
|
||||
open = color("#" <> name <> "<", :map, opts)
|
||||
sep = color(",", :map, opts)
|
||||
close = color(">", :map, opts)
|
||||
|
||||
fun = fn
|
||||
%{field: field}, opts -> Inspect.List.keyword({field, Map.get(map, field)}, opts)
|
||||
@@ -705,25 +649,6 @@ defimpl Inspect, for: Any do
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Range do
|
||||
import Inspect.Algebra
|
||||
import Kernel, except: [inspect: 2]
|
||||
|
||||
def inspect(first..last//1, opts) when last >= first do
|
||||
concat([to_doc(first, opts), "..", to_doc(last, opts)])
|
||||
end
|
||||
|
||||
def inspect(first..last//step, opts) do
|
||||
concat([to_doc(first, opts), "..", to_doc(last, opts), "//", to_doc(step, opts)])
|
||||
end
|
||||
|
||||
# TODO: Remove me on v2.0
|
||||
def inspect(%{__struct__: Range, first: first, last: last} = range, opts) do
|
||||
step = if first <= last, do: 1, else: -1
|
||||
inspect(Map.put(range, :step, step), opts)
|
||||
end
|
||||
end
|
||||
|
||||
require Protocol
|
||||
|
||||
Protocol.derive(
|
||||
|
||||
+205
-442
File diff suppressed because it is too large
Load Diff
@@ -1,90 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
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
|
||||
|
||||
defimpl Inspect, for: Inspect.Error do
|
||||
@impl true
|
||||
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
|
||||
message = Exception.message(inspect_error)
|
||||
format_output(message, stacktrace)
|
||||
end
|
||||
|
||||
defp format_output(message, [_ | _] = stacktrace) do
|
||||
stacktrace = Exception.format_stacktrace(stacktrace)
|
||||
|
||||
"""
|
||||
#Inspect.Error<
|
||||
#{Inspect.Error.pad(message, 2)}
|
||||
|
||||
Stacktrace:
|
||||
|
||||
#{stacktrace}
|
||||
>\
|
||||
"""
|
||||
end
|
||||
|
||||
defp format_output(message, []) do
|
||||
"""
|
||||
#Inspect.Error<
|
||||
#{Inspect.Error.pad(message, 2)}
|
||||
>\
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,3 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
# SPDX-FileCopyrightText: 2012 Plataformatec
|
||||
|
||||
defmodule Integer do
|
||||
@moduledoc """
|
||||
Functions for working with integers.
|
||||
@@ -193,16 +189,13 @@ defmodule Integer do
|
||||
@spec digits(integer, pos_integer) :: [integer, ...]
|
||||
def digits(integer, base \\ 10)
|
||||
when is_integer(integer) and is_integer(base) and base >= 2 do
|
||||
case integer do
|
||||
0 -> [0]
|
||||
_integer -> digits(integer, base, [])
|
||||
end
|
||||
do_digits(integer, base, [])
|
||||
end
|
||||
|
||||
defp digits(0, _base, acc), do: acc
|
||||
defp do_digits(integer, base, acc) when abs(integer) < base, do: [integer | acc]
|
||||
|
||||
defp digits(integer, base, acc),
|
||||
do: digits(div(integer, base), base, [rem(integer, base) | acc])
|
||||
defp do_digits(integer, base, acc),
|
||||
do: do_digits(div(integer, base), base, [rem(integer, base) | acc])
|
||||
|
||||
@doc """
|
||||
Returns the integer represented by the ordered `digits`.
|
||||
@@ -224,16 +217,16 @@ defmodule Integer do
|
||||
"""
|
||||
@spec undigits([integer], pos_integer) :: integer
|
||||
def undigits(digits, base \\ 10) when is_list(digits) and is_integer(base) and base >= 2 do
|
||||
undigits(digits, base, 0)
|
||||
do_undigits(digits, base, 0)
|
||||
end
|
||||
|
||||
defp undigits([], _base, acc), do: acc
|
||||
defp do_undigits([], _base, acc), do: acc
|
||||
|
||||
defp undigits([digit | _], base, _) when is_integer(digit) and digit >= base,
|
||||
defp do_undigits([digit | _], base, _) when is_integer(digit) and digit >= base,
|
||||
do: raise(ArgumentError, "invalid digit #{digit} in base #{base}")
|
||||
|
||||
defp undigits([digit | tail], base, acc) when is_integer(digit),
|
||||
do: undigits(tail, base, acc * base + digit)
|
||||
defp do_undigits([digit | tail], base, acc) when is_integer(digit),
|
||||
do: do_undigits(tail, base, acc * base + digit)
|
||||
|
||||
@doc """
|
||||
Parses a text representation of an integer.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user