mirror of
https://github.com/leptos-rs/leptos.git
synced 2025-12-27 16:54:41 -05:00
Compare commits
1 Commits
4063
...
workspace-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0af181594 |
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -29,15 +29,11 @@ Steps to reproduce the behavior:
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Next Steps**
|
||||
|
||||
- [ ] I will make a PR
|
||||
- [ ] I would like to make a PR, but need help getting started
|
||||
- [ ] I want someone else to take the time to fix this
|
||||
- [ ] This is a low priority for me and is just shared for your information
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
28
.github/workflows/autofix.yml
vendored
28
.github/workflows/autofix.yml
vendored
@@ -21,19 +21,33 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with: {toolchain: "nightly-2025-04-16", components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
with: {toolchain: nightly, components: "rustfmt, clippy", target: "wasm32-unknown-unknown", rustflags: ""}
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Install cargo-all-features
|
||||
run: cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- name: Format the workspace
|
||||
run: cargo fmt --all
|
||||
- name: Clippy the workspace
|
||||
run: cargo all-features clippy --allow-dirty --fix --lib --no-deps
|
||||
- run: |
|
||||
echo "Formatting the workspace"
|
||||
cargo fmt --all
|
||||
|
||||
echo "Running Clippy against each member's features (default features included)"
|
||||
for member in $(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | .name'); do
|
||||
echo "Working on member $member":
|
||||
echo -e "\tdefault-features/no-features:"
|
||||
# this will also run on members with no features or default features
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member"
|
||||
|
||||
features=$(cargo metadata --no-deps --format-version 1 | jq -r ".packages[] | select(.name == \"$member\") | .features | keys[]")
|
||||
for feature in $features; do
|
||||
if [ "$feature" = "default" ]; then
|
||||
continue
|
||||
fi
|
||||
echo -e "\tfeature $feature"
|
||||
cargo clippy --allow-dirty --fix --lib --package "$member" --features "$feature"
|
||||
done
|
||||
done
|
||||
- uses: autofix-ci/action@v1.3.1
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
|
||||
30
.github/workflows/ci-changed-examples.yml
vendored
Normal file
30
.github/workflows/ci-changed-examples.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: CI Changed Examples
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
jobs:
|
||||
get-example-changed:
|
||||
uses: ./.github/workflows/get-example-changed.yml
|
||||
get-matrix:
|
||||
needs: [get-example-changed]
|
||||
uses: ./.github/workflows/get-changed-examples-matrix.yml
|
||||
with:
|
||||
example_changed: ${{ fromJSON(needs.get-example-changed.outputs.example_changed) }}
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-example-changed, get-matrix]
|
||||
if: needs.get-example-changed.outputs.example_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
27
.github/workflows/ci-examples.yml
vendored
Normal file
27
.github/workflows/ci-examples.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: CI Examples
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
get-examples-matrix:
|
||||
uses: ./.github/workflows/get-examples-matrix.yml
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-leptos-changed, get-examples-matrix]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-examples-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
cargo_make_task: "ci"
|
||||
toolchain: stable
|
||||
31
.github/workflows/ci-semver.yml
vendored
Normal file
31
.github/workflows/ci-semver.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: CI semver
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
test:
|
||||
needs: [get-leptos-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true' && github.event.pull_request.labels[0].name != 'breaking'
|
||||
name: Run semver check (nightly-2024-08-01)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
with:
|
||||
rust-toolchain: nightly-2024-08-01
|
||||
49
.github/workflows/ci.yml
vendored
49
.github/workflows/ci.yml
vendored
@@ -4,26 +4,17 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- leptos_0.6
|
||||
- leptos_0.8
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
get-leptos-changed:
|
||||
uses: ./.github/workflows/get-leptos-changed.yml
|
||||
get-leptos-matrix:
|
||||
uses: ./.github/workflows/get-leptos-matrix.yml
|
||||
get-example-changed:
|
||||
uses: ./.github/workflows/get-example-changed.yml
|
||||
get-examples-matrix:
|
||||
uses: ./.github/workflows/get-examples-matrix.yml
|
||||
test-members:
|
||||
name: CI (members)
|
||||
test:
|
||||
name: CI
|
||||
needs: [get-leptos-changed, get-leptos-matrix]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
|
||||
strategy:
|
||||
@@ -32,37 +23,5 @@ jobs:
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
test-examples:
|
||||
name: CI (examples)
|
||||
needs: [test-members, get-examples-matrix]
|
||||
if: ${{ success() }}
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-examples-matrix.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
test-only-examples:
|
||||
name: CI (examples)
|
||||
needs: [get-leptos-changed, get-example-changed]
|
||||
if: needs.get-leptos-changed.outputs.leptos_changed != 'true' && needs.get-example-changed.outputs.example_changed == 'true'
|
||||
strategy:
|
||||
matrix: ${{ fromJSON(needs.get-example-changed.outputs.matrix) }}
|
||||
fail-fast: false
|
||||
uses: ./.github/workflows/run-cargo-make-task.yml
|
||||
with:
|
||||
directory: ${{ matrix.directory }}
|
||||
semver-check:
|
||||
name: SemVer check (stable)
|
||||
needs: [get-leptos-changed, test-members, test-examples]
|
||||
if: ${{ success() && needs.get-leptos-changed.outputs.leptos_changed == 'true' && !contains(github.event.pull_request.labels.*.name, 'breaking') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Glib
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Semver Checks
|
||||
uses: obi1kenobi/cargo-semver-checks-action@v2
|
||||
cargo_make_task: "ci"
|
||||
toolchain: nightly-2024-08-01
|
||||
|
||||
54
.github/workflows/get-changed-examples-matrix.yml
vendored
Normal file
54
.github/workflows/get-changed-examples-matrix.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Changed Examples Matrix Call
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
example_changed:
|
||||
description: "Example Changed"
|
||||
required: true
|
||||
type: boolean
|
||||
outputs:
|
||||
matrix:
|
||||
description: "Matrix"
|
||||
value: ${{ jobs.get-example-changed.outputs.matrix }}
|
||||
|
||||
jobs:
|
||||
get-example-changed:
|
||||
name: Get Changed Example Matrix
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get example project directories that changed
|
||||
id: changed-dirs
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
dir_names: true
|
||||
dir_names_max_depth: "2"
|
||||
files: |
|
||||
examples/**
|
||||
!examples/cargo-make/**
|
||||
!examples/gtk/**
|
||||
!examples/Makefile.toml
|
||||
!examples/*.md
|
||||
json: true
|
||||
quotepath: false
|
||||
|
||||
- name: List example project directories that changed
|
||||
run: echo '${{ steps.changed-dirs.outputs.all_changed_files }}'
|
||||
|
||||
- name: Set Matrix
|
||||
id: set-matrix
|
||||
run: |
|
||||
if [ ${{ inputs.example_changed }} == 'true' ]; then
|
||||
# Create matrix with changed directories
|
||||
echo "matrix={\"directory\":${{ steps.changed-dirs.outputs.all_changed_files }}}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Create matrix with one item to prevent an empty vector error
|
||||
echo "matrix={\"directory\":[\"NO_CHANGE\"]}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
12
.github/workflows/get-example-changed.yml
vendored
12
.github/workflows/get-example-changed.yml
vendored
@@ -5,18 +5,12 @@ on:
|
||||
example_changed:
|
||||
description: "Example Changed"
|
||||
value: ${{ jobs.get-example-changed.outputs.example_changed }}
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
matrix:
|
||||
description: "Example Changed Directories"
|
||||
value: ${{ jobs.get-example-changed.outputs.matrix }}
|
||||
jobs:
|
||||
get-example-changed:
|
||||
name: Get Example Changed
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
example_changed: ${{ steps.set-example-changed.outputs.example_changed }}
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
matrix: ${{ steps.set-example-changed.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -24,7 +18,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Get example files that changed
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
files: |
|
||||
examples/**
|
||||
@@ -33,11 +27,7 @@ jobs:
|
||||
!examples/*.md
|
||||
- name: List example files that changed
|
||||
run: echo '${{ steps.changed-files.outputs.all_changed_files }}'
|
||||
- name: Install jq
|
||||
run: sudo apt-get install jq
|
||||
- name: Set example_changed
|
||||
id: set-example-changed
|
||||
run: |
|
||||
echo "example_changed=${{ steps.changed-files.outputs.any_changed }}" >> "$GITHUB_OUTPUT"
|
||||
# This is for test-only-examples workflow in ci.yml
|
||||
echo "matrix={\"directory\": $(echo '${{ steps.changed-files.outputs.all_changed_files }}' | tr ' ' '\n' | awk -F'/' '{print $1 "/" $2}'| sort -u | jq -R -s -c 'split("\n") | .[:-1]')}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
2
.github/workflows/get-leptos-changed.yml
vendored
2
.github/workflows/get-leptos-changed.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Get source files that changed
|
||||
id: changed-source
|
||||
uses: tj-actions/changed-files@v46
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
files_ignore: |
|
||||
.*/**/*
|
||||
|
||||
104
.github/workflows/run-cargo-make-task.yml
vendored
104
.github/workflows/run-cargo-make-task.yml
vendored
@@ -5,21 +5,20 @@ on:
|
||||
directory:
|
||||
required: true
|
||||
type: string
|
||||
cargo_make_task:
|
||||
required: true
|
||||
type: string
|
||||
toolchain:
|
||||
required: true
|
||||
type: string
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
RUSTFLAGS: ${{ inputs.erased_mode && '--cfg erase_components' || '' }}
|
||||
LEPTOS_TAILWIND_VERSION: v4.0.14
|
||||
LEPTOS_SASS_VERSION: 1.86.0
|
||||
jobs:
|
||||
test:
|
||||
name: "Run (${{ matrix.toolchain }}) (erased_mode: ${{ matrix.erased_mode && 'enabled' || 'disabled' }})"
|
||||
name: Run ${{ inputs.cargo_make_task }} (${{ inputs.toolchain }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
toolchain: [stable, nightly-2025-04-16]
|
||||
erased_mode: [true, false]
|
||||
steps:
|
||||
- name: Free Disk Space
|
||||
run: |
|
||||
@@ -27,25 +26,11 @@ jobs:
|
||||
df -h
|
||||
sudo rm -rf /usr/local/.ghcup
|
||||
sudo rm -rf /opt/hostedtoolcache/CodeQL
|
||||
sudo rm -rf /usr/local/lib/android
|
||||
sudo rm -rf /usr/local/lib/android/sdk/ndk
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/share/boost
|
||||
sudo rm -rf /usr/local/lib/node_modules
|
||||
|
||||
# following lines currenly not needed as it takes too much time
|
||||
# the new isolated CI doesn't need much space to test libraries
|
||||
#
|
||||
# uncommet only if nneded
|
||||
#
|
||||
# sudo apt-get clean
|
||||
# sudo apt-get purge -y '^ghc-.*' '^dotnet-.*' '^llvm-.*' '^mono-.*' '^php.*' '^ruby.*'
|
||||
# sudo apt-get autoremove -y
|
||||
# sudo apt-get clean
|
||||
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
# docker system prune -af
|
||||
# docker image prune -af
|
||||
# docker volume prune -f
|
||||
sudo apt-get clean
|
||||
echo "Disk space after cleanup:"
|
||||
df -h
|
||||
# Setup environment
|
||||
@@ -57,46 +42,41 @@ jobs:
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
targets: wasm32-unknown-unknown
|
||||
components: clippy,rustfmt
|
||||
toolchain: ${{ inputs.toolchain }}
|
||||
- name: Add wasm32-unknown-unknown
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
- name: Setup cargo-make
|
||||
uses: davidB/rust-cargo-make@v1
|
||||
- name: Cargo generate-lockfile
|
||||
run: cargo generate-lockfile
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install binstall
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
- name: Install wasm-bindgen
|
||||
run: cargo binstall wasm-bindgen-cli --no-confirm
|
||||
- name: Install cargo-leptos
|
||||
run: cargo binstall cargo-leptos --locked --no-confirm
|
||||
- name: Install cargo-make
|
||||
run: cargo binstall cargo-make --no-confirm
|
||||
- name: Install nextest
|
||||
run: cargo binstall cargo-nextest --no-confirm
|
||||
- name: Install cargo-all-features
|
||||
run: cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
run: cargo binstall cargo-leptos --no-confirm
|
||||
- name: Install Trunk
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: cargo binstall trunk --no-confirm
|
||||
uses: jetli/trunk-action@v0.5.0
|
||||
with:
|
||||
version: "latest"
|
||||
- name: Print Trunk Version
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: trunk --version
|
||||
- name: Install Node.js
|
||||
if: contains(inputs.directory, 'examples')
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
if: contains(inputs.directory, 'examples')
|
||||
id: pnpm-install
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
- name: Get pnpm store directory
|
||||
if: contains(inputs.directory, 'examples')
|
||||
id: pnpm-cache
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
if: contains(inputs.directory, 'examples')
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
@@ -104,9 +84,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Maybe install chromedriver
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
project_makefile='${{inputs.directory}}/Makefile.toml'
|
||||
project_makefile=${{inputs.directory}}/Makefile.toml
|
||||
webdriver_count=$(cat $project_makefile | grep "cargo-make/webdriver.toml" | wc -l)
|
||||
if [ $webdriver_count -eq 1 ]; then
|
||||
if ! command -v chromedriver &>/dev/null; then
|
||||
@@ -120,9 +99,8 @@ jobs:
|
||||
echo chromedriver is not required
|
||||
fi
|
||||
- name: Maybe install playwright browser dependencies
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
for pw_path in $(find '${{inputs.directory}}' -name playwright.config.ts)
|
||||
for pw_path in $(find ${{inputs.directory}} -name playwright.config.ts)
|
||||
do
|
||||
pw_dir=$(dirname $pw_path)
|
||||
if [ ! -v $pw_dir ]; then
|
||||
@@ -134,41 +112,17 @@ jobs:
|
||||
fi
|
||||
done
|
||||
- name: Install Deno
|
||||
if: contains(inputs.directory, 'examples')
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
- name: Maybe install gtk-rs dependencies
|
||||
if: contains(inputs.directory, 'gtk')
|
||||
run: |
|
||||
sudo apt-get install -y libglib2.0-dev libgio2.0-cil-dev libgraphene-1.0-dev libcairo2-dev libpango1.0-dev libgtk-4-dev
|
||||
- name: Install Tailwind and Sass dependencies
|
||||
if: contains(inputs.directory, 'examples')
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
tailwindcss_version=$(echo "$LEPTOS_TAILWIND_VERSION" | sed 's/^v//')
|
||||
sass_version="$LEPTOS_SASS_VERSION"
|
||||
pnpm add "tailwindcss@$tailwindcss_version" "@tailwindcss/cli@$tailwindcss_version" "sass@$sass_version"
|
||||
|
||||
echo "Tailwind CSS version:"
|
||||
./node_modules/.bin/tailwindcss --version
|
||||
|
||||
echo "Sass version:"
|
||||
./node_modules/.bin/sass --version
|
||||
if [ ! -z $(echo ${{inputs.directory}} | grep gtk) ]; then
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libglib2.0-dev libgio2.0-cil-dev libgraphene-1.0-dev libcairo2-dev libpango1.0-dev libgtk-4-dev
|
||||
fi
|
||||
# Run Cargo Make Task
|
||||
- name: ${{ inputs.cargo_make_task }}
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
cargo make --no-workspace --profile=github-actions ci
|
||||
# Check if the counter_isomorphic can be built with leptos_debuginfo cfg flag in release mode
|
||||
- name: ${{ inputs.cargo_make_task }} with --cfg=leptos_debuginfo
|
||||
if: contains(inputs.directory, 'counter_isomorphic')
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
RUSTFLAGS="$RUSTFLAGS --cfg leptos_debuginfo" cargo leptos build --release
|
||||
- name: Clean up ${{ inputs.directory }}
|
||||
if: always()
|
||||
run: |
|
||||
cd '${{ inputs.directory }}'
|
||||
cargo clean || true
|
||||
rm -rf node_modules || true
|
||||
cd ${{ inputs.directory }}
|
||||
cargo make --profile=github-actions ${{ inputs.cargo_make_task }}
|
||||
|
||||
1627
Cargo.lock
generated
1627
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
90
Cargo.toml
90
Cargo.toml
@@ -40,81 +40,36 @@ members = [
|
||||
exclude = ["benchmarks", "examples", "projects"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.2"
|
||||
version = "0.7.4"
|
||||
edition = "2021"
|
||||
rust-version = "1.76"
|
||||
|
||||
[workspace.dependencies]
|
||||
# members
|
||||
throw_error = { path = "./any_error/", version = "0.3.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.3.0" }
|
||||
throw_error = { path = "./any_error/", version = "0.2.0" }
|
||||
any_spawner = { path = "./any_spawner/", version = "0.2.0" }
|
||||
const_str_slice_concat = { path = "./const_str_slice_concat", version = "0.1" }
|
||||
either_of = { path = "./either_of/", version = "0.1.5" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.3.0" }
|
||||
leptos = { path = "./leptos", version = "0.8.2" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.8.2" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.8.2" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.8.2" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.8.2" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.8.2" }
|
||||
leptos_router = { path = "./router", version = "0.8.2" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.8.2" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.8.2" }
|
||||
leptos_meta = { path = "./meta", version = "0.8.2" }
|
||||
either_of = { path = "./either_of/", version = "0.1.0" }
|
||||
hydration_context = { path = "./hydration_context", version = "0.2.0" }
|
||||
leptos = { path = "./leptos", version = "0.7.4" }
|
||||
leptos_config = { path = "./leptos_config", version = "0.7.4" }
|
||||
leptos_dom = { path = "./leptos_dom", version = "0.7.4" }
|
||||
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.7.4" }
|
||||
leptos_integration_utils = { path = "./integrations/utils", version = "0.7.4" }
|
||||
leptos_macro = { path = "./leptos_macro", version = "0.7.4" }
|
||||
leptos_router = { path = "./router", version = "0.7.4" }
|
||||
leptos_router_macro = { path = "./router_macro", version = "0.7.4" }
|
||||
leptos_server = { path = "./leptos_server", version = "0.7.4" }
|
||||
leptos_meta = { path = "./meta", version = "0.7.4" }
|
||||
next_tuple = { path = "./next_tuple", version = "0.1.0" }
|
||||
oco_ref = { path = "./oco", version = "0.2.0" }
|
||||
or_poisoned = { path = "./or_poisoned", version = "0.1.0" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.2.0" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.2.0" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.2.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.8.2" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.8.2" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.8.2" }
|
||||
tachys = { path = "./tachys", version = "0.2.0" }
|
||||
|
||||
# common deps
|
||||
itertools = { default-features = false, version = "0.14.0" }
|
||||
convert_case = { default-features = false, version = "0.8.0" }
|
||||
serde_json = { default-features = false, version = "1.0" }
|
||||
trybuild = { default-features = false, version = "1.0" }
|
||||
typed-builder = { default-features = false, version = "0.21.0" }
|
||||
thiserror = { default-features = false, version = "2.0" }
|
||||
wasm-bindgen = { default-features = false, version = "0.2.100" }
|
||||
indexmap = { default-features = false, version = "2.9" }
|
||||
rstml = { default-features = false, version = "0.12.1" }
|
||||
rustc_version = { default-features = false, version = "0.4.1" }
|
||||
guardian = { default-features = false, version = "1.3" }
|
||||
rustc-hash = { default-features = false, version = "2.1" }
|
||||
once_cell = { default-features = false, version = "1.21" }
|
||||
actix-web = { default-features = false, version = "4.10" }
|
||||
tracing = { default-features = false, version = "0.1.41" }
|
||||
slotmap = { default-features = false, version = "1.0" }
|
||||
futures = { default-features = false, version = "0.3.31" }
|
||||
dashmap = { default-features = false, version = "6.1" }
|
||||
pin-project-lite = { default-features = false, version = "0.2.16" }
|
||||
send_wrapper = { default-features = false, version = "0.6.0" }
|
||||
tokio-test = { default-features = false, version = "0.4.4" }
|
||||
html-escape = { default-features = false, version = "0.2.13" }
|
||||
proc-macro-error2 = { default-features = false, version = "2.0" }
|
||||
const_format = { default-features = false, version = "0.2.34" }
|
||||
gloo-net = { default-features = false, version = "0.6.0" }
|
||||
url = { default-features = false, version = "2.5" }
|
||||
tokio = { default-features = false, version = "1.44" }
|
||||
base64 = { default-features = false, version = "0.22.1" }
|
||||
cfg-if = { default-features = false, version = "1.0" }
|
||||
wasm-bindgen-futures = { default-features = false, version = "0.4.50" }
|
||||
tower = { default-features = false, version = "0.5.2" }
|
||||
proc-macro2 = { default-features = false, version = "1.0" }
|
||||
serde = { default-features = false, version = "1.0" }
|
||||
parking_lot = { default-features = false, version = "0.12.3" }
|
||||
axum = { default-features = false, version = "0.8.3" }
|
||||
serde_qs = { default-features = false, version = "0.15.0" }
|
||||
syn = { default-features = false, version = "2.0" }
|
||||
xxhash-rust = { default-features = false, version = "0.8.15" }
|
||||
paste = { default-features = false, version = "1.0" }
|
||||
quote = { default-features = false, version = "1.0" }
|
||||
web-sys = { default-features = false, version = "0.3.77" }
|
||||
js-sys = { default-features = false, version = "0.3.77" }
|
||||
reactive_graph = { path = "./reactive_graph", version = "0.1.4" }
|
||||
reactive_stores = { path = "./reactive_stores", version = "0.1.3" }
|
||||
reactive_stores_macro = { path = "./reactive_stores_macro", version = "0.1.0" }
|
||||
server_fn = { path = "./server_fn", version = "0.7.4" }
|
||||
server_fn_macro = { path = "./server_fn_macro", version = "0.7.4" }
|
||||
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.7.4" }
|
||||
tachys = { path = "./tachys", version = "0.1.4" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
@@ -123,7 +78,6 @@ opt-level = 'z'
|
||||
|
||||
[workspace.metadata.cargo-all-features]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
max_combination_size = 2
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
|
||||
@@ -10,8 +10,8 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
workspace = false
|
||||
clear = true
|
||||
dependencies = [
|
||||
{ name = "lint", path = "examples/counter_without_macros" },
|
||||
{ name = "lint", path = "examples/counters_stable" },
|
||||
{ name = "check", path = "examples/counter_without_macros" },
|
||||
{ name = "check", path = "examples/counters_stable" },
|
||||
]
|
||||
|
||||
[tasks.ci-examples]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
[](https://crates.io/crates/leptos)
|
||||
[](https://docs.rs/leptos)
|
||||

|
||||
[](https://discord.gg/YdRAhS7eQB)
|
||||
[](https://matrix.to/#/#leptos:matrix.org)
|
||||
|
||||
@@ -21,7 +20,7 @@ use leptos::*;
|
||||
#[component]
|
||||
pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
|
||||
// create a reactive signal with the initial value
|
||||
let (value, set_value) = signal(initial_value);
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
|
||||
// create event handlers for our buttons
|
||||
// note that `value` and `set_value` are `Copy`, so it's super easy to move them into closures
|
||||
@@ -46,7 +45,7 @@ pub fn SimpleCounter(initial_value: i32) -> impl IntoView {
|
||||
pub fn SimpleCounterWithBuilder(initial_value: i32) -> impl IntoView {
|
||||
use leptos::html::*;
|
||||
|
||||
let (value, set_value) = signal(initial_value);
|
||||
let (value, set_value) = create_signal(initial_value);
|
||||
let clear = move |_| set_value(0);
|
||||
let decrement = move |_| set_value.update(|value| *value -= 1);
|
||||
let increment = move |_| set_value.update(|value| *value += 1);
|
||||
@@ -168,14 +167,14 @@ Yew is the most-used library for Rust web UI development, but there are several
|
||||
- **Performance:** This has huge performance implications: Leptos is simply much faster at both creating and updating the UI than Yew is.
|
||||
- **Server integration:** Yew was created in an era in which browser-rendered single-page apps (SPAs) were the dominant paradigm. While Leptos supports client-side rendering, it also focuses on integrating with the server side of your application via server functions and multiple modes of serving HTML, including out-of-order streaming.
|
||||
|
||||
### How is this different from Dioxus?
|
||||
- ### How is this different from Dioxus?
|
||||
|
||||
Like Leptos, Dioxus is a framework for building UIs using web technologies. However, there are significant differences in approach and features.
|
||||
|
||||
- **VDOM vs. fine-grained:** While Dioxus has a performant virtual DOM (VDOM), it still uses coarse-grained/component-scoped reactivity: changing a stateful value reruns the component function and diffs the old UI against the new one. Leptos components use a different mental model, creating (and returning) actual DOM nodes and setting up a reactive system to update those DOM nodes.
|
||||
- **Web vs. desktop priorities:** Dioxus uses Leptos server functions in its fullstack mode, but does not have the same `<Suspense>`-based support for things like streaming HTML rendering, or share the same focus on holistic web performance. Leptos tends to prioritize holistic web performance (streaming HTML rendering, smaller WASM binary sizes, etc.), whereas Dioxus has an unparalleled experience when building desktop apps, because your application logic runs as a native Rust binary.
|
||||
|
||||
### How is this different from Sycamore?
|
||||
- ### How is this different from Sycamore?
|
||||
|
||||
Sycamore and Leptos are both heavily influenced by SolidJS. At this point, Leptos has a larger community and ecosystem and is more actively developed. Other differences:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "throw_error"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -10,4 +10,4 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
error,
|
||||
fmt::{self, Display},
|
||||
future::Future,
|
||||
ops,
|
||||
mem, ops,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
@@ -17,6 +17,11 @@ use std::{
|
||||
|
||||
/* Wrapper Types */
|
||||
|
||||
/// This is a result type into which any error can be converted.
|
||||
///
|
||||
/// Results are stored as [`Error`].
|
||||
pub type Result<T, E = Error> = core::result::Result<T, E>;
|
||||
|
||||
/// A generic wrapper for any error.
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(transparent)]
|
||||
@@ -104,7 +109,7 @@ pub fn get_error_hook() -> Option<Arc<dyn ErrorHook>> {
|
||||
/// Sets the current thread-local error hook, which will be invoked when [`throw`] is called.
|
||||
pub fn set_error_hook(hook: Arc<dyn ErrorHook>) -> ResetErrorHookOnDrop {
|
||||
ResetErrorHookOnDrop(
|
||||
ERROR_HOOK.with_borrow_mut(|this| Option::replace(this, hook)),
|
||||
ERROR_HOOK.with_borrow_mut(|this| mem::replace(this, Some(hook))),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "any_spawner"
|
||||
version = "0.3.0"
|
||||
version = "0.2.1"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -10,24 +10,14 @@ edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-executor = { version = "1.13.1", optional = true }
|
||||
futures = { workspace = true, default-features = true }
|
||||
glib = { version = "0.20.9", optional = true }
|
||||
thiserror = { workspace = true , default-features = true }
|
||||
tokio = { optional = true, default-features = false, features = [
|
||||
futures = "0.3.31"
|
||||
glib = { version = "0.20.6", optional = true }
|
||||
thiserror = "2.0"
|
||||
tokio = { version = "1.41", optional = true, default-features = false, features = [
|
||||
"rt",
|
||||
] , workspace = true }
|
||||
tracing = { optional = true , workspace = true, default-features = true }
|
||||
wasm-bindgen-futures = { optional = true , workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures-lite = { version = "2.6.0", default-features = false }
|
||||
tokio = { default-features = false, features = [
|
||||
"rt",
|
||||
"macros",
|
||||
"time",
|
||||
] , workspace = true }
|
||||
wasm-bindgen-test = { version = "0.3.50" }
|
||||
serial_test = "3.2.0"
|
||||
] }
|
||||
tracing = { version = "0.1.41", optional = true }
|
||||
wasm-bindgen-futures = { version = "0.4.47", optional = true }
|
||||
|
||||
[features]
|
||||
async-executor = ["dep:async-executor"]
|
||||
@@ -44,4 +34,3 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["tracing"]
|
||||
max_combination_size = 2
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
]
|
||||
extend = { path = "../cargo-make/main.toml" }
|
||||
|
||||
@@ -11,16 +11,18 @@
|
||||
//! - no "join handle" or other result is returned from the spawn
|
||||
//! - the `Future` must output `()`
|
||||
//!
|
||||
//! ```no_run
|
||||
//! ```rust
|
||||
//! use any_spawner::Executor;
|
||||
//!
|
||||
//! // make sure an Executor has been initialized with one of the init_ functions
|
||||
//!
|
||||
//! # if false {
|
||||
//! // spawn a thread-safe Future
|
||||
//! Executor::spawn(async { /* ... */ });
|
||||
//!
|
||||
//! // spawn a Future that is !Send
|
||||
//! Executor::spawn_local(async { /* ... */ });
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
@@ -35,67 +37,15 @@ pub type PinnedFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||
/// A future that has been pinned.
|
||||
pub type PinnedLocalFuture<T> = Pin<Box<dyn Future<Output = T>>>;
|
||||
|
||||
// Type alias for the spawn function pointer.
|
||||
type SpawnFn = fn(PinnedFuture<()>);
|
||||
// Type alias for the spawn_local function pointer.
|
||||
type SpawnLocalFn = fn(PinnedLocalFuture<()>);
|
||||
// Type alias for the poll_local function pointer.
|
||||
type PollLocalFn = fn();
|
||||
|
||||
/// Holds the function pointers for the current global executor.
|
||||
#[derive(Clone, Copy)]
|
||||
struct ExecutorFns {
|
||||
spawn: SpawnFn,
|
||||
spawn_local: SpawnLocalFn,
|
||||
poll_local: PollLocalFn,
|
||||
}
|
||||
|
||||
// Use a single OnceLock to ensure atomic initialization of all functions.
|
||||
static EXECUTOR_FNS: OnceLock<ExecutorFns> = OnceLock::new();
|
||||
|
||||
// No-op functions to use when an executor doesn't support a specific operation.
|
||||
#[cfg(any(feature = "tokio", feature = "wasm-bindgen", feature = "glib"))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_poll() {}
|
||||
|
||||
#[cfg(all(not(feature = "wasm-bindgen"), not(debug_assertions)))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn(_: PinnedFuture<()>) {
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!(
|
||||
"Warning: Executor::spawn called, but no global 'spawn' function is \
|
||||
configured (perhaps only spawn_local is supported, e.g., on wasm \
|
||||
without threading?)."
|
||||
);
|
||||
}
|
||||
|
||||
// Wasm panics if you spawn without an executor
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn(_: PinnedFuture<()>) {
|
||||
panic!(
|
||||
"Executor::spawn called, but no global 'spawn' function is configured."
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
fn no_op_spawn_local(_: PinnedLocalFuture<()>) {
|
||||
panic!(
|
||||
"Executor::spawn_local called, but no global 'spawn_local' function \
|
||||
is configured."
|
||||
);
|
||||
}
|
||||
static SPAWN: OnceLock<fn(PinnedFuture<()>)> = OnceLock::new();
|
||||
static SPAWN_LOCAL: OnceLock<fn(PinnedLocalFuture<()>)> = OnceLock::new();
|
||||
static POLL_LOCAL: OnceLock<fn()> = OnceLock::new();
|
||||
|
||||
/// Errors that can occur when using the executor.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ExecutorError {
|
||||
/// The executor has already been set.
|
||||
#[error("Global executor has already been set.")]
|
||||
#[error("Executor has already been set.")]
|
||||
AlreadySet,
|
||||
}
|
||||
|
||||
@@ -104,143 +54,150 @@ pub struct Executor;
|
||||
|
||||
impl Executor {
|
||||
/// Spawns a thread-safe [`Future`].
|
||||
///
|
||||
/// Uses the globally configured executor.
|
||||
/// Panics if no global executor has been initialized.
|
||||
#[inline(always)]
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn spawn(fut: impl Future<Output = ()> + Send + 'static) {
|
||||
let pinned_fut = Box::pin(fut);
|
||||
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.spawn)(pinned_fut)
|
||||
if let Some(spawner) = SPAWN.get() {
|
||||
spawner(Box::pin(fut))
|
||||
} else {
|
||||
// No global executor set.
|
||||
handle_uninitialized_spawn(pinned_fut);
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn() before \
|
||||
the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawns a [`Future`] that cannot be sent across threads.
|
||||
/// ```rust
|
||||
/// use any_spawner::Executor;
|
||||
///
|
||||
/// Uses the globally configured executor.
|
||||
/// Panics if no global executor has been initialized.
|
||||
#[inline(always)]
|
||||
/// # if false {
|
||||
/// // spawn a thread-safe Future
|
||||
/// Executor::spawn_local(async { /* ... */ });
|
||||
/// # }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
pub fn spawn_local(fut: impl Future<Output = ()> + 'static) {
|
||||
let pinned_fut = Box::pin(fut);
|
||||
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.spawn_local)(pinned_fut)
|
||||
if let Some(spawner) = SPAWN_LOCAL.get() {
|
||||
spawner(Box::pin(fut))
|
||||
} else {
|
||||
// No global executor set.
|
||||
handle_uninitialized_spawn_local(pinned_fut);
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
tracing::error!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
panic!(
|
||||
"At {}, tried to spawn a Future with Executor::spawn_local() \
|
||||
before the Executor had been set.",
|
||||
std::panic::Location::caller()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits until the next "tick" of the current async executor.
|
||||
/// Respects the global executor.
|
||||
#[inline(always)]
|
||||
pub async fn tick() {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
#[cfg(not(all(feature = "wasm-bindgen", target_family = "wasm")))]
|
||||
Executor::spawn(async move {
|
||||
_ = tx.send(());
|
||||
});
|
||||
#[cfg(all(feature = "wasm-bindgen", target_family = "wasm"))]
|
||||
Executor::spawn_local(async move {
|
||||
_ = tx.send(());
|
||||
});
|
||||
|
||||
_ = rx.await;
|
||||
}
|
||||
|
||||
/// Polls the global async executor.
|
||||
///
|
||||
/// Uses the globally configured executor.
|
||||
/// Does nothing if the global executor does not support polling.
|
||||
#[inline(always)]
|
||||
/// Polls the current async executor.
|
||||
/// Not all async executors support polling, so this function may not do anything.
|
||||
pub fn poll_local() {
|
||||
if let Some(fns) = EXECUTOR_FNS.get() {
|
||||
(fns.poll_local)()
|
||||
if let Some(poller) = POLL_LOCAL.get() {
|
||||
poller()
|
||||
}
|
||||
// If not initialized or doesn't support polling, do nothing gracefully.
|
||||
}
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Globally sets the [`tokio`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `tokio` feature to be activated on this crate.
|
||||
#[cfg(feature = "tokio")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
|
||||
pub fn init_tokio() -> Result<(), ExecutorError> {
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
tokio::spawn(fut);
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
tokio::task::spawn_local(fut);
|
||||
},
|
||||
// Tokio doesn't have an explicit global poll function like LocalPool::run_until_stalled
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`wasm-bindgen-futures`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `wasm-bindgen` feature to be activated on this crate.
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "wasm-bindgen")))]
|
||||
pub fn init_wasm_bindgen() -> Result<(), ExecutorError> {
|
||||
let executor_impl = ExecutorFns {
|
||||
// wasm-bindgen-futures only supports spawn_local
|
||||
spawn: no_op_spawn,
|
||||
spawn_local: |fut| {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
},
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`glib`] runtime as the executor used to spawn tasks.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `glib` feature to be activated on this crate.
|
||||
#[cfg(feature = "glib")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "glib")))]
|
||||
pub fn init_glib() -> Result<(), ExecutorError> {
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn(fut);
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
let main_context = glib::MainContext::default();
|
||||
main_context.spawn_local(fut);
|
||||
},
|
||||
// Glib needs event loop integration, explicit polling isn't the standard model here.
|
||||
poll_local: no_op_poll,
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`futures`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `futures-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "futures-executor")]
|
||||
@@ -252,11 +209,9 @@ impl Executor {
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Keep the lazy-init ThreadPool and thread-local LocalPool for spawn_local impl
|
||||
static THREAD_POOL: OnceLock<ThreadPool> = OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_POOL: RefCell<LocalPool> = RefCell::new(LocalPool::new());
|
||||
// SPAWNER is derived from LOCAL_POOL, keep it for efficiency inside the closure
|
||||
static SPAWNER: LocalSpawner = LOCAL_POOL.with(|pool| pool.borrow().spawner());
|
||||
}
|
||||
|
||||
@@ -267,248 +222,140 @@ impl Executor {
|
||||
})
|
||||
}
|
||||
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
get_thread_pool()
|
||||
.spawn(fut)
|
||||
.expect("failed to spawn future on ThreadPool");
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
// Use the thread_local SPAWNER derived from LOCAL_POOL
|
||||
.expect("failed to spawn future");
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
SPAWNER.with(|spawner| {
|
||||
spawner
|
||||
.spawn_local(fut)
|
||||
.expect("failed to spawn local future");
|
||||
spawner.spawn_local(fut).expect("failed to spawn future");
|
||||
});
|
||||
},
|
||||
poll_local: || {
|
||||
// Use the thread_local LOCAL_POOL
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
LOCAL_POOL.with(|pool| {
|
||||
// Use try_borrow_mut to prevent panic during re-entrant calls
|
||||
if let Ok(mut pool) = pool.try_borrow_mut() {
|
||||
pool.run_until_stalled();
|
||||
}
|
||||
// If already borrowed, we're likely in a nested poll, so do nothing.
|
||||
// If we couldn't borrow_mut, we're in a nested call to poll, so we don't need to do anything.
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets the [`async_executor`] executor as the executor used to spawn tasks,
|
||||
/// lazily creating a thread pool to spawn tasks into.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
///
|
||||
/// Requires the `async-executor` feature to be activated on this crate.
|
||||
#[cfg(feature = "async-executor")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "async-executor")))]
|
||||
pub fn init_async_executor() -> Result<(), ExecutorError> {
|
||||
use async_executor::{Executor as AsyncExecutor, LocalExecutor};
|
||||
use async_executor::{Executor, LocalExecutor};
|
||||
|
||||
// Keep the lazy-init global Executor and thread-local LocalExecutor for spawn_local impl
|
||||
static ASYNC_EXECUTOR: OnceLock<AsyncExecutor<'static>> =
|
||||
OnceLock::new();
|
||||
static THREAD_POOL: OnceLock<Executor> = OnceLock::new();
|
||||
thread_local! {
|
||||
static LOCAL_EXECUTOR_POOL: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||
static LOCAL_POOL: LocalExecutor<'static> = const { LocalExecutor::new() };
|
||||
}
|
||||
|
||||
fn get_async_executor() -> &'static AsyncExecutor<'static> {
|
||||
ASYNC_EXECUTOR.get_or_init(AsyncExecutor::new)
|
||||
fn get_thread_pool() -> &'static Executor<'static> {
|
||||
THREAD_POOL.get_or_init(Executor::new)
|
||||
}
|
||||
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
get_async_executor().spawn(fut).detach();
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
LOCAL_EXECUTOR_POOL.with(|pool| pool.spawn(fut).detach());
|
||||
},
|
||||
poll_local: || {
|
||||
LOCAL_EXECUTOR_POOL.with(|pool| {
|
||||
// try_tick polls the local executor without blocking
|
||||
// This prevents issues if called recursively or from within a task.
|
||||
pool.try_tick();
|
||||
});
|
||||
},
|
||||
};
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
get_thread_pool().spawn(fut).detach();
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
LOCAL_POOL.with(|pool| pool.spawn(fut).detach());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
LOCAL_POOL.with(|pool| pool.try_tick());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Globally sets a custom executor as the executor used to spawn tasks.
|
||||
///
|
||||
/// Requires the custom executor to be `Send + Sync` as it will be stored statically.
|
||||
///
|
||||
/// Returns `Err(_)` if a global executor has already been set.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
pub fn init_custom_executor(
|
||||
custom_executor: impl CustomExecutor + Send + Sync + 'static,
|
||||
) -> Result<(), ExecutorError> {
|
||||
// Store the custom executor instance itself to call its methods.
|
||||
// Use Box for dynamic dispatch.
|
||||
static CUSTOM_EXECUTOR_INSTANCE: OnceLock<
|
||||
Box<dyn CustomExecutor + Send + Sync>,
|
||||
> = OnceLock::new();
|
||||
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
static EXECUTOR: OnceLock<Box<dyn CustomExecutor + Send + Sync>> =
|
||||
OnceLock::new();
|
||||
EXECUTOR
|
||||
.set(Box::new(custom_executor))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
|
||||
// Now set the ExecutorFns using the stored instance
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
// Unwrap is safe because we just set it successfully or returned Err.
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().spawn(fut);
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().spawn_local(fut);
|
||||
},
|
||||
poll_local: || {
|
||||
CUSTOM_EXECUTOR_INSTANCE.get().unwrap().poll_local();
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
// If setting EXECUTOR_FNS fails (extremely unlikely race if called *concurrently*
|
||||
// with another init_* after CUSTOM_EXECUTOR_INSTANCE was set), we technically
|
||||
// leave CUSTOM_EXECUTOR_INSTANCE set but EXECUTOR_FNS not. This is an edge case,
|
||||
// but the primary race condition is solved.
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
EXECUTOR.get().unwrap().spawn(fut);
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| EXECUTOR.get().unwrap().spawn_local(fut))
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| EXECUTOR.get().unwrap().poll_local())
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets a custom executor *for the current thread only*.
|
||||
/// Locally sets a custom executor as the executor used to spawn tasks
|
||||
/// in the current thread.
|
||||
///
|
||||
/// This overrides the global executor for calls to `spawn`, `spawn_local`, and `poll_local`
|
||||
/// made *from the current thread*. It does not affect other threads or the global state.
|
||||
///
|
||||
/// The provided `custom_executor` must implement [`CustomExecutor`] and `'static`, but does
|
||||
/// **not** need to be `Send` or `Sync`.
|
||||
///
|
||||
/// Returns `Err(ExecutorError::AlreadySet)` if a *local* executor has already been set
|
||||
/// *for this thread*.
|
||||
/// Returns `Err(_)` if an executor has already been set.
|
||||
pub fn init_local_custom_executor(
|
||||
custom_executor: impl CustomExecutor + 'static,
|
||||
) -> Result<(), ExecutorError> {
|
||||
// Store the custom executor instance itself to call its methods.
|
||||
// Use Box for dynamic dispatch.
|
||||
thread_local! {
|
||||
static CUSTOM_EXECUTOR_INSTANCE: OnceLock<
|
||||
Box<dyn CustomExecutor>,
|
||||
> = OnceLock::new();
|
||||
};
|
||||
|
||||
CUSTOM_EXECUTOR_INSTANCE.with(|this| {
|
||||
static EXECUTOR: OnceLock<Box<dyn CustomExecutor>> = OnceLock::new();
|
||||
}
|
||||
EXECUTOR.with(|this| {
|
||||
this.set(Box::new(custom_executor))
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
})?;
|
||||
|
||||
// Now set the ExecutorFns using the stored instance
|
||||
let executor_impl = ExecutorFns {
|
||||
spawn: |fut| {
|
||||
// Unwrap is safe because we just set it successfully or returned Err.
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().spawn(fut));
|
||||
},
|
||||
spawn_local: |fut| {
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().spawn_local(fut));
|
||||
},
|
||||
poll_local: || {
|
||||
CUSTOM_EXECUTOR_INSTANCE
|
||||
.with(|this| this.get().unwrap().poll_local());
|
||||
},
|
||||
};
|
||||
|
||||
EXECUTOR_FNS
|
||||
.set(executor_impl)
|
||||
.map_err(|_| ExecutorError::AlreadySet)
|
||||
SPAWN
|
||||
.set(|fut| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().spawn(fut));
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
SPAWN_LOCAL
|
||||
.set(|fut| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().spawn_local(fut));
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
POLL_LOCAL
|
||||
.set(|| {
|
||||
EXECUTOR.with(|this| this.get().unwrap().poll_local());
|
||||
})
|
||||
.map_err(|_| ExecutorError::AlreadySet)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for custom executors.
|
||||
/// Custom executors can be used to integrate with any executor that supports spawning futures.
|
||||
///
|
||||
/// If used with `init_custom_executor`, the implementation must be `Send + Sync + 'static`.
|
||||
///
|
||||
/// All methods can be called recursively. Implementors should be mindful of potential
|
||||
/// deadlocks or excessive resource consumption if recursive calls are not handled carefully
|
||||
/// (e.g., using `try_borrow_mut` or non-blocking polls within implementations).
|
||||
///
|
||||
/// All methods can be called recursively.
|
||||
pub trait CustomExecutor {
|
||||
/// Spawns a future, usually on a thread pool.
|
||||
fn spawn(&self, fut: PinnedFuture<()>);
|
||||
/// Spawns a local future. May require calling `poll_local` to make progress.
|
||||
fn spawn_local(&self, fut: PinnedLocalFuture<()>);
|
||||
/// Polls the executor, if it supports polling. Implementations should ideally be
|
||||
/// non-blocking or use mechanisms like `try_tick` or `try_borrow_mut` to handle
|
||||
/// re-entrant calls safely.
|
||||
/// Polls the executor, if it supports polling.
|
||||
fn poll_local(&self);
|
||||
}
|
||||
|
||||
// Ensure CustomExecutor is object-safe
|
||||
#[allow(dead_code)]
|
||||
fn test_object_safety(_: Box<dyn CustomExecutor + Send + Sync>) {} // Added Send + Sync constraint here for global usage
|
||||
|
||||
/// Handles the case where `Executor::spawn` is called without an initialized executor.
|
||||
#[cold] // Less likely path
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn handle_uninitialized_spawn(_fut: PinnedFuture<()>) {
|
||||
let caller = std::panic::Location::caller();
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
{
|
||||
tracing::error!(
|
||||
target: "any_spawner",
|
||||
spawn_caller=%caller,
|
||||
"Executor::spawn called before a global executor was initialized. Task dropped."
|
||||
);
|
||||
// Drop the future implicitly after logging
|
||||
drop(_fut);
|
||||
}
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
{
|
||||
panic!(
|
||||
"At {caller}, tried to spawn a Future with Executor::spawn() \
|
||||
before a global executor was initialized."
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function.
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
no_op_spawn(_fut);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the case where `Executor::spawn_local` is called without an initialized executor.
|
||||
#[cold] // Less likely path
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
fn handle_uninitialized_spawn_local(_fut: PinnedLocalFuture<()>) {
|
||||
let caller = std::panic::Location::caller();
|
||||
#[cfg(all(debug_assertions, feature = "tracing"))]
|
||||
{
|
||||
tracing::error!(
|
||||
target: "any_spawner",
|
||||
spawn_caller=%caller,
|
||||
"Executor::spawn_local called before a global executor was initialized. \
|
||||
Task likely dropped or panicked."
|
||||
);
|
||||
// Fall through to panic or no-op depending on build/target
|
||||
}
|
||||
#[cfg(all(debug_assertions, not(feature = "tracing")))]
|
||||
{
|
||||
panic!(
|
||||
"At {caller}, tried to spawn a Future with \
|
||||
Executor::spawn_local() before a global executor was initialized."
|
||||
);
|
||||
}
|
||||
// In release builds (without tracing), call the specific no-op function (which usually panics).
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
no_op_spawn_local(_fut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
use any_spawner::{Executor, ExecutorError};
|
||||
|
||||
#[test]
|
||||
fn test_already_set_error() {
|
||||
struct SimpleExecutor;
|
||||
|
||||
impl any_spawner::CustomExecutor for SimpleExecutor {
|
||||
fn spawn(&self, _fut: any_spawner::PinnedFuture<()>) {}
|
||||
fn spawn_local(&self, _fut: any_spawner::PinnedLocalFuture<()>) {}
|
||||
fn poll_local(&self) {}
|
||||
}
|
||||
|
||||
// First initialization should succeed
|
||||
Executor::init_custom_executor(SimpleExecutor)
|
||||
.expect("First initialization failed");
|
||||
|
||||
// Second initialization should fail with AlreadySet error
|
||||
let result = Executor::init_custom_executor(SimpleExecutor);
|
||||
assert!(matches!(result, Err(ExecutorError::AlreadySet)));
|
||||
|
||||
// First local initialization should fail
|
||||
let result = Executor::init_local_custom_executor(SimpleExecutor);
|
||||
assert!(matches!(result, Err(ExecutorError::AlreadySet)));
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#![cfg(feature = "async-executor")]
|
||||
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
// A simple async executor for testing
|
||||
struct TestExecutor {
|
||||
tasks: Mutex<Vec<Pin<Box<dyn Future<Output = ()> + Send + 'static>>>>,
|
||||
}
|
||||
|
||||
impl TestExecutor {
|
||||
fn new() -> Self {
|
||||
TestExecutor {
|
||||
tasks: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
self.tasks.lock().unwrap().push(Box::pin(future));
|
||||
}
|
||||
|
||||
fn run_all(&self) {
|
||||
// Take all tasks out to process them
|
||||
let tasks = self.tasks.lock().unwrap().drain(..).collect::<Vec<_>>();
|
||||
|
||||
// Use a basic future executor to run each task to completion
|
||||
for mut task in tasks {
|
||||
// Use futures-lite's block_on to complete the future
|
||||
futures::executor::block_on(async {
|
||||
unsafe {
|
||||
let task_mut = Pin::new_unchecked(&mut task);
|
||||
let _ = std::future::Future::poll(
|
||||
task_mut,
|
||||
&mut std::task::Context::from_waker(
|
||||
futures::task::noop_waker_ref(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_async_executor() {
|
||||
let executor = Arc::new(TestExecutor::new());
|
||||
let executor_clone = executor.clone();
|
||||
|
||||
// Create a spawner function that will use our test executor
|
||||
let spawner = move |future| {
|
||||
executor_clone.spawn(future);
|
||||
};
|
||||
|
||||
// Prepare test data
|
||||
let counter = Arc::new(Mutex::new(0));
|
||||
let counter_clone = counter.clone();
|
||||
|
||||
// Use the spawner to spawn a task
|
||||
spawner(async move {
|
||||
*counter_clone.lock().unwrap() += 1;
|
||||
});
|
||||
|
||||
// Run all tasks
|
||||
executor.run_all();
|
||||
|
||||
// Check if the task completed correctly
|
||||
assert_eq!(*counter.lock().unwrap(), 1);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use any_spawner::Executor;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_custom_executor() {
|
||||
// Define a simple custom executor
|
||||
struct TestExecutor {
|
||||
spawn_called: Arc<AtomicBool>,
|
||||
spawn_local_called: Arc<AtomicBool>,
|
||||
poll_local_called: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl any_spawner::CustomExecutor for TestExecutor {
|
||||
fn spawn(&self, fut: any_spawner::PinnedFuture<()>) {
|
||||
self.spawn_called.store(true, Ordering::SeqCst);
|
||||
// Execute the future immediately (this works for simple test futures)
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn spawn_local(&self, fut: any_spawner::PinnedLocalFuture<()>) {
|
||||
self.spawn_local_called.store(true, Ordering::SeqCst);
|
||||
// Execute the future immediately
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn poll_local(&self) {
|
||||
self.poll_local_called.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
let spawn_called = Arc::new(AtomicBool::new(false));
|
||||
let spawn_local_called = Arc::new(AtomicBool::new(false));
|
||||
let poll_local_called = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let executor = TestExecutor {
|
||||
spawn_called: spawn_called.clone(),
|
||||
spawn_local_called: spawn_local_called.clone(),
|
||||
poll_local_called: poll_local_called.clone(),
|
||||
};
|
||||
|
||||
// Initialize with our custom executor
|
||||
Executor::init_custom_executor(executor)
|
||||
.expect("Failed to initialize custom executor");
|
||||
|
||||
// Test spawn
|
||||
Executor::spawn(async {
|
||||
// Simple task
|
||||
});
|
||||
assert!(spawn_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test spawn_local
|
||||
Executor::spawn_local(async {
|
||||
// Simple local task
|
||||
});
|
||||
assert!(spawn_local_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test poll_local
|
||||
Executor::poll_local();
|
||||
assert!(poll_local_called.load(Ordering::SeqCst));
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
use any_spawner::{CustomExecutor, Executor, PinnedFuture, PinnedLocalFuture};
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_create_custom_executor() {
|
||||
use futures::{
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_executor_tick() {
|
||||
// Initialize the tokio executor
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let value = Arc::new(Mutex::new(false));
|
||||
let value_clone = value.clone();
|
||||
|
||||
// Spawn a task that sets the value after a tick
|
||||
Executor::spawn(async move {
|
||||
Executor::tick().await;
|
||||
*value_clone.lock().unwrap() = true;
|
||||
});
|
||||
|
||||
// Allow some time for the task to complete
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// Check that the value was set
|
||||
assert!(*value.lock().unwrap());
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_futures_executor() {
|
||||
// Initialize the futures executor
|
||||
Executor::init_futures_executor()
|
||||
.expect("Failed to initialize futures executor");
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let result = Arc::new(Mutex::new(None));
|
||||
let result_clone = result.clone();
|
||||
|
||||
// Spawn a task
|
||||
Executor::spawn(async move {
|
||||
tx.send(84).expect("Failed to send value");
|
||||
});
|
||||
|
||||
// Spawn a task that waits for the result
|
||||
Executor::spawn(async move {
|
||||
match rx.await {
|
||||
Ok(val) => *result_clone.lock().unwrap() = Some(val),
|
||||
Err(_) => panic!("Failed to receive value"),
|
||||
}
|
||||
});
|
||||
|
||||
// Poll a few times to ensure the task completes
|
||||
for _ in 0..10 {
|
||||
Executor::poll_local();
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
|
||||
if result.lock().unwrap().is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(*result.lock().unwrap(), Some(84));
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
#![cfg(feature = "futures-executor")]
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
use any_spawner::Executor;
|
||||
// All tests in this file use the same executor.
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_spawn_local_future() {
|
||||
use std::rc::Rc;
|
||||
@@ -15,6 +15,7 @@ fn can_spawn_local_future() {
|
||||
Executor::spawn(async {});
|
||||
}
|
||||
|
||||
#[cfg(feature = "futures-executor")]
|
||||
#[test]
|
||||
fn can_make_local_progress() {
|
||||
use std::sync::{
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
#![cfg(feature = "glib")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use glib::{MainContext, MainLoop};
|
||||
use serial_test::serial;
|
||||
use std::{
|
||||
cell::Cell,
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
// Helper to run a future to completion on a dedicated glib MainContext.
|
||||
// Returns true if the future completed within the timeout, false otherwise.
|
||||
fn run_on_glib_context<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let _ = Executor::init_glib();
|
||||
|
||||
let context = MainContext::default();
|
||||
let main_loop = MainLoop::new(Some(&context), false);
|
||||
let main_loop_clone = main_loop.clone();
|
||||
|
||||
Executor::spawn(async move {
|
||||
fut.await;
|
||||
main_loop_clone.quit();
|
||||
});
|
||||
|
||||
main_loop.run();
|
||||
}
|
||||
|
||||
// Helper to run a local (!Send) future on the glib context.
|
||||
fn run_local_on_glib_context<F>(fut: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
let _ = Executor::init_glib();
|
||||
|
||||
let context = MainContext::default();
|
||||
let main_loop = MainLoop::new(Some(&context), false);
|
||||
let main_loop_clone = main_loop.clone();
|
||||
|
||||
Executor::spawn_local(async move {
|
||||
fut.await;
|
||||
main_loop_clone.quit();
|
||||
});
|
||||
|
||||
main_loop.run();
|
||||
}
|
||||
|
||||
// This test must run after a test that successfully initializes glib,
|
||||
// or within its own process.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_spawn() {
|
||||
let success_flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = success_flag.clone();
|
||||
|
||||
run_on_glib_context(async move {
|
||||
// Simulate async work
|
||||
futures_lite::future::yield_now().await;
|
||||
flag_clone.store(true, Ordering::SeqCst);
|
||||
|
||||
// We need to give the spawned task time to run.
|
||||
// The run_on_glib_context handles the main loop.
|
||||
// We just need to ensure spawn happened correctly.
|
||||
// Let's wait a tiny bit within the driving future to ensure spawn gets processed.
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
});
|
||||
|
||||
assert!(
|
||||
success_flag.load(Ordering::SeqCst),
|
||||
"Spawned future did not complete successfully"
|
||||
);
|
||||
}
|
||||
|
||||
// Similar conditions as test_glib_spawn regarding initialization state.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_spawn_local() {
|
||||
let success_flag = Rc::new(Cell::new(false));
|
||||
let flag_clone = success_flag.clone();
|
||||
|
||||
run_local_on_glib_context(async move {
|
||||
// Use Rc to make the future !Send
|
||||
let non_send_data = Rc::new(Cell::new(10));
|
||||
|
||||
let data = non_send_data.get();
|
||||
assert_eq!(data, 10, "Rc data should be accessible");
|
||||
non_send_data.set(20); // Modify non-Send data
|
||||
|
||||
// Simulate async work
|
||||
futures_lite::future::yield_now().await;
|
||||
|
||||
assert_eq!(
|
||||
non_send_data.get(),
|
||||
20,
|
||||
"Rc data should persist modification"
|
||||
);
|
||||
flag_clone.set(true);
|
||||
|
||||
// Wait a tiny bit
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
});
|
||||
|
||||
assert!(
|
||||
success_flag.get(),
|
||||
"Spawned local future did not complete successfully"
|
||||
);
|
||||
}
|
||||
|
||||
// Test Executor::tick with glib backend
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_tick() {
|
||||
run_on_glib_context(async {
|
||||
let value = Arc::new(Mutex::new(false));
|
||||
let value_clone = value.clone();
|
||||
|
||||
// Spawn a task that sets the value after a tick
|
||||
Executor::spawn(async move {
|
||||
Executor::tick().await;
|
||||
*value_clone.lock().unwrap() = true;
|
||||
});
|
||||
|
||||
// Allow some time for the task to complete
|
||||
glib::timeout_future(Duration::from_millis(10)).await;
|
||||
|
||||
// Check that the value was set
|
||||
assert!(*value.lock().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
// Test Executor::poll_local with glib backend (should be a no-op)
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_glib_poll_local_is_no_op() {
|
||||
// Ensure glib executor is initialized
|
||||
let _ = Executor::init_glib();
|
||||
// poll_local for glib is configured as a no-op
|
||||
// Calling it should not panic or cause issues.
|
||||
Executor::poll_local();
|
||||
Executor::poll_local();
|
||||
|
||||
println!("Executor::poll_local called successfully (expected no-op).");
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
use any_spawner::Executor;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_local_custom_executor() {
|
||||
// Define a thread-local custom executor
|
||||
struct LocalTestExecutor {
|
||||
spawn_called: Arc<AtomicBool>,
|
||||
spawn_local_called: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl any_spawner::CustomExecutor for LocalTestExecutor {
|
||||
fn spawn(&self, fut: any_spawner::PinnedFuture<()>) {
|
||||
self.spawn_called.store(true, Ordering::SeqCst);
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn spawn_local(&self, fut: any_spawner::PinnedLocalFuture<()>) {
|
||||
self.spawn_local_called.store(true, Ordering::SeqCst);
|
||||
futures::executor::block_on(fut);
|
||||
}
|
||||
|
||||
fn poll_local(&self) {
|
||||
// No-op for this test
|
||||
}
|
||||
}
|
||||
|
||||
let local_spawn_called = Arc::new(AtomicBool::new(false));
|
||||
let local_spawn_local_called = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let local_executor = LocalTestExecutor {
|
||||
spawn_called: local_spawn_called.clone(),
|
||||
spawn_local_called: local_spawn_local_called.clone(),
|
||||
};
|
||||
|
||||
// Initialize a thread-local executor
|
||||
Executor::init_local_custom_executor(local_executor)
|
||||
.expect("Failed to initialize local custom executor");
|
||||
|
||||
// Test spawn - should use the thread-local executor
|
||||
Executor::spawn(async {
|
||||
// Simple task
|
||||
});
|
||||
assert!(local_spawn_called.load(Ordering::SeqCst));
|
||||
|
||||
// Test spawn_local - should use the thread-local executor
|
||||
Executor::spawn_local(async {
|
||||
// Simple local task
|
||||
});
|
||||
assert!(local_spawn_local_called.load(Ordering::SeqCst));
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_multiple_tasks() {
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let counter = Arc::new(Mutex::new(0));
|
||||
let tasks = 10;
|
||||
let mut handles = Vec::new();
|
||||
|
||||
// Spawn multiple tasks that increment the counter
|
||||
for _ in 0..tasks {
|
||||
let counter_clone = counter.clone();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
Executor::spawn(async move {
|
||||
*counter_clone.lock().unwrap() += 1;
|
||||
tx.send(()).expect("Failed to send completion signal");
|
||||
});
|
||||
|
||||
handles.push(rx);
|
||||
}
|
||||
|
||||
// Wait for all tasks to complete
|
||||
for handle in handles {
|
||||
handle.await.expect("Task failed");
|
||||
}
|
||||
|
||||
// Verify that all tasks incremented the counter
|
||||
assert_eq!(*counter.lock().unwrap(), tasks);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#![cfg(feature = "tokio")]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tokio_executor() {
|
||||
// Initialize the tokio executor
|
||||
Executor::init_tokio().expect("Failed to initialize tokio executor");
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a task that sends a value
|
||||
Executor::spawn(async move {
|
||||
tx.send(42).expect("Failed to send value");
|
||||
});
|
||||
|
||||
// Wait for the spawned task to complete
|
||||
assert_eq!(rx.await.unwrap(), 42);
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#![cfg(all(feature = "wasm-bindgen", target_family = "wasm"))]
|
||||
|
||||
use any_spawner::Executor;
|
||||
use futures::channel::oneshot;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
};
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_wasm_bindgen_spawn_local() {
|
||||
// Initialize the wasm-bindgen executor
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Create a channel to verify the task completes
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Spawn a local task (wasm doesn't support sending futures between threads)
|
||||
Executor::spawn_local(async move {
|
||||
// Simulate some async work
|
||||
Executor::tick().await;
|
||||
tx.send(42).expect("Failed to send result");
|
||||
});
|
||||
|
||||
// Wait for the task to complete
|
||||
let result = rx.await.expect("Failed to receive result");
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_wasm_bindgen_tick() {
|
||||
// Initialize the wasm-bindgen executor if not already initialized
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
let flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = flag.clone();
|
||||
|
||||
// Spawn a task that will set the flag
|
||||
Executor::spawn_local(async move {
|
||||
flag_clone.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
// Wait for a tick, which should allow the spawned task to run
|
||||
Executor::tick().await;
|
||||
|
||||
// Verify the flag was set
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
async fn test_multiple_wasm_bindgen_tasks() {
|
||||
// Initialize once for all tests
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Create channels for multiple tasks
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
|
||||
// Spawn multiple tasks
|
||||
Executor::spawn_local(async move {
|
||||
tx1.send("task1").expect("Failed to send from task1");
|
||||
});
|
||||
|
||||
Executor::spawn_local(async move {
|
||||
tx2.send("task2").expect("Failed to send from task2");
|
||||
});
|
||||
|
||||
// Wait for both tasks to complete
|
||||
let (result1, result2) = futures::join!(rx1, rx2);
|
||||
|
||||
assert_eq!(result1.unwrap(), "task1");
|
||||
assert_eq!(result2.unwrap(), "task2");
|
||||
}
|
||||
|
||||
// This test verifies that spawn (not local) fails on wasm as expected
|
||||
#[wasm_bindgen_test]
|
||||
#[should_panic]
|
||||
fn test_wasm_bindgen_spawn_errors() {
|
||||
let _ = Executor::init_wasm_bindgen();
|
||||
|
||||
// Using should_panic to test that Executor::spawn panics in wasm
|
||||
Executor::spawn(async {
|
||||
// This should panic since wasm-bindgen doesn't support Send futures
|
||||
});
|
||||
}
|
||||
@@ -23,7 +23,7 @@ tokio-test = "0.4.0"
|
||||
miniserde = "0.1.0"
|
||||
gloo = "0.8.0"
|
||||
uuid = { version = "1.0", features = ["serde", "v4", "wasm-bindgen"] }
|
||||
wasm-bindgen = "0.2.100"
|
||||
wasm-bindgen = "0.2.0"
|
||||
lazy_static = "1.0"
|
||||
log = "0.4.0"
|
||||
strum = "0.24.0"
|
||||
|
||||
7
cargo-make/check.toml
Normal file
7
cargo-make/check.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[tasks.check]
|
||||
alias = "check-all"
|
||||
|
||||
[tasks.check-all]
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
@@ -6,15 +6,13 @@ env = { LEPTOS_PROJECT_DIRECTORY = "../" }
|
||||
args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
|
||||
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["install-clippy"]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"all-features",
|
||||
"clippy",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"clippy::print_stdout",
|
||||
"clippy",
|
||||
"--all-features",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"clippy::print_stdout",
|
||||
]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
extend = [{ path = "./lint.toml" }, { path = "./test.toml" }]
|
||||
extend = [
|
||||
{ path = "./check.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./test.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
RUSTFLAGS = ""
|
||||
@@ -8,4 +12,4 @@ LEPTOS_OUTPUT_NAME = "ci" # allows examples to check/build without cargo-leptos
|
||||
RUSTFLAGS = "-D warnings"
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = ["lint", "test-each-feature", "doctests"]
|
||||
dependencies = ["lint", "test"]
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
[tasks.test-each-feature]
|
||||
env = { "NEXTEST_NO_TESTS" = "warn" }
|
||||
command = "cargo"
|
||||
args = ["all-features", "nextest", "run", "--all-targets"]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
[tasks.test]
|
||||
alias = "test-all"
|
||||
|
||||
# This can be removed once doctests is supported in nextest
|
||||
# https://github.com/nextest-rs/nextest/issues/16
|
||||
[tasks.doctests]
|
||||
[tasks.test-all]
|
||||
command = "cargo"
|
||||
args = ["all-features", "test", "--doc"]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
args = ["test-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[tasks.post-test]
|
||||
dependencies = ["test-wasm"]
|
||||
|
||||
[tasks.test-wasm]
|
||||
env = { CARGO_MAKE_WASM_TEST_ARGS = "--headless --chrome --features=wasm-bindgen" }
|
||||
command = "cargo"
|
||||
args = ["make", "wasm-pack-test"]
|
||||
@@ -31,7 +31,7 @@ pub const fn const_concat(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
@@ -59,7 +59,7 @@ pub const fn const_concat_with_prefix(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
@@ -116,7 +116,7 @@ pub const fn const_concat_with_separator(
|
||||
let mut i = 0;
|
||||
|
||||
// have it iterate over bytes manually, because, again,
|
||||
// no mutable references in const fns
|
||||
// no mutable refernces in const fns
|
||||
while i < x.len() {
|
||||
buffer[position] = x[i];
|
||||
position += 1;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "either_of"
|
||||
version = "0.1.6"
|
||||
version = "0.1.3"
|
||||
authors = ["Greg Johnston"]
|
||||
license = "MIT"
|
||||
readme = "../README.md"
|
||||
@@ -10,9 +10,4 @@ rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
pin-project-lite = { workspace = true, default-features = true }
|
||||
paste = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["no_std"]
|
||||
no_std = []
|
||||
pin-project-lite = "0.2.15"
|
||||
|
||||
@@ -1,489 +1,33 @@
|
||||
#![cfg_attr(feature = "no_std", no_std)]
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
//! Utilities for working with enumerated types that contain one of `2..n` other types.
|
||||
|
||||
use core::{
|
||||
cmp::Ordering,
|
||||
fmt::Display,
|
||||
future::Future,
|
||||
iter::{Product, Sum},
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use paste::paste;
|
||||
use pin_project_lite::pin_project;
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
use std::error::Error; // TODO: replace with core::error::Error once MSRV is >= 1.81.0
|
||||
|
||||
macro_rules! tuples {
|
||||
($name:ident + $fut_name:ident + $fut_proj:ident {
|
||||
$($ty:ident => ($($rest_variant:ident),*) + <$($mapped_ty:ident),+>),+$(,)?
|
||||
}) => {
|
||||
tuples!($name + $fut_name + $fut_proj {
|
||||
$($ty($ty) => ($($rest_variant),*) + <$($mapped_ty),+>),+
|
||||
});
|
||||
};
|
||||
($name:ident + $fut_name:ident + $fut_proj:ident {
|
||||
$($variant:ident($ty:ident) => ($($rest_variant:ident),*) + <$($mapped_ty:ident),+>),+$(,)?
|
||||
}) => {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum $name<$($ty),+> {
|
||||
$($variant ($ty),)+
|
||||
}
|
||||
|
||||
impl<$($ty),+> $name<$($ty),+> {
|
||||
paste! {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn map<$([<F $ty>]),+, $([<$ty 1>]),+>(self, $([<$variant:lower>]: [<F $ty>]),+) -> $name<$([<$ty 1>]),+>
|
||||
where
|
||||
$([<F $ty>]: FnOnce($ty) -> [<$ty 1>],)+
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(inner) => $name::$variant([<$variant:lower>](inner)),)+
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
pub fn [<map_ $variant:lower>]<Fun, [<$ty 1>]>(self, f: Fun) -> $name<$($mapped_ty),+>
|
||||
where
|
||||
Fun: FnOnce($ty) -> [<$ty 1>],
|
||||
{
|
||||
match self {
|
||||
$name::$variant(inner) => $name::$variant(f(inner)),
|
||||
$($name::$rest_variant(inner) => $name::$rest_variant(inner),)*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn [<inspect_ $variant:lower>]<Fun, [<$ty 1>]>(self, f: Fun) -> Self
|
||||
where
|
||||
Fun: FnOnce(&$ty),
|
||||
{
|
||||
if let $name::$variant(inner) = &self {
|
||||
f(inner);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn [<is_ $variant:lower>](&self) -> bool {
|
||||
matches!(self, $name::$variant(_))
|
||||
}
|
||||
|
||||
pub fn [<as_ $variant:lower>](&self) -> Option<&$ty> {
|
||||
match self {
|
||||
$name::$variant(inner) => Some(inner),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn [<as_ $variant:lower _mut>](&mut self) -> Option<&mut $ty> {
|
||||
match self {
|
||||
$name::$variant(inner) => Some(inner),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn [<unwrap_ $variant:lower>](self) -> $ty {
|
||||
match self {
|
||||
$name::$variant(inner) => inner,
|
||||
_ => panic!(concat!(
|
||||
"called `unwrap_", stringify!([<$variant:lower>]), "()` on a non-`", stringify!($variant), "` variant of `", stringify!($name), "`"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn [<into_ $variant:lower>](self) -> Result<$ty, Self> {
|
||||
match self {
|
||||
$name::$variant(inner) => Ok(inner),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($ty),+> Display for $name<$($ty),+>
|
||||
where
|
||||
$($ty: Display,)+
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
$($name::$variant(this) => this.fmt(f),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
impl<$($ty),+> Error for $name<$($ty),+>
|
||||
where
|
||||
$($ty: Error,)+
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
$($name::$variant(this) => this.source(),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, $($ty),+> Iterator for $name<$($ty),+>
|
||||
where
|
||||
$($ty: Iterator<Item = Item>,)+
|
||||
{
|
||||
type Item = Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$variant(i) => i.next(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
match self {
|
||||
$($name::$variant(i) => i.size_hint(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn count(self) -> usize
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.count(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn last(self) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.last(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$variant(i) => i.nth(n),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn for_each<Fun>(self, f: Fun)
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Self::Item),
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.for_each(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn collect<Col: FromIterator<Self::Item>>(self) -> Col
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.collect(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn partition<Col, Fun>(self, f: Fun) -> (Col, Col)
|
||||
where
|
||||
Self: Sized,
|
||||
Col: Default + Extend<Self::Item>,
|
||||
Fun: FnMut(&Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.partition(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn fold<Acc, Fun>(self, init: Acc, f: Fun) -> Acc
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Acc, Self::Item) -> Acc,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.fold(init, f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn reduce<Fun>(self, f: Fun) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Self::Item, Self::Item) -> Self::Item,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.reduce(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn all<Fun>(&mut self, f: Fun) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.all(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn any<Fun>(&mut self, f: Fun) -> bool
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.any(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn find<Pre>(&mut self, predicate: Pre) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Pre: FnMut(&Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.find(predicate),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn find_map<Out, Fun>(&mut self, f: Fun) -> Option<Out>
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(Self::Item) -> Option<Out>,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.find_map(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn position<Pre>(&mut self, predicate: Pre) -> Option<usize>
|
||||
where
|
||||
Self: Sized,
|
||||
Pre: FnMut(Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.position(predicate),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn max(self) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Item: Ord,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.max(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn min(self) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Item: Ord,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.min(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn max_by_key<Key: Ord, Fun>(self, f: Fun) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(&Self::Item) -> Key,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.max_by_key(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn max_by<Cmp>(self, compare: Cmp) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Cmp: FnMut(&Self::Item, &Self::Item) -> Ordering,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.max_by(compare),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn min_by_key<Key: Ord, Fun>(self, f: Fun) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Fun: FnMut(&Self::Item) -> Key,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.min_by_key(f),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn min_by<Cmp>(self, compare: Cmp) -> Option<Self::Item>
|
||||
where
|
||||
Self: Sized,
|
||||
Cmp: FnMut(&Self::Item, &Self::Item) -> Ordering,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.min_by(compare),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn sum<Out>(self) -> Out
|
||||
where
|
||||
Self: Sized,
|
||||
Out: Sum<Self::Item>,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.sum(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn product<Out>(self) -> Out
|
||||
where
|
||||
Self: Sized,
|
||||
Out: Product<Self::Item>,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.product(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp<Other>(self, other: Other) -> Ordering
|
||||
where
|
||||
Other: IntoIterator<Item = Self::Item>,
|
||||
Self::Item: Ord,
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.cmp(other),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn partial_cmp<Other>(self, other: Other) -> Option<Ordering>
|
||||
where
|
||||
Other: IntoIterator,
|
||||
Self::Item: PartialOrd<Other::Item>,
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.partial_cmp(other),)+
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: uncomment once MSRV is >= 1.82.0
|
||||
// fn is_sorted(self) -> bool
|
||||
// where
|
||||
// Self: Sized,
|
||||
// Self::Item: PartialOrd,
|
||||
// {
|
||||
// match self {
|
||||
// $($name::$variant(i) => i.is_sorted(),)+
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn is_sorted_by<Cmp>(self, compare: Cmp) -> bool
|
||||
// where
|
||||
// Self: Sized,
|
||||
// Cmp: FnMut(&Self::Item, &Self::Item) -> bool,
|
||||
// {
|
||||
// match self {
|
||||
// $($name::$variant(i) => i.is_sorted_by(compare),)+
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn is_sorted_by_key<Fun, Key>(self, f: Fun) -> bool
|
||||
// where
|
||||
// Self: Sized,
|
||||
// Fun: FnMut(Self::Item) -> Key,
|
||||
// Key: PartialOrd,
|
||||
// {
|
||||
// match self {
|
||||
// $($name::$variant(i) => i.is_sorted_by_key(f),)+
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
impl<Item, $($ty),+> ExactSizeIterator for $name<$($ty),+>
|
||||
where
|
||||
$($ty: ExactSizeIterator<Item = Item>,)+
|
||||
{
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
$($name::$variant(i) => i.len(),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, $($ty),+> DoubleEndedIterator for $name<$($ty),+>
|
||||
where
|
||||
$($ty: DoubleEndedIterator<Item = Item>,)+
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$variant(i) => i.next_back(),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$variant(i) => i.nth_back(n),)+
|
||||
}
|
||||
}
|
||||
|
||||
fn rfind<Pre>(&mut self, predicate: Pre) -> Option<Self::Item>
|
||||
where
|
||||
Pre: FnMut(&Self::Item) -> bool,
|
||||
{
|
||||
match self {
|
||||
$($name::$variant(i) => i.rfind(predicate),)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = $fut_proj]
|
||||
pub enum $fut_name<$($ty),+> {
|
||||
$($variant { #[pin] inner: $ty },)+
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($ty),+> Future for $fut_name<$($ty),+>
|
||||
where
|
||||
$($ty: Future,)+
|
||||
{
|
||||
type Output = $name<$($ty::Output),+>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this {
|
||||
$($fut_proj::$variant { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready($name::$variant(inner)),
|
||||
},)+
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
tuples!(Either + EitherFuture + EitherFutureProj {
|
||||
Left(A) => (Right) + <A1, B>,
|
||||
Right(B) => (Left) + <A, B1>,
|
||||
});
|
||||
impl<Item, A, B> Iterator for Either<A, B>
|
||||
where
|
||||
A: Iterator<Item = Item>,
|
||||
B: Iterator<Item = Item>,
|
||||
{
|
||||
type Item = Item;
|
||||
|
||||
impl<A, B> Either<A, B> {
|
||||
pub fn swap(self) -> Either<B, A> {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Either::Left(a) => Either::Right(a),
|
||||
Either::Right(b) => Either::Left(b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> From<Result<A, B>> for Either<A, B> {
|
||||
fn from(value: Result<A, B>) -> Self {
|
||||
match value {
|
||||
Ok(left) => Either::Left(left),
|
||||
Err(right) => Either::Right(right),
|
||||
Either::Left(i) => i.next(),
|
||||
Either::Right(i) => i.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -546,20 +90,6 @@ impl<T, E> EitherOr for Result<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> EitherOr for Either<A, B> {
|
||||
type Left = A;
|
||||
type Right = B;
|
||||
|
||||
#[inline]
|
||||
fn either_or<FA, A1, FB, B1>(self, a: FA, b: FB) -> Either<A1, B1>
|
||||
where
|
||||
FA: FnOnce(<Self as EitherOr>::Left) -> A1,
|
||||
FB: FnOnce(<Self as EitherOr>::Right) -> B1,
|
||||
{
|
||||
self.map(a, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_either_or() {
|
||||
let right = false.either_or(|_| 'a', |_| 12);
|
||||
@@ -580,177 +110,109 @@ fn test_either_or() {
|
||||
let result: Result<i32, _> = Err("12");
|
||||
let right = result.either_or(|a| a, |b| b.chars().next());
|
||||
assert!(matches!(right, Either::Right(Some('1'))));
|
||||
|
||||
let either = Either::<i32, char>::Left(12);
|
||||
let left = either.either_or(|a| a, |b| b);
|
||||
assert!(matches!(left, Either::Left(12)));
|
||||
|
||||
let either = Either::<i32, char>::Right('a');
|
||||
let right = either.either_or(|a| a, |b| b);
|
||||
assert!(matches!(right, Either::Right('a')));
|
||||
}
|
||||
|
||||
tuples!(EitherOf3 + EitherOf3Future + EitherOf3FutureProj {
|
||||
A => (B, C) + <A1, B, C>,
|
||||
B => (A, C) + <A, B1, C>,
|
||||
C => (A, B) + <A, B, C1>,
|
||||
});
|
||||
tuples!(EitherOf4 + EitherOf4Future + EitherOf4FutureProj {
|
||||
A => (B, C, D) + <A1, B, C, D>,
|
||||
B => (A, C, D) + <A, B1, C, D>,
|
||||
C => (A, B, D) + <A, B, C1, D>,
|
||||
D => (A, B, C) + <A, B, C, D1>,
|
||||
});
|
||||
tuples!(EitherOf5 + EitherOf5Future + EitherOf5FutureProj {
|
||||
A => (B, C, D, E) + <A1, B, C, D, E>,
|
||||
B => (A, C, D, E) + <A, B1, C, D, E>,
|
||||
C => (A, B, D, E) + <A, B, C1, D, E>,
|
||||
D => (A, B, C, E) + <A, B, C, D1, E>,
|
||||
E => (A, B, C, D) + <A, B, C, D, E1>,
|
||||
});
|
||||
tuples!(EitherOf6 + EitherOf6Future + EitherOf6FutureProj {
|
||||
A => (B, C, D, E, F) + <A1, B, C, D, E, F>,
|
||||
B => (A, C, D, E, F) + <A, B1, C, D, E, F>,
|
||||
C => (A, B, D, E, F) + <A, B, C1, D, E, F>,
|
||||
D => (A, B, C, E, F) + <A, B, C, D1, E, F>,
|
||||
E => (A, B, C, D, F) + <A, B, C, D, E1, F>,
|
||||
F => (A, B, C, D, E) + <A, B, C, D, E, F1>,
|
||||
});
|
||||
tuples!(EitherOf7 + EitherOf7Future + EitherOf7FutureProj {
|
||||
A => (B, C, D, E, F, G) + <A1, B, C, D, E, F, G>,
|
||||
B => (A, C, D, E, F, G) + <A, B1, C, D, E, F, G>,
|
||||
C => (A, B, D, E, F, G) + <A, B, C1, D, E, F, G>,
|
||||
D => (A, B, C, E, F, G) + <A, B, C, D1, E, F, G>,
|
||||
E => (A, B, C, D, F, G) + <A, B, C, D, E1, F, G>,
|
||||
F => (A, B, C, D, E, G) + <A, B, C, D, E, F1, G>,
|
||||
G => (A, B, C, D, E, F) + <A, B, C, D, E, F, G1>,
|
||||
});
|
||||
tuples!(EitherOf8 + EitherOf8Future + EitherOf8FutureProj {
|
||||
A => (B, C, D, E, F, G, H) + <A1, B, C, D, E, F, G, H>,
|
||||
B => (A, C, D, E, F, G, H) + <A, B1, C, D, E, F, G, H>,
|
||||
C => (A, B, D, E, F, G, H) + <A, B, C1, D, E, F, G, H>,
|
||||
D => (A, B, C, E, F, G, H) + <A, B, C, D1, E, F, G, H>,
|
||||
E => (A, B, C, D, F, G, H) + <A, B, C, D, E1, F, G, H>,
|
||||
F => (A, B, C, D, E, G, H) + <A, B, C, D, E, F1, G, H>,
|
||||
G => (A, B, C, D, E, F, H) + <A, B, C, D, E, F, G1, H>,
|
||||
H => (A, B, C, D, E, F, G) + <A, B, C, D, E, F, G, H1>,
|
||||
});
|
||||
tuples!(EitherOf9 + EitherOf9Future + EitherOf9FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I) + <A1, B, C, D, E, F, G, H, I>,
|
||||
B => (A, C, D, E, F, G, H, I) + <A, B1, C, D, E, F, G, H, I>,
|
||||
C => (A, B, D, E, F, G, H, I) + <A, B, C1, D, E, F, G, H, I>,
|
||||
D => (A, B, C, E, F, G, H, I) + <A, B, C, D1, E, F, G, H, I>,
|
||||
E => (A, B, C, D, F, G, H, I) + <A, B, C, D, E1, F, G, H, I>,
|
||||
F => (A, B, C, D, E, G, H, I) + <A, B, C, D, E, F1, G, H, I>,
|
||||
G => (A, B, C, D, E, F, H, I) + <A, B, C, D, E, F, G1, H, I>,
|
||||
H => (A, B, C, D, E, F, G, I) + <A, B, C, D, E, F, G, H1, I>,
|
||||
I => (A, B, C, D, E, F, G, H) + <A, B, C, D, E, F, G, H, I1>,
|
||||
});
|
||||
tuples!(EitherOf10 + EitherOf10Future + EitherOf10FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J) + <A1, B, C, D, E, F, G, H, I, J>,
|
||||
B => (A, C, D, E, F, G, H, I, J) + <A, B1, C, D, E, F, G, H, I, J>,
|
||||
C => (A, B, D, E, F, G, H, I, J) + <A, B, C1, D, E, F, G, H, I, J>,
|
||||
D => (A, B, C, E, F, G, H, I, J) + <A, B, C, D1, E, F, G, H, I, J>,
|
||||
E => (A, B, C, D, F, G, H, I, J) + <A, B, C, D, E1, F, G, H, I, J>,
|
||||
F => (A, B, C, D, E, G, H, I, J) + <A, B, C, D, E, F1, G, H, I, J>,
|
||||
G => (A, B, C, D, E, F, H, I, J) + <A, B, C, D, E, F, G1, H, I, J>,
|
||||
H => (A, B, C, D, E, F, G, I, J) + <A, B, C, D, E, F, G, H1, I, J>,
|
||||
I => (A, B, C, D, E, F, G, H, J) + <A, B, C, D, E, F, G, H, I1, J>,
|
||||
J => (A, B, C, D, E, F, G, H, I) + <A, B, C, D, E, F, G, H, I, J1>,
|
||||
});
|
||||
tuples!(EitherOf11 + EitherOf11Future + EitherOf11FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K) + <A1, B, C, D, E, F, G, H, I, J, K>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K) + <A, B1, C, D, E, F, G, H, I, J, K>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K) + <A, B, C1, D, E, F, G, H, I, J, K>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K) + <A, B, C, D1, E, F, G, H, I, J, K>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K) + <A, B, C, D, E1, F, G, H, I, J, K>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K) + <A, B, C, D, E, F1, G, H, I, J, K>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K) + <A, B, C, D, E, F, G1, H, I, J, K>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K) + <A, B, C, D, E, F, G, H1, I, J, K>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K) + <A, B, C, D, E, F, G, H, I1, J, K>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K) + <A, B, C, D, E, F, G, H, I, J1, K>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J) + <A, B, C, D, E, F, G, H, I, J, K1>,
|
||||
});
|
||||
tuples!(EitherOf12 + EitherOf12Future + EitherOf12FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K, L) + <A1, B, C, D, E, F, G, H, I, J, K, L>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K, L) + <A, B1, C, D, E, F, G, H, I, J, K, L>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K, L) + <A, B, C1, D, E, F, G, H, I, J, K, L>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K, L) + <A, B, C, D1, E, F, G, H, I, J, K, L>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K, L) + <A, B, C, D, E1, F, G, H, I, J, K, L>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K, L) + <A, B, C, D, E, F1, G, H, I, J, K, L>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K, L) + <A, B, C, D, E, F, G1, H, I, J, K, L>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K, L) + <A, B, C, D, E, F, G, H1, I, J, K, L>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K, L) + <A, B, C, D, E, F, G, H, I1, J, K, L>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K, L) + <A, B, C, D, E, F, G, H, I, J1, K, L>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J, L) + <A, B, C, D, E, F, G, H, I, J, K1, L>,
|
||||
L => (A, B, C, D, E, F, G, H, I, J, K) + <A, B, C, D, E, F, G, H, I, J, K, L1>,
|
||||
});
|
||||
tuples!(EitherOf13 + EitherOf13Future + EitherOf13FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K, L, M) + <A1, B, C, D, E, F, G, H, I, J, K, L, M>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K, L, M) + <A, B1, C, D, E, F, G, H, I, J, K, L, M>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K, L, M) + <A, B, C1, D, E, F, G, H, I, J, K, L, M>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K, L, M) + <A, B, C, D1, E, F, G, H, I, J, K, L, M>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K, L, M) + <A, B, C, D, E1, F, G, H, I, J, K, L, M>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K, L, M) + <A, B, C, D, E, F1, G, H, I, J, K, L, M>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K, L, M) + <A, B, C, D, E, F, G1, H, I, J, K, L, M>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K, L, M) + <A, B, C, D, E, F, G, H1, I, J, K, L, M>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K, L, M) + <A, B, C, D, E, F, G, H, I1, J, K, L, M>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K, L, M) + <A, B, C, D, E, F, G, H, I, J1, K, L, M>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J, L, M) + <A, B, C, D, E, F, G, H, I, J, K1, L, M>,
|
||||
L => (A, B, C, D, E, F, G, H, I, J, K, M) + <A, B, C, D, E, F, G, H, I, J, K, L1, M>,
|
||||
M => (A, B, C, D, E, F, G, H, I, J, K, L) + <A, B, C, D, E, F, G, H, I, J, K, L, M1>,
|
||||
});
|
||||
tuples!(EitherOf14 + EitherOf14Future + EitherOf14FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K, L, M, N) + <A1, B, C, D, E, F, G, H, I, J, K, L, M, N>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K, L, M, N) + <A, B1, C, D, E, F, G, H, I, J, K, L, M, N>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K, L, M, N) + <A, B, C1, D, E, F, G, H, I, J, K, L, M, N>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K, L, M, N) + <A, B, C, D1, E, F, G, H, I, J, K, L, M, N>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K, L, M, N) + <A, B, C, D, E1, F, G, H, I, J, K, L, M, N>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K, L, M, N) + <A, B, C, D, E, F1, G, H, I, J, K, L, M, N>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K, L, M, N) + <A, B, C, D, E, F, G1, H, I, J, K, L, M, N>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K, L, M, N) + <A, B, C, D, E, F, G, H1, I, J, K, L, M, N>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K, L, M, N) + <A, B, C, D, E, F, G, H, I1, J, K, L, M, N>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K, L, M, N) + <A, B, C, D, E, F, G, H, I, J1, K, L, M, N>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J, L, M, N) + <A, B, C, D, E, F, G, H, I, J, K1, L, M, N>,
|
||||
L => (A, B, C, D, E, F, G, H, I, J, K, M, N) + <A, B, C, D, E, F, G, H, I, J, K, L1, M, N>,
|
||||
M => (A, B, C, D, E, F, G, H, I, J, K, L, N) + <A, B, C, D, E, F, G, H, I, J, K, L, M1, N>,
|
||||
N => (A, B, C, D, E, F, G, H, I, J, K, L, M) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N1>,
|
||||
});
|
||||
tuples!(EitherOf15 + EitherOf15Future + EitherOf15FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K, L, M, N, O) + <A1, B, C, D, E, F, G, H, I, J, K, L, M, N, O>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K, L, M, N, O) + <A, B1, C, D, E, F, G, H, I, J, K, L, M, N, O>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K, L, M, N, O) + <A, B, C1, D, E, F, G, H, I, J, K, L, M, N, O>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K, L, M, N, O) + <A, B, C, D1, E, F, G, H, I, J, K, L, M, N, O>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K, L, M, N, O) + <A, B, C, D, E1, F, G, H, I, J, K, L, M, N, O>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K, L, M, N, O) + <A, B, C, D, E, F1, G, H, I, J, K, L, M, N, O>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K, L, M, N, O) + <A, B, C, D, E, F, G1, H, I, J, K, L, M, N, O>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K, L, M, N, O) + <A, B, C, D, E, F, G, H1, I, J, K, L, M, N, O>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K, L, M, N, O) + <A, B, C, D, E, F, G, H, I1, J, K, L, M, N, O>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K, L, M, N, O) + <A, B, C, D, E, F, G, H, I, J1, K, L, M, N, O>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J, L, M, N, O) + <A, B, C, D, E, F, G, H, I, J, K1, L, M, N, O>,
|
||||
L => (A, B, C, D, E, F, G, H, I, J, K, M, N, O) + <A, B, C, D, E, F, G, H, I, J, K, L1, M, N, O>,
|
||||
M => (A, B, C, D, E, F, G, H, I, J, K, L, N, O) + <A, B, C, D, E, F, G, H, I, J, K, L, M1, N, O>,
|
||||
N => (A, B, C, D, E, F, G, H, I, J, K, L, M, O) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N1, O>,
|
||||
O => (A, B, C, D, E, F, G, H, I, J, K, L, M, N) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O1>,
|
||||
});
|
||||
tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj {
|
||||
A => (B, C, D, E, F, G, H, I, J, K, L, M, N, O, P) + <A1, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P>,
|
||||
B => (A, C, D, E, F, G, H, I, J, K, L, M, N, O, P) + <A, B1, C, D, E, F, G, H, I, J, K, L, M, N, O, P>,
|
||||
C => (A, B, D, E, F, G, H, I, J, K, L, M, N, O, P) + <A, B, C1, D, E, F, G, H, I, J, K, L, M, N, O, P>,
|
||||
D => (A, B, C, E, F, G, H, I, J, K, L, M, N, O, P) + <A, B, C, D1, E, F, G, H, I, J, K, L, M, N, O, P>,
|
||||
E => (A, B, C, D, F, G, H, I, J, K, L, M, N, O, P) + <A, B, C, D, E1, F, G, H, I, J, K, L, M, N, O, P>,
|
||||
F => (A, B, C, D, E, G, H, I, J, K, L, M, N, O, P) + <A, B, C, D, E, F1, G, H, I, J, K, L, M, N, O, P>,
|
||||
G => (A, B, C, D, E, F, H, I, J, K, L, M, N, O, P) + <A, B, C, D, E, F, G1, H, I, J, K, L, M, N, O, P>,
|
||||
H => (A, B, C, D, E, F, G, I, J, K, L, M, N, O, P) + <A, B, C, D, E, F, G, H1, I, J, K, L, M, N, O, P>,
|
||||
I => (A, B, C, D, E, F, G, H, J, K, L, M, N, O, P) + <A, B, C, D, E, F, G, H, I1, J, K, L, M, N, O, P>,
|
||||
J => (A, B, C, D, E, F, G, H, I, K, L, M, N, O, P) + <A, B, C, D, E, F, G, H, I, J1, K, L, M, N, O, P>,
|
||||
K => (A, B, C, D, E, F, G, H, I, J, L, M, N, O, P) + <A, B, C, D, E, F, G, H, I, J, K1, L, M, N, O, P>,
|
||||
L => (A, B, C, D, E, F, G, H, I, J, K, M, N, O, P) + <A, B, C, D, E, F, G, H, I, J, K, L1, M, N, O, P>,
|
||||
M => (A, B, C, D, E, F, G, H, I, J, K, L, N, O, P) + <A, B, C, D, E, F, G, H, I, J, K, L, M1, N, O, P>,
|
||||
N => (A, B, C, D, E, F, G, H, I, J, K, L, M, O, P) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N1, O, P>,
|
||||
O => (A, B, C, D, E, F, G, H, I, J, K, L, M, N, P) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O1, P>,
|
||||
P => (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) + <A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P1>,
|
||||
});
|
||||
pin_project! {
|
||||
#[project = EitherFutureProj]
|
||||
pub enum EitherFuture<A, B> {
|
||||
Left { #[pin] inner: A },
|
||||
Right { #[pin] inner: B },
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, B> Future for EitherFuture<A, B>
|
||||
where
|
||||
A: Future,
|
||||
B: Future,
|
||||
{
|
||||
type Output = Either<A::Output, B::Output>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this {
|
||||
EitherFutureProj::Left { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready(Either::Left(inner)),
|
||||
},
|
||||
EitherFutureProj::Right { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready(Either::Right(inner)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tuples {
|
||||
($name:ident + $fut_name:ident + $fut_proj:ident => $($ty:ident),*) => {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum $name<$($ty,)*> {
|
||||
$($ty ($ty),)*
|
||||
}
|
||||
|
||||
impl<$($ty,)*> Display for $name<$($ty,)*>
|
||||
where
|
||||
$($ty: Display,)*
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
$($name::$ty(this) => this.fmt(f),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Item, $($ty,)*> Iterator for $name<$($ty,)*>
|
||||
where
|
||||
$($ty: Iterator<Item = Item>,)*
|
||||
{
|
||||
type Item = Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
$($name::$ty(i) => i.next(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = $fut_proj]
|
||||
pub enum $fut_name<$($ty,)*> {
|
||||
$($ty { #[pin] inner: $ty },)*
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($ty,)*> Future for $fut_name<$($ty,)*>
|
||||
where
|
||||
$($ty: Future,)*
|
||||
{
|
||||
type Output = $name<$($ty::Output,)*>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
match this {
|
||||
$($fut_proj::$ty { inner } => match inner.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(inner) => Poll::Ready($name::$ty(inner)),
|
||||
},)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tuples!(EitherOf3 + EitherOf3Future + EitherOf3FutureProj => A, B, C);
|
||||
tuples!(EitherOf4 + EitherOf4Future + EitherOf4FutureProj => A, B, C, D);
|
||||
tuples!(EitherOf5 + EitherOf5Future + EitherOf5FutureProj => A, B, C, D, E);
|
||||
tuples!(EitherOf6 + EitherOf6Future + EitherOf6FutureProj => A, B, C, D, E, F);
|
||||
tuples!(EitherOf7 + EitherOf7Future + EitherOf7FutureProj => A, B, C, D, E, F, G);
|
||||
tuples!(EitherOf8 + EitherOf8Future + EitherOf8FutureProj => A, B, C, D, E, F, G, H);
|
||||
tuples!(EitherOf9 + EitherOf9Future + EitherOf9FutureProj => A, B, C, D, E, F, G, H, I);
|
||||
tuples!(EitherOf10 + EitherOf10Future + EitherOf10FutureProj => A, B, C, D, E, F, G, H, I, J);
|
||||
tuples!(EitherOf11 + EitherOf11Future + EitherOf11FutureProj => A, B, C, D, E, F, G, H, I, J, K);
|
||||
tuples!(EitherOf12 + EitherOf12Future + EitherOf12FutureProj => A, B, C, D, E, F, G, H, I, J, K, L);
|
||||
tuples!(EitherOf13 + EitherOf13Future + EitherOf13FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M);
|
||||
tuples!(EitherOf14 + EitherOf14Future + EitherOf14FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N);
|
||||
tuples!(EitherOf15 + EitherOf15Future + EitherOf15FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
|
||||
tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj => A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
|
||||
|
||||
/// Matches over the first expression and returns an either ([`Either`], [`EitherOf3`], ... [`EitherOf8`])
|
||||
/// composed of the values returned by the match arms.
|
||||
@@ -776,20 +238,20 @@ tuples!(EitherOf16 + EitherOf16Future + EitherOf16FutureProj {
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! either {
|
||||
($match:expr, $left_pattern:pat => $left_expression:expr, $right_pattern:pat => $right_expression:expr$(,)?) => {
|
||||
($match:expr, $left_pattern:pat => $left_expression:expr, $right_pattern:pat => $right_expression:expr,) => {
|
||||
match $match {
|
||||
$left_pattern => $crate::Either::Left($left_expression),
|
||||
$right_pattern => $crate::Either::Right($right_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf3::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf3::B($b_expression),
|
||||
$c_pattern => $crate::EitherOf3::C($c_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf4::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf4::B($b_expression),
|
||||
@@ -797,7 +259,7 @@ macro_rules! either {
|
||||
$d_pattern => $crate::EitherOf4::D($d_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf5::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf5::B($b_expression),
|
||||
@@ -806,7 +268,7 @@ macro_rules! either {
|
||||
$e_pattern => $crate::EitherOf5::E($e_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf6::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf6::B($b_expression),
|
||||
@@ -816,7 +278,7 @@ macro_rules! either {
|
||||
$f_pattern => $crate::EitherOf6::F($f_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf7::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf7::B($b_expression),
|
||||
@@ -827,7 +289,7 @@ macro_rules! either {
|
||||
$g_pattern => $crate::EitherOf7::G($g_expression),
|
||||
}
|
||||
};
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr$(,)?) => {
|
||||
($match:expr, $a_pattern:pat => $a_expression:expr, $b_pattern:pat => $b_expression:expr, $c_pattern:pat => $c_expression:expr, $d_pattern:pat => $d_expression:expr, $e_pattern:pat => $e_expression:expr, $f_pattern:pat => $f_expression:expr, $g_pattern:pat => $g_expression:expr, $h_pattern:pat => $h_expression:expr,) => {
|
||||
match $match {
|
||||
$a_pattern => $crate::EitherOf8::A($a_expression),
|
||||
$b_pattern => $crate::EitherOf8::B($b_expression),
|
||||
@@ -841,67 +303,56 @@ macro_rules! either {
|
||||
}; // if you need more eithers feel free to open a PR ;-)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// compile time test
|
||||
#[test]
|
||||
fn either_macro() {
|
||||
let _: Either<&str, f64> = either!(12,
|
||||
12 => "12",
|
||||
_ => 0.0,
|
||||
);
|
||||
let _: EitherOf3<&str, f64, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf4<&str, f64, char, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf5<&str, f64, char, f32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf6<&str, f64, char, f32, u8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf7<&str, f64, char, f32, u8, i8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf8<&str, f64, char, f32, u8, i8, u32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
18 => 42u32,
|
||||
_ => 12,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn unwrap_wrong_either() {
|
||||
Either::<i32, &str>::Left(0).unwrap_right();
|
||||
}
|
||||
// compile time test
|
||||
#[test]
|
||||
fn either_macro() {
|
||||
let _: Either<&str, f64> = either!(12,
|
||||
12 => "12",
|
||||
_ => 0.0,
|
||||
);
|
||||
let _: EitherOf3<&str, f64, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf4<&str, f64, char, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf5<&str, f64, char, f32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf6<&str, f64, char, f32, u8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf7<&str, f64, char, f32, u8, i8, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
_ => 12,
|
||||
);
|
||||
let _: EitherOf8<&str, f64, char, f32, u8, i8, u32, i32> = either!(12,
|
||||
12 => "12",
|
||||
13 => 0.0,
|
||||
14 => ' ',
|
||||
15 => 0.0f32,
|
||||
16 => 24u8,
|
||||
17 => 2i8,
|
||||
18 => 42u32,
|
||||
_ => 12,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1.0"
|
||||
gloo-utils = "0.2.0"
|
||||
@@ -20,27 +20,18 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.39", features = [
|
||||
"rt-multi-thread",
|
||||
"macros",
|
||||
"time",
|
||||
], optional = true }
|
||||
tokio = { version = "1.39", features = [ "rt-multi-thread", "macros", "time" ], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = { version = "0.3.69", features = [
|
||||
"AddEventListenerOptions",
|
||||
"Document",
|
||||
"Element",
|
||||
"Event",
|
||||
"EventListener",
|
||||
"EventTarget",
|
||||
"Performance",
|
||||
"Window",
|
||||
], optional = true }
|
||||
web-sys = { version = "0.3.69", features = [ "AddEventListenerOptions", "Document", "Element", "Event", "EventListener", "EventTarget", "Performance", "Window" ], optional = true }
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate", "dep:js-sys", "dep:web-sys"]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"dep:js-sys",
|
||||
"dep:web-sys",
|
||||
]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:http-body-util",
|
||||
@@ -65,7 +56,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
2
examples/axum_js_ssr/rust-toolchain.toml
Normal file
2
examples/axum_js_ssr/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable" # test change
|
||||
@@ -26,7 +26,8 @@ async fn main() {
|
||||
};
|
||||
use axum_js_ssr::app::*;
|
||||
use http_body_util::BodyExt;
|
||||
use leptos::{logging::log, prelude::*};
|
||||
use leptos::logging::log;
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
|
||||
latency::LATENCY.get_or_init(|| [0, 4, 40, 400].iter().cycle().into());
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
extend = [
|
||||
{ path = "./lint.toml" }
|
||||
]
|
||||
|
||||
[tasks.make-target-site-dir]
|
||||
command = "mkdir"
|
||||
args = ["-p", "target/site"]
|
||||
@@ -20,16 +24,21 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy"]
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check-release]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.lint]
|
||||
dependencies = ["make-target-site-dir", "check-style"]
|
||||
|
||||
[tasks.start-client]
|
||||
dependencies = ["install-cargo-leptos"]
|
||||
command = "cargo"
|
||||
args = ["leptos", "watch", "--release", "-P"]
|
||||
args = ["leptos", "watch", "--release", "-P"]
|
||||
@@ -1,11 +1,5 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.install-cargo-leptos]
|
||||
install_crate = { crate_name = "cargo-leptos", binary = "cargo-leptos", test_arg = "--help" }
|
||||
args = ["--locked"]
|
||||
|
||||
[tasks.cargo-leptos-e2e]
|
||||
command = "cargo"
|
||||
@@ -21,14 +15,16 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy"]
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check-release]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.start-client]
|
||||
dependencies = ["install-cargo-leptos"]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.build]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "build"]
|
||||
args = ["build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
|
||||
[tasks.build]
|
||||
install_crate = { crate_name = "wasm-pack", binary = "wasm-pack", test_arg = "--help" }
|
||||
clear = true
|
||||
@@ -19,11 +14,13 @@ clear = true
|
||||
dependencies = ["check-debug", "check-release"]
|
||||
|
||||
[tasks.check-debug]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy"]
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check-release]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy", "--release"]
|
||||
args = ["check-all-features", "--release"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
[tasks.cargo-all-features]
|
||||
install_script = '''
|
||||
cargo install --git https://github.com/sabify/cargo-all-features --branch arbitrary-command-support
|
||||
'''
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--no-deps --all-targets --all-features -- -D warnings" }
|
||||
|
||||
[tasks.lint]
|
||||
dependencies = ["check-format-flow", "clippy-each-feature"]
|
||||
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["cargo-all-features"]
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy", "--no-deps", "--", "-D", "warnings"]
|
||||
[tasks.check-style]
|
||||
dependencies = ["check-format-flow", "clippy-flow"]
|
||||
|
||||
[tasks.check-format]
|
||||
env = { LEPTOS_PROJECT_DIRECTORY = "../../" }
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
extend = [
|
||||
{ path = "./compile.toml" },
|
||||
{ path = "./clean.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./node.toml" },
|
||||
{ path = "./process.toml" },
|
||||
{ path = "./compile.toml" },
|
||||
{ path = "./clean.toml" },
|
||||
{ path = "./lint.toml" },
|
||||
{ path = "./node.toml" },
|
||||
{ path = "./process.toml" },
|
||||
]
|
||||
|
||||
# CI Stages
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = ["prepare", "lint", "test-flow", "integration-test"]
|
||||
dependencies = ["prepare", "lint", "build", "test-flow", "integration-test"]
|
||||
|
||||
[tasks.prepare]
|
||||
dependencies = ["setup-node"]
|
||||
|
||||
[tasks.lint]
|
||||
dependencies = ["check-style"]
|
||||
|
||||
[tasks.integration-test]
|
||||
|
||||
# Support Local Runs
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/playwright.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.integration-test]
|
||||
dependencies = ["build", "start-client", "test-playwright"]
|
||||
description = "Run integration test with automated start and stop of processes"
|
||||
env = { SPAWN_CLIENT_PROCESS = "1" }
|
||||
run_task = { name = ["start", "wait-test-stop"], parallel = true }
|
||||
|
||||
[tasks.wait-test-stop]
|
||||
private = true
|
||||
dependencies = ["wait-server", "test-playwright", "stop"]
|
||||
|
||||
[tasks.wait-server]
|
||||
script = '''
|
||||
for run in {1..12}; do
|
||||
echo "Waiting to ensure server is started..."
|
||||
sleep 10
|
||||
done
|
||||
echo "Times up, running tests"
|
||||
'''
|
||||
|
||||
@@ -6,6 +6,5 @@ command = "trunk"
|
||||
args = ["build"]
|
||||
|
||||
[tasks.start-client]
|
||||
script = '''
|
||||
trunk serve -q "${@}" &
|
||||
'''
|
||||
command = "trunk"
|
||||
args = ["serve", "${@}"]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[tasks.test]
|
||||
env = { RUN_CARGO_TEST = false }
|
||||
condition = { env_true = ["RUN_CARGO_TEST"] }
|
||||
|
||||
[tasks.post-test]
|
||||
dependencies = ["test-wasm"]
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z" />
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
|
||||
<head>
|
||||
<link data-trunk rel="rust" data-wasm-opt="z"/>
|
||||
<link data-trunk rel="icon" type="image/ico" href="/public/favicon.ico"/>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use counter::*;
|
||||
use leptos::{mount::mount_to, prelude::*, task::tick};
|
||||
use leptos::mount::mount_to;
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::tick;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.build]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "build"]
|
||||
args = ["build-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
dependencies = ["cargo-all-features"]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["all-features", "clippy"]
|
||||
args = ["check-all-features"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -5,7 +5,8 @@ use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
use counters::Counters;
|
||||
use leptos::{prelude::*, task::tick};
|
||||
use leptos::prelude::*;
|
||||
use leptos::task::tick;
|
||||
use web_sys::HtmlElement;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@@ -23,9 +24,8 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">0</span> from <span \
|
||||
data-testid=\"counters\">0</span> counters.</p><ul><!----></ul>"
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">0</span> from <span data-testid=\"counters\">0</span> counters.</p><ul><!----></ul>"
|
||||
);
|
||||
|
||||
// add 3 counters
|
||||
@@ -39,9 +39,8 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">0</span> from <span \
|
||||
data-testid=\"counters\">3</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">0</span> from <span data-testid=\"counters\">3</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>0</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
@@ -82,9 +81,8 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">6</span> from <span \
|
||||
data-testid=\"counters\">3</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">6</span> from <span data-testid=\"counters\">3</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>1</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
@@ -108,9 +106,8 @@ async fn inc() {
|
||||
assert_eq!(
|
||||
div.inner_html(),
|
||||
"<button>Add Counter</button><button>Add 1000 \
|
||||
Counters</button><button>Clear Counters</button><p>Total: <span \
|
||||
data-testid=\"total\">5</span> from <span \
|
||||
data-testid=\"counters\">2</span> \
|
||||
Counters</button><button>Clear Counters</button><p>Total: \
|
||||
<span data-testid=\"total\">5</span> from <span data-testid=\"counters\">2</span> \
|
||||
counters.</p><ul><li><button>-1</button><input \
|
||||
type=\"text\"><span>2</span><button>+1</button><button>x</button></\
|
||||
li><li><button>-1</button><input \
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -13,7 +13,7 @@ leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
leptos_meta = { path = "../../meta" }
|
||||
leptos_router = { path = "../../router" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -36,7 +36,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "leptos_axum"]
|
||||
skip_feature_sets = [["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -45,7 +45,7 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/special/{id}", get(custom_handler))
|
||||
.route("/special/:id", get(custom_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use leptos::prelude::*;
|
||||
use leptos::tachys::html::style::style;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -15,7 +16,7 @@ pub enum CatError {
|
||||
|
||||
type CatCount = usize;
|
||||
|
||||
async fn fetch_cats(count: CatCount) -> Result<Vec<String>, Error> {
|
||||
async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
||||
if count > 0 {
|
||||
gloo_timers::future::TimeoutFuture::new(1000).await;
|
||||
// make the request
|
||||
@@ -41,7 +42,11 @@ async fn fetch_cats(count: CatCount) -> Result<Vec<String>, Error> {
|
||||
pub fn fetch_example() -> impl IntoView {
|
||||
let (cat_count, set_cat_count) = signal::<CatCount>(1);
|
||||
|
||||
let cats = LocalResource::new(move || fetch_cats(cat_count.get()));
|
||||
// we use new_unsync here because the reqwasm request type isn't Send
|
||||
// if we were doing SSR, then
|
||||
// 1) we'd want to use a Resource, so the data would be serialized to the client
|
||||
// 2) we'd need to make sure there was a thread-local spawner set up
|
||||
let cats = AsyncDerived::new_unsync(move || fetch_cats(cat_count.get()));
|
||||
|
||||
let fallback = move |errors: ArcRwSignal<Errors>| {
|
||||
let error_list = move || {
|
||||
@@ -61,6 +66,8 @@ pub fn fetch_example() -> impl IntoView {
|
||||
}
|
||||
};
|
||||
|
||||
let spreadable = style(("background-color", "AliceBlue"));
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<label>
|
||||
@@ -75,7 +82,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
/>
|
||||
|
||||
</label>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> }>
|
||||
<Transition fallback=|| view! { <div>"Loading..."</div> } {..spreadable}>
|
||||
<ErrorBoundary fallback>
|
||||
<ul>
|
||||
{move || Suspend::new(async move {
|
||||
@@ -85,7 +92,7 @@ pub fn fetch_example() -> impl IntoView {
|
||||
.map(|s| {
|
||||
view! {
|
||||
<li>
|
||||
<img src=s.clone() />
|
||||
<img src=s.clone()/>
|
||||
</li>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ pub fn main() {
|
||||
|
||||
fmt()
|
||||
.with_writer(
|
||||
// To avoid trace events in the browser from showing their
|
||||
// To avoide trace events in the browser from showing their
|
||||
// JS backtrace, which is very annoying, in my opinion
|
||||
MakeConsoleWriter::default()
|
||||
.map_trace_level_to(tracing::Level::DEBUG),
|
||||
|
||||
@@ -43,7 +43,7 @@ codegen-units = 1
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["actix-files", "actix-web", "leptos_actix"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
format!("https://node-hnapi.herokuapp.com/{path}")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::api::{self, User};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -47,7 +47,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use leptos::logging;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn story(path: &str) -> String {
|
||||
format!("https://node-hnapi.herokuapp.com/{path}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::get, Router};
|
||||
use axum::Router;
|
||||
use hackernews_axum::{shell, App};
|
||||
use leptos::config::get_configuration;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
@@ -13,15 +13,6 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/favicon.ico",
|
||||
get(|| async {
|
||||
(
|
||||
[("content-type", "image/x-icon")],
|
||||
include_bytes!("../public/favicon.ico"),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::api::{self, User};
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[component]
|
||||
|
||||
@@ -20,7 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.8.1", optional = true, features = ["http2"] }
|
||||
axum = { version = "0.7.5", optional = true, features = ["http2"] }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = [
|
||||
"fs",
|
||||
@@ -57,8 +57,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
max_combination_size = 2
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,16 +1,8 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-compress.toml" },
|
||||
]
|
||||
|
||||
[tasks.ci]
|
||||
dependencies = [
|
||||
"prepare",
|
||||
"make-target-site-dir",
|
||||
"lint",
|
||||
"test-flow",
|
||||
"integration-test",
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/cargo-leptos-compress.toml" },
|
||||
]
|
||||
|
||||
[env]
|
||||
|
||||
CLIENT_PROCESS_NAME = "hackernews_islands"
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -3,7 +3,6 @@ use axum::{
|
||||
http::{header, Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
use rust_embed::Embed;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
@@ -12,7 +11,7 @@ const DEV_MODE: bool = false;
|
||||
#[cfg(debug_assertions)]
|
||||
const DEV_MODE: bool = true;
|
||||
|
||||
#[derive(Embed)]
|
||||
#[derive(rust_embed::RustEmbed)]
|
||||
#[folder = "target/site/"]
|
||||
struct Assets;
|
||||
|
||||
@@ -26,17 +25,12 @@ pub async fn file_and_error_handler(
|
||||
.map(|h| h.to_str().unwrap_or("none"))
|
||||
.unwrap_or("none")
|
||||
.to_string();
|
||||
let static_result = get_static_file(uri.clone(), accept_encoding).await;
|
||||
let res = get_static_file(uri.clone(), accept_encoding).await.unwrap();
|
||||
|
||||
match static_result {
|
||||
Ok(res) => {
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not found.").into_response()
|
||||
}
|
||||
}
|
||||
Err(e) => e.into_response(),
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "Not found.").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::routing::get;
|
||||
pub use axum::Router;
|
||||
use hackernews_islands::*;
|
||||
pub use leptos::config::get_configuration;
|
||||
@@ -26,7 +25,6 @@ async fn main() {
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/favicon.ico", get(fallback::file_and_error_handler))
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn Stories() -> impl IntoView {
|
||||
let stories = Resource::new(
|
||||
move || (page(), story_type()),
|
||||
move |(page, story_type)| async move {
|
||||
fetch_stories(story_type, page).await.ok()
|
||||
fetch_stories(category(&story_type), page).await.ok()
|
||||
},
|
||||
);
|
||||
let (pending, set_pending) = signal(false);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos::either::Either;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::Meta;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
use leptos_router::components::A;
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[server]
|
||||
pub async fn fetch_story(
|
||||
@@ -13,7 +15,7 @@ pub async fn fetch_story(
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| async move {
|
||||
if id.is_empty() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::api;
|
||||
use leptos::{either::Either, prelude::*, server::Resource};
|
||||
use leptos::server::Resource;
|
||||
use leptos::{either::Either, prelude::*};
|
||||
use leptos_router::hooks::use_params_map;
|
||||
|
||||
#[server]
|
||||
|
||||
@@ -23,7 +23,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
tracing = "0.1.40"
|
||||
gloo-net = { version = "0.6.0", features = ["http"] }
|
||||
reqwest = { version = "0.12.5", features = ["json"] }
|
||||
axum = { version = "0.8.1", default-features = false, optional = true }
|
||||
axum = { version = "0.7.5", default-features = false, optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
http = { version = "1.1", optional = true }
|
||||
web-sys = { version = "0.3.70", features = [
|
||||
@@ -57,7 +57,7 @@ ssr = [
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "http", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -7,7 +7,7 @@ use send_wrapper::SendWrapper;
|
||||
#[component]
|
||||
pub fn Story() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
let story = Resource::new_blocking(
|
||||
let story = Resource::new(
|
||||
move || params.read().get("id").unwrap_or_default(),
|
||||
move |id| {
|
||||
SendWrapper::new(async move {
|
||||
|
||||
@@ -10,12 +10,15 @@ crate-type = ["cdylib", "rlib"]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
futures = "0.3.30"
|
||||
http = "1.1"
|
||||
leptos = { path = "../../leptos", features = ["tracing", "islands"] }
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
] }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
@@ -41,7 +44,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -13,21 +13,19 @@ http = "1.1"
|
||||
leptos = { path = "../../leptos", features = [
|
||||
"tracing",
|
||||
"islands",
|
||||
"islands-router",
|
||||
] }
|
||||
leptos_router = { path = "../../router" }
|
||||
server_fn = { path = "../../server_fn", features = ["serde-lite"] }
|
||||
leptos_axum = { path = "../../integrations/axum", features = [
|
||||
"islands-router",
|
||||
"dont-use-islands-router",
|
||||
], optional = true }
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
axum = { version = "0.8.1", optional = true }
|
||||
axum = { version = "0.7.5", optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.2", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.39", features = ["full"], optional = true }
|
||||
wasm-bindgen = "0.2.100"
|
||||
serde_json = "1.0.133"
|
||||
wasm-bindgen = "0.2.93"
|
||||
|
||||
[features]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
@@ -49,7 +47,7 @@ panic = "abort"
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "sqlx", "leptos_axum"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"], []]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
|
||||
[package.metadata.leptos]
|
||||
# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
|
||||
@@ -60,11 +58,11 @@ site-root = "target/site"
|
||||
# Defaults to pkg
|
||||
site-pkg-dir = "pkg"
|
||||
# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to <site-root>/<site-pkg>/app.css
|
||||
style-file = "style.css"
|
||||
style-file = "./style.css"
|
||||
# [Optional] Files in the asset-dir will be copied to the site-root directory
|
||||
assets-dir = "public"
|
||||
# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
|
||||
site-addr = "127.0.0.1:3009"
|
||||
site-addr = "127.0.0.1:3000"
|
||||
# The port to use for automatic reload monitoring
|
||||
reload-port = 3001
|
||||
# The browserlist query used for optimizing the CSS.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
140
examples/islands_router/public/routing.js
Normal file
140
examples/islands_router/public/routing.js
Normal file
@@ -0,0 +1,140 @@
|
||||
window.addEventListener("click", async (ev) => {
|
||||
// confirm that this is an <a> that meets our requirements
|
||||
if (
|
||||
ev.defaultPrevented ||
|
||||
ev.button !== 0 ||
|
||||
ev.metaKey ||
|
||||
ev.altKey ||
|
||||
ev.ctrlKey ||
|
||||
ev.shiftKey
|
||||
)
|
||||
return;
|
||||
|
||||
/** @type HTMLAnchorElement | undefined;*/
|
||||
const a = ev
|
||||
.composedPath()
|
||||
.find(el => el instanceof Node && el.nodeName.toUpperCase() === "A");
|
||||
|
||||
if (!a) return;
|
||||
|
||||
const svg = a.namespaceURI === "http://www.w3.org/2000/svg";
|
||||
const href = svg ? a.href.baseVal : a.href;
|
||||
const target = svg ? a.target.baseVal : a.target;
|
||||
if (target || (!href && !a.hasAttribute("state"))) return;
|
||||
|
||||
const rel = (a.getAttribute("rel") || "").split(/\s+/);
|
||||
if (a.hasAttribute("download") || (rel && rel.includes("external"))) return;
|
||||
|
||||
const url = svg ? new URL(href, document.baseURI) : new URL(href);
|
||||
if (
|
||||
url.origin !== window.location.origin // ||
|
||||
// TODO base
|
||||
//(basePath && url.pathname && !url.pathname.toLowerCase().startsWith(basePath.toLowerCase()))
|
||||
)
|
||||
return;
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
// fetch the new page
|
||||
const resp = await fetch(url);
|
||||
const htmlString = await resp.text();
|
||||
|
||||
// Use DOMParser to parse the HTML string
|
||||
const parser = new DOMParser();
|
||||
// TODO parse from the request stream instead?
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
|
||||
// The 'doc' variable now contains the parsed DOM
|
||||
const transition = async () => {
|
||||
const oldDocWalker = document.createTreeWalker(document);
|
||||
const newDocWalker = doc.createTreeWalker(doc);
|
||||
let oldNode = oldDocWalker.currentNode;
|
||||
let newNode = newDocWalker.currentNode;
|
||||
while(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
oldNode = oldDocWalker.currentNode;
|
||||
newNode = newDocWalker.currentNode;
|
||||
// if the nodes are different, we need to replace the old with the new
|
||||
// because of the typed view tree, this should never actually happen
|
||||
if (oldNode.nodeType !== newNode.nodeType) {
|
||||
oldNode.replaceWith(newNode);
|
||||
}
|
||||
// if it's a text node, just update the text with the new text
|
||||
else if (oldNode.nodeType === Node.TEXT_NODE) {
|
||||
oldNode.textContent = newNode.textContent;
|
||||
}
|
||||
// if it's an element, replace if it's a different tag, or update attributes
|
||||
else if (oldNode.nodeType === Node.ELEMENT_NODE) {
|
||||
/** @type Element */
|
||||
const oldEl = oldNode;
|
||||
/** @type Element */
|
||||
const newEl = newNode;
|
||||
if (oldEl.tagName !== newEl.tagName) {
|
||||
oldEl.replaceWith(newEl);
|
||||
}
|
||||
else {
|
||||
for(const attr of newEl.attributes) {
|
||||
oldEl.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we use comment "branch marker" nodes to distinguish between different branches in the statically-typed view tree
|
||||
// if one of these marker is hit, then there are two options
|
||||
// 1) it's the same branch, and we just keep walking until the end
|
||||
// 2) it's a different branch, in which case the old can be replaced with the new wholesale
|
||||
else if (oldNode.nodeType === Node.COMMENT_NODE) {
|
||||
const oldText = oldNode.textContent;
|
||||
const newText = newNode.textContent;
|
||||
if(oldText.startsWith("bo") && newText !== oldText) {
|
||||
oldDocWalker.nextNode();
|
||||
newDocWalker.nextNode();
|
||||
const oldRange = new Range();
|
||||
const newRange = new Range();
|
||||
let oldBranches = 1;
|
||||
let newBranches = 1;
|
||||
while(oldBranches > 0 && newBranches > 0) {
|
||||
if(oldDocWalker.nextNode() && newDocWalker.nextNode()) {
|
||||
console.log(oldDocWalker.currentNode, newDocWalker.currentNode);
|
||||
if(oldDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(oldDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
oldBranches += 1;
|
||||
} else if(oldDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
oldBranches -= 1;
|
||||
}
|
||||
}
|
||||
if(newDocWalker.currentNode.nodeType === Node.COMMENT_NODE) {
|
||||
if(newDocWalker.currentNode.textContent.startsWith("bo")) {
|
||||
newBranches += 1;
|
||||
} else if(newDocWalker.currentNode.textContent.startsWith("bc")) {
|
||||
|
||||
newBranches -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
oldRange.setStartAfter(oldNode);
|
||||
oldRange.setEndBefore(oldDocWalker.currentNode);
|
||||
newRange.setStartAfter(newNode);
|
||||
newRange.setEndBefore(newDocWalker.currentNode);
|
||||
const newContents = newRange.extractContents();
|
||||
oldRange.deleteContents();
|
||||
oldRange.insertNode(newContents);
|
||||
oldNode.replaceWith(newNode);
|
||||
oldDocWalker.currentNode.replaceWith(newDocWalker.currentNode);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} }
|
||||
}
|
||||
};
|
||||
// Not all browsers support startViewTransition; see https://caniuse.com/?search=startViewTransition
|
||||
if (document.startViewTransition) {
|
||||
await document.startViewTransition(transition);
|
||||
} else {
|
||||
await transition()
|
||||
}
|
||||
window.history.pushState(undefined, null, url);
|
||||
});
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "stable" # test change
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
use leptos::{
|
||||
either::{Either, EitherOf3},
|
||||
prelude::*,
|
||||
};
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::{
|
||||
components::{Route, Router, Routes},
|
||||
hooks::{use_params_map, use_query_map},
|
||||
path,
|
||||
components::{FlatRoutes, Route, Router},
|
||||
StaticSegment,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
@@ -17,7 +12,7 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone()/>
|
||||
<HydrationScripts options=options islands=true islands_router=true/>
|
||||
<HydrationScripts options=options islands=true/>
|
||||
<link rel="stylesheet" id="leptos" href="/pkg/islands.css"/>
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico"/>
|
||||
</head>
|
||||
@@ -31,180 +26,34 @@ pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<script src="/routing.js"></script>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Contacts"</h1>
|
||||
<h1>"My Application"</h1>
|
||||
</header>
|
||||
<nav>
|
||||
<a href="/">"Home"</a>
|
||||
<a href="/about">"About"</a>
|
||||
<a href="/">"Page A"</a>
|
||||
<a href="/b">"Page B"</a>
|
||||
</nav>
|
||||
<main>
|
||||
<Routes fallback=|| "Not found.">
|
||||
<Route path=path!("") view=Home/>
|
||||
<Route path=path!("user/:id") view=Details/>
|
||||
<Route path=path!("about") view=About/>
|
||||
</Routes>
|
||||
<p>
|
||||
<label>"Home Checkbox" <input type="checkbox"/></label>
|
||||
</p>
|
||||
<FlatRoutes fallback=|| "Not found.">
|
||||
<Route path=StaticSegment("") view=PageA/>
|
||||
<Route path=StaticSegment("b") view=PageB/>
|
||||
</FlatRoutes>
|
||||
</main>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn search(query: String) -> Result<Vec<User>, ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let data: Vec<User> = serde_json::from_str(&users)?;
|
||||
let query = query.to_ascii_lowercase();
|
||||
Ok(data
|
||||
.into_iter()
|
||||
.filter(|user| {
|
||||
user.first_name.to_ascii_lowercase().contains(&query)
|
||||
|| user.last_name.to_ascii_lowercase().contains(&query)
|
||||
|| user.email.to_ascii_lowercase().contains(&query)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_user(id: u32) -> Result<(), ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let mut data: Vec<User> = serde_json::from_str(&users)?;
|
||||
data.retain(|user| user.id != id);
|
||||
let new_json = serde_json::to_string(&data)?;
|
||||
tokio::fs::write("./mock_data.json", &new_json).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct User {
|
||||
id: u32,
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
email: String,
|
||||
#[component]
|
||||
pub fn PageA() -> impl IntoView {
|
||||
view! { <label>"Page A" <input type="checkbox"/></label> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> impl IntoView {
|
||||
let q = use_query_map();
|
||||
let q = move || q.read().get("q");
|
||||
let data = Resource::new(q, |q| async move {
|
||||
if let Some(q) = q {
|
||||
search(q).await
|
||||
} else {
|
||||
Ok(vec![])
|
||||
}
|
||||
});
|
||||
let delete_user_action = ServerAction::<DeleteUser>::new();
|
||||
|
||||
let view = move || {
|
||||
Suspend::new(async move {
|
||||
let users = data.await.unwrap();
|
||||
if q().is_none() {
|
||||
EitherOf3::A(view! {
|
||||
<p class="note">"Enter a search to begin viewing contacts."</p>
|
||||
})
|
||||
} else if users.is_empty() {
|
||||
EitherOf3::B(view! {
|
||||
<p class="note">"No users found matching that search."</p>
|
||||
})
|
||||
} else {
|
||||
EitherOf3::C(view! {
|
||||
<table>
|
||||
<tbody>
|
||||
<For
|
||||
each=move || users.clone()
|
||||
key=|user| user.id
|
||||
let:user
|
||||
>
|
||||
<tr>
|
||||
<td>{user.first_name}</td>
|
||||
<td>{user.last_name}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
<a href=format!("/user/{}", user.id)>"Details"</a>
|
||||
<input type="checkbox"/>
|
||||
<ActionForm action=delete_user_action>
|
||||
<input type="hidden" name="id" value=user.id/>
|
||||
<input type="submit" value="Delete"/>
|
||||
</ActionForm>
|
||||
</td>
|
||||
</tr>
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
view! {
|
||||
<section class="page">
|
||||
<form method="GET" class="search">
|
||||
<input type="search" name="q" value=q autofocus oninput="this.form.requestSubmit()"/>
|
||||
<input type="submit"/>
|
||||
</form>
|
||||
<Suspense fallback=|| view! { <p>"Loading..."</p> }>{view}</Suspense>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Details() -> impl IntoView {
|
||||
#[server]
|
||||
pub async fn get_user(id: u32) -> Result<Option<User>, ServerFnError> {
|
||||
let users = tokio::fs::read_to_string("./mock_data.json").await?;
|
||||
let data: Vec<User> = serde_json::from_str(&users)?;
|
||||
Ok(data.iter().find(|user| user.id == id).cloned())
|
||||
}
|
||||
let params = use_params_map();
|
||||
let id = move || {
|
||||
params
|
||||
.read()
|
||||
.get("id")
|
||||
.and_then(|id| id.parse::<u32>().ok())
|
||||
};
|
||||
let user = Resource::new(id, |id| async move {
|
||||
match id {
|
||||
None => Ok(None),
|
||||
Some(id) => get_user(id).await,
|
||||
}
|
||||
});
|
||||
|
||||
move || {
|
||||
Suspend::new(async move {
|
||||
user.await.map(|user| match user {
|
||||
None => Either::Left(view! {
|
||||
<section class="page">
|
||||
<h2>"Not found."</h2>
|
||||
<p>"Sorry — we couldn’t find that user."</p>
|
||||
</section>
|
||||
}),
|
||||
Some(user) => Either::Right(view! {
|
||||
<section class="page">
|
||||
<h2>{user.first_name} " " { user.last_name}</h2>
|
||||
<p class="email">{user.email}</p>
|
||||
</section>
|
||||
}),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn About() -> impl IntoView {
|
||||
view! {
|
||||
<section class="page">
|
||||
<h2>"About"</h2>
|
||||
<p>"This demo is intended to show off an experimental “islands router” feature, which mimics the smooth transitions and user experience of client-side routing while minimizing the amount of code that actually runs in the browser."</p>
|
||||
<p>"By default, all the content in this application is only rendered on the server. But you can add client-side interactivity via islands like this one:"</p>
|
||||
<Counter/>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
#[island]
|
||||
pub fn Counter() -> impl IntoView {
|
||||
let count = RwSignal::new(0);
|
||||
view! {
|
||||
<button class="counter" on:click=move |_| *count.write() += 1>{count}</button>
|
||||
}
|
||||
pub fn PageB() -> impl IntoView {
|
||||
view! { <label>"Page B" <input type="checkbox"/></label> }
|
||||
}
|
||||
|
||||
@@ -1,52 +1,3 @@
|
||||
body {
|
||||
font-family: system-ui, sans-serif;
|
||||
background-color: #f6f6fa;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: ui-rounded, 'Hiragino Maru Gothic ProN', Quicksand, Comfortaa, Manjari, 'Arial Rounded MT', 'Arial Rounded MT Bold', Calibri, source-sans-pro, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
nav a {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
form.search {
|
||||
display: flex;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
td {
|
||||
min-width: 10rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
table {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
td:last-child > * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.note, .note {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.counter {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
margin: auto;
|
||||
.pending {
|
||||
color: purple;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
extend = [
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
{ path = "../cargo-make/main.toml" },
|
||||
{ path = "../cargo-make/wasm-test.toml" },
|
||||
{ path = "../cargo-make/trunk_server.toml" },
|
||||
]
|
||||
|
||||
[tasks.clippy-each-feature]
|
||||
dependencies = ["cargo-all-features"]
|
||||
[tasks.build]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = [
|
||||
"all-features",
|
||||
"clippy",
|
||||
"--target",
|
||||
"wasm32-unknown-unknown",
|
||||
"--no-deps",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings",
|
||||
]
|
||||
args = ["build-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.check]
|
||||
toolchain = "stable"
|
||||
command = "cargo"
|
||||
args = ["check-all-features", "--target", "wasm32-unknown-unknown"]
|
||||
install_crate = "cargo-all-features"
|
||||
|
||||
[tasks.pre-clippy]
|
||||
env = { CARGO_MAKE_CLIPPY_ARGS = "--all-targets --all-features --target wasm32-unknown-unknown -- -D warnings" }
|
||||
|
||||
2
examples/js-framework-benchmark/rust-toolchain.toml
Normal file
2
examples/js-framework-benchmark/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user