Compare commits
8 commits
main
...
patch5-bet
Author | SHA1 | Date | |
---|---|---|---|
|
de1dab748d | ||
|
46482d30ca | ||
|
aab303127c | ||
|
2630c6ff23 | ||
|
235bb64358 | ||
|
4cdd6de7d6 | ||
|
a60e5ada56 | ||
|
ba7f1a969d |
504 changed files with 25207 additions and 44709 deletions
60
.github/workflows/ci.yaml
vendored
60
.github/workflows/ci.yaml
vendored
|
@ -2,10 +2,10 @@ name: CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
fmt:
|
fmt:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
@ -28,7 +28,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
components: clippy
|
components: clippy
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Build rust docs
|
- name: Build rust docs
|
||||||
|
@ -51,6 +51,9 @@ jobs:
|
||||||
- name: Install doxygen
|
- name: Install doxygen
|
||||||
run: sudo apt-get install -y doxygen
|
run: sudo apt-get install -y doxygen
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Build C docs
|
||||||
|
run: ./scripts/ci/cmake-docs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -64,50 +67,23 @@ jobs:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
with:
|
with:
|
||||||
arguments: '--manifest-path ./rust/Cargo.toml'
|
|
||||||
command: check ${{ matrix.checks }}
|
command: check ${{ matrix.checks }}
|
||||||
|
|
||||||
wasm_tests:
|
wasm_tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install wasm-bindgen-cli
|
- name: Install wasm-pack
|
||||||
run: cargo install wasm-bindgen-cli wasm-opt
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
- name: Install wasm32 target
|
|
||||||
run: rustup target add wasm32-unknown-unknown
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: ./scripts/ci/wasm_tests
|
run: ./scripts/ci/wasm_tests
|
||||||
deno_tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: denoland/setup-deno@v1
|
|
||||||
with:
|
|
||||||
deno-version: v1.x
|
|
||||||
- name: Install wasm-bindgen-cli
|
|
||||||
run: cargo install wasm-bindgen-cli wasm-opt
|
|
||||||
- name: Install wasm32 target
|
|
||||||
run: rustup target add wasm32-unknown-unknown
|
|
||||||
- name: run tests
|
|
||||||
run: ./scripts/ci/deno_tests
|
|
||||||
|
|
||||||
js_fmt:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: install
|
|
||||||
run: yarn global add prettier
|
|
||||||
- name: format
|
|
||||||
run: prettier -c javascript/.prettierrc javascript
|
|
||||||
|
|
||||||
js_tests:
|
js_tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install wasm-bindgen-cli
|
- name: Install wasm-pack
|
||||||
run: cargo install wasm-bindgen-cli wasm-opt
|
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
- name: Install wasm32 target
|
|
||||||
run: rustup target add wasm32-unknown-unknown
|
|
||||||
- name: run tests
|
- name: run tests
|
||||||
run: ./scripts/ci/js_tests
|
run: ./scripts/ci/js_tests
|
||||||
|
|
||||||
|
@ -118,7 +94,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly-2023-01-26
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install CMocka
|
- name: Install CMocka
|
||||||
|
@ -127,8 +103,6 @@ jobs:
|
||||||
uses: jwlawson/actions-setup-cmake@v1.12
|
uses: jwlawson/actions-setup-cmake@v1.12
|
||||||
with:
|
with:
|
||||||
cmake-version: latest
|
cmake-version: latest
|
||||||
- name: Install rust-src
|
|
||||||
run: rustup component add rust-src
|
|
||||||
- name: Build and test C bindings
|
- name: Build and test C bindings
|
||||||
run: ./scripts/ci/cmake-build Release Static
|
run: ./scripts/ci/cmake-build Release Static
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -138,7 +112,9 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
toolchain:
|
toolchain:
|
||||||
- 1.67.0
|
- 1.60.0
|
||||||
|
- nightly
|
||||||
|
continue-on-error: ${{ matrix.toolchain == 'nightly' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
|
@ -157,7 +133,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- run: ./scripts/ci/build-test
|
- run: ./scripts/ci/build-test
|
||||||
|
@ -170,7 +146,7 @@ jobs:
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: 1.67.0
|
toolchain: 1.60.0
|
||||||
default: true
|
default: true
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- run: ./scripts/ci/build-test
|
- run: ./scripts/ci/build-test
|
||||||
|
|
18
.github/workflows/docs.yaml
vendored
18
.github/workflows/docs.yaml
vendored
|
@ -30,16 +30,28 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clean
|
command: clean
|
||||||
args: --manifest-path ./rust/Cargo.toml --doc
|
args: --doc
|
||||||
|
|
||||||
- name: Build Rust docs
|
- name: Build Rust docs
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: doc
|
command: doc
|
||||||
args: --manifest-path ./rust/Cargo.toml --workspace --all-features --no-deps
|
args: --workspace --all-features --no-deps
|
||||||
|
|
||||||
- name: Move Rust docs
|
- name: Move Rust docs
|
||||||
run: mkdir -p docs && mv rust/target/doc/* docs/.
|
run: mkdir -p docs && mv target/doc/* docs/.
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install doxygen
|
||||||
|
run: sudo apt-get install -y doxygen
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build C docs
|
||||||
|
run: ./scripts/ci/cmake-docs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Move C docs
|
||||||
|
run: mkdir -p docs/automerge-c && mv automerge-c/build/src/html/* docs/automerge-c/.
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Configure root page
|
- name: Configure root page
|
||||||
|
|
214
.github/workflows/release.yaml
vendored
214
.github/workflows/release.yaml
vendored
|
@ -1,214 +0,0 @@
|
||||||
name: Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_if_wasm_version_upgraded:
|
|
||||||
name: Check if WASM version has been upgraded
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
wasm_version: ${{ steps.version-updated.outputs.current-package-version }}
|
|
||||||
wasm_has_updated: ${{ steps.version-updated.outputs.has-updated }}
|
|
||||||
steps:
|
|
||||||
- uses: JiPaix/package-json-updated-action@v1.0.5
|
|
||||||
id: version-updated
|
|
||||||
with:
|
|
||||||
path: rust/automerge-wasm/package.json
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish-wasm:
|
|
||||||
name: Publish WASM package
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- check_if_wasm_version_upgraded
|
|
||||||
# We create release only if the version in the package.json has been upgraded
|
|
||||||
if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true'
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '16.x'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
- uses: denoland/setup-deno@v1
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
- name: Get rid of local github workflows
|
|
||||||
run: rm -r .github/workflows
|
|
||||||
- name: Remove tmp_branch if it exists
|
|
||||||
run: git push origin :tmp_branch || true
|
|
||||||
- run: git checkout -b tmp_branch
|
|
||||||
- name: Install wasm-bindgen-cli
|
|
||||||
run: cargo install wasm-bindgen-cli wasm-opt
|
|
||||||
- name: Install wasm32 target
|
|
||||||
run: rustup target add wasm32-unknown-unknown
|
|
||||||
- name: run wasm js tests
|
|
||||||
id: wasm_js_tests
|
|
||||||
run: ./scripts/ci/wasm_tests
|
|
||||||
- name: run wasm deno tests
|
|
||||||
id: wasm_deno_tests
|
|
||||||
run: ./scripts/ci/deno_tests
|
|
||||||
- name: build release
|
|
||||||
id: build_release
|
|
||||||
run: |
|
|
||||||
npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm run release
|
|
||||||
- name: Collate deno release files
|
|
||||||
if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
|
|
||||||
run: |
|
|
||||||
mkdir $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
cp $GITHUB_WORKSPACE/rust/automerge-wasm/deno/* $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
cp $GITHUB_WORKSPACE/rust/automerge-wasm/index.d.ts $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
cp $GITHUB_WORKSPACE/rust/automerge-wasm/README.md $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
cp $GITHUB_WORKSPACE/rust/automerge-wasm/LICENSE $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
sed -i '1i /// <reference types="./index.d.ts" />' $GITHUB_WORKSPACE/deno_wasm_dist/automerge_wasm.js
|
|
||||||
- name: Create npm release
|
|
||||||
if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
|
|
||||||
run: |
|
|
||||||
if [ "$(npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm show . version)" = "$VERSION" ]; then
|
|
||||||
echo "This version is already published"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
EXTRA_ARGS="--access public"
|
|
||||||
if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then
|
|
||||||
echo "Is pre-release version"
|
|
||||||
EXTRA_ARGS="$EXTRA_ARGS --tag next"
|
|
||||||
fi
|
|
||||||
if [ "$NODE_AUTH_TOKEN" = "" ]; then
|
|
||||||
echo "Can't publish on NPM, You need a NPM_TOKEN secret."
|
|
||||||
false
|
|
||||||
fi
|
|
||||||
npm publish $GITHUB_WORKSPACE/rust/automerge-wasm $EXTRA_ARGS
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
|
|
||||||
- name: Commit wasm deno release files
|
|
||||||
run: |
|
|
||||||
git config --global user.name "actions"
|
|
||||||
git config --global user.email actions@github.com
|
|
||||||
git add $GITHUB_WORKSPACE/deno_wasm_dist
|
|
||||||
git commit -am "Add deno release files"
|
|
||||||
git push origin tmp_branch
|
|
||||||
- name: Tag wasm release
|
|
||||||
if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: Automerge Wasm v${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
|
|
||||||
tag_name: js/automerge-wasm-${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
|
|
||||||
target_commitish: tmp_branch
|
|
||||||
generate_release_notes: false
|
|
||||||
draft: false
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Remove tmp_branch
|
|
||||||
run: git push origin :tmp_branch
|
|
||||||
check_if_js_version_upgraded:
|
|
||||||
name: Check if JS version has been upgraded
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
js_version: ${{ steps.version-updated.outputs.current-package-version }}
|
|
||||||
js_has_updated: ${{ steps.version-updated.outputs.has-updated }}
|
|
||||||
steps:
|
|
||||||
- uses: JiPaix/package-json-updated-action@v1.0.5
|
|
||||||
id: version-updated
|
|
||||||
with:
|
|
||||||
path: javascript/package.json
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
publish-js:
|
|
||||||
name: Publish JS package
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- check_if_js_version_upgraded
|
|
||||||
- check_if_wasm_version_upgraded
|
|
||||||
- publish-wasm
|
|
||||||
# We create release only if the version in the package.json has been upgraded and after the WASM release
|
|
||||||
if: |
|
|
||||||
(always() && ! cancelled()) &&
|
|
||||||
(needs.publish-wasm.result == 'success' || needs.publish-wasm.result == 'skipped') &&
|
|
||||||
needs.check_if_js_version_upgraded.outputs.js_has_updated == 'true'
|
|
||||||
steps:
|
|
||||||
- uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: '16.x'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
- uses: denoland/setup-deno@v1
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.ref }}
|
|
||||||
- name: Get rid of local github workflows
|
|
||||||
run: rm -r .github/workflows
|
|
||||||
- name: Remove js_tmp_branch if it exists
|
|
||||||
run: git push origin :js_tmp_branch || true
|
|
||||||
- run: git checkout -b js_tmp_branch
|
|
||||||
- name: check js formatting
|
|
||||||
run: |
|
|
||||||
yarn global add prettier
|
|
||||||
prettier -c javascript/.prettierrc javascript
|
|
||||||
- name: run js tests
|
|
||||||
id: js_tests
|
|
||||||
run: |
|
|
||||||
cargo install wasm-bindgen-cli wasm-opt
|
|
||||||
rustup target add wasm32-unknown-unknown
|
|
||||||
./scripts/ci/js_tests
|
|
||||||
- name: build js release
|
|
||||||
id: build_release
|
|
||||||
run: |
|
|
||||||
npm --prefix $GITHUB_WORKSPACE/javascript run build
|
|
||||||
- name: build js deno release
|
|
||||||
id: build_deno_release
|
|
||||||
run: |
|
|
||||||
VERSION=$WASM_VERSION npm --prefix $GITHUB_WORKSPACE/javascript run deno:build
|
|
||||||
env:
|
|
||||||
WASM_VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
|
|
||||||
- name: run deno tests
|
|
||||||
id: deno_tests
|
|
||||||
run: |
|
|
||||||
npm --prefix $GITHUB_WORKSPACE/javascript run deno:test
|
|
||||||
- name: Collate deno release files
|
|
||||||
if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
|
|
||||||
run: |
|
|
||||||
mkdir $GITHUB_WORKSPACE/deno_js_dist
|
|
||||||
cp $GITHUB_WORKSPACE/javascript/deno_dist/* $GITHUB_WORKSPACE/deno_js_dist
|
|
||||||
- name: Create npm release
|
|
||||||
if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
|
|
||||||
run: |
|
|
||||||
if [ "$(npm --prefix $GITHUB_WORKSPACE/javascript show . version)" = "$VERSION" ]; then
|
|
||||||
echo "This version is already published"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
EXTRA_ARGS="--access public"
|
|
||||||
if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then
|
|
||||||
echo "Is pre-release version"
|
|
||||||
EXTRA_ARGS="$EXTRA_ARGS --tag next"
|
|
||||||
fi
|
|
||||||
if [ "$NODE_AUTH_TOKEN" = "" ]; then
|
|
||||||
echo "Can't publish on NPM, You need a NPM_TOKEN secret."
|
|
||||||
false
|
|
||||||
fi
|
|
||||||
npm publish $GITHUB_WORKSPACE/javascript $EXTRA_ARGS
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
|
||||||
VERSION: ${{ needs.check_if_js_version_upgraded.outputs.js_version }}
|
|
||||||
- name: Commit js deno release files
|
|
||||||
run: |
|
|
||||||
git config --global user.name "actions"
|
|
||||||
git config --global user.email actions@github.com
|
|
||||||
git add $GITHUB_WORKSPACE/deno_js_dist
|
|
||||||
git commit -am "Add deno js release files"
|
|
||||||
git push origin js_tmp_branch
|
|
||||||
- name: Tag JS release
|
|
||||||
if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: Automerge v${{ needs.check_if_js_version_upgraded.outputs.js_version }}
|
|
||||||
tag_name: js/automerge-${{ needs.check_if_js_version_upgraded.outputs.js_version }}
|
|
||||||
target_commitish: js_tmp_branch
|
|
||||||
generate_release_notes: false
|
|
||||||
draft: false
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Remove js_tmp_branch
|
|
||||||
run: git push origin :js_tmp_branch
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
|
/target
|
||||||
/.direnv
|
/.direnv
|
||||||
perf.*
|
perf.*
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
build/
|
build/
|
||||||
|
automerge/proptest-regressions/
|
||||||
.vim/*
|
.vim/*
|
||||||
/target
|
|
||||||
|
|
|
@ -3,15 +3,19 @@ members = [
|
||||||
"automerge",
|
"automerge",
|
||||||
"automerge-c",
|
"automerge-c",
|
||||||
"automerge-cli",
|
"automerge-cli",
|
||||||
"automerge-test",
|
|
||||||
"automerge-wasm",
|
"automerge-wasm",
|
||||||
"edit-trace",
|
"edit-trace",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
debug = true
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
opt-level = 'z'
|
||||||
|
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[profile.release.package.automerge-wasm]
|
||||||
|
debug = false
|
||||||
|
opt-level = 'z'
|
20
Makefile
Normal file
20
Makefile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
.PHONY: rust
|
||||||
|
rust:
|
||||||
|
cd automerge && cargo test
|
||||||
|
|
||||||
|
.PHONY: wasm
|
||||||
|
wasm:
|
||||||
|
cd automerge-wasm && yarn
|
||||||
|
cd automerge-wasm && yarn build
|
||||||
|
cd automerge-wasm && yarn test
|
||||||
|
cd automerge-wasm && yarn link
|
||||||
|
|
||||||
|
.PHONY: js
|
||||||
|
js: wasm
|
||||||
|
cd automerge-js && yarn
|
||||||
|
cd automerge-js && yarn link "automerge-wasm"
|
||||||
|
cd automerge-js && yarn test
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
git clean -x -d -f
|
198
README.md
198
README.md
|
@ -1,4 +1,4 @@
|
||||||
# Automerge
|
# Automerge RS
|
||||||
|
|
||||||
<img src='./img/sign.svg' width='500' alt='Automerge logo' />
|
<img src='./img/sign.svg' width='500' alt='Automerge logo' />
|
||||||
|
|
||||||
|
@ -7,141 +7,103 @@
|
||||||
[![ci](https://github.com/automerge/automerge-rs/actions/workflows/ci.yaml/badge.svg)](https://github.com/automerge/automerge-rs/actions/workflows/ci.yaml)
|
[![ci](https://github.com/automerge/automerge-rs/actions/workflows/ci.yaml/badge.svg)](https://github.com/automerge/automerge-rs/actions/workflows/ci.yaml)
|
||||||
[![docs](https://github.com/automerge/automerge-rs/actions/workflows/docs.yaml/badge.svg)](https://github.com/automerge/automerge-rs/actions/workflows/docs.yaml)
|
[![docs](https://github.com/automerge/automerge-rs/actions/workflows/docs.yaml/badge.svg)](https://github.com/automerge/automerge-rs/actions/workflows/docs.yaml)
|
||||||
|
|
||||||
Automerge is a library which provides fast implementations of several different
|
This is a Rust library implementation of the [Automerge](https://github.com/automerge/automerge) file format and network protocol. Its focus is to support the creation of Automerge implementations in other languages, currently; WASM, JS and C. A `libautomerge` if you will.
|
||||||
CRDTs, a compact compression format for these CRDTs, and a sync protocol for
|
|
||||||
efficiently transmitting those changes over the network. The objective of the
|
|
||||||
project is to support [local-first](https://www.inkandswitch.com/local-first/) applications in the same way that relational
|
|
||||||
databases support server applications - by providing mechanisms for persistence
|
|
||||||
which allow application developers to avoid thinking about hard distributed
|
|
||||||
computing problems. Automerge aims to be PostgreSQL for your local-first app.
|
|
||||||
|
|
||||||
If you're looking for documentation on the JavaScript implementation take a look
|
The original [Automerge](https://github.com/automerge/automerge) project (written in JS from the ground up) is still very much maintained and recommended. Indeed it is because of the success of that project that the next stage of Automerge is being explored here. Hopefully Rust can offer a more performant and scalable Automerge, opening up even more use cases.
|
||||||
at https://automerge.org/docs/hello/. There are other implementations in both
|
|
||||||
Rust and C, but they are earlier and don't have documentation yet. You can find
|
|
||||||
them in `rust/automerge` and `rust/automerge-c` if you are comfortable
|
|
||||||
reading the code and tests to figure out how to use them.
|
|
||||||
|
|
||||||
If you're familiar with CRDTs and interested in the design of Automerge in
|
|
||||||
particular take a look at https://automerge.org/docs/how-it-works/backend/
|
|
||||||
|
|
||||||
Finally, if you want to talk to us about this project please [join the
|
|
||||||
Slack](https://join.slack.com/t/automerge/shared_invite/zt-e4p3760n-kKh7r3KRH1YwwNfiZM8ktw)
|
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This project is formed of a core Rust implementation which is exposed via FFI in
|
The project has 5 components:
|
||||||
javascript+WASM, C, and soon other languages. Alex
|
|
||||||
([@alexjg](https://github.com/alexjg/)]) is working full time on maintaining
|
|
||||||
automerge, other members of Ink and Switch are also contributing time and there
|
|
||||||
are several other maintainers. The focus is currently on shipping the new JS
|
|
||||||
package. We expect to be iterating the API and adding new features over the next
|
|
||||||
six months so there will likely be several major version bumps in all packages
|
|
||||||
in that time.
|
|
||||||
|
|
||||||
In general we try and respect semver.
|
1. [_automerge_](automerge) - The main Rust implementation of the library.
|
||||||
|
2. [_automerge-wasm_](automerge-wasm) - A JS/WASM interface to the underlying Rust library. This API is generally mature and in use in a handful of projects.
|
||||||
|
3. [_automerge-js_](automerge-js) - This is a Javascript library using the WASM interface to export the same public API of the primary Automerge project. Currently this project passes all of Automerge's tests but has not been used in any real project or packaged as an NPM. Alpha testers welcome.
|
||||||
|
4. [_automerge-c_](automerge-c) - This is a C library intended to be an FFI integration point for all other languages. It is currently a work in progress and not yet ready for any testing.
|
||||||
|
5. [_automerge-cli_](automerge-cli) - An experimental CLI wrapper around the Rust library. Currently not functional.
|
||||||
|
|
||||||
### JavaScript
|
## How?
|
||||||
|
|
||||||
A stable release of the javascript package is currently available as
|
The magic of the architecture is built around the `OpTree`. This is a data structure
|
||||||
`@automerge/automerge@2.0.0` where. pre-release verisions of the `2.0.1` are
|
which supports efficiently inserting new operations and realising values of
|
||||||
available as `2.0.1-alpha.n`. `2.0.1*` packages are also available for Deno at
|
existing operations. Most interactions with the `OpTree` are in the form of
|
||||||
https://deno.land/x/automerge
|
implementations of `TreeQuery` - a trait which can be used to traverse the
|
||||||
|
`OpTree` and producing state of some kind. User facing operations are exposed on
|
||||||
|
an `Automerge` object, under the covers these operations typically instantiate
|
||||||
|
some `TreeQuery` and run it over the `OpTree`.
|
||||||
|
|
||||||
### Rust
|
## Development
|
||||||
|
|
||||||
The rust codebase is currently oriented around producing a performant backend
|
Please feel free to open issues and pull requests.
|
||||||
for the Javascript wrapper and as such the API for Rust code is low level and
|
|
||||||
not well documented. We will be returning to this over the next few months but
|
|
||||||
for now you will need to be comfortable reading the tests and asking questions
|
|
||||||
to figure out how to use it. If you are looking to build rust applications which
|
|
||||||
use automerge you may want to look into
|
|
||||||
[autosurgeon](https://github.com/alexjg/autosurgeon)
|
|
||||||
|
|
||||||
## Repository Organisation
|
### Running CI
|
||||||
|
|
||||||
- `./rust` - the rust rust implementation and also the Rust components of
|
The steps CI will run are all defined in `./scripts/ci`. Obviously CI will run
|
||||||
platform specific wrappers (e.g. `automerge-wasm` for the WASM API or
|
everything when you submit a PR, but if you want to run everything locally
|
||||||
`automerge-c` for the C FFI bindings)
|
before you push you can run `./scripts/ci/run` to run everything.
|
||||||
- `./javascript` - The javascript library which uses `automerge-wasm`
|
|
||||||
internally but presents a more idiomatic javascript interface
|
|
||||||
- `./scripts` - scripts which are useful to maintenance of the repository.
|
|
||||||
This includes the scripts which are run in CI.
|
|
||||||
- `./img` - static assets for use in `.md` files
|
|
||||||
|
|
||||||
## Building
|
### Running the JS tests
|
||||||
|
|
||||||
To build this codebase you will need:
|
You will need to have [node](https://nodejs.org/en/), [yarn](https://yarnpkg.com/getting-started/install), [rust](https://rustup.rs/) and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) installed.
|
||||||
|
|
||||||
- `rust`
|
To build and test the rust library:
|
||||||
- `node`
|
|
||||||
- `yarn`
|
|
||||||
- `cmake`
|
|
||||||
- `cmocka`
|
|
||||||
|
|
||||||
You will also need to install the following with `cargo install`
|
```shell
|
||||||
|
$ cd automerge
|
||||||
- `wasm-bindgen-cli`
|
$ cargo test
|
||||||
- `wasm-opt`
|
|
||||||
- `cargo-deny`
|
|
||||||
|
|
||||||
And ensure you have added the `wasm32-unknown-unknown` target for rust cross-compilation.
|
|
||||||
|
|
||||||
The various subprojects (the rust code, the wrapper projects) have their own
|
|
||||||
build instructions, but to run the tests that will be run in CI you can run
|
|
||||||
`./scripts/ci/run`.
|
|
||||||
|
|
||||||
### For macOS
|
|
||||||
|
|
||||||
These instructions worked to build locally on macOS 13.1 (arm64) as of
|
|
||||||
Nov 29th 2022.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# clone the repo
|
|
||||||
git clone https://github.com/automerge/automerge-rs
|
|
||||||
cd automerge-rs
|
|
||||||
|
|
||||||
# install rustup
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
||||||
|
|
||||||
# install homebrew
|
|
||||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
||||||
|
|
||||||
# install cmake, node, cmocka
|
|
||||||
brew install cmake node cmocka
|
|
||||||
|
|
||||||
# install yarn
|
|
||||||
npm install --global yarn
|
|
||||||
|
|
||||||
# install javascript dependencies
|
|
||||||
yarn --cwd ./javascript
|
|
||||||
|
|
||||||
# install rust dependencies
|
|
||||||
cargo install wasm-bindgen-cli wasm-opt cargo-deny
|
|
||||||
|
|
||||||
# get nightly rust to produce optimized automerge-c builds
|
|
||||||
rustup toolchain install nightly
|
|
||||||
rustup component add rust-src --toolchain nightly
|
|
||||||
|
|
||||||
# add wasm target in addition to current architecture
|
|
||||||
rustup target add wasm32-unknown-unknown
|
|
||||||
|
|
||||||
# Run ci script
|
|
||||||
./scripts/ci/run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If your build fails to find `cmocka.h` you may need to teach it about homebrew's
|
To build and test the wasm library:
|
||||||
installation location:
|
|
||||||
|
|
||||||
```
|
```shell
|
||||||
export CPATH=/opt/homebrew/include
|
## setup
|
||||||
export LIBRARY_PATH=/opt/homebrew/lib
|
$ cd automerge-wasm
|
||||||
./scripts/ci/run
|
$ yarn
|
||||||
|
|
||||||
|
## building or testing
|
||||||
|
$ yarn build
|
||||||
|
$ yarn test
|
||||||
|
|
||||||
|
## without this the js library wont automatically use changes
|
||||||
|
$ yarn link
|
||||||
|
|
||||||
|
## cutting a release or doing benchmarking
|
||||||
|
$ yarn release
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
To test the js library. This is where most of the tests reside.
|
||||||
|
|
||||||
Please try and split your changes up into relatively independent commits which
|
```shell
|
||||||
change one subsystem at a time and add good commit messages which describe what
|
## setup
|
||||||
the change is and why you're making it (err on the side of longer commit
|
$ cd automerge-js
|
||||||
messages). `git blame` should give future maintainers a good idea of why
|
$ yarn
|
||||||
something is the way it is.
|
$ yarn link "automerge-wasm"
|
||||||
|
|
||||||
|
## testing
|
||||||
|
$ yarn test
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally, to build and test the C bindings with CMake:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
## setup
|
||||||
|
$ cd automerge-c
|
||||||
|
$ mkdir -p build
|
||||||
|
$ cd build
|
||||||
|
$ cmake -S .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
|
||||||
|
## building and testing
|
||||||
|
$ cmake --build . --target test_automerge
|
||||||
|
```
|
||||||
|
|
||||||
|
To add debugging symbols, replace `Release` with `Debug`.
|
||||||
|
To build a shared library instead of a static one, replace `OFF` with `ON`.
|
||||||
|
|
||||||
|
The C bindings can be built and tested on any platform for which CMake is
|
||||||
|
available but the steps for doing so vary across platforms and are too numerous
|
||||||
|
to list here.
|
||||||
|
|
||||||
|
## Benchmarking
|
||||||
|
|
||||||
|
The [`edit-trace`](edit-trace) folder has the main code for running the edit trace benchmarking.
|
||||||
|
|
||||||
|
## The old Rust project
|
||||||
|
If you are looking for the origional `automerge-rs` project that can be used as a wasm backend to the javascript implementation, it can be found [here](https://github.com/automerge/automerge-rs/tree/automerge-1.0).
|
||||||
|
|
32
TODO.md
Normal file
32
TODO.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
### next steps:
|
||||||
|
1. C API
|
||||||
|
2. port rust command line tool
|
||||||
|
3. fast load
|
||||||
|
|
||||||
|
### ergonomics:
|
||||||
|
1. value() -> () or something that into's a value
|
||||||
|
|
||||||
|
### automerge:
|
||||||
|
1. single pass (fast) load
|
||||||
|
2. micro-patches / bare bones observation API / fully hydrated documents
|
||||||
|
|
||||||
|
### future:
|
||||||
|
1. handle columns with unknown data in and out
|
||||||
|
2. branches with different indexes
|
||||||
|
|
||||||
|
### Peritext
|
||||||
|
1. add mark / remove mark -- type, start/end elemid (inclusive,exclusive)
|
||||||
|
2. track any formatting ops that start or end on a character
|
||||||
|
3. ops right before the character, ops right after that character
|
||||||
|
4. query a single character - character, plus marks that start or end on that character
|
||||||
|
what is its current formatting,
|
||||||
|
what are the ops that include that in their span,
|
||||||
|
None = same as last time, Set( bold, italic ),
|
||||||
|
keep these on index
|
||||||
|
5. op probably belongs with the start character - possible packed at the beginning or end of the list
|
||||||
|
|
||||||
|
### maybe:
|
||||||
|
1. tables
|
||||||
|
|
||||||
|
### no:
|
||||||
|
1. cursors
|
3
automerge-c/.gitignore
vendored
Normal file
3
automerge-c/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
automerge
|
||||||
|
automerge.h
|
||||||
|
automerge.o
|
141
automerge-c/CMakeLists.txt
Normal file
141
automerge-c/CMakeLists.txt
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Parse the library name, project name and project version out of Cargo's TOML file.
|
||||||
|
set(CARGO_LIB_SECTION OFF)
|
||||||
|
|
||||||
|
set(LIBRARY_NAME "")
|
||||||
|
|
||||||
|
set(CARGO_PKG_SECTION OFF)
|
||||||
|
|
||||||
|
set(CARGO_PKG_NAME "")
|
||||||
|
|
||||||
|
set(CARGO_PKG_VERSION "")
|
||||||
|
|
||||||
|
file(READ Cargo.toml TOML_STRING)
|
||||||
|
|
||||||
|
string(REPLACE ";" "\\\\;" TOML_STRING "${TOML_STRING}")
|
||||||
|
|
||||||
|
string(REPLACE "\n" ";" TOML_LINES "${TOML_STRING}")
|
||||||
|
|
||||||
|
foreach(TOML_LINE IN ITEMS ${TOML_LINES})
|
||||||
|
string(REGEX MATCH "^\\[(lib|package)\\]$" _ ${TOML_LINE})
|
||||||
|
|
||||||
|
if(CMAKE_MATCH_1 STREQUAL "lib")
|
||||||
|
set(CARGO_LIB_SECTION ON)
|
||||||
|
|
||||||
|
set(CARGO_PKG_SECTION OFF)
|
||||||
|
elseif(CMAKE_MATCH_1 STREQUAL "package")
|
||||||
|
set(CARGO_LIB_SECTION OFF)
|
||||||
|
|
||||||
|
set(CARGO_PKG_SECTION ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REGEX MATCH "^name += +\"([^\"]+)\"$" _ ${TOML_LINE})
|
||||||
|
|
||||||
|
if(CMAKE_MATCH_1 AND (CARGO_LIB_SECTION AND NOT CARGO_PKG_SECTION))
|
||||||
|
set(LIBRARY_NAME "${CMAKE_MATCH_1}")
|
||||||
|
elseif(CMAKE_MATCH_1 AND (NOT CARGO_LIB_SECTION AND CARGO_PKG_SECTION))
|
||||||
|
set(CARGO_PKG_NAME "${CMAKE_MATCH_1}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(REGEX MATCH "^version += +\"([^\"]+)\"$" _ ${TOML_LINE})
|
||||||
|
|
||||||
|
if(CMAKE_MATCH_1 AND CARGO_PKG_SECTION)
|
||||||
|
set(CARGO_PKG_VERSION "${CMAKE_MATCH_1}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBRARY_NAME AND (CARGO_PKG_NAME AND CARGO_PKG_VERSION))
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
project(${CARGO_PKG_NAME} VERSION ${CARGO_PKG_VERSION} LANGUAGES C DESCRIPTION "C bindings for the Automerge Rust backend.")
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
|
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
|
||||||
|
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
string(MAKE_C_IDENTIFIER ${PROJECT_NAME} SYMBOL_PREFIX)
|
||||||
|
|
||||||
|
string(TOUPPER ${SYMBOL_PREFIX} SYMBOL_PREFIX)
|
||||||
|
|
||||||
|
set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target")
|
||||||
|
|
||||||
|
set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
|
||||||
|
set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}")
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
# Generate and install the configuration header.
|
||||||
|
math(EXPR INTEGER_PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR} * 100000")
|
||||||
|
|
||||||
|
math(EXPR INTEGER_PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR} * 100")
|
||||||
|
|
||||||
|
math(EXPR INTEGER_PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
||||||
|
|
||||||
|
math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + ${INTEGER_PROJECT_VERSION_MINOR} + ${INTEGER_PROJECT_VERSION_PATCH}")
|
||||||
|
|
||||||
|
configure_file(
|
||||||
|
${CMAKE_MODULE_PATH}/config.h.in
|
||||||
|
config.h
|
||||||
|
@ONLY
|
||||||
|
NEWLINE_STYLE LF
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES ${CMAKE_BINARY_DIR}/config.h
|
||||||
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_subdirectory(test EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(examples EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
|
# Generate and install .cmake files
|
||||||
|
set(PROJECT_CONFIG_NAME "${PROJECT_NAME}-config")
|
||||||
|
|
||||||
|
set(PROJECT_CONFIG_VERSION_NAME "${PROJECT_CONFIG_NAME}-version")
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
COMPATIBILITY ExactVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
# The namespace label starts with the title-cased library name.
|
||||||
|
string(SUBSTRING ${LIBRARY_NAME} 0 1 NS_FIRST)
|
||||||
|
|
||||||
|
string(SUBSTRING ${LIBRARY_NAME} 1 -1 NS_REST)
|
||||||
|
|
||||||
|
string(TOUPPER ${NS_FIRST} NS_FIRST)
|
||||||
|
|
||||||
|
string(TOLOWER ${NS_REST} NS_REST)
|
||||||
|
|
||||||
|
string(CONCAT NAMESPACE ${NS_FIRST} ${NS_REST} "::")
|
||||||
|
|
||||||
|
# \note CMake doesn't automate the exporting of an imported library's targets
|
||||||
|
# so the package configuration script must do it.
|
||||||
|
configure_package_config_file(
|
||||||
|
${CMAKE_MODULE_PATH}/${PROJECT_CONFIG_NAME}.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
|
||||||
|
DESTINATION
|
||||||
|
${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
|
||||||
|
)
|
|
@ -7,8 +7,8 @@ license = "MIT"
|
||||||
rust-version = "1.57.0"
|
rust-version = "1.57.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "automerge_core"
|
name = "automerge"
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["cdylib", "staticlib"]
|
||||||
bench = false
|
bench = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
|
@ -19,4 +19,4 @@ libc = "^0.2"
|
||||||
smol_str = "^0.1.21"
|
smol_str = "^0.1.21"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cbindgen = "^0.24"
|
cbindgen = "^0.20"
|
97
automerge-c/README.md
Normal file
97
automerge-c/README.md
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
|
||||||
|
## Methods we need to support
|
||||||
|
|
||||||
|
### Basic management
|
||||||
|
|
||||||
|
1. `AMcreate()`
|
||||||
|
1. `AMclone(doc)`
|
||||||
|
1. `AMfree(doc)`
|
||||||
|
1. `AMconfig(doc, key, val)` // set actor
|
||||||
|
1. `actor = get_actor(doc)`
|
||||||
|
|
||||||
|
### Transactions
|
||||||
|
|
||||||
|
1. `AMpendingOps(doc)`
|
||||||
|
1. `AMcommit(doc, message, time)`
|
||||||
|
1. `AMrollback(doc)`
|
||||||
|
|
||||||
|
### Write
|
||||||
|
|
||||||
|
1. `AMset{Map|List}(doc, obj, prop, value)`
|
||||||
|
1. `AMinsert(doc, obj, index, value)`
|
||||||
|
1. `AMpush(doc, obj, value)`
|
||||||
|
1. `AMdel{Map|List}(doc, obj, prop)`
|
||||||
|
1. `AMinc{Map|List}(doc, obj, prop, value)`
|
||||||
|
1. `AMspliceText(doc, obj, start, num_del, text)`
|
||||||
|
|
||||||
|
### Read (the heads argument is optional and can be on an `at` variant)
|
||||||
|
|
||||||
|
1. `AMkeys(doc, obj, heads)`
|
||||||
|
1. `AMlength(doc, obj, heads)`
|
||||||
|
1. `AMlistRange(doc, obj, heads)`
|
||||||
|
1. `AMmapRange(doc, obj, heads)`
|
||||||
|
1. `AMvalues(doc, obj, heads)`
|
||||||
|
1. `AMtext(doc, obj, heads)`
|
||||||
|
|
||||||
|
### Sync
|
||||||
|
|
||||||
|
1. `AMgenerateSyncMessage(doc, state)`
|
||||||
|
1. `AMreceiveSyncMessage(doc, state, message)`
|
||||||
|
1. `AMinitSyncState()`
|
||||||
|
|
||||||
|
### Save / Load
|
||||||
|
|
||||||
|
1. `AMload(data)`
|
||||||
|
1. `AMloadIncremental(doc, data)`
|
||||||
|
1. `AMsave(doc)`
|
||||||
|
1. `AMsaveIncremental(doc)`
|
||||||
|
|
||||||
|
### Low Level Access
|
||||||
|
|
||||||
|
1. `AMapplyChanges(doc, changes)`
|
||||||
|
1. `AMgetChanges(doc, deps)`
|
||||||
|
1. `AMgetChangesAdded(doc1, doc2)`
|
||||||
|
1. `AMgetHeads(doc)`
|
||||||
|
1. `AMgetLastLocalChange(doc)`
|
||||||
|
1. `AMgetMissingDeps(doc, heads)`
|
||||||
|
|
||||||
|
### Encode/Decode
|
||||||
|
|
||||||
|
1. `AMencodeChange(change)`
|
||||||
|
1. `AMdecodeChange(change)`
|
||||||
|
1. `AMencodeSyncMessage(change)`
|
||||||
|
1. `AMdecodeSyncMessage(change)`
|
||||||
|
1. `AMencodeSyncState(change)`
|
||||||
|
1. `AMdecodeSyncState(change)`
|
||||||
|
|
||||||
|
## Open Question - Memory management
|
||||||
|
|
||||||
|
Most of these calls return one or more items of arbitrary length. Doing memory management in C is tricky. This is my proposed solution...
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
```
|
||||||
|
// returns 1 or zero opids
|
||||||
|
n = automerge_set(doc, "_root", "hello", datatype, value);
|
||||||
|
if (n) {
|
||||||
|
automerge_pop(doc, &obj, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns n values
|
||||||
|
n = automerge_values(doc, "_root", "hello");
|
||||||
|
for (i = 0; i<n ;i ++) {
|
||||||
|
automerge_pop_value(doc, &value, &datatype, len);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
There would be one pop method per object type. Users allocs and frees the buffers. Multiple return values would result in multiple pops. Too small buffers would error and allow retry.
|
||||||
|
|
||||||
|
|
||||||
|
### Formats
|
||||||
|
|
||||||
|
Actors - We could do (bytes,len) or a hex encoded string?.
|
||||||
|
ObjIds - We could do flat bytes of the ExId struct but lets do human readable strings for now - the struct would be faster but opque
|
||||||
|
Heads - Might as well make it a flat buffer `(n, hash, hash, ...)`
|
||||||
|
Changes - Put them all in a flat concatenated buffer
|
||||||
|
Encode/Decode - to json strings?
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
||||||
let config = cbindgen::Config::from_file("cbindgen.toml")
|
let config = cbindgen::Config::from_file("cbindgen.toml")
|
||||||
.expect("Unable to find cbindgen.toml configuration file");
|
.expect("Unable to find cbindgen.toml configuration file");
|
||||||
|
|
||||||
if let Ok(writer) = cbindgen::generate_with_config(crate_dir, config) {
|
if let Ok(writer) = cbindgen::generate_with_config(&crate_dir, config) {
|
||||||
// \note CMake sets this environment variable before invoking Cargo so
|
// \note CMake sets this environment variable before invoking Cargo so
|
||||||
// that it can direct the generated header file into its
|
// that it can direct the generated header file into its
|
||||||
// out-of-source build directory for post-processing.
|
// out-of-source build directory for post-processing.
|
|
@ -1,7 +1,7 @@
|
||||||
after_includes = """\n
|
after_includes = """\n
|
||||||
/**
|
/**
|
||||||
* \\defgroup enumerations Public Enumerations
|
* \\defgroup enumerations Public Enumerations
|
||||||
* Symbolic names for integer constants.
|
Symbolic names for integer constants.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,23 +12,21 @@ after_includes = """\n
|
||||||
#define AM_ROOT NULL
|
#define AM_ROOT NULL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \\memberof AMdoc
|
* \\memberof AMchangeHash
|
||||||
* \\def AM_CHANGE_HASH_SIZE
|
* \\def AM_CHANGE_HASH_SIZE
|
||||||
* \\brief The count of bytes in a change hash.
|
* \\brief The count of bytes in a change hash.
|
||||||
*/
|
*/
|
||||||
#define AM_CHANGE_HASH_SIZE 32
|
#define AM_CHANGE_HASH_SIZE 32
|
||||||
"""
|
"""
|
||||||
autogen_warning = """
|
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
|
||||||
/**
|
|
||||||
* \\file
|
|
||||||
* \\brief All constants, functions and types in the core Automerge C API.
|
|
||||||
*
|
|
||||||
* \\warning This file is auto-generated by cbindgen.
|
|
||||||
*/
|
|
||||||
"""
|
|
||||||
documentation = true
|
documentation = true
|
||||||
documentation_style = "doxy"
|
documentation_style = "doxy"
|
||||||
include_guard = "AUTOMERGE_C_H"
|
header = """
|
||||||
|
/** \\file
|
||||||
|
* All constants, functions and types in the Automerge library's C API.
|
||||||
|
*/
|
||||||
|
"""
|
||||||
|
include_guard = "AUTOMERGE_H"
|
||||||
includes = []
|
includes = []
|
||||||
language = "C"
|
language = "C"
|
||||||
line_length = 140
|
line_length = 140
|
14
automerge-c/cmake/config.h.in
Normal file
14
automerge-c/cmake/config.h.in
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef @SYMBOL_PREFIX@_CONFIG_H
|
||||||
|
#define @SYMBOL_PREFIX@_CONFIG_H
|
||||||
|
|
||||||
|
/* This header is auto-generated by CMake. */
|
||||||
|
|
||||||
|
#define @SYMBOL_PREFIX@_VERSION @INTEGER_PROJECT_VERSION@
|
||||||
|
|
||||||
|
#define @SYMBOL_PREFIX@_MAJOR_VERSION (@SYMBOL_PREFIX@_VERSION / 100000)
|
||||||
|
|
||||||
|
#define @SYMBOL_PREFIX@_MINOR_VERSION ((@SYMBOL_PREFIX@_VERSION / 100) % 1000)
|
||||||
|
|
||||||
|
#define @SYMBOL_PREFIX@_PATCH_VERSION (@SYMBOL_PREFIX@_VERSION % 100)
|
||||||
|
|
||||||
|
#endif /* @SYMBOL_PREFIX@_CONFIG_H */
|
|
@ -1,6 +1,4 @@
|
||||||
# This CMake script is used to perform string substitutions within a generated
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
# file.
|
|
||||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
|
||||||
|
|
||||||
if(NOT DEFINED MATCH_REGEX)
|
if(NOT DEFINED MATCH_REGEX)
|
||||||
message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.")
|
message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.")
|
|
@ -1,6 +1,4 @@
|
||||||
# This CMake script is used to force Cargo to regenerate the header file for the
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
# core bindings after the out-of-source build directory has been cleaned.
|
|
||||||
cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
|
|
||||||
|
|
||||||
if(NOT DEFINED CONDITION)
|
if(NOT DEFINED CONDITION)
|
||||||
message(FATAL_ERROR "Variable \"CONDITION\" is not defined.")
|
message(FATAL_ERROR "Variable \"CONDITION\" is not defined.")
|
|
@ -1,39 +1,41 @@
|
||||||
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
${LIBRARY_NAME}_quickstart
|
example_quickstart
|
||||||
quickstart.c
|
quickstart.c
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C)
|
set_target_properties(example_quickstart PROPERTIES LINKER_LANGUAGE C)
|
||||||
|
|
||||||
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
||||||
# contain a non-existent path so its build-time include directory
|
# contain a non-existent path so its build-time include directory
|
||||||
# must be specified for all of its dependent targets instead.
|
# must be specified for all of its dependent targets instead.
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${LIBRARY_NAME}_quickstart
|
example_quickstart
|
||||||
PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
|
PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME})
|
target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME})
|
||||||
|
|
||||||
add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts)
|
add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts)
|
||||||
|
|
||||||
if(BUILD_SHARED_LIBS AND WIN32)
|
if(BUILD_SHARED_LIBS AND WIN32)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET ${LIBRARY_NAME}_quickstart
|
TARGET example_quickstart
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||||
${CMAKE_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
COMMENT "Copying the DLL built by Cargo into the examples directory..."
|
COMMENT "Copying the DLL built by Cargo into the examples directory..."
|
||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET ${LIBRARY_NAME}_quickstart
|
TARGET example_quickstart
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND
|
COMMAND
|
||||||
${LIBRARY_NAME}_quickstart
|
example_quickstart
|
||||||
COMMENT
|
COMMENT
|
||||||
"Running the example quickstart..."
|
"Running the example quickstart..."
|
||||||
VERBATIM
|
VERBATIM
|
|
@ -5,5 +5,5 @@
|
||||||
```shell
|
```shell
|
||||||
cmake -E make_directory automerge-c/build
|
cmake -E make_directory automerge-c/build
|
||||||
cmake -S automerge-c -B automerge-c/build
|
cmake -S automerge-c -B automerge-c/build
|
||||||
cmake --build automerge-c/build --target automerge_quickstart
|
cmake --build automerge-c/build --target example_quickstart
|
||||||
```
|
```
|
146
automerge-c/examples/quickstart.c
Normal file
146
automerge-c/examples/quickstart.c
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
|
||||||
|
static void abort_cb(AMresultStack**, uint8_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Based on https://automerge.github.io/docs/quickstart
|
||||||
|
*/
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
AMresultStack* stack = NULL;
|
||||||
|
AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
|
||||||
|
AMobjId const* const cards = AMpush(&stack,
|
||||||
|
AMmapPutObject(doc1, AM_ROOT, "cards", AM_OBJ_TYPE_LIST),
|
||||||
|
AM_VALUE_OBJ_ID,
|
||||||
|
abort_cb).obj_id;
|
||||||
|
AMobjId const* const card1 = AMpush(&stack,
|
||||||
|
AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
|
||||||
|
AM_VALUE_OBJ_ID,
|
||||||
|
abort_cb).obj_id;
|
||||||
|
AMfree(AMmapPutStr(doc1, card1, "title", "Rewrite everything in Clojure"));
|
||||||
|
AMfree(AMmapPutBool(doc1, card1, "done", false));
|
||||||
|
AMobjId const* const card2 = AMpush(&stack,
|
||||||
|
AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
|
||||||
|
AM_VALUE_OBJ_ID,
|
||||||
|
abort_cb).obj_id;
|
||||||
|
AMfree(AMmapPutStr(doc1, card2, "title", "Rewrite everything in Haskell"));
|
||||||
|
AMfree(AMmapPutBool(doc1, card2, "done", false));
|
||||||
|
AMfree(AMcommit(doc1, "Add card", NULL));
|
||||||
|
|
||||||
|
AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
|
||||||
|
AMfree(AMmerge(doc2, doc1));
|
||||||
|
|
||||||
|
AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes;
|
||||||
|
doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc;
|
||||||
|
|
||||||
|
AMfree(AMmapPutBool(doc1, card1, "done", true));
|
||||||
|
AMfree(AMcommit(doc1, "Mark card as done", NULL));
|
||||||
|
|
||||||
|
AMfree(AMlistDelete(doc2, cards, 0));
|
||||||
|
AMfree(AMcommit(doc2, "Delete card", NULL));
|
||||||
|
|
||||||
|
AMfree(AMmerge(doc1, doc2));
|
||||||
|
|
||||||
|
AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes;
|
||||||
|
AMchange const* change = NULL;
|
||||||
|
while ((change = AMchangesNext(&changes, 1)) != NULL) {
|
||||||
|
AMbyteSpan const change_hash = AMchangeHash(change);
|
||||||
|
AMchangeHashes const heads = AMpush(&stack,
|
||||||
|
AMchangeHashesInit(&change_hash, 1),
|
||||||
|
AM_VALUE_CHANGE_HASHES,
|
||||||
|
abort_cb).change_hashes;
|
||||||
|
printf("%s %ld\n", AMchangeMessage(change), AMobjSize(doc1, cards, &heads));
|
||||||
|
}
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char const* discriminant_suffix(AMvalueVariant const);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Prints an error message to `stderr`, deallocates all results in the
|
||||||
|
* given stack and exits.
|
||||||
|
*
|
||||||
|
* \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||||
|
* \pre \p stack` != NULL`.
|
||||||
|
* \post `*stack == NULL`.
|
||||||
|
*/
|
||||||
|
static void abort_cb(AMresultStack** stack, uint8_t discriminant) {
|
||||||
|
static char buffer[512] = {0};
|
||||||
|
|
||||||
|
char const* suffix = NULL;
|
||||||
|
if (!stack) {
|
||||||
|
suffix = "Stack*";
|
||||||
|
}
|
||||||
|
else if (!*stack) {
|
||||||
|
suffix = "Stack";
|
||||||
|
}
|
||||||
|
else if (!(*stack)->result) {
|
||||||
|
suffix = "";
|
||||||
|
}
|
||||||
|
if (suffix) {
|
||||||
|
fprintf(stderr, "Null `AMresult%s*`.", suffix);
|
||||||
|
AMfreeStack(stack);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AMstatus const status = AMresultStatus((*stack)->result);
|
||||||
|
switch (status) {
|
||||||
|
case AM_STATUS_ERROR: strcpy(buffer, "Error"); break;
|
||||||
|
case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break;
|
||||||
|
case AM_STATUS_OK: break;
|
||||||
|
default: sprintf(buffer, "Unknown `AMstatus` tag %d", status);
|
||||||
|
}
|
||||||
|
if (buffer[0]) {
|
||||||
|
fprintf(stderr, "%s; %s.", buffer, AMerrorMessage((*stack)->result));
|
||||||
|
AMfreeStack(stack);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AMvalue const value = AMresultValue((*stack)->result);
|
||||||
|
fprintf(stderr, "Unexpected tag `AM_VALUE_%s` (%d); expected `AM_VALUE_%s`.",
|
||||||
|
discriminant_suffix(value.tag),
|
||||||
|
value.tag,
|
||||||
|
discriminant_suffix(discriminant));
|
||||||
|
AMfreeStack(stack);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets the suffix for a discriminant's corresponding string
|
||||||
|
* representation.
|
||||||
|
*
|
||||||
|
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||||
|
* \return A UTF-8 string.
|
||||||
|
*/
|
||||||
|
static char const* discriminant_suffix(AMvalueVariant const discriminant) {
|
||||||
|
char const* suffix = NULL;
|
||||||
|
switch (discriminant) {
|
||||||
|
case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break;
|
||||||
|
case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break;
|
||||||
|
case AM_VALUE_BYTES: suffix = "BYTES"; break;
|
||||||
|
case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break;
|
||||||
|
case AM_VALUE_CHANGES: suffix = "CHANGES"; break;
|
||||||
|
case AM_VALUE_COUNTER: suffix = "COUNTER"; break;
|
||||||
|
case AM_VALUE_DOC: suffix = "DOC"; break;
|
||||||
|
case AM_VALUE_F64: suffix = "F64"; break;
|
||||||
|
case AM_VALUE_INT: suffix = "INT"; break;
|
||||||
|
case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break;
|
||||||
|
case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break;
|
||||||
|
case AM_VALUE_NULL: suffix = "NULL"; break;
|
||||||
|
case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break;
|
||||||
|
case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break;
|
||||||
|
case AM_VALUE_STR: suffix = "STR"; break;
|
||||||
|
case AM_VALUE_STRS: suffix = "STRINGS"; break;
|
||||||
|
case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break;
|
||||||
|
case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break;
|
||||||
|
case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break;
|
||||||
|
case AM_VALUE_UINT: suffix = "UINT"; break;
|
||||||
|
case AM_VALUE_VOID: suffix = "VOID"; break;
|
||||||
|
default: suffix = "...";
|
||||||
|
}
|
||||||
|
return suffix;
|
||||||
|
}
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
250
automerge-c/src/CMakeLists.txt
Normal file
250
automerge-c/src/CMakeLists.txt
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
|
|
||||||
|
find_program (
|
||||||
|
CARGO_CMD
|
||||||
|
"cargo"
|
||||||
|
PATHS "$ENV{CARGO_HOME}/bin"
|
||||||
|
DOC "The Cargo command"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT CARGO_CMD)
|
||||||
|
message(FATAL_ERROR "Cargo (Rust package manager) not found! Install it and/or set the CARGO_HOME environment variable.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER)
|
||||||
|
|
||||||
|
if(BUILD_TYPE_LOWER STREQUAL debug)
|
||||||
|
set(CARGO_BUILD_TYPE "debug")
|
||||||
|
|
||||||
|
set(CARGO_FLAG "")
|
||||||
|
else()
|
||||||
|
set(CARGO_BUILD_TYPE "release")
|
||||||
|
|
||||||
|
set(CARGO_FLAG "--release")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CARGO_FEATURES "")
|
||||||
|
|
||||||
|
set(CARGO_CURRENT_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}")
|
||||||
|
|
||||||
|
set(
|
||||||
|
CARGO_OUTPUT
|
||||||
|
${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||||
|
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||||
|
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
# \note The basename of an import library output by Cargo is the filename
|
||||||
|
# of its corresponding shared library.
|
||||||
|
list(APPEND CARGO_OUTPUT ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT
|
||||||
|
${CARGO_OUTPUT}
|
||||||
|
COMMAND
|
||||||
|
# \note cbindgen won't regenerate its output header file after it's
|
||||||
|
# been removed but it will after its configuration file has been
|
||||||
|
# updated.
|
||||||
|
${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file_touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES}
|
||||||
|
MAIN_DEPENDENCY
|
||||||
|
lib.rs
|
||||||
|
DEPENDS
|
||||||
|
actor_id.rs
|
||||||
|
byte_span.rs
|
||||||
|
change_hashes.rs
|
||||||
|
change.rs
|
||||||
|
changes.rs
|
||||||
|
doc.rs
|
||||||
|
doc/list.rs
|
||||||
|
doc/list/item.rs
|
||||||
|
doc/list/items.rs
|
||||||
|
doc/map.rs
|
||||||
|
doc/map/item.rs
|
||||||
|
doc/map/items.rs
|
||||||
|
doc/utils.rs
|
||||||
|
obj.rs
|
||||||
|
obj/item.rs
|
||||||
|
obj/items.rs
|
||||||
|
result.rs
|
||||||
|
result_stack.rs
|
||||||
|
strs.rs
|
||||||
|
sync.rs
|
||||||
|
sync/have.rs
|
||||||
|
sync/haves.rs
|
||||||
|
sync/message.rs
|
||||||
|
sync/state.rs
|
||||||
|
${CMAKE_SOURCE_DIR}/build.rs
|
||||||
|
${CMAKE_SOURCE_DIR}/Cargo.toml
|
||||||
|
${CMAKE_SOURCE_DIR}/cbindgen.toml
|
||||||
|
WORKING_DIRECTORY
|
||||||
|
${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT
|
||||||
|
"Producing the library artifacts with Cargo..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
${LIBRARY_NAME}_artifacts ALL
|
||||||
|
DEPENDS ${CARGO_OUTPUT}
|
||||||
|
)
|
||||||
|
|
||||||
|
# \note cbindgen's naming behavior isn't fully configurable and it ignores
|
||||||
|
# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252).
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ${LIBRARY_NAME}_artifacts
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND
|
||||||
|
# Compensate for cbindgen's variant struct naming.
|
||||||
|
${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+_[^_]+\)_Body -DREPLACE_EXPR=AM\\1 -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||||
|
COMMAND
|
||||||
|
# Compensate for cbindgen's union tag enum type naming.
|
||||||
|
${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+\)_Tag -DREPLACE_EXPR=AM\\1Variant -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||||
|
COMMAND
|
||||||
|
# Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase".
|
||||||
|
${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||||
|
COMMAND
|
||||||
|
# Compensate for cbindgen ignoring `std:mem::size_of<usize>()` calls.
|
||||||
|
${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
|
||||||
|
WORKING_DIRECTORY
|
||||||
|
${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT
|
||||||
|
"Compensating for cbindgen deficits..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
if(BUILD_SHARED_LIBS)
|
||||||
|
if(WIN32)
|
||||||
|
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||||
|
else()
|
||||||
|
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS")
|
||||||
|
|
||||||
|
# \note The basename of an import library output by Cargo is the filename
|
||||||
|
# of its corresponding shared library.
|
||||||
|
set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
set(LIBRARY_NO_SONAME "${WIN32}")
|
||||||
|
|
||||||
|
set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
set(LIBRARY_TYPE "SHARED")
|
||||||
|
else()
|
||||||
|
set(LIBRARY_DEFINE_SYMBOL "")
|
||||||
|
|
||||||
|
set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
|
||||||
|
|
||||||
|
set(LIBRARY_IMPLIB "")
|
||||||
|
|
||||||
|
set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
set(LIBRARY_NO_SONAME "TRUE")
|
||||||
|
|
||||||
|
set(LIBRARY_SONAME "")
|
||||||
|
|
||||||
|
set(LIBRARY_TYPE "STATIC")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL)
|
||||||
|
|
||||||
|
set_target_properties(
|
||||||
|
${LIBRARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
# \note Cargo writes a debug build into a nested directory instead of
|
||||||
|
# decorating its name.
|
||||||
|
DEBUG_POSTFIX ""
|
||||||
|
DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}"
|
||||||
|
IMPORTED_IMPLIB "${LIBRARY_IMPLIB}"
|
||||||
|
IMPORTED_LOCATION "${LIBRARY_LOCATION}"
|
||||||
|
IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}"
|
||||||
|
IMPORTED_SONAME "${LIBRARY_SONAME}"
|
||||||
|
LINKER_LANGUAGE C
|
||||||
|
PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||||
|
SOVERSION "${PROJECT_VERSION_MAJOR}"
|
||||||
|
VERSION "${PROJECT_VERSION}"
|
||||||
|
# \note Cargo exports all of the symbols automatically.
|
||||||
|
WINDOWS_EXPORT_ALL_SYMBOLS "TRUE"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${LIBRARY_NAME} INTERFACE $<TARGET_PROPERTY:${LIBRARY_NAME},DEFINE_SYMBOL>)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
${LIBRARY_NAME}
|
||||||
|
INTERFACE
|
||||||
|
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||||
|
|
||||||
|
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
|
||||||
|
set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32)
|
||||||
|
else()
|
||||||
|
list(APPEND LIBRARY_DEPENDENCIES m)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES})
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_IMPLIB>
|
||||||
|
TYPE LIB
|
||||||
|
# \note The basename of an import library output by Cargo is the filename
|
||||||
|
# of its corresponding shared library.
|
||||||
|
RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
||||||
|
OPTIONAL
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}")
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},IMPORTED_LOCATION>
|
||||||
|
RENAME "${LIBRARY_FILE_NAME}"
|
||||||
|
DESTINATION ${LIBRARY_DESTINATION}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES $<TARGET_PROPERTY:${LIBRARY_NAME},PUBLIC_HEADER>
|
||||||
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(Doxygen OPTIONAL_COMPONENTS dot)
|
||||||
|
|
||||||
|
if(DOXYGEN_FOUND)
|
||||||
|
set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>")
|
||||||
|
|
||||||
|
set(DOXYGEN_GENERATE_LATEX YES)
|
||||||
|
|
||||||
|
set(DOXYGEN_PDF_HYPERLINKS YES)
|
||||||
|
|
||||||
|
set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png")
|
||||||
|
|
||||||
|
set(DOXYGEN_SORT_BRIEF_DOCS YES)
|
||||||
|
|
||||||
|
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
|
||||||
|
|
||||||
|
doxygen_add_docs(
|
||||||
|
${LIBRARY_NAME}_docs
|
||||||
|
"${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
|
||||||
|
"${CMAKE_SOURCE_DIR}/README.md"
|
||||||
|
USE_STAMP_FILE
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMENT "Producing documentation with Doxygen..."
|
||||||
|
)
|
||||||
|
|
||||||
|
# \note A Doxygen input file isn't a file-level dependency so the Doxygen
|
||||||
|
# command must instead depend upon a target that outputs the file or
|
||||||
|
# it will just output an error message when it can't be found.
|
||||||
|
add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts)
|
||||||
|
endif()
|
166
automerge-c/src/actor_id.rs
Normal file
166
automerge-c/src/actor_id.rs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::byte_span::AMbyteSpan;
|
||||||
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
|
/// \struct AMactorId
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief An actor's unique identifier.
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMactorId {
|
||||||
|
body: *const am::ActorId,
|
||||||
|
c_str: RefCell<Option<CString>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMactorId {
|
||||||
|
pub fn new(actor_id: &am::ActorId) -> Self {
|
||||||
|
Self {
|
||||||
|
body: actor_id,
|
||||||
|
c_str: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_c_str(&self) -> *const c_char {
|
||||||
|
let mut c_str = self.c_str.borrow_mut();
|
||||||
|
match c_str.as_mut() {
|
||||||
|
None => {
|
||||||
|
let hex_str = unsafe { (*self.body).to_hex_string() };
|
||||||
|
c_str.insert(CString::new(hex_str).unwrap()).as_ptr()
|
||||||
|
}
|
||||||
|
Some(hex_str) => hex_str.as_ptr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<am::ActorId> for AMactorId {
|
||||||
|
fn as_ref(&self) -> &am::ActorId {
|
||||||
|
unsafe { &*self.body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Gets the value of an actor identifier as a sequence of bytes.
|
||||||
|
///
|
||||||
|
/// \param[in] actor_id A pointer to an `AMactorId` struct.
|
||||||
|
/// \pre \p actor_id `!= NULL`.
|
||||||
|
/// \return An `AMbyteSpan` struct.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// actor_id must be a valid pointer to an AMactorId
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpan {
|
||||||
|
match actor_id.as_ref() {
|
||||||
|
Some(actor_id) => actor_id.as_ref().into(),
|
||||||
|
None => AMbyteSpan::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Compares two actor identifiers.
|
||||||
|
///
|
||||||
|
/// \param[in] actor_id1 A pointer to an `AMactorId` struct.
|
||||||
|
/// \param[in] actor_id2 A pointer to an `AMactorId` struct.
|
||||||
|
/// \return `-1` if \p actor_id1 `<` \p actor_id2, `0` if
|
||||||
|
/// \p actor_id1 `==` \p actor_id2 and `1` if
|
||||||
|
/// \p actor_id1 `>` \p actor_id2.
|
||||||
|
/// \pre \p actor_id1 `!= NULL`.
|
||||||
|
/// \pre \p actor_id2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// actor_id1 must be a valid pointer to an AMactorId
|
||||||
|
/// actor_id2 must be a valid pointer to an AMactorId
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdCmp(
|
||||||
|
actor_id1: *const AMactorId,
|
||||||
|
actor_id2: *const AMactorId,
|
||||||
|
) -> isize {
|
||||||
|
match (actor_id1.as_ref(), actor_id2.as_ref()) {
|
||||||
|
(Some(actor_id1), Some(actor_id2)) => match actor_id1.as_ref().cmp(actor_id2.as_ref()) {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
},
|
||||||
|
(None, Some(_)) => -1,
|
||||||
|
(Some(_), None) => 1,
|
||||||
|
(None, None) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Allocates a new actor identifier and initializes it with a random
|
||||||
|
/// UUID.
|
||||||
|
///
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMactorId` struct.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult {
|
||||||
|
to_result(Ok::<am::ActorId, am::AutomergeError>(am::ActorId::random()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Allocates a new actor identifier and initializes it from a sequence
|
||||||
|
/// of bytes.
|
||||||
|
///
|
||||||
|
/// \param[in] src A pointer to a contiguous sequence of bytes.
|
||||||
|
/// \param[in] count The number of bytes to copy from \p src.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMactorId` struct.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdInitBytes(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
|
let slice = std::slice::from_raw_parts(src, count);
|
||||||
|
to_result(Ok::<am::ActorId, am::InvalidActorId>(am::ActorId::from(
|
||||||
|
slice,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Allocates a new actor identifier and initializes it from a
|
||||||
|
/// hexadecimal string.
|
||||||
|
///
|
||||||
|
/// \param[in] hex_str A UTF-8 string.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMactorId` struct.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// hex_str must be a null-terminated array of `c_char`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdInitStr(hex_str: *const c_char) -> *mut AMresult {
|
||||||
|
to_result(am::ActorId::from_str(
|
||||||
|
CStr::from_ptr(hex_str).to_str().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMactorId
|
||||||
|
/// \brief Gets the value of an actor identifier as a hexadecimal string.
|
||||||
|
///
|
||||||
|
/// \param[in] actor_id A pointer to an `AMactorId` struct.
|
||||||
|
/// \pre \p actor_id `!= NULL`.
|
||||||
|
/// \return A UTF-8 string.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// actor_id must be a valid pointer to an AMactorId
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMactorIdStr(actor_id: *const AMactorId) -> *const c_char {
|
||||||
|
match actor_id.as_ref() {
|
||||||
|
Some(actor_id) => actor_id.as_c_str(),
|
||||||
|
None => std::ptr::null::<c_char>(),
|
||||||
|
}
|
||||||
|
}
|
64
automerge-c/src/byte_span.rs
Normal file
64
automerge-c/src/byte_span.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use automerge as am;
|
||||||
|
|
||||||
|
/// \struct AMbyteSpan
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A view onto a contiguous sequence of bytes.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMbyteSpan {
|
||||||
|
/// A pointer to an array of bytes.
|
||||||
|
/// \attention <b>NEVER CALL `free()` ON \p src!</b>
|
||||||
|
/// \warning \p src is only valid until the `AMfree()` function is called
|
||||||
|
/// on the `AMresult` struct that stores the array of bytes to
|
||||||
|
/// which it points.
|
||||||
|
pub src: *const u8,
|
||||||
|
/// The number of bytes in the array.
|
||||||
|
pub count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMbyteSpan {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
src: std::ptr::null(),
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&am::ActorId> for AMbyteSpan {
|
||||||
|
fn from(actor: &am::ActorId) -> Self {
|
||||||
|
let slice = actor.to_bytes();
|
||||||
|
Self {
|
||||||
|
src: slice.as_ptr(),
|
||||||
|
count: slice.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&mut am::ActorId> for AMbyteSpan {
|
||||||
|
fn from(actor: &mut am::ActorId) -> Self {
|
||||||
|
let slice = actor.to_bytes();
|
||||||
|
Self {
|
||||||
|
src: slice.as_ptr(),
|
||||||
|
count: slice.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&am::ChangeHash> for AMbyteSpan {
|
||||||
|
fn from(change_hash: &am::ChangeHash) -> Self {
|
||||||
|
Self {
|
||||||
|
src: change_hash.0.as_ptr(),
|
||||||
|
count: change_hash.0.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&[u8]> for AMbyteSpan {
|
||||||
|
fn from(slice: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
src: slice.as_ptr(),
|
||||||
|
count: slice.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,10 @@
|
||||||
use automerge as am;
|
use automerge as am;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
use crate::byte_span::AMbyteSpan;
|
use crate::byte_span::AMbyteSpan;
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
use crate::result::{to_result, AMresult};
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
macro_rules! to_change {
|
macro_rules! to_change {
|
||||||
|
@ -9,7 +12,7 @@ macro_rules! to_change {
|
||||||
let handle = $handle.as_ref();
|
let handle = $handle.as_ref();
|
||||||
match handle {
|
match handle {
|
||||||
Some(b) => b,
|
Some(b) => b,
|
||||||
None => return AMresult::error("Invalid `AMchange*`").into(),
|
None => return AMresult::err("Invalid AMchange pointer").into(),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -20,31 +23,43 @@ macro_rules! to_change {
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub struct AMchange {
|
pub struct AMchange {
|
||||||
body: *mut am::Change,
|
body: *mut am::Change,
|
||||||
change_hash: RefCell<Option<am::ChangeHash>>,
|
c_msg: RefCell<Option<CString>>,
|
||||||
|
c_changehash: RefCell<Option<am::ChangeHash>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AMchange {
|
impl AMchange {
|
||||||
pub fn new(change: &mut am::Change) -> Self {
|
pub fn new(change: &mut am::Change) -> Self {
|
||||||
Self {
|
Self {
|
||||||
body: change,
|
body: change,
|
||||||
change_hash: Default::default(),
|
c_msg: Default::default(),
|
||||||
|
c_changehash: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message(&self) -> AMbyteSpan {
|
pub fn message(&self) -> *const c_char {
|
||||||
if let Some(message) = unsafe { (*self.body).message() } {
|
let mut c_msg = self.c_msg.borrow_mut();
|
||||||
return message.as_str().as_bytes().into();
|
match c_msg.as_mut() {
|
||||||
|
None => {
|
||||||
|
if let Some(message) = unsafe { (*self.body).message() } {
|
||||||
|
return c_msg
|
||||||
|
.insert(CString::new(message.as_bytes()).unwrap())
|
||||||
|
.as_ptr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(message) => {
|
||||||
|
return message.as_ptr();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Default::default()
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash(&self) -> AMbyteSpan {
|
pub fn hash(&self) -> AMbyteSpan {
|
||||||
let mut change_hash = self.change_hash.borrow_mut();
|
let mut c_changehash = self.c_changehash.borrow_mut();
|
||||||
if let Some(change_hash) = change_hash.as_ref() {
|
if let Some(c_changehash) = c_changehash.as_ref() {
|
||||||
change_hash.into()
|
c_changehash.into()
|
||||||
} else {
|
} else {
|
||||||
let hash = unsafe { (*self.body).hash() };
|
let hash = unsafe { (*self.body).hash() };
|
||||||
let ptr = change_hash.insert(hash);
|
let ptr = c_changehash.insert(hash);
|
||||||
AMbyteSpan {
|
AMbyteSpan {
|
||||||
src: ptr.0.as_ptr(),
|
src: ptr.0.as_ptr(),
|
||||||
count: hash.as_ref().len(),
|
count: hash.as_ref().len(),
|
||||||
|
@ -69,12 +84,12 @@ impl AsRef<am::Change> for AMchange {
|
||||||
/// \brief Gets the first referenced actor identifier in a change.
|
/// \brief Gets the first referenced actor identifier in a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
|
/// \pre \p change `!= NULL`.
|
||||||
/// \pre \p change `!= NULL`
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// `AMactorId` struct.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// change must be a valid pointer to an AMchange
|
/// change must be a valid pointer to an AMchange
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -88,8 +103,8 @@ pub unsafe extern "C" fn AMchangeActorId(change: *const AMchange) -> *mut AMresu
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
/// \brief Compresses the raw bytes of a change.
|
/// \brief Compresses the raw bytes of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in,out] change A pointer to an `AMchange` struct.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -105,20 +120,18 @@ pub unsafe extern "C" fn AMchangeCompress(change: *mut AMchange) {
|
||||||
/// \brief Gets the dependencies of a change.
|
/// \brief Gets the dependencies of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return A pointer to an `AMchangeHashes` struct or `NULL`.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// change must be a valid pointer to an AMchange
|
/// change must be a valid pointer to an AMchange
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult {
|
pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes {
|
||||||
to_result(match change.as_ref() {
|
match change.as_ref() {
|
||||||
Some(change) => change.as_ref().deps(),
|
Some(change) => AMchangeHashes::new(change.as_ref().deps()),
|
||||||
None => Default::default(),
|
None => AMchangeHashes::default(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
|
@ -126,7 +139,7 @@ pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return An `AMbyteSpan` struct.
|
/// \return An `AMbyteSpan` struct.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -136,38 +149,36 @@ pub unsafe extern "C" fn AMchangeExtraBytes(change: *const AMchange) -> AMbyteSp
|
||||||
if let Some(change) = change.as_ref() {
|
if let Some(change) = change.as_ref() {
|
||||||
change.as_ref().extra_bytes().into()
|
change.as_ref().extra_bytes().into()
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
AMbyteSpan::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
/// \brief Allocates a new change and initializes it from an array of bytes value.
|
/// \brief Loads a sequence of bytes into a change.
|
||||||
///
|
///
|
||||||
/// \param[in] src A pointer to an array of bytes.
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
/// \param[in] count The count of bytes to load from the array pointed to by
|
/// \param[in] count The number of bytes in \p src to load.
|
||||||
/// \p src.
|
/// \return A pointer to an `AMresult` struct containing an `AMchange` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item.
|
/// \pre \p src `!= NULL`.
|
||||||
/// \pre \p src `!= NULL`
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
/// \pre `sizeof(`\p src `) > 0`
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
/// \pre \p count `<= sizeof(`\p src `)`
|
/// in order to prevent a memory leak.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// src must be a byte array of length `>= count`
|
/// src must be a byte array of size `>= count`
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult {
|
pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
let data = std::slice::from_raw_parts(src, count);
|
let mut data = Vec::new();
|
||||||
to_result(am::Change::from_bytes(data.to_vec()))
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(am::Change::from_bytes(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
/// \brief Gets the hash of a change.
|
/// \brief Gets the hash of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return An `AMbyteSpan` struct for a change hash.
|
/// \return A change hash as an `AMbyteSpan` struct.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -176,7 +187,7 @@ pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut
|
||||||
pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan {
|
pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan {
|
||||||
match change.as_ref() {
|
match change.as_ref() {
|
||||||
Some(change) => change.hash(),
|
Some(change) => change.hash(),
|
||||||
None => Default::default(),
|
None => AMbyteSpan::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,8 +195,8 @@ pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan {
|
||||||
/// \brief Tests the emptiness of a change.
|
/// \brief Tests the emptiness of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return `true` if \p change is empty, `false` otherwise.
|
/// \return A boolean.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -199,37 +210,12 @@ pub unsafe extern "C" fn AMchangeIsEmpty(change: *const AMchange) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
|
||||||
/// \brief Loads a document into a sequence of changes.
|
|
||||||
///
|
|
||||||
/// \param[in] src A pointer to an array of bytes.
|
|
||||||
/// \param[in] count The count of bytes to load from the array pointed to by
|
|
||||||
/// \p src.
|
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items.
|
|
||||||
/// \pre \p src `!= NULL`
|
|
||||||
/// \pre `sizeof(`\p src `) > 0`
|
|
||||||
/// \pre \p count `<= sizeof(`\p src `)`
|
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// src must be a byte array of length `>= count`
|
|
||||||
#[no_mangle]
|
|
||||||
pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult {
|
|
||||||
let data = std::slice::from_raw_parts(src, count);
|
|
||||||
to_result::<Result<Vec<am::Change>, _>>(
|
|
||||||
am::Automerge::load(data)
|
|
||||||
.and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
/// \brief Gets the maximum operation index of a change.
|
/// \brief Gets the maximum operation index of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -247,18 +233,18 @@ pub unsafe extern "C" fn AMchangeMaxOp(change: *const AMchange) -> u64 {
|
||||||
/// \brief Gets the message of a change.
|
/// \brief Gets the message of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return An `AMbyteSpan` struct for a UTF-8 string.
|
/// \return A UTF-8 string or `NULL`.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// change must be a valid pointer to an AMchange
|
/// change must be a valid pointer to an AMchange
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan {
|
pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> *const c_char {
|
||||||
if let Some(change) = change.as_ref() {
|
if let Some(change) = change.as_ref() {
|
||||||
return change.message();
|
return change.message();
|
||||||
};
|
};
|
||||||
Default::default()
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
|
@ -266,7 +252,7 @@ pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -285,7 +271,7 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 {
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -293,9 +279,10 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 {
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize {
|
pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize {
|
||||||
if let Some(change) = change.as_ref() {
|
if let Some(change) = change.as_ref() {
|
||||||
return change.as_ref().len();
|
change.as_ref().len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
}
|
}
|
||||||
0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMchange
|
/// \memberof AMchange
|
||||||
|
@ -303,7 +290,7 @@ pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize {
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -322,7 +309,7 @@ pub unsafe extern "C" fn AMchangeStartOp(change: *const AMchange) -> u64 {
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return A 64-bit signed integer.
|
/// \return A 64-bit signed integer.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -340,8 +327,8 @@ pub unsafe extern "C" fn AMchangeTime(change: *const AMchange) -> i64 {
|
||||||
/// \brief Gets the raw bytes of a change.
|
/// \brief Gets the raw bytes of a change.
|
||||||
///
|
///
|
||||||
/// \param[in] change A pointer to an `AMchange` struct.
|
/// \param[in] change A pointer to an `AMchange` struct.
|
||||||
/// \return An `AMbyteSpan` struct for an array of bytes.
|
/// \return An `AMbyteSpan` struct.
|
||||||
/// \pre \p change `!= NULL`
|
/// \pre \p change `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -351,6 +338,30 @@ pub unsafe extern "C" fn AMchangeRawBytes(change: *const AMchange) -> AMbyteSpan
|
||||||
if let Some(change) = change.as_ref() {
|
if let Some(change) = change.as_ref() {
|
||||||
change.as_ref().raw_bytes().into()
|
change.as_ref().raw_bytes().into()
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
AMbyteSpan::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchange
|
||||||
|
/// \brief Loads a document into a sequence of changes.
|
||||||
|
///
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes in \p src to load.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a sequence of
|
||||||
|
/// `AMchange` structs.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result::<Result<Vec<am::Change>, _>>(
|
||||||
|
am::Automerge::load(&data)
|
||||||
|
.and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())),
|
||||||
|
)
|
||||||
|
}
|
399
automerge-c/src/change_hashes.rs
Normal file
399
automerge-c/src/change_hashes.rs
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::byte_span::AMbyteSpan;
|
||||||
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(change_hashes: &[am::ChangeHash], offset: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
len: change_hashes.len(),
|
||||||
|
offset,
|
||||||
|
ptr: change_hashes.as_ptr() as *const c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[am::ChangeHash] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) };
|
||||||
|
let value = &slice[self.get_index()];
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[am::ChangeHash] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) };
|
||||||
|
Some(&slice[self.get_index()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMchangeHashes
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of change hashes.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMchangeHashes {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMchangeHashes {
|
||||||
|
pub fn new(change_hashes: &[am::ChangeHash]) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(change_hashes, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[am::ChangeHash]> for AMchangeHashes {
|
||||||
|
fn as_ref(&self) -> &[am::ChangeHash] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::ChangeHash, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMchangeHashes {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Advances an iterator over a sequence of change hashes by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesAdvance(change_hashes: *mut AMchangeHashes, n: isize) {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||||
|
change_hashes.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Compares the sequences of change hashes underlying a pair of
|
||||||
|
/// iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] change_hashes1 A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \param[in] change_hashes2 A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \return `-1` if \p change_hashes1 `<` \p change_hashes2, `0` if
|
||||||
|
/// \p change_hashes1 `==` \p change_hashes2 and `1` if
|
||||||
|
/// \p change_hashes1 `>` \p change_hashes2.
|
||||||
|
/// \pre \p change_hashes1 `!= NULL`.
|
||||||
|
/// \pre \p change_hashes2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes1 must be a valid pointer to an AMchangeHashes
|
||||||
|
/// change_hashes2 must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesCmp(
|
||||||
|
change_hashes1: *const AMchangeHashes,
|
||||||
|
change_hashes2: *const AMchangeHashes,
|
||||||
|
) -> isize {
|
||||||
|
match (change_hashes1.as_ref(), change_hashes2.as_ref()) {
|
||||||
|
(Some(change_hashes1), Some(change_hashes2)) => {
|
||||||
|
match change_hashes1.as_ref().cmp(change_hashes2.as_ref()) {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(_)) => -1,
|
||||||
|
(Some(_), None) => 1,
|
||||||
|
(None, None) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Allocates an iterator over a sequence of change hashes and
|
||||||
|
/// initializes it from a sequence of byte spans.
|
||||||
|
///
|
||||||
|
/// \param[in] src A pointer to an array of `AMbyteSpan` structs.
|
||||||
|
/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// src must be an AMbyteSpan array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult {
|
||||||
|
let mut change_hashes = Vec::<am::ChangeHash>::new();
|
||||||
|
for n in 0..count {
|
||||||
|
let byte_span = &*src.add(n);
|
||||||
|
let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count);
|
||||||
|
match slice.try_into() {
|
||||||
|
Ok(change_hash) => {
|
||||||
|
change_hashes.push(change_hash);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return to_result(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_result(Ok::<Vec<am::ChangeHash>, am::InvalidChangeHashSlice>(
|
||||||
|
change_hashes,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Gets the change hash at the current position of an iterator over a
|
||||||
|
/// sequence of change hashes and then advances it by at most \p |n|
|
||||||
|
/// positions where the sign of \p n is relative to the iterator's
|
||||||
|
/// direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes
|
||||||
|
/// was previously advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesNext(
|
||||||
|
change_hashes: *mut AMchangeHashes,
|
||||||
|
n: isize,
|
||||||
|
) -> AMbyteSpan {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||||
|
if let Some(change_hash) = change_hashes.next(n) {
|
||||||
|
return change_hash.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AMbyteSpan::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Advances an iterator over a sequence of change hashes by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the change hash at its new
|
||||||
|
/// position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes is
|
||||||
|
/// presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesPrev(
|
||||||
|
change_hashes: *mut AMchangeHashes,
|
||||||
|
n: isize,
|
||||||
|
) -> AMbyteSpan {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_mut() {
|
||||||
|
if let Some(change_hash) = change_hashes.prev(n) {
|
||||||
|
return change_hash.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AMbyteSpan::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Gets the size of the sequence of change hashes underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \return The count of values in \p change_hashes.
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesSize(change_hashes: *const AMchangeHashes) -> usize {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||||
|
change_hashes.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Creates an iterator over the same sequence of change hashes as the
|
||||||
|
/// given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \return An `AMchangeHashes` struct
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesReversed(
|
||||||
|
change_hashes: *const AMchangeHashes,
|
||||||
|
) -> AMchangeHashes {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||||
|
change_hashes.reversed()
|
||||||
|
} else {
|
||||||
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchangeHashes
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of change hashes as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct.
|
||||||
|
/// \return An `AMchangeHashes` struct
|
||||||
|
/// \pre \p change_hashes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// change_hashes must be a valid pointer to an AMchangeHashes
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangeHashesRewound(
|
||||||
|
change_hashes: *const AMchangeHashes,
|
||||||
|
) -> AMchangeHashes {
|
||||||
|
if let Some(change_hashes) = change_hashes.as_ref() {
|
||||||
|
change_hashes.rewound()
|
||||||
|
} else {
|
||||||
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
|
}
|
398
automerge-c/src/changes.rs
Normal file
398
automerge-c/src/changes.rs
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::byte_span::AMbyteSpan;
|
||||||
|
use crate::change::AMchange;
|
||||||
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
storage: *mut c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap<usize, AMchange>) -> Self {
|
||||||
|
let storage: *mut BTreeMap<usize, AMchange> = storage;
|
||||||
|
Self {
|
||||||
|
len: changes.len(),
|
||||||
|
offset,
|
||||||
|
ptr: changes.as_ptr() as *const c_void,
|
||||||
|
storage: storage as *mut c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const AMchange> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &mut [am::Change] =
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) };
|
||||||
|
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMchange>) };
|
||||||
|
let index = self.get_index();
|
||||||
|
let value = match storage.get_mut(&index) {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
storage.insert(index, AMchange::new(&mut slice[index]));
|
||||||
|
storage.get_mut(&index).unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const AMchange> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &mut [am::Change] =
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) };
|
||||||
|
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMchange>) };
|
||||||
|
let index = self.get_index();
|
||||||
|
Some(match storage.get_mut(&index) {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
storage.insert(index, AMchange::new(&mut slice[index]));
|
||||||
|
storage.get_mut(&index).unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
storage: self.storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
storage: self.storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
(&detail as *const Detail) as *const u8,
|
||||||
|
USIZE_USIZE_USIZE_USIZE_,
|
||||||
|
)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMchanges
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of changes.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMchanges {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMchanges {
|
||||||
|
pub fn new(changes: &[am::Change], storage: &mut BTreeMap<usize, AMchange>) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(changes, 0, &mut *storage).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const AMchange> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const AMchange> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[am::Change]> for AMchanges {
|
||||||
|
fn as_ref(&self) -> &[am::Change] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::Change, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMchanges {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Advances an iterator over a sequence of changes by at most \p |n|
|
||||||
|
/// positions where the sign of \p n is relative to the iterator's
|
||||||
|
/// direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesAdvance(changes: *mut AMchanges, n: isize) {
|
||||||
|
if let Some(changes) = changes.as_mut() {
|
||||||
|
changes.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Tests the equality of two sequences of changes underlying a pair of
|
||||||
|
/// iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] changes1 A pointer to an `AMchanges` struct.
|
||||||
|
/// \param[in] changes2 A pointer to an `AMchanges` struct.
|
||||||
|
/// \return `true` if \p changes1 `==` \p changes2 and `false` otherwise.
|
||||||
|
/// \pre \p changes1 `!= NULL`.
|
||||||
|
/// \pre \p changes2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes1 must be a valid pointer to an AMchanges
|
||||||
|
/// changes2 must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesEqual(
|
||||||
|
changes1: *const AMchanges,
|
||||||
|
changes2: *const AMchanges,
|
||||||
|
) -> bool {
|
||||||
|
match (changes1.as_ref(), changes2.as_ref()) {
|
||||||
|
(Some(changes1), Some(changes2)) => changes1.as_ref() == changes2.as_ref(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Allocates an iterator over a sequence of changes and initializes it
|
||||||
|
/// from a sequence of byte spans.
|
||||||
|
///
|
||||||
|
/// \param[in] src A pointer to an array of `AMbyteSpan` structs.
|
||||||
|
/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// src must be an AMbyteSpan array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult {
|
||||||
|
let mut changes = Vec::<am::Change>::new();
|
||||||
|
for n in 0..count {
|
||||||
|
let byte_span = &*src.add(n);
|
||||||
|
let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count);
|
||||||
|
match slice.try_into() {
|
||||||
|
Ok(change) => {
|
||||||
|
changes.push(change);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return to_result(Err::<Vec<am::Change>, am::LoadChangeError>(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_result(Ok::<Vec<am::Change>, am::LoadChangeError>(changes))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Gets the change at the current position of an iterator over a
|
||||||
|
/// sequence of changes and then advances it by at most \p |n| positions
|
||||||
|
/// where the sign of \p n is relative to the iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes was
|
||||||
|
/// previously advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesNext(changes: *mut AMchanges, n: isize) -> *const AMchange {
|
||||||
|
if let Some(changes) = changes.as_mut() {
|
||||||
|
if let Some(change) = changes.next(n) {
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Advances an iterator over a sequence of changes by at most \p |n|
|
||||||
|
/// positions where the sign of \p n is relative to the iterator's
|
||||||
|
/// direction and then gets the change at its new position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes is
|
||||||
|
/// presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesPrev(changes: *mut AMchanges, n: isize) -> *const AMchange {
|
||||||
|
if let Some(changes) = changes.as_mut() {
|
||||||
|
if let Some(change) = changes.prev(n) {
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Gets the size of the sequence of changes underlying an iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \return The count of values in \p changes.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesSize(changes: *const AMchanges) -> usize {
|
||||||
|
if let Some(changes) = changes.as_ref() {
|
||||||
|
changes.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Creates an iterator over the same sequence of changes as the given
|
||||||
|
/// one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \return An `AMchanges` struct.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesReversed(changes: *const AMchanges) -> AMchanges {
|
||||||
|
if let Some(changes) = changes.as_ref() {
|
||||||
|
changes.reversed()
|
||||||
|
} else {
|
||||||
|
AMchanges::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMchanges
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of changes as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \return An `AMchanges` struct
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// changes must be a valid pointer to an AMchanges
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMchangesRewound(changes: *const AMchanges) -> AMchanges {
|
||||||
|
if let Some(changes) = changes.as_ref() {
|
||||||
|
changes.rewound()
|
||||||
|
} else {
|
||||||
|
AMchanges::default()
|
||||||
|
}
|
||||||
|
}
|
833
automerge-c/src/doc.rs
Normal file
833
automerge-c/src/doc.rs
Normal file
|
@ -0,0 +1,833 @@
|
||||||
|
use automerge as am;
|
||||||
|
use automerge::transaction::{CommitOptions, Transactable};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::actor_id::AMactorId;
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
use crate::obj::AMobjId;
|
||||||
|
use crate::result::{to_result, AMresult, AMvalue};
|
||||||
|
use crate::sync::{to_sync_message, AMsyncMessage, AMsyncState};
|
||||||
|
|
||||||
|
pub mod list;
|
||||||
|
pub mod map;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
use crate::changes::AMchanges;
|
||||||
|
use crate::doc::utils::to_str;
|
||||||
|
use crate::doc::utils::{to_actor_id, to_doc, to_doc_mut, to_obj_id};
|
||||||
|
|
||||||
|
macro_rules! to_changes {
|
||||||
|
($handle:expr) => {{
|
||||||
|
let handle = $handle.as_ref();
|
||||||
|
match handle {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return AMresult::err("Invalid AMchanges pointer").into(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! to_index {
|
||||||
|
($index:expr, $len:expr, $param_name:expr) => {{
|
||||||
|
if $index > $len && $index != usize::MAX {
|
||||||
|
return AMresult::err(&format!("Invalid {} {}", $param_name, $index)).into();
|
||||||
|
}
|
||||||
|
std::cmp::min($index, $len)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! to_sync_state_mut {
|
||||||
|
($handle:expr) => {{
|
||||||
|
let handle = $handle.as_mut();
|
||||||
|
match handle {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return AMresult::err("Invalid AMsyncState pointer").into(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMdoc
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A JSON-like CRDT.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AMdoc(am::AutoCommit);
|
||||||
|
|
||||||
|
impl AMdoc {
|
||||||
|
pub fn new(auto_commit: am::AutoCommit) -> Self {
|
||||||
|
Self(auto_commit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<am::AutoCommit> for AMdoc {
|
||||||
|
fn as_ref(&self) -> &am::AutoCommit {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AMdoc {
|
||||||
|
type Target = am::AutoCommit;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for AMdoc {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Applies a sequence of changes to a document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] changes A pointer to an `AMchanges` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p changes `!= NULL`.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// changes must be a valid pointer to an AMchanges.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMapplyChanges(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
changes: *const AMchanges,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let changes = to_changes!(changes);
|
||||||
|
to_result(doc.apply_changes(changes.as_ref().to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Allocates storage for a document and initializes it by duplicating
|
||||||
|
/// the given document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMdoc` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMclone(doc: *const AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
to_result(doc.as_ref().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Allocates a new document and initializes it with defaults.
|
||||||
|
///
|
||||||
|
/// \param[in] actor_id A pointer to an `AMactorId` struct or `NULL` for a
|
||||||
|
/// random one.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMdoc` struct.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// actor_id must be a valid pointer to an AMactorId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMcreate(actor_id: *const AMactorId) -> *mut AMresult {
|
||||||
|
to_result(match actor_id.as_ref() {
|
||||||
|
Some(actor_id) => am::AutoCommit::new().with_actor(actor_id.as_ref().clone()),
|
||||||
|
None => am::AutoCommit::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Commits the current operations on a document with an optional
|
||||||
|
/// message and/or time override as seconds since the epoch.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] message A UTF-8 string or `NULL`.
|
||||||
|
/// \param[in] time A pointer to a `time_t` value or `NULL`.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||||
|
/// with one element.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMcommit(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
message: *const c_char,
|
||||||
|
time: *const libc::time_t,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let mut options = CommitOptions::default();
|
||||||
|
if !message.is_null() {
|
||||||
|
options.set_message(to_str(message));
|
||||||
|
}
|
||||||
|
if let Some(time) = time.as_ref() {
|
||||||
|
options.set_time(*time);
|
||||||
|
}
|
||||||
|
to_result(doc.commit_with(options))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Tests the equality of two documents after closing their respective
|
||||||
|
/// transactions.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc1 An `AMdoc` struct.
|
||||||
|
/// \param[in,out] doc2 An `AMdoc` struct.
|
||||||
|
/// \return `true` if \p doc1 `==` \p doc2 and `false` otherwise.
|
||||||
|
/// \pre \p doc1 `!= NULL`.
|
||||||
|
/// \pre \p doc2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// doc1 must be a valid pointer to an AMdoc
|
||||||
|
/// doc2 must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMequal(doc1: *mut AMdoc, doc2: *mut AMdoc) -> bool {
|
||||||
|
match (doc1.as_mut(), doc2.as_mut()) {
|
||||||
|
(Some(doc1), Some(doc2)) => doc1.document().get_heads() == doc2.document().get_heads(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Forks this document at the current or a historical point for use by
|
||||||
|
/// a different actor.
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||||
|
/// point or `NULL` for the current point.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMdoc` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.fork()),
|
||||||
|
Some(heads) => to_result(doc.fork_at(heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Generates a synchronization message for a peer based upon the given
|
||||||
|
/// synchronization state.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in,out] sync_state A pointer to an `AMsyncState` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing either a pointer to an
|
||||||
|
/// `AMsyncMessage` struct or a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p sync_state `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgenerateSyncMessage(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
sync_state: *mut AMsyncState,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let sync_state = to_sync_state_mut!(sync_state);
|
||||||
|
to_result(doc.generate_sync_message(sync_state.as_mut()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets a document's actor identifier.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMactorId` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetActorId(doc: *const AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
to_result(Ok::<am::ActorId, am::AutomergeError>(
|
||||||
|
doc.get_actor().clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the change added to a document by its respective hash.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes in \p src.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre \p count `>= AM_CHANGE_HASH_SIZE`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// src must be a byte array of size `>= automerge::types::HASH_SIZE`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetChangeByHash(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
src: *const u8,
|
||||||
|
count: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let slice = std::slice::from_raw_parts(src, count);
|
||||||
|
match slice.try_into() {
|
||||||
|
Ok(change_hash) => to_result(doc.get_change_by_hash(&change_hash)),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the changes added to a document by their respective hashes.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] have_deps A pointer to an `AMchangeHashes` struct or `NULL`.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetChanges(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
have_deps: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let empty_deps = Vec::<am::ChangeHash>::new();
|
||||||
|
let have_deps = match have_deps.as_ref() {
|
||||||
|
Some(have_deps) => have_deps.as_ref(),
|
||||||
|
None => &empty_deps,
|
||||||
|
};
|
||||||
|
to_result(doc.get_changes(have_deps))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the changes added to a second document that weren't added to
|
||||||
|
/// a first document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc1 An `AMdoc` struct.
|
||||||
|
/// \param[in,out] doc2 An `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct.
|
||||||
|
/// \pre \p doc1 `!= NULL`.
|
||||||
|
/// \pre \p doc2 `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc1 must be a valid pointer to an AMdoc
|
||||||
|
/// doc2 must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetChangesAdded(doc1: *mut AMdoc, doc2: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let doc1 = to_doc_mut!(doc1);
|
||||||
|
let doc2 = to_doc_mut!(doc2);
|
||||||
|
to_result(doc1.get_changes_added(doc2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current heads of a document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetHeads(doc: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(Ok::<Vec<am::ChangeHash>, am::AutomergeError>(
|
||||||
|
doc.get_heads(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the hashes of the changes in a document that aren't transitive
|
||||||
|
/// dependencies of the given hashes of changes.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct or `NULL`.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetMissingDeps(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let empty_heads = Vec::<am::ChangeHash>::new();
|
||||||
|
let heads = match heads.as_ref() {
|
||||||
|
Some(heads) => heads.as_ref(),
|
||||||
|
None => &empty_heads,
|
||||||
|
};
|
||||||
|
to_result(doc.get_missing_deps(heads))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the last change made to a document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing either an `AMchange`
|
||||||
|
/// struct or a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMgetLastLocalChange(doc: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.get_last_local_change())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical keys of a map object.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// keys or `NULL` for current keys.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMstrs` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMkeys(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.keys(obj_id)),
|
||||||
|
Some(heads) => to_result(doc.keys_at(obj_id, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Allocates storage for a document and initializes it with the compact
|
||||||
|
/// form of an incremental save.
|
||||||
|
///
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes in \p src to load.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMdoc` struct.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMload(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(am::AutoCommit::load(&data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Loads the compact form of an incremental save into a document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes in \p src to load.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing the number of
|
||||||
|
/// operations loaded from \p src.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMloadIncremental(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
src: *const u8,
|
||||||
|
count: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(doc.load_incremental(&data))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Applies all of the changes in \p src which are not in \p dest to
|
||||||
|
/// \p dest.
|
||||||
|
///
|
||||||
|
/// \param[in,out] dest A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in,out] src A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p dest `!= NULL`.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// dest must be a valid pointer to an AMdoc
|
||||||
|
/// src must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmerge(dest: *mut AMdoc, src: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let dest = to_doc_mut!(dest);
|
||||||
|
to_result(dest.merge(to_doc_mut!(src)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical size of an object.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// size or `NULL` for current size.
|
||||||
|
/// \return A 64-bit unsigned integer.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjSize(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> usize {
|
||||||
|
if let Some(doc) = doc.as_ref() {
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => doc.length(obj_id),
|
||||||
|
Some(heads) => doc.length_at(obj_id, heads.as_ref()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical values of an object within its entire
|
||||||
|
/// range.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// items or `NULL` for current items.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjValues(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.values(obj_id)),
|
||||||
|
Some(heads) => to_result(doc.values_at(obj_id, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the number of pending operations added during a document's
|
||||||
|
/// current transaction.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return The count of pending operations for \p doc.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMpendingOps(doc: *const AMdoc) -> usize {
|
||||||
|
if let Some(doc) = doc.as_ref() {
|
||||||
|
doc.pending_ops()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Receives a synchronization message from a peer based upon a given
|
||||||
|
/// synchronization state.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in,out] sync_state A pointer to an `AMsyncState` struct.
|
||||||
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p sync_state `!= NULL`.
|
||||||
|
/// \pre \p sync_message `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMreceiveSyncMessage(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
sync_state: *mut AMsyncState,
|
||||||
|
sync_message: *const AMsyncMessage,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let sync_state = to_sync_state_mut!(sync_state);
|
||||||
|
let sync_message = to_sync_message!(sync_message);
|
||||||
|
to_result(doc.receive_sync_message(sync_state.as_mut(), sync_message.as_ref().clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Cancels the pending operations added during a document's current
|
||||||
|
/// transaction and gets the number of cancellations.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return The count of pending operations for \p doc that were cancelled.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMrollback(doc: *mut AMdoc) -> usize {
|
||||||
|
if let Some(doc) = doc.as_mut() {
|
||||||
|
doc.rollback()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Saves the entirety of a document into a compact form.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||||
|
/// an `AMbyteSpan` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsave(doc: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(Ok(doc.save()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Saves the changes to a document since its last save into a compact
|
||||||
|
/// form.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||||
|
/// an `AMbyteSpan` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsaveIncremental(doc: *mut AMdoc) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(Ok(doc.save_incremental()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts the actor identifier of a document.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] actor_id A pointer to an `AMactorId` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p actor_id `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// actor_id must be a valid pointer to an AMactorId
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsetActorId(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
actor_id: *const AMactorId,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let actor_id = to_actor_id!(actor_id);
|
||||||
|
doc.set_actor(actor_id.as_ref().clone());
|
||||||
|
to_result(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Splices values into and/or removes values from the identified object
|
||||||
|
/// at a given position within it.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] pos A position in the object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate one past its end.
|
||||||
|
/// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate
|
||||||
|
/// all of them.
|
||||||
|
/// \param[in] src A pointer to an array of `AMvalue` structs.
|
||||||
|
/// \param[in] count The number of `AMvalue` structs in \p src to load.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`.
|
||||||
|
/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`.
|
||||||
|
/// \pre `(`\p src `!= NULL and 1 <=` \p count `<= sizeof(`\p src`)/
|
||||||
|
/// sizeof(AMvalue)) or `\p src `== NULL or `\p count `== 0`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// src must be an AMvalue array of size `>= count` or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsplice(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
pos: usize,
|
||||||
|
del: usize,
|
||||||
|
src: *const AMvalue,
|
||||||
|
count: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let len = doc.length(obj_id);
|
||||||
|
let pos = to_index!(pos, len, "pos");
|
||||||
|
let del = to_index!(del, len, "del");
|
||||||
|
let mut vals: Vec<am::ScalarValue> = vec![];
|
||||||
|
if !(src.is_null() || count == 0) {
|
||||||
|
let c_vals = std::slice::from_raw_parts(src, count);
|
||||||
|
for c_val in c_vals {
|
||||||
|
match c_val.try_into() {
|
||||||
|
Ok(s) => {
|
||||||
|
vals.push(s);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return AMresult::err(&e.to_string()).into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to_result(doc.splice(obj_id, pos, del, vals))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Splices characters into and/or removes characters from the
|
||||||
|
/// identified object at a given position within it.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] pos A position in the text object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate one past its end.
|
||||||
|
/// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate
|
||||||
|
/// all of them.
|
||||||
|
/// \param[in] text A UTF-8 string.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`.
|
||||||
|
/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// text must be a null-terminated array of `c_char` or NULL.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMspliceText(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
pos: usize,
|
||||||
|
del: usize,
|
||||||
|
text: *const c_char,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let len = doc.length(obj_id);
|
||||||
|
let pos = to_index!(pos, len, "pos");
|
||||||
|
let del = to_index!(del, len, "del");
|
||||||
|
to_result(doc.splice_text(obj_id, pos, del, &to_str(text)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical string represented by a text object.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// keys or `NULL` for current keys.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a UTF-8 string.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMtext(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.text(obj_id)),
|
||||||
|
Some(heads) => to_result(doc.text_at(obj_id, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
604
automerge-c/src/doc/list.rs
Normal file
604
automerge-c/src/doc/list.rs
Normal file
|
@ -0,0 +1,604 @@
|
||||||
|
use automerge as am;
|
||||||
|
use automerge::transaction::Transactable;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
use crate::doc::{to_doc, to_doc_mut, to_obj_id, to_str, AMdoc};
|
||||||
|
use crate::obj::{AMobjId, AMobjType};
|
||||||
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
|
pub mod item;
|
||||||
|
pub mod items;
|
||||||
|
|
||||||
|
macro_rules! adjust {
|
||||||
|
($index:expr, $insert:expr, $len:expr) => {{
|
||||||
|
// An empty object can only be inserted into.
|
||||||
|
let insert = $insert || $len == 0;
|
||||||
|
let end = if insert { $len } else { $len - 1 };
|
||||||
|
if $index > end && $index != usize::MAX {
|
||||||
|
return AMresult::err(&format!("Invalid index {}", $index)).into();
|
||||||
|
}
|
||||||
|
(std::cmp::min($index, end), insert)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! to_range {
|
||||||
|
($begin:expr, $end:expr) => {{
|
||||||
|
if $begin > $end {
|
||||||
|
return AMresult::err(&format!("Invalid range [{}-{})", $begin, $end)).into();
|
||||||
|
};
|
||||||
|
($begin..$end)
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Deletes an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistDelete(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||||
|
to_result(doc.delete(obj_id, index))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||||
|
/// value or `NULL` for the current value.
|
||||||
|
/// \return A pointer to an `AMresult` struct that doesn't contain a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistGet(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.get(obj_id, index)),
|
||||||
|
Some(heads) => to_result(doc.get_at(obj_id, index, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets all of the historical values at an index in a list object until
|
||||||
|
/// its current one or a specific one.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||||
|
/// last value or `NULL` for the current last value.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistGetAll(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.get_all(obj_id, index)),
|
||||||
|
Some(heads) => to_result(doc.get_all_at(obj_id, index, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Increments a counter at an index in a list object by the given
|
||||||
|
/// value.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistIncrement(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, _) = adjust!(index, false, doc.length(obj_id));
|
||||||
|
to_result(doc.increment(obj_id, index, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a boolean as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A boolean.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutBool(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: bool,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let value = am::ScalarValue::Boolean(value);
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a sequence of bytes as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p src before \p index instead of
|
||||||
|
/// writing \p src over \p index.
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes to copy from \p src.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutBytes(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
src: *const u8,
|
||||||
|
count: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let mut value = Vec::new();
|
||||||
|
value.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a CRDT counter as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutCounter(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let value = am::ScalarValue::Counter(value.into());
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a float as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A 64-bit float.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutF64(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: f64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a signed integer as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutInt(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts null as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutNull(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, ())
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, ())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts an empty object as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] obj_type An `AMobjIdType` enum tag.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMobjId` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutObject(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
obj_type: AMobjType,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let object = obj_type.into();
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert_object(obj_id, index, object)
|
||||||
|
} else {
|
||||||
|
doc.put_object(obj_id, index, object)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a UTF-8 string as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A UTF-8 string.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \pre \p value `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// value must be a null-terminated array of `c_char`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutStr(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: *const c_char,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let value = to_str(value);
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a Lamport timestamp as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutTimestamp(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
let value = am::ScalarValue::Timestamp(value);
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts an unsigned integer as the value at an index in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] index An index in the list object identified by \p obj_id or
|
||||||
|
/// `SIZE_MAX` to indicate its last index if \p insert
|
||||||
|
/// `== false` or one past its last index if \p insert
|
||||||
|
/// `== true`.
|
||||||
|
/// \param[in] insert A flag to insert \p value before \p index instead of
|
||||||
|
/// writing \p value over \p index.
|
||||||
|
/// \param[in] value A 64-bit unsigned integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistPutUint(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
index: usize,
|
||||||
|
insert: bool,
|
||||||
|
value: u64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let (index, insert) = adjust!(index, insert, doc.length(obj_id));
|
||||||
|
to_result(if insert {
|
||||||
|
doc.insert(obj_id, index, value)
|
||||||
|
} else {
|
||||||
|
doc.put(obj_id, index, value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical indices and values of the list object
|
||||||
|
/// within the given range.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] begin The first index in a range of indices.
|
||||||
|
/// \param[in] end At least one past the last index in a range of indices.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// indices and values or `NULL` for current indices and
|
||||||
|
/// values.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMlistItems`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p begin `<=` \p end `<= SIZE_MAX`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistRange(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
begin: usize,
|
||||||
|
end: usize,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
let range = to_range!(begin, end);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.list_range(obj_id, range)),
|
||||||
|
Some(heads) => to_result(doc.list_range_at(obj_id, range, heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
100
automerge-c/src/doc/list/item.rs
Normal file
100
automerge-c/src/doc/list/item.rs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use crate::obj::AMobjId;
|
||||||
|
use crate::result::AMvalue;
|
||||||
|
|
||||||
|
/// \struct AMlistItem
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief An item in a list object.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AMlistItem {
|
||||||
|
/// The index of an item in a list object.
|
||||||
|
index: usize,
|
||||||
|
/// The object identifier of an item in a list object.
|
||||||
|
obj_id: AMobjId,
|
||||||
|
/// The value of an item in a list object.
|
||||||
|
value: (am::Value<'static>, RefCell<Option<CString>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMlistItem {
|
||||||
|
pub fn new(index: usize, value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||||
|
Self {
|
||||||
|
index,
|
||||||
|
obj_id: AMobjId::new(obj_id),
|
||||||
|
value: (value, Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AMlistItem {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.index == other.index && self.obj_id == other.obj_id && self.value.0 == other.value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl From<&AMlistItem> for (usize, am::Value<'static>, am::ObjId) {
|
||||||
|
fn from(list_item: &AMlistItem) -> Self {
|
||||||
|
(list_item.index, list_item.value.0.clone(), list_item.obj_id.as_ref().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// \memberof AMlistItem
|
||||||
|
/// \brief Gets the index of an item in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||||
|
/// \return A 64-bit unsigned integer.
|
||||||
|
/// \pre \p list_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// list_item must be a valid pointer to an AMlistItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemIndex(list_item: *const AMlistItem) -> usize {
|
||||||
|
if let Some(list_item) = list_item.as_ref() {
|
||||||
|
list_item.index
|
||||||
|
} else {
|
||||||
|
usize::MAX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItem
|
||||||
|
/// \brief Gets the object identifier of an item in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||||
|
/// \return A pointer to an `AMobjId` struct.
|
||||||
|
/// \pre \p list_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// list_item must be a valid pointer to an AMlistItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemObjId(list_item: *const AMlistItem) -> *const AMobjId {
|
||||||
|
if let Some(list_item) = list_item.as_ref() {
|
||||||
|
&list_item.obj_id
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItem
|
||||||
|
/// \brief Gets the value of an item in a list object.
|
||||||
|
///
|
||||||
|
/// \param[in] list_item A pointer to an `AMlistItem` struct.
|
||||||
|
/// \return An `AMvalue` struct.
|
||||||
|
/// \pre \p list_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// list_item must be a valid pointer to an AMlistItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemValue<'a>(list_item: *const AMlistItem) -> AMvalue<'a> {
|
||||||
|
if let Some(list_item) = list_item.as_ref() {
|
||||||
|
(&list_item.value.0, &list_item.value.1).into()
|
||||||
|
} else {
|
||||||
|
AMvalue::Void
|
||||||
|
}
|
||||||
|
}
|
348
automerge-c/src/doc/list/items.rs
Normal file
348
automerge-c/src/doc/list/items.rs
Normal file
|
@ -0,0 +1,348 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::doc::list::item::AMlistItem;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(list_items: &[AMlistItem], offset: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
len: list_items.len(),
|
||||||
|
offset,
|
||||||
|
ptr: list_items.as_ptr() as *const c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMlistItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) };
|
||||||
|
let value = &slice[self.get_index()];
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMlistItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) };
|
||||||
|
Some(&slice[self.get_index()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMlistItems
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of list object items.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMlistItems {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMlistItems {
|
||||||
|
pub fn new(list_items: &[AMlistItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(list_items, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[AMlistItem]> for AMlistItems {
|
||||||
|
fn as_ref(&self) -> &[AMlistItem] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMlistItem, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMlistItems {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Advances an iterator over a sequence of list object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsAdvance(list_items: *mut AMlistItems, n: isize) {
|
||||||
|
if let Some(list_items) = list_items.as_mut() {
|
||||||
|
list_items.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Tests the equality of two sequences of list object items underlying
|
||||||
|
/// a pair of iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] list_items1 A pointer to an `AMlistItems` struct.
|
||||||
|
/// \param[in] list_items2 A pointer to an `AMlistItems` struct.
|
||||||
|
/// \return `true` if \p list_items1 `==` \p list_items2 and `false` otherwise.
|
||||||
|
/// \pre \p list_items1 `!= NULL`.
|
||||||
|
/// \pre \p list_items2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items1 must be a valid pointer to an AMlistItems
|
||||||
|
/// list_items2 must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsEqual(
|
||||||
|
list_items1: *const AMlistItems,
|
||||||
|
list_items2: *const AMlistItems,
|
||||||
|
) -> bool {
|
||||||
|
match (list_items1.as_ref(), list_items2.as_ref()) {
|
||||||
|
(Some(list_items1), Some(list_items2)) => list_items1.as_ref() == list_items2.as_ref(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Gets the list object item at the current position of an iterator
|
||||||
|
/// over a sequence of list object items and then advances it by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMlistItem` struct that's `NULL` when
|
||||||
|
/// \p list_items was previously advanced past its forward/reverse
|
||||||
|
/// limit.
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsNext(
|
||||||
|
list_items: *mut AMlistItems,
|
||||||
|
n: isize,
|
||||||
|
) -> *const AMlistItem {
|
||||||
|
if let Some(list_items) = list_items.as_mut() {
|
||||||
|
if let Some(list_item) = list_items.next(n) {
|
||||||
|
return list_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Advances an iterator over a sequence of list object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the list object item at its new
|
||||||
|
/// position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMlistItem` struct that's `NULL` when
|
||||||
|
/// \p list_items is presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsPrev(
|
||||||
|
list_items: *mut AMlistItems,
|
||||||
|
n: isize,
|
||||||
|
) -> *const AMlistItem {
|
||||||
|
if let Some(list_items) = list_items.as_mut() {
|
||||||
|
if let Some(list_item) = list_items.prev(n) {
|
||||||
|
return list_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Gets the size of the sequence of list object items underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \return The count of values in \p list_items.
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsSize(list_items: *const AMlistItems) -> usize {
|
||||||
|
if let Some(list_items) = list_items.as_ref() {
|
||||||
|
list_items.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Creates an iterator over the same sequence of list object items as
|
||||||
|
/// the given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \return An `AMlistItems` struct
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsReversed(list_items: *const AMlistItems) -> AMlistItems {
|
||||||
|
if let Some(list_items) = list_items.as_ref() {
|
||||||
|
list_items.reversed()
|
||||||
|
} else {
|
||||||
|
AMlistItems::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMlistItems
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of list object items as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] list_items A pointer to an `AMlistItems` struct.
|
||||||
|
/// \return An `AMlistItems` struct
|
||||||
|
/// \pre \p list_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// list_items must be a valid pointer to an AMlistItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMlistItemsRewound(list_items: *const AMlistItems) -> AMlistItems {
|
||||||
|
if let Some(list_items) = list_items.as_ref() {
|
||||||
|
list_items.rewound()
|
||||||
|
} else {
|
||||||
|
AMlistItems::default()
|
||||||
|
}
|
||||||
|
}
|
506
automerge-c/src/doc/map.rs
Normal file
506
automerge-c/src/doc/map.rs
Normal file
|
@ -0,0 +1,506 @@
|
||||||
|
use automerge as am;
|
||||||
|
use automerge::transaction::Transactable;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
use crate::doc::utils::to_str;
|
||||||
|
use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc};
|
||||||
|
use crate::obj::{AMobjId, AMobjType};
|
||||||
|
use crate::result::{to_result, AMresult};
|
||||||
|
|
||||||
|
pub mod item;
|
||||||
|
pub mod items;
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Deletes a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapDelete(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.delete(to_obj_id!(obj_id), to_str(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical value for a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by
|
||||||
|
/// \p obj_id.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||||
|
/// value or `NULL` for the current value.
|
||||||
|
/// \return A pointer to an `AMresult` struct that doesn't contain a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapGet(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.get(obj_id, to_str(key))),
|
||||||
|
Some(heads) => to_result(doc.get_at(obj_id, to_str(key), heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets all of the historical values for a key in a map object until
|
||||||
|
/// its current one or a specific one.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by
|
||||||
|
/// \p obj_id.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical
|
||||||
|
/// last value or `NULL` for the current last value.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapGetAll(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match heads.as_ref() {
|
||||||
|
None => to_result(doc.get_all(obj_id, to_str(key))),
|
||||||
|
Some(heads) => to_result(doc.get_all_at(obj_id, to_str(key), heads.as_ref())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Increments a counter for a key in a map object by the given value.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapIncrement(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.increment(to_obj_id!(obj_id), to_str(key), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a boolean as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A boolean.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutBool(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: bool,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a sequence of bytes as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
|
/// \param[in] count The number of bytes to copy from \p src.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \pre \p src `!= NULL`.
|
||||||
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
/// src must be a byte array of size `>= count`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutBytes(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
src: *const u8,
|
||||||
|
count: usize,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
vec.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), vec))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a CRDT counter as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutCounter(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(
|
||||||
|
to_obj_id!(obj_id),
|
||||||
|
to_str(key),
|
||||||
|
am::ScalarValue::Counter(value.into()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts null as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutNull(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts an empty object as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] obj_type An `AMobjIdType` enum tag.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
|
/// `AMobjId` struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutObject(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
obj_type: AMobjType,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put_object(to_obj_id!(obj_id), to_str(key), obj_type.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a float as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit float.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutF64(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: f64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a signed integer as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutInt(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a UTF-8 string as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A UTF-8 string.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \pre \p value `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
/// value must be a null-terminated array of `c_char`
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutStr(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: *const c_char,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), to_str(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts a Lamport timestamp as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit signed integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutTimestamp(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: i64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(
|
||||||
|
to_obj_id!(obj_id),
|
||||||
|
to_str(key),
|
||||||
|
am::ScalarValue::Timestamp(value),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Puts an unsigned integer as the value of a key in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in,out] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] key A UTF-8 string key for the map object identified by \p obj_id.
|
||||||
|
/// \param[in] value A 64-bit unsigned integer.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing a void.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre \p key `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// key must be a c string of the map key to be used
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapPutUint(
|
||||||
|
doc: *mut AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
key: *const c_char,
|
||||||
|
value: u64,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc_mut!(doc);
|
||||||
|
to_result(doc.put(to_obj_id!(obj_id), to_str(key), value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMdoc
|
||||||
|
/// \brief Gets the current or historical keys and values of the map object
|
||||||
|
/// within the given range.
|
||||||
|
///
|
||||||
|
/// \param[in] doc A pointer to an `AMdoc` struct.
|
||||||
|
/// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`.
|
||||||
|
/// \param[in] begin The first key in a subrange or `NULL` to indicate the
|
||||||
|
/// absolute first key.
|
||||||
|
/// \param[in] end The key one past the last key in a subrange or `NULL` to
|
||||||
|
/// indicate one past the absolute last key.
|
||||||
|
/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical
|
||||||
|
/// keys and values or `NULL` for current keys and values.
|
||||||
|
/// \return A pointer to an `AMresult` struct containing an `AMmapItems`
|
||||||
|
/// struct.
|
||||||
|
/// \pre \p doc `!= NULL`.
|
||||||
|
/// \pre `strcmp(`\p begin, \p end`) != 1` if \p begin `!= NULL` and \p end `!= NULL`.
|
||||||
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
|
/// \internal
|
||||||
|
/// # Safety
|
||||||
|
/// doc must be a valid pointer to an AMdoc
|
||||||
|
/// obj_id must be a valid pointer to an AMobjId or std::ptr::null()
|
||||||
|
/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null()
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapRange(
|
||||||
|
doc: *const AMdoc,
|
||||||
|
obj_id: *const AMobjId,
|
||||||
|
begin: *const c_char,
|
||||||
|
end: *const c_char,
|
||||||
|
heads: *const AMchangeHashes,
|
||||||
|
) -> *mut AMresult {
|
||||||
|
let doc = to_doc!(doc);
|
||||||
|
let obj_id = to_obj_id!(obj_id);
|
||||||
|
match (begin.as_ref(), end.as_ref()) {
|
||||||
|
(Some(_), Some(_)) => {
|
||||||
|
let (begin, end) = (to_str(begin), to_str(end));
|
||||||
|
if begin > end {
|
||||||
|
return AMresult::err(&format!("Invalid range [{}-{})", begin, end)).into();
|
||||||
|
};
|
||||||
|
let bounds = begin..end;
|
||||||
|
if let Some(heads) = heads.as_ref() {
|
||||||
|
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||||
|
} else {
|
||||||
|
to_result(doc.map_range(obj_id, bounds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(_), None) => {
|
||||||
|
let bounds = to_str(begin)..;
|
||||||
|
if let Some(heads) = heads.as_ref() {
|
||||||
|
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||||
|
} else {
|
||||||
|
to_result(doc.map_range(obj_id, bounds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
let bounds = ..to_str(end);
|
||||||
|
if let Some(heads) = heads.as_ref() {
|
||||||
|
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||||
|
} else {
|
||||||
|
to_result(doc.map_range(obj_id, bounds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
let bounds = ..;
|
||||||
|
if let Some(heads) = heads.as_ref() {
|
||||||
|
to_result(doc.map_range_at(obj_id, bounds, heads.as_ref()))
|
||||||
|
} else {
|
||||||
|
to_result(doc.map_range(obj_id, bounds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
automerge-c/src/doc/map/item.rs
Normal file
101
automerge-c/src/doc/map/item.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::obj::AMobjId;
|
||||||
|
use crate::result::AMvalue;
|
||||||
|
|
||||||
|
/// \struct AMmapItem
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief An item in a map object.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AMmapItem {
|
||||||
|
/// The key of an item in a map object.
|
||||||
|
key: CString,
|
||||||
|
/// The object identifier of an item in a map object.
|
||||||
|
obj_id: AMobjId,
|
||||||
|
/// The value of an item in a map object.
|
||||||
|
value: (am::Value<'static>, RefCell<Option<CString>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMmapItem {
|
||||||
|
pub fn new(key: &'static str, value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||||
|
Self {
|
||||||
|
key: CString::new(key).unwrap(),
|
||||||
|
obj_id: AMobjId::new(obj_id),
|
||||||
|
value: (value, Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AMmapItem {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.key == other.key && self.obj_id == other.obj_id && self.value.0 == other.value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
impl From<&AMmapItem> for (String, am::Value<'static>, am::ObjId) {
|
||||||
|
fn from(map_item: &AMmapItem) -> Self {
|
||||||
|
(map_item.key.into_string().unwrap(), map_item.value.0.clone(), map_item.obj_id.as_ref().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// \memberof AMmapItem
|
||||||
|
/// \brief Gets the key of an item in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||||
|
/// \return A 64-bit unsigned integer.
|
||||||
|
/// \pre \p map_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// map_item must be a valid pointer to an AMmapItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemKey(map_item: *const AMmapItem) -> *const c_char {
|
||||||
|
if let Some(map_item) = map_item.as_ref() {
|
||||||
|
map_item.key.as_ptr()
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItem
|
||||||
|
/// \brief Gets the object identifier of an item in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||||
|
/// \return A pointer to an `AMobjId` struct.
|
||||||
|
/// \pre \p map_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// map_item must be a valid pointer to an AMmapItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemObjId(map_item: *const AMmapItem) -> *const AMobjId {
|
||||||
|
if let Some(map_item) = map_item.as_ref() {
|
||||||
|
&map_item.obj_id
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItem
|
||||||
|
/// \brief Gets the value of an item in a map object.
|
||||||
|
///
|
||||||
|
/// \param[in] map_item A pointer to an `AMmapItem` struct.
|
||||||
|
/// \return An `AMvalue` struct.
|
||||||
|
/// \pre \p map_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// map_item must be a valid pointer to an AMmapItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemValue<'a>(map_item: *const AMmapItem) -> AMvalue<'a> {
|
||||||
|
if let Some(map_item) = map_item.as_ref() {
|
||||||
|
(&map_item.value.0, &map_item.value.1).into()
|
||||||
|
} else {
|
||||||
|
AMvalue::Void
|
||||||
|
}
|
||||||
|
}
|
340
automerge-c/src/doc/map/items.rs
Normal file
340
automerge-c/src/doc/map/items.rs
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::doc::map::item::AMmapItem;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(map_items: &[AMmapItem], offset: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
len: map_items.len(),
|
||||||
|
offset,
|
||||||
|
ptr: map_items.as_ptr() as *const c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMmapItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) };
|
||||||
|
let value = &slice[self.get_index()];
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMmapItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) };
|
||||||
|
Some(&slice[self.get_index()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMmapItems
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of map object items.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMmapItems {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMmapItems {
|
||||||
|
pub fn new(map_items: &[AMmapItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(map_items, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[AMmapItem]> for AMmapItems {
|
||||||
|
fn as_ref(&self) -> &[AMmapItem] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMmapItem, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMmapItems {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Advances an iterator over a sequence of map object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsAdvance(map_items: *mut AMmapItems, n: isize) {
|
||||||
|
if let Some(map_items) = map_items.as_mut() {
|
||||||
|
map_items.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Tests the equality of two sequences of map object items underlying
|
||||||
|
/// a pair of iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] map_items1 A pointer to an `AMmapItems` struct.
|
||||||
|
/// \param[in] map_items2 A pointer to an `AMmapItems` struct.
|
||||||
|
/// \return `true` if \p map_items1 `==` \p map_items2 and `false` otherwise.
|
||||||
|
/// \pre \p map_items1 `!= NULL`.
|
||||||
|
/// \pre \p map_items2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items1 must be a valid pointer to an AMmapItems
|
||||||
|
/// map_items2 must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsEqual(
|
||||||
|
map_items1: *const AMmapItems,
|
||||||
|
map_items2: *const AMmapItems,
|
||||||
|
) -> bool {
|
||||||
|
match (map_items1.as_ref(), map_items2.as_ref()) {
|
||||||
|
(Some(map_items1), Some(map_items2)) => map_items1.as_ref() == map_items2.as_ref(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Gets the map object item at the current position of an iterator
|
||||||
|
/// over a sequence of map object items and then advances it by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items
|
||||||
|
/// was previously advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsNext(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem {
|
||||||
|
if let Some(map_items) = map_items.as_mut() {
|
||||||
|
if let Some(map_item) = map_items.next(n) {
|
||||||
|
return map_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Advances an iterator over a sequence of map object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the map object item at its new
|
||||||
|
/// position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items
|
||||||
|
/// is presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsPrev(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem {
|
||||||
|
if let Some(map_items) = map_items.as_mut() {
|
||||||
|
if let Some(map_item) = map_items.prev(n) {
|
||||||
|
return map_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Gets the size of the sequence of map object items underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \return The count of values in \p map_items.
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsSize(map_items: *const AMmapItems) -> usize {
|
||||||
|
if let Some(map_items) = map_items.as_ref() {
|
||||||
|
map_items.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Creates an iterator over the same sequence of map object items as
|
||||||
|
/// the given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \return An `AMmapItems` struct
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsReversed(map_items: *const AMmapItems) -> AMmapItems {
|
||||||
|
if let Some(map_items) = map_items.as_ref() {
|
||||||
|
map_items.reversed()
|
||||||
|
} else {
|
||||||
|
AMmapItems::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMmapItems
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence of map object items as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] map_items A pointer to an `AMmapItems` struct.
|
||||||
|
/// \return An `AMmapItems` struct
|
||||||
|
/// \pre \p map_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// map_items must be a valid pointer to an AMmapItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMmapItemsRewound(map_items: *const AMmapItems) -> AMmapItems {
|
||||||
|
if let Some(map_items) = map_items.as_ref() {
|
||||||
|
map_items.rewound()
|
||||||
|
} else {
|
||||||
|
AMmapItems::default()
|
||||||
|
}
|
||||||
|
}
|
57
automerge-c/src/doc/utils.rs
Normal file
57
automerge-c/src/doc/utils.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
macro_rules! to_actor_id {
|
||||||
|
($handle:expr) => {{
|
||||||
|
let handle = $handle.as_ref();
|
||||||
|
match handle {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return AMresult::err("Invalid AMactorId pointer").into(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use to_actor_id;
|
||||||
|
|
||||||
|
macro_rules! to_doc {
|
||||||
|
($handle:expr) => {{
|
||||||
|
let handle = $handle.as_ref();
|
||||||
|
match handle {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return AMresult::err("Invalid AMdoc pointer").into(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use to_doc;
|
||||||
|
|
||||||
|
macro_rules! to_doc_mut {
|
||||||
|
($handle:expr) => {{
|
||||||
|
let handle = $handle.as_mut();
|
||||||
|
match handle {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return AMresult::err("Invalid AMdoc pointer").into(),
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use to_doc_mut;
|
||||||
|
|
||||||
|
macro_rules! to_obj_id {
|
||||||
|
($handle:expr) => {{
|
||||||
|
match $handle.as_ref() {
|
||||||
|
Some(obj_id) => obj_id,
|
||||||
|
None => &automerge::ROOT,
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use to_obj_id;
|
||||||
|
|
||||||
|
pub(crate) unsafe fn to_str(c: *const c_char) -> String {
|
||||||
|
if !c.is_null() {
|
||||||
|
CStr::from_ptr(c).to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
String::default()
|
||||||
|
}
|
||||||
|
}
|
11
automerge-c/src/lib.rs
Normal file
11
automerge-c/src/lib.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
mod actor_id;
|
||||||
|
mod byte_span;
|
||||||
|
mod change;
|
||||||
|
mod change_hashes;
|
||||||
|
mod changes;
|
||||||
|
mod doc;
|
||||||
|
mod obj;
|
||||||
|
mod result;
|
||||||
|
mod result_stack;
|
||||||
|
mod strs;
|
||||||
|
mod sync;
|
|
@ -1,32 +1,11 @@
|
||||||
use automerge as am;
|
use automerge as am;
|
||||||
use std::any::type_name;
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::actor_id::AMactorId;
|
use crate::actor_id::AMactorId;
|
||||||
|
|
||||||
macro_rules! to_obj_id {
|
pub mod item;
|
||||||
($handle:expr) => {{
|
pub mod items;
|
||||||
match $handle.as_ref() {
|
|
||||||
Some(obj_id) => obj_id,
|
|
||||||
None => &automerge::ROOT,
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use to_obj_id;
|
|
||||||
|
|
||||||
macro_rules! to_obj_type {
|
|
||||||
($c_obj_type:expr) => {{
|
|
||||||
let result: Result<am::ObjType, am::AutomergeError> = (&$c_obj_type).try_into();
|
|
||||||
match result {
|
|
||||||
Ok(obj_type) => obj_type,
|
|
||||||
Err(e) => return AMresult::error(&e.to_string()).into(),
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use to_obj_type;
|
|
||||||
|
|
||||||
/// \struct AMobjId
|
/// \struct AMobjId
|
||||||
/// \installed_headerfile
|
/// \installed_headerfile
|
||||||
|
@ -76,11 +55,11 @@ impl Deref for AMobjId {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMobjId
|
/// \memberof AMobjId
|
||||||
/// \brief Gets the actor identifier component of an object identifier.
|
/// \brief Gets the actor identifier of an object identifier.
|
||||||
///
|
///
|
||||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||||
/// \return A pointer to an `AMactorId` struct or `NULL`.
|
/// \return A pointer to an `AMactorId` struct or `NULL`.
|
||||||
/// \pre \p obj_id `!= NULL`
|
/// \pre \p obj_id `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -94,11 +73,11 @@ pub unsafe extern "C" fn AMobjIdActorId(obj_id: *const AMobjId) -> *const AMacto
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMobjId
|
/// \memberof AMobjId
|
||||||
/// \brief Gets the counter component of an object identifier.
|
/// \brief Gets the counter of an object identifier.
|
||||||
///
|
///
|
||||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p obj_id `!= NULL`
|
/// \pre \p obj_id `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -121,9 +100,8 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 {
|
||||||
/// \param[in] obj_id1 A pointer to an `AMobjId` struct.
|
/// \param[in] obj_id1 A pointer to an `AMobjId` struct.
|
||||||
/// \param[in] obj_id2 A pointer to an `AMobjId` struct.
|
/// \param[in] obj_id2 A pointer to an `AMobjId` struct.
|
||||||
/// \return `true` if \p obj_id1 `==` \p obj_id2 and `false` otherwise.
|
/// \return `true` if \p obj_id1 `==` \p obj_id2 and `false` otherwise.
|
||||||
/// \pre \p obj_id1 `!= NULL`
|
/// \pre \p obj_id1 `!= NULL`.
|
||||||
/// \pre \p obj_id1 `!= NULL`
|
/// \pre \p obj_id2 `!= NULL`.
|
||||||
/// \post `!(`\p obj_id1 `&&` \p obj_id2 `) -> false`
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// #Safety
|
/// #Safety
|
||||||
|
@ -133,28 +111,26 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 {
|
||||||
pub unsafe extern "C" fn AMobjIdEqual(obj_id1: *const AMobjId, obj_id2: *const AMobjId) -> bool {
|
pub unsafe extern "C" fn AMobjIdEqual(obj_id1: *const AMobjId, obj_id2: *const AMobjId) -> bool {
|
||||||
match (obj_id1.as_ref(), obj_id2.as_ref()) {
|
match (obj_id1.as_ref(), obj_id2.as_ref()) {
|
||||||
(Some(obj_id1), Some(obj_id2)) => obj_id1 == obj_id2,
|
(Some(obj_id1), Some(obj_id2)) => obj_id1 == obj_id2,
|
||||||
(None, None) | (None, Some(_)) | (Some(_), None) => false,
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMobjId
|
/// \memberof AMobjId
|
||||||
/// \brief Gets the index component of an object identifier.
|
/// \brief Gets the index of an object identifier.
|
||||||
///
|
///
|
||||||
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
/// \param[in] obj_id A pointer to an `AMobjId` struct.
|
||||||
/// \return A 64-bit unsigned integer.
|
/// \return A 64-bit unsigned integer.
|
||||||
/// \pre \p obj_id `!= NULL`
|
/// \pre \p obj_id `!= NULL`.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// obj_id must be a valid pointer to an AMobjId
|
/// obj_id must be a valid pointer to an AMobjId
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize {
|
pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize {
|
||||||
use am::ObjId::*;
|
|
||||||
|
|
||||||
if let Some(obj_id) = obj_id.as_ref() {
|
if let Some(obj_id) = obj_id.as_ref() {
|
||||||
match obj_id.as_ref() {
|
match obj_id.as_ref() {
|
||||||
Id(_, _, index) => *index,
|
am::ObjId::Id(_, _, index) => *index,
|
||||||
Root => 0,
|
am::ObjId::Root => 0,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
usize::MAX
|
usize::MAX
|
||||||
|
@ -163,13 +139,9 @@ pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize {
|
||||||
|
|
||||||
/// \ingroup enumerations
|
/// \ingroup enumerations
|
||||||
/// \enum AMobjType
|
/// \enum AMobjType
|
||||||
/// \installed_headerfile
|
|
||||||
/// \brief The type of an object value.
|
/// \brief The type of an object value.
|
||||||
#[derive(PartialEq, Eq)]
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum AMobjType {
|
pub enum AMobjType {
|
||||||
/// The default tag, not a type signifier.
|
|
||||||
Default = 0,
|
|
||||||
/// A list.
|
/// A list.
|
||||||
List = 1,
|
List = 1,
|
||||||
/// A key-value map.
|
/// A key-value map.
|
||||||
|
@ -178,39 +150,12 @@ pub enum AMobjType {
|
||||||
Text,
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AMobjType {
|
impl From<AMobjType> for am::ObjType {
|
||||||
fn default() -> Self {
|
fn from(o: AMobjType) -> Self {
|
||||||
Self::Default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&am::ObjType> for AMobjType {
|
|
||||||
fn from(o: &am::ObjType) -> Self {
|
|
||||||
use am::ObjType::*;
|
|
||||||
|
|
||||||
match o {
|
match o {
|
||||||
List => Self::List,
|
AMobjType::Map => am::ObjType::Map,
|
||||||
Map | Table => Self::Map,
|
AMobjType::List => am::ObjType::List,
|
||||||
Text => Self::Text,
|
AMobjType::Text => am::ObjType::Text,
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&AMobjType> for am::ObjType {
|
|
||||||
type Error = am::AutomergeError;
|
|
||||||
|
|
||||||
fn try_from(c_obj_type: &AMobjType) -> Result<Self, Self::Error> {
|
|
||||||
use am::AutomergeError::InvalidValueType;
|
|
||||||
use AMobjType::*;
|
|
||||||
|
|
||||||
match c_obj_type {
|
|
||||||
List => Ok(Self::List),
|
|
||||||
Map => Ok(Self::Map),
|
|
||||||
Text => Ok(Self::Text),
|
|
||||||
_ => Err(InvalidValueType {
|
|
||||||
expected: type_name::<Self>().to_string(),
|
|
||||||
unexpected: type_name::<AMobjType>().to_string(),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
76
automerge-c/src/obj/item.rs
Normal file
76
automerge-c/src/obj/item.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
use crate::obj::AMobjId;
|
||||||
|
use crate::result::AMvalue;
|
||||||
|
|
||||||
|
/// \struct AMobjItem
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief An item in an object.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AMobjItem {
|
||||||
|
/// The object identifier of an item in an object.
|
||||||
|
obj_id: AMobjId,
|
||||||
|
/// The value of an item in an object.
|
||||||
|
value: (am::Value<'static>, RefCell<Option<CString>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMobjItem {
|
||||||
|
pub fn new(value: am::Value<'static>, obj_id: am::ObjId) -> Self {
|
||||||
|
Self {
|
||||||
|
obj_id: AMobjId::new(obj_id),
|
||||||
|
value: (value, Default::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for AMobjItem {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.obj_id == other.obj_id && self.value.0 == other.value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&AMobjItem> for (am::Value<'static>, am::ObjId) {
|
||||||
|
fn from(obj_item: &AMobjItem) -> Self {
|
||||||
|
(obj_item.value.0.clone(), obj_item.obj_id.as_ref().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItem
|
||||||
|
/// \brief Gets the object identifier of an item in an object.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_item A pointer to an `AMobjItem` struct.
|
||||||
|
/// \return A pointer to an `AMobjId` struct.
|
||||||
|
/// \pre \p obj_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// obj_item must be a valid pointer to an AMobjItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemObjId(obj_item: *const AMobjItem) -> *const AMobjId {
|
||||||
|
if let Some(obj_item) = obj_item.as_ref() {
|
||||||
|
&obj_item.obj_id
|
||||||
|
} else {
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItem
|
||||||
|
/// \brief Gets the value of an item in an object.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_item A pointer to an `AMobjItem` struct.
|
||||||
|
/// \return An `AMvalue` struct.
|
||||||
|
/// \pre \p obj_item `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// obj_item must be a valid pointer to an AMobjItem
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemValue<'a>(obj_item: *const AMobjItem) -> AMvalue<'a> {
|
||||||
|
if let Some(obj_item) = obj_item.as_ref() {
|
||||||
|
(&obj_item.value.0, &obj_item.value.1).into()
|
||||||
|
} else {
|
||||||
|
AMvalue::Void
|
||||||
|
}
|
||||||
|
}
|
341
automerge-c/src/obj/items.rs
Normal file
341
automerge-c/src/obj/items.rs
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::obj::item::AMobjItem;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(obj_items: &[AMobjItem], offset: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
len: obj_items.len(),
|
||||||
|
offset,
|
||||||
|
ptr: obj_items.as_ptr() as *const c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMobjItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) };
|
||||||
|
let value = &slice[self.get_index()];
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[AMobjItem] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) };
|
||||||
|
Some(&slice[self.get_index()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMobjItems
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of object items.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMobjItems {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMobjItems {
|
||||||
|
pub fn new(obj_items: &[AMobjItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(obj_items, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[AMobjItem]> for AMobjItems {
|
||||||
|
fn as_ref(&self) -> &[AMobjItem] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const AMobjItem, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMobjItems {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Advances an iterator over a sequence of object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsAdvance(obj_items: *mut AMobjItems, n: isize) {
|
||||||
|
if let Some(obj_items) = obj_items.as_mut() {
|
||||||
|
obj_items.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Tests the equality of two sequences of object items underlying a
|
||||||
|
/// pair of iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_items1 A pointer to an `AMobjItems` struct.
|
||||||
|
/// \param[in] obj_items2 A pointer to an `AMobjItems` struct.
|
||||||
|
/// \return `true` if \p obj_items1 `==` \p obj_items2 and `false` otherwise.
|
||||||
|
/// \pre \p obj_items1 `!= NULL`.
|
||||||
|
/// \pre \p obj_items2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items1 must be a valid pointer to an AMobjItems
|
||||||
|
/// obj_items2 must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsEqual(
|
||||||
|
obj_items1: *const AMobjItems,
|
||||||
|
obj_items2: *const AMobjItems,
|
||||||
|
) -> bool {
|
||||||
|
match (obj_items1.as_ref(), obj_items2.as_ref()) {
|
||||||
|
(Some(obj_items1), Some(obj_items2)) => obj_items1.as_ref() == obj_items2.as_ref(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Gets the object item at the current position of an iterator over a
|
||||||
|
/// sequence of object items and then advances it by at most \p |n|
|
||||||
|
/// positions where the sign of \p n is relative to the iterator's
|
||||||
|
/// direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items
|
||||||
|
/// was previously advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsNext(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem {
|
||||||
|
if let Some(obj_items) = obj_items.as_mut() {
|
||||||
|
if let Some(obj_item) = obj_items.next(n) {
|
||||||
|
return obj_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Advances an iterator over a sequence of object items by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the object item at its new
|
||||||
|
/// position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items
|
||||||
|
/// is presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsPrev(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem {
|
||||||
|
if let Some(obj_items) = obj_items.as_mut() {
|
||||||
|
if let Some(obj_item) = obj_items.prev(n) {
|
||||||
|
return obj_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Gets the size of the sequence of object items underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \return The count of values in \p obj_items.
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsSize(obj_items: *const AMobjItems) -> usize {
|
||||||
|
if let Some(obj_items) = obj_items.as_ref() {
|
||||||
|
obj_items.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Creates an iterator over the same sequence of object items as the
|
||||||
|
/// given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \return An `AMobjItems` struct
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsReversed(obj_items: *const AMobjItems) -> AMobjItems {
|
||||||
|
if let Some(obj_items) = obj_items.as_ref() {
|
||||||
|
obj_items.reversed()
|
||||||
|
} else {
|
||||||
|
AMobjItems::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMobjItems
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of object items as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] obj_items A pointer to an `AMobjItems` struct.
|
||||||
|
/// \return An `AMobjItems` struct
|
||||||
|
/// \pre \p obj_items `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// obj_items must be a valid pointer to an AMobjItems
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMobjItemsRewound(obj_items: *const AMobjItems) -> AMobjItems {
|
||||||
|
if let Some(obj_items) = obj_items.as_ref() {
|
||||||
|
obj_items.rewound()
|
||||||
|
} else {
|
||||||
|
AMobjItems::default()
|
||||||
|
}
|
||||||
|
}
|
914
automerge-c/src/result.rs
Normal file
914
automerge-c/src/result.rs
Normal file
|
@ -0,0 +1,914 @@
|
||||||
|
use automerge as am;
|
||||||
|
use libc::strcmp;
|
||||||
|
use smol_str::SmolStr;
|
||||||
|
use std::any::type_name;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::ops::{Range, RangeFrom, RangeFull, RangeTo};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use crate::actor_id::AMactorId;
|
||||||
|
use crate::byte_span::AMbyteSpan;
|
||||||
|
use crate::change::AMchange;
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
use crate::changes::AMchanges;
|
||||||
|
use crate::doc::list::{item::AMlistItem, items::AMlistItems};
|
||||||
|
use crate::doc::map::{item::AMmapItem, items::AMmapItems};
|
||||||
|
use crate::doc::utils::to_str;
|
||||||
|
use crate::doc::AMdoc;
|
||||||
|
use crate::obj::item::AMobjItem;
|
||||||
|
use crate::obj::items::AMobjItems;
|
||||||
|
use crate::obj::AMobjId;
|
||||||
|
use crate::strs::AMstrs;
|
||||||
|
use crate::sync::{AMsyncMessage, AMsyncState};
|
||||||
|
|
||||||
|
/// \struct AMvalue
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A discriminated union of value type variants for a result.
|
||||||
|
///
|
||||||
|
/// \enum AMvalueVariant
|
||||||
|
/// \brief A value type discriminant.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::actor_id
|
||||||
|
/// An actor identifier as a pointer to an `AMactorId` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::boolean
|
||||||
|
/// A boolean.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::bytes
|
||||||
|
/// A sequence of bytes as an `AMbyteSpan` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::change_hashes
|
||||||
|
/// A sequence of change hashes as an `AMchangeHashes` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::changes
|
||||||
|
/// A sequence of changes as an `AMchanges` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::counter
|
||||||
|
/// A CRDT counter.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::doc
|
||||||
|
/// A document as a pointer to an `AMdoc` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::f64
|
||||||
|
/// A 64-bit float.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::int_
|
||||||
|
/// A 64-bit signed integer.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::list_items
|
||||||
|
/// A sequence of list object items as an `AMlistItems` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::map_items
|
||||||
|
/// A sequence of map object items as an `AMmapItems` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::obj_id
|
||||||
|
/// An object identifier as a pointer to an `AMobjId` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::obj_items
|
||||||
|
/// A sequence of object items as an `AMobjItems` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::str
|
||||||
|
/// A UTF-8 string.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::strs
|
||||||
|
/// A sequence of UTF-8 strings as an `AMstrs` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::sync_message
|
||||||
|
/// A synchronization message as a pointer to an `AMsyncMessage` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::sync_state
|
||||||
|
/// A synchronization state as a pointer to an `AMsyncState` struct.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::tag
|
||||||
|
/// The variant discriminator.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::timestamp
|
||||||
|
/// A Lamport timestamp.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::uint
|
||||||
|
/// A 64-bit unsigned integer.
|
||||||
|
///
|
||||||
|
/// \var AMvalue::unknown
|
||||||
|
/// A value of unknown type as an `AMunknownValue` struct.
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AMvalue<'a> {
|
||||||
|
/// A void variant.
|
||||||
|
/// \note This tag is unalphabetized so that a zeroed struct will have it.
|
||||||
|
Void,
|
||||||
|
/// An actor identifier variant.
|
||||||
|
ActorId(&'a AMactorId),
|
||||||
|
/// A boolean variant.
|
||||||
|
Boolean(bool),
|
||||||
|
/// A byte array variant.
|
||||||
|
Bytes(AMbyteSpan),
|
||||||
|
/// A change hashes variant.
|
||||||
|
ChangeHashes(AMchangeHashes),
|
||||||
|
/// A changes variant.
|
||||||
|
Changes(AMchanges),
|
||||||
|
/// A CRDT counter variant.
|
||||||
|
Counter(i64),
|
||||||
|
/// A document variant.
|
||||||
|
Doc(*mut AMdoc),
|
||||||
|
/// A 64-bit float variant.
|
||||||
|
F64(f64),
|
||||||
|
/// A 64-bit signed integer variant.
|
||||||
|
Int(i64),
|
||||||
|
/// A list items variant.
|
||||||
|
ListItems(AMlistItems),
|
||||||
|
/// A map items variant.
|
||||||
|
MapItems(AMmapItems),
|
||||||
|
/// A null variant.
|
||||||
|
Null,
|
||||||
|
/// An object identifier variant.
|
||||||
|
ObjId(&'a AMobjId),
|
||||||
|
/// An object items variant.
|
||||||
|
ObjItems(AMobjItems),
|
||||||
|
/// A UTF-8 string variant.
|
||||||
|
Str(*const libc::c_char),
|
||||||
|
/// A UTF-8 strings variant.
|
||||||
|
Strs(AMstrs),
|
||||||
|
/// A synchronization message variant.
|
||||||
|
SyncMessage(&'a AMsyncMessage),
|
||||||
|
/// A synchronization state variant.
|
||||||
|
SyncState(&'a mut AMsyncState),
|
||||||
|
/// A Lamport timestamp variant.
|
||||||
|
Timestamp(i64),
|
||||||
|
/// A 64-bit unsigned integer variant.
|
||||||
|
Uint(u64),
|
||||||
|
/// An unknown type of scalar value variant.
|
||||||
|
Unknown(AMunknownValue),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq for AMvalue<'a> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
use AMvalue::*;
|
||||||
|
|
||||||
|
match (self, other) {
|
||||||
|
(ActorId(lhs), ActorId(rhs)) => *lhs == *rhs,
|
||||||
|
(Boolean(lhs), Boolean(rhs)) => lhs == rhs,
|
||||||
|
(Bytes(lhs), Bytes(rhs)) => lhs == rhs,
|
||||||
|
(ChangeHashes(lhs), ChangeHashes(rhs)) => lhs == rhs,
|
||||||
|
(Changes(lhs), Changes(rhs)) => lhs == rhs,
|
||||||
|
(Counter(lhs), Counter(rhs)) => lhs == rhs,
|
||||||
|
(Doc(lhs), Doc(rhs)) => *lhs == *rhs,
|
||||||
|
(F64(lhs), F64(rhs)) => lhs == rhs,
|
||||||
|
(Int(lhs), Int(rhs)) => lhs == rhs,
|
||||||
|
(ListItems(lhs), ListItems(rhs)) => lhs == rhs,
|
||||||
|
(MapItems(lhs), MapItems(rhs)) => lhs == rhs,
|
||||||
|
(ObjId(lhs), ObjId(rhs)) => *lhs == *rhs,
|
||||||
|
(ObjItems(lhs), ObjItems(rhs)) => lhs == rhs,
|
||||||
|
(Str(lhs), Str(rhs)) => unsafe { strcmp(*lhs, *rhs) == 0 },
|
||||||
|
(Strs(lhs), Strs(rhs)) => lhs == rhs,
|
||||||
|
(SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs,
|
||||||
|
(SyncState(lhs), SyncState(rhs)) => *lhs == *rhs,
|
||||||
|
(Timestamp(lhs), Timestamp(rhs)) => lhs == rhs,
|
||||||
|
(Uint(lhs), Uint(rhs)) => lhs == rhs,
|
||||||
|
(Unknown(lhs), Unknown(rhs)) => lhs == rhs,
|
||||||
|
(Null, Null) | (Void, Void) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(&am::Value<'_>, &RefCell<Option<CString>>)> for AMvalue<'_> {
|
||||||
|
fn from((value, c_str): (&am::Value<'_>, &RefCell<Option<CString>>)) -> Self {
|
||||||
|
match value {
|
||||||
|
am::Value::Scalar(scalar) => match scalar.as_ref() {
|
||||||
|
am::ScalarValue::Boolean(flag) => AMvalue::Boolean(*flag),
|
||||||
|
am::ScalarValue::Bytes(bytes) => AMvalue::Bytes(bytes.as_slice().into()),
|
||||||
|
am::ScalarValue::Counter(counter) => AMvalue::Counter(counter.into()),
|
||||||
|
am::ScalarValue::F64(float) => AMvalue::F64(*float),
|
||||||
|
am::ScalarValue::Int(int) => AMvalue::Int(*int),
|
||||||
|
am::ScalarValue::Null => AMvalue::Null,
|
||||||
|
am::ScalarValue::Str(smol_str) => {
|
||||||
|
let mut c_str = c_str.borrow_mut();
|
||||||
|
AMvalue::Str(match c_str.as_mut() {
|
||||||
|
None => {
|
||||||
|
let value_str = CString::new(smol_str.to_string()).unwrap();
|
||||||
|
c_str.insert(value_str).as_ptr()
|
||||||
|
}
|
||||||
|
Some(value_str) => value_str.as_ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
am::ScalarValue::Timestamp(timestamp) => AMvalue::Timestamp(*timestamp),
|
||||||
|
am::ScalarValue::Uint(uint) => AMvalue::Uint(*uint),
|
||||||
|
am::ScalarValue::Unknown { bytes, type_code } => AMvalue::Unknown(AMunknownValue {
|
||||||
|
bytes: bytes.as_slice().into(),
|
||||||
|
type_code: *type_code,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// \todo Confirm that an object variant should be ignored
|
||||||
|
// when there's no object ID variant.
|
||||||
|
am::Value::Object(_) => AMvalue::Void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&AMvalue<'_>> for u8 {
|
||||||
|
fn from(value: &AMvalue) -> Self {
|
||||||
|
use AMvalue::*;
|
||||||
|
|
||||||
|
// \warning These numbers must correspond to the order in which the
|
||||||
|
// variants of an AMvalue are declared within it.
|
||||||
|
match value {
|
||||||
|
ActorId(_) => 1,
|
||||||
|
Boolean(_) => 2,
|
||||||
|
Bytes(_) => 3,
|
||||||
|
ChangeHashes(_) => 4,
|
||||||
|
Changes(_) => 5,
|
||||||
|
Counter(_) => 6,
|
||||||
|
Doc(_) => 7,
|
||||||
|
F64(_) => 8,
|
||||||
|
Int(_) => 9,
|
||||||
|
ListItems(_) => 10,
|
||||||
|
MapItems(_) => 11,
|
||||||
|
Null => 12,
|
||||||
|
ObjId(_) => 13,
|
||||||
|
ObjItems(_) => 14,
|
||||||
|
Str(_) => 15,
|
||||||
|
Strs(_) => 16,
|
||||||
|
SyncMessage(_) => 17,
|
||||||
|
SyncState(_) => 18,
|
||||||
|
Timestamp(_) => 19,
|
||||||
|
Uint(_) => 20,
|
||||||
|
Unknown(..) => 21,
|
||||||
|
Void => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&AMvalue<'_>> for am::ScalarValue {
|
||||||
|
type Error = am::AutomergeError;
|
||||||
|
|
||||||
|
fn try_from(c_value: &AMvalue) -> Result<Self, Self::Error> {
|
||||||
|
use am::AutomergeError::InvalidValueType;
|
||||||
|
use AMvalue::*;
|
||||||
|
|
||||||
|
let expected = type_name::<am::ScalarValue>().to_string();
|
||||||
|
match c_value {
|
||||||
|
Boolean(b) => Ok(am::ScalarValue::Boolean(*b)),
|
||||||
|
Bytes(span) => {
|
||||||
|
let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) };
|
||||||
|
Ok(am::ScalarValue::Bytes(slice.to_vec()))
|
||||||
|
}
|
||||||
|
Counter(c) => Ok(am::ScalarValue::Counter(c.into())),
|
||||||
|
F64(f) => Ok(am::ScalarValue::F64(*f)),
|
||||||
|
Int(i) => Ok(am::ScalarValue::Int(*i)),
|
||||||
|
Str(c_str) => {
|
||||||
|
let smol_str = unsafe { SmolStr::new(to_str(*c_str)) };
|
||||||
|
Ok(am::ScalarValue::Str(smol_str))
|
||||||
|
}
|
||||||
|
Timestamp(t) => Ok(am::ScalarValue::Timestamp(*t)),
|
||||||
|
Uint(u) => Ok(am::ScalarValue::Uint(*u)),
|
||||||
|
Null => Ok(am::ScalarValue::Null),
|
||||||
|
Unknown(AMunknownValue { bytes, type_code }) => {
|
||||||
|
let slice = unsafe { std::slice::from_raw_parts(bytes.src, bytes.count) };
|
||||||
|
Ok(am::ScalarValue::Unknown {
|
||||||
|
bytes: slice.to_vec(),
|
||||||
|
type_code: *type_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ActorId(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMactorId>().to_string(),
|
||||||
|
}),
|
||||||
|
ChangeHashes(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMchangeHashes>().to_string(),
|
||||||
|
}),
|
||||||
|
Changes(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMchanges>().to_string(),
|
||||||
|
}),
|
||||||
|
Doc(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMdoc>().to_string(),
|
||||||
|
}),
|
||||||
|
ListItems(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMlistItems>().to_string(),
|
||||||
|
}),
|
||||||
|
MapItems(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMmapItems>().to_string(),
|
||||||
|
}),
|
||||||
|
ObjId(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMobjId>().to_string(),
|
||||||
|
}),
|
||||||
|
ObjItems(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMobjItems>().to_string(),
|
||||||
|
}),
|
||||||
|
Strs(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMstrs>().to_string(),
|
||||||
|
}),
|
||||||
|
SyncMessage(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMsyncMessage>().to_string(),
|
||||||
|
}),
|
||||||
|
SyncState(_) => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<AMsyncState>().to_string(),
|
||||||
|
}),
|
||||||
|
Void => Err(InvalidValueType {
|
||||||
|
expected,
|
||||||
|
unexpected: type_name::<()>().to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMvalue
|
||||||
|
/// \brief Tests the equality of two values.
|
||||||
|
///
|
||||||
|
/// \param[in] value1 A pointer to an `AMvalue` struct.
|
||||||
|
/// \param[in] value2 A pointer to an `AMvalue` struct.
|
||||||
|
/// \return `true` if \p value1 `==` \p value2 and `false` otherwise.
|
||||||
|
/// \pre \p value1 `!= NULL`.
|
||||||
|
/// \pre \p value2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// value1 must be a valid AMvalue pointer
|
||||||
|
/// value2 must be a valid AMvalue pointer
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMvalueEqual(value1: *const AMvalue, value2: *const AMvalue) -> bool {
|
||||||
|
match (value1.as_ref(), value2.as_ref()) {
|
||||||
|
(Some(value1), Some(value2)) => *value1 == *value2,
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMresult
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A discriminated union of result variants.
|
||||||
|
pub enum AMresult {
|
||||||
|
ActorId(am::ActorId, Option<AMactorId>),
|
||||||
|
ChangeHashes(Vec<am::ChangeHash>),
|
||||||
|
Changes(Vec<am::Change>, Option<BTreeMap<usize, AMchange>>),
|
||||||
|
Doc(Box<AMdoc>),
|
||||||
|
Error(CString),
|
||||||
|
ListItems(Vec<AMlistItem>),
|
||||||
|
MapItems(Vec<AMmapItem>),
|
||||||
|
ObjId(AMobjId),
|
||||||
|
ObjItems(Vec<AMobjItem>),
|
||||||
|
String(CString),
|
||||||
|
Strings(Vec<CString>),
|
||||||
|
SyncMessage(AMsyncMessage),
|
||||||
|
SyncState(Box<AMsyncState>),
|
||||||
|
Value(am::Value<'static>, RefCell<Option<CString>>),
|
||||||
|
Void,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMresult {
|
||||||
|
pub(crate) fn err(s: &str) -> Self {
|
||||||
|
AMresult::Error(CString::new(s).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::AutoCommit> for AMresult {
|
||||||
|
fn from(auto_commit: am::AutoCommit) -> Self {
|
||||||
|
AMresult::Doc(Box::new(AMdoc::new(auto_commit)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::ChangeHash> for AMresult {
|
||||||
|
fn from(change_hash: am::ChangeHash) -> Self {
|
||||||
|
AMresult::ChangeHashes(vec![change_hash])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::Keys<'_, '_>> for AMresult {
|
||||||
|
fn from(keys: am::Keys<'_, '_>) -> Self {
|
||||||
|
let cstrings: Vec<CString> = keys.map(|s| CString::new(s).unwrap()).collect();
|
||||||
|
AMresult::Strings(cstrings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::KeysAt<'_, '_>> for AMresult {
|
||||||
|
fn from(keys: am::KeysAt<'_, '_>) -> Self {
|
||||||
|
let cstrings: Vec<CString> = keys.map(|s| CString::new(s).unwrap()).collect();
|
||||||
|
AMresult::Strings(cstrings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::ListRange<'static, Range<usize>>> for AMresult {
|
||||||
|
fn from(list_range: am::ListRange<'static, Range<usize>>) -> Self {
|
||||||
|
AMresult::ListItems(
|
||||||
|
list_range
|
||||||
|
.map(|(i, v, o)| AMlistItem::new(i, v.clone(), o))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::ListRangeAt<'static, Range<usize>>> for AMresult {
|
||||||
|
fn from(list_range: am::ListRangeAt<'static, Range<usize>>) -> Self {
|
||||||
|
AMresult::ListItems(
|
||||||
|
list_range
|
||||||
|
.map(|(i, v, o)| AMlistItem::new(i, v.clone(), o))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRange<'static, Range<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRange<'static, Range<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRangeAt<'static, Range<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRangeAt<'static, Range<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRange<'static, RangeFrom<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRange<'static, RangeFrom<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRangeAt<'static, RangeFrom<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRangeAt<'static, RangeFrom<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRange<'static, RangeFull>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRange<'static, RangeFull>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRangeAt<'static, RangeFull>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRange<'static, RangeTo<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRange<'static, RangeTo<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::MapRangeAt<'static, RangeTo<String>>> for AMresult {
|
||||||
|
fn from(map_range: am::MapRangeAt<'static, RangeTo<String>>) -> Self {
|
||||||
|
let map_items: Vec<AMmapItem> = map_range
|
||||||
|
.map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o))
|
||||||
|
.collect();
|
||||||
|
AMresult::MapItems(map_items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::sync::State> for AMresult {
|
||||||
|
fn from(state: am::sync::State) -> Self {
|
||||||
|
AMresult::SyncState(Box::new(AMsyncState::new(state)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<am::Values<'static>> for AMresult {
|
||||||
|
fn from(pairs: am::Values<'static>) -> Self {
|
||||||
|
AMresult::ObjItems(pairs.map(|(v, o)| AMobjItem::new(v.clone(), o)).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<(am::Value<'static>, am::ObjId)>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<(am::Value<'static>, am::ObjId)>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(pairs) => AMresult::ObjItems(
|
||||||
|
pairs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(v, o)| AMobjItem::new(v, o))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AMresult> for *mut AMresult {
|
||||||
|
fn from(b: AMresult) -> Self {
|
||||||
|
Box::into_raw(Box::new(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<&am::Change>> for AMresult {
|
||||||
|
fn from(maybe: Option<&am::Change>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Some(change) => AMresult::Changes(vec![change.clone()], None),
|
||||||
|
None => AMresult::Void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Option<am::sync::Message>> for AMresult {
|
||||||
|
fn from(maybe: Option<am::sync::Message>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Some(message) => AMresult::SyncMessage(AMsyncMessage::new(message)),
|
||||||
|
None => AMresult::Void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<(), am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<(), am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(()) => AMresult::Void,
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Result<am::ActorId, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::ActorId, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(actor_id) => AMresult::ActorId(actor_id, None),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::ActorId, am::InvalidActorId>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::ActorId, am::InvalidActorId>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(actor_id) => AMresult::ActorId(actor_id, None),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::AutoCommit, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::AutoCommit, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(auto_commit) => AMresult::Doc(Box::new(AMdoc::new(auto_commit))),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::Change, am::LoadChangeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::Change, am::LoadChangeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(change) => AMresult::Changes(vec![change], None),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::ObjId, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::ObjId, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(obj_id) => AMresult::ObjId(AMobjId::new(obj_id)),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::sync::Message, am::sync::ReadMessageError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::sync::Message, am::sync::ReadMessageError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(message) => AMresult::SyncMessage(AMsyncMessage::new(message)),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::sync::State, am::sync::DecodeStateError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::sync::State, am::sync::DecodeStateError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(state) => AMresult::SyncState(Box::new(AMsyncState::new(state))),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<am::Value<'static>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<am::Value<'static>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(value) => AMresult::Value(value, Default::default()),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Option<(am::Value<'static>, am::ObjId)>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Option<(am::Value<'static>, am::ObjId)>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(Some((value, obj_id))) => match value {
|
||||||
|
am::Value::Object(_) => AMresult::ObjId(AMobjId::new(obj_id)),
|
||||||
|
_ => AMresult::Value(value, Default::default()),
|
||||||
|
},
|
||||||
|
Ok(None) => AMresult::Void,
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<String, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<String, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(string) => AMresult::String(CString::new(string).unwrap()),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<usize, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<usize, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(size) => AMresult::Value(am::Value::uint(size as u64), Default::default()),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<am::Change>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<am::Change>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(changes) => AMresult::Changes(changes, None),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<am::Change>, am::LoadChangeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<am::Change>, am::LoadChangeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(changes) => AMresult::Changes(changes, None),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<&am::Change>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<&am::Change>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(changes) => {
|
||||||
|
let changes: Vec<am::Change> =
|
||||||
|
changes.iter().map(|&change| change.clone()).collect();
|
||||||
|
AMresult::Changes(changes, None)
|
||||||
|
}
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<am::ChangeHash>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<am::ChangeHash>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(change_hashes) => AMresult::ChangeHashes(change_hashes),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<am::ChangeHash>, am::InvalidChangeHashSlice>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<am::ChangeHash>, am::InvalidChangeHashSlice>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(change_hashes) => AMresult::ChangeHashes(change_hashes),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Result<Vec<u8>, am::AutomergeError>> for AMresult {
|
||||||
|
fn from(maybe: Result<Vec<u8>, am::AutomergeError>) -> Self {
|
||||||
|
match maybe {
|
||||||
|
Ok(bytes) => AMresult::Value(am::Value::bytes(bytes), Default::default()),
|
||||||
|
Err(e) => AMresult::err(&e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<&am::Change>> for AMresult {
|
||||||
|
fn from(changes: Vec<&am::Change>) -> Self {
|
||||||
|
let changes: Vec<am::Change> = changes.iter().map(|&change| change.clone()).collect();
|
||||||
|
AMresult::Changes(changes, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<am::ChangeHash>> for AMresult {
|
||||||
|
fn from(change_hashes: Vec<am::ChangeHash>) -> Self {
|
||||||
|
AMresult::ChangeHashes(change_hashes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for AMresult {
|
||||||
|
fn from(bytes: Vec<u8>) -> Self {
|
||||||
|
AMresult::Value(am::Value::bytes(bytes), Default::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_result<R: Into<AMresult>>(r: R) -> *mut AMresult {
|
||||||
|
(r.into()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \ingroup enumerations
|
||||||
|
/// \enum AMstatus
|
||||||
|
/// \brief The status of an API call.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AMstatus {
|
||||||
|
/// Success.
|
||||||
|
/// \note This tag is unalphabetized so that `0` indicates success.
|
||||||
|
Ok,
|
||||||
|
/// Failure due to an error.
|
||||||
|
Error,
|
||||||
|
/// Failure due to an invalid result.
|
||||||
|
InvalidResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresult
|
||||||
|
/// \brief Gets a result's error message string.
|
||||||
|
///
|
||||||
|
/// \param[in] result A pointer to an `AMresult` struct.
|
||||||
|
/// \return A UTF-8 string value or `NULL`.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// result must be a valid pointer to an AMresult
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMerrorMessage(result: *const AMresult) -> *const c_char {
|
||||||
|
match result.as_ref() {
|
||||||
|
Some(AMresult::Error(s)) => s.as_ptr(),
|
||||||
|
_ => std::ptr::null::<c_char>(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresult
|
||||||
|
/// \brief Deallocates the storage for a result.
|
||||||
|
///
|
||||||
|
/// \param[in,out] result A pointer to an `AMresult` struct.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// result must be a valid pointer to an AMresult
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMfree(result: *mut AMresult) {
|
||||||
|
if !result.is_null() {
|
||||||
|
let result: AMresult = *Box::from_raw(result);
|
||||||
|
drop(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresult
|
||||||
|
/// \brief Gets the size of a result's value.
|
||||||
|
///
|
||||||
|
/// \param[in] result A pointer to an `AMresult` struct.
|
||||||
|
/// \return The count of values in \p result.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// result must be a valid pointer to an AMresult
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize {
|
||||||
|
if let Some(result) = result.as_ref() {
|
||||||
|
use AMresult::*;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Error(_) | Void => 0,
|
||||||
|
ActorId(_, _)
|
||||||
|
| Doc(_)
|
||||||
|
| ObjId(_)
|
||||||
|
| String(_)
|
||||||
|
| SyncMessage(_)
|
||||||
|
| SyncState(_)
|
||||||
|
| Value(_, _) => 1,
|
||||||
|
ChangeHashes(change_hashes) => change_hashes.len(),
|
||||||
|
Changes(changes, _) => changes.len(),
|
||||||
|
ListItems(list_items) => list_items.len(),
|
||||||
|
MapItems(map_items) => map_items.len(),
|
||||||
|
ObjItems(obj_items) => obj_items.len(),
|
||||||
|
Strings(cstrings) => cstrings.len(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresult
|
||||||
|
/// \brief Gets the status code of a result.
|
||||||
|
///
|
||||||
|
/// \param[in] result A pointer to an `AMresult` struct.
|
||||||
|
/// \return An `AMstatus` enum tag.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// result must be a valid pointer to an AMresult
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMresultStatus(result: *const AMresult) -> AMstatus {
|
||||||
|
match result.as_ref() {
|
||||||
|
Some(AMresult::Error(_)) => AMstatus::Error,
|
||||||
|
None => AMstatus::InvalidResult,
|
||||||
|
_ => AMstatus::Ok,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresult
|
||||||
|
/// \brief Gets a result's value.
|
||||||
|
///
|
||||||
|
/// \param[in] result A pointer to an `AMresult` struct.
|
||||||
|
/// \return An `AMvalue` struct.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// result must be a valid pointer to an AMresult
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMresultValue<'a>(result: *mut AMresult) -> AMvalue<'a> {
|
||||||
|
let mut content = AMvalue::Void;
|
||||||
|
if let Some(result) = result.as_mut() {
|
||||||
|
match result {
|
||||||
|
AMresult::ActorId(actor_id, c_actor_id) => match c_actor_id {
|
||||||
|
None => {
|
||||||
|
content = AMvalue::ActorId(&*c_actor_id.insert(AMactorId::new(&*actor_id)));
|
||||||
|
}
|
||||||
|
Some(c_actor_id) => {
|
||||||
|
content = AMvalue::ActorId(&*c_actor_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AMresult::ChangeHashes(change_hashes) => {
|
||||||
|
content = AMvalue::ChangeHashes(AMchangeHashes::new(change_hashes));
|
||||||
|
}
|
||||||
|
AMresult::Changes(changes, storage) => {
|
||||||
|
content = AMvalue::Changes(AMchanges::new(
|
||||||
|
changes,
|
||||||
|
storage.get_or_insert(BTreeMap::new()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
AMresult::Doc(doc) => content = AMvalue::Doc(&mut **doc),
|
||||||
|
AMresult::Error(_) => {}
|
||||||
|
AMresult::ListItems(list_items) => {
|
||||||
|
content = AMvalue::ListItems(AMlistItems::new(list_items));
|
||||||
|
}
|
||||||
|
AMresult::MapItems(map_items) => {
|
||||||
|
content = AMvalue::MapItems(AMmapItems::new(map_items));
|
||||||
|
}
|
||||||
|
AMresult::ObjId(obj_id) => {
|
||||||
|
content = AMvalue::ObjId(obj_id);
|
||||||
|
}
|
||||||
|
AMresult::ObjItems(obj_items) => {
|
||||||
|
content = AMvalue::ObjItems(AMobjItems::new(obj_items));
|
||||||
|
}
|
||||||
|
AMresult::String(cstring) => content = AMvalue::Str(cstring.as_ptr()),
|
||||||
|
AMresult::Strings(cstrings) => {
|
||||||
|
content = AMvalue::Strs(AMstrs::new(cstrings));
|
||||||
|
}
|
||||||
|
AMresult::SyncMessage(sync_message) => {
|
||||||
|
content = AMvalue::SyncMessage(sync_message);
|
||||||
|
}
|
||||||
|
AMresult::SyncState(sync_state) => {
|
||||||
|
content = AMvalue::SyncState(&mut *sync_state);
|
||||||
|
}
|
||||||
|
AMresult::Value(value, value_str) => {
|
||||||
|
content = (&*value, &*value_str).into();
|
||||||
|
}
|
||||||
|
AMresult::Void => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
content
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMunknownValue
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A value (typically for a `set` operation) whose type is unknown.
|
||||||
|
///
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AMunknownValue {
|
||||||
|
/// The value's raw bytes.
|
||||||
|
bytes: AMbyteSpan,
|
||||||
|
/// The value's encoded type identifier.
|
||||||
|
type_code: u8,
|
||||||
|
}
|
156
automerge-c/src/result_stack.rs
Normal file
156
automerge-c/src/result_stack.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
use crate::result::{AMfree, AMresult, AMresultStatus, AMresultValue, AMstatus, AMvalue};
|
||||||
|
|
||||||
|
/// \struct AMresultStack
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A node in a singly-linked list of result pointers.
|
||||||
|
///
|
||||||
|
/// \note Using this data structure is purely optional because its only purpose
|
||||||
|
/// is to make memory management tolerable for direct usage of this API
|
||||||
|
/// in C, C++ and Objective-C.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AMresultStack {
|
||||||
|
/// A result to be deallocated.
|
||||||
|
pub result: *mut AMresult,
|
||||||
|
/// The next node in the singly-linked list or `NULL`.
|
||||||
|
pub next: *mut AMresultStack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMresultStack {
|
||||||
|
pub fn new(result: *mut AMresult, next: *mut AMresultStack) -> Self {
|
||||||
|
Self { result, next }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresultStack
|
||||||
|
/// \brief Deallocates the storage for a stack of results.
|
||||||
|
///
|
||||||
|
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
/// \return The number of `AMresult` structs freed.
|
||||||
|
/// \pre \p stack `!= NULL`.
|
||||||
|
/// \post `*stack == NULL`.
|
||||||
|
/// \note Calling this function is purely optional because its only purpose is
|
||||||
|
/// to make memory management tolerable for direct usage of this API in
|
||||||
|
/// C, C++ and Objective-C.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// stack must be a valid AMresultStack pointer pointer
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMfreeStack(stack: *mut *mut AMresultStack) -> usize {
|
||||||
|
if stack.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut count: usize = 0;
|
||||||
|
while !(*stack).is_null() {
|
||||||
|
AMfree(AMpop(stack));
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresultStack
|
||||||
|
/// \brief Gets the topmost result from the stack after removing it.
|
||||||
|
///
|
||||||
|
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
/// \return A pointer to an `AMresult` struct or `NULL`.
|
||||||
|
/// \pre \p stack `!= NULL`.
|
||||||
|
/// \post `*stack == NULL`.
|
||||||
|
/// \note Calling this function is purely optional because its only purpose is
|
||||||
|
/// to make memory management tolerable for direct usage of this API in
|
||||||
|
/// C, C++ and Objective-C.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// stack must be a valid AMresultStack pointer pointer
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMpop(stack: *mut *mut AMresultStack) -> *mut AMresult {
|
||||||
|
if stack.is_null() || (*stack).is_null() {
|
||||||
|
return std::ptr::null_mut();
|
||||||
|
}
|
||||||
|
let top = Box::from_raw(*stack);
|
||||||
|
*stack = top.next;
|
||||||
|
let result = top.result;
|
||||||
|
drop(top);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMresultStack
|
||||||
|
/// \brief The prototype of a function to be called when a value matching the
|
||||||
|
/// given discriminant cannot be extracted from the result at the top of
|
||||||
|
/// the given stack.
|
||||||
|
///
|
||||||
|
/// \note Implementing this function is purely optional because its only purpose
|
||||||
|
/// is to make memory management tolerable for direct usage of this API
|
||||||
|
/// in C, C++ and Objective-C.
|
||||||
|
pub type AMpushCallback =
|
||||||
|
Option<extern "C" fn(stack: *mut *mut AMresultStack, discriminant: u8) -> ()>;
|
||||||
|
|
||||||
|
/// \memberof AMresultStack
|
||||||
|
/// \brief Pushes the given result onto the given stack and then either extracts
|
||||||
|
/// a value matching the given discriminant from that result or,
|
||||||
|
/// failing that, calls the given function and gets a void value instead.
|
||||||
|
///
|
||||||
|
/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
/// \param[in] result A pointer to an `AMresult` struct.
|
||||||
|
/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag.
|
||||||
|
/// \param[in] callback A pointer to a function with the same signature as
|
||||||
|
/// `AMpushCallback()` or `NULL`.
|
||||||
|
/// \return An `AMvalue` struct.
|
||||||
|
/// \pre \p stack `!= NULL`.
|
||||||
|
/// \pre \p result `!= NULL`.
|
||||||
|
/// \warning If \p stack `== NULL` then \p result is deallocated in order to
|
||||||
|
/// prevent a memory leak.
|
||||||
|
/// \note Calling this function is purely optional because its only purpose is
|
||||||
|
/// to make memory management tolerable for direct usage of this API in
|
||||||
|
/// C, C++ and Objective-C.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// stack must be a valid AMresultStack pointer pointer
|
||||||
|
/// result must be a valid AMresult pointer
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMpush<'a>(
|
||||||
|
stack: *mut *mut AMresultStack,
|
||||||
|
result: *mut AMresult,
|
||||||
|
discriminant: u8,
|
||||||
|
callback: AMpushCallback,
|
||||||
|
) -> AMvalue<'a> {
|
||||||
|
if stack.is_null() {
|
||||||
|
// There's no stack to push the result onto so it has to be freed in
|
||||||
|
// order to prevent a memory leak.
|
||||||
|
AMfree(result);
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
callback(stack, discriminant);
|
||||||
|
}
|
||||||
|
return AMvalue::Void;
|
||||||
|
} else if result.is_null() {
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
callback(stack, discriminant);
|
||||||
|
}
|
||||||
|
return AMvalue::Void;
|
||||||
|
}
|
||||||
|
// Always push the result onto the stack, even if it's wrong, so that the
|
||||||
|
// given callback can retrieve it.
|
||||||
|
let node = Box::new(AMresultStack::new(result, *stack));
|
||||||
|
let top = Box::into_raw(node);
|
||||||
|
*stack = top;
|
||||||
|
// Test that the result contains a value.
|
||||||
|
match AMresultStatus(result) {
|
||||||
|
AMstatus::Ok => {}
|
||||||
|
_ => {
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
callback(stack, discriminant);
|
||||||
|
}
|
||||||
|
return AMvalue::Void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Test that the result's value matches the given discriminant.
|
||||||
|
let value = AMresultValue(result);
|
||||||
|
if discriminant != u8::from(&value) {
|
||||||
|
if let Some(callback) = callback {
|
||||||
|
callback(stack, discriminant);
|
||||||
|
}
|
||||||
|
return AMvalue::Void;
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
344
automerge-c/src/strs.rs
Normal file
344
automerge-c/src/strs.rs
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::ffi::{c_void, CString};
|
||||||
|
use std::mem::size_of;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(c_strings: &[CString], offset: isize) -> Self {
|
||||||
|
Self {
|
||||||
|
len: c_strings.len(),
|
||||||
|
offset,
|
||||||
|
ptr: c_strings.as_ptr() as *const c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const c_char> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[CString] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const CString, self.len) };
|
||||||
|
let value = slice[self.get_index()].as_ptr();
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const c_char> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[CString] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const CString, self.len) };
|
||||||
|
Some(slice[self.get_index()].as_ptr())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMstrs
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of UTF-8 strings.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMstrs {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMstrs {
|
||||||
|
pub fn new(c_strings: &[CString]) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(c_strings, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const c_char> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const c_char> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[CString]> for AMstrs {
|
||||||
|
fn as_ref(&self) -> &[CString] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const CString, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMstrs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Advances an iterator over a sequence of UTF-8 strings by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsAdvance(strs: *mut AMstrs, n: isize) {
|
||||||
|
if let Some(strs) = strs.as_mut() {
|
||||||
|
strs.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Compares the sequences of UTF-8 strings underlying a pair of
|
||||||
|
/// iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] strs1 A pointer to an `AMstrs` struct.
|
||||||
|
/// \param[in] strs2 A pointer to an `AMstrs` struct.
|
||||||
|
/// \return `-1` if \p strs1 `<` \p strs2, `0` if
|
||||||
|
/// \p strs1 `==` \p strs2 and `1` if
|
||||||
|
/// \p strs1 `>` \p strs2.
|
||||||
|
/// \pre \p strs1 `!= NULL`.
|
||||||
|
/// \pre \p strs2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs1 must be a valid pointer to an AMstrs
|
||||||
|
/// strs2 must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsCmp(strs1: *const AMstrs, strs2: *const AMstrs) -> isize {
|
||||||
|
match (strs1.as_ref(), strs2.as_ref()) {
|
||||||
|
(Some(strs1), Some(strs2)) => match strs1.as_ref().cmp(strs2.as_ref()) {
|
||||||
|
Ordering::Less => -1,
|
||||||
|
Ordering::Equal => 0,
|
||||||
|
Ordering::Greater => 1,
|
||||||
|
},
|
||||||
|
(None, Some(_)) => -1,
|
||||||
|
(Some(_), None) => 1,
|
||||||
|
(None, None) => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Gets the key at the current position of an iterator over a sequence
|
||||||
|
/// of UTF-8 strings and then advances it by at most \p |n| positions
|
||||||
|
/// where the sign of \p n is relative to the iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A UTF-8 string that's `NULL` when \p strs was previously advanced
|
||||||
|
/// past its forward/reverse limit.
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsNext(strs: *mut AMstrs, n: isize) -> *const c_char {
|
||||||
|
if let Some(strs) = strs.as_mut() {
|
||||||
|
if let Some(key) = strs.next(n) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Advances an iterator over a sequence of UTF-8 strings by at most
|
||||||
|
/// \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the key at its new position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A UTF-8 string that's `NULL` when \p strs is presently advanced
|
||||||
|
/// past its forward/reverse limit.
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsPrev(strs: *mut AMstrs, n: isize) -> *const c_char {
|
||||||
|
if let Some(strs) = strs.as_mut() {
|
||||||
|
if let Some(key) = strs.prev(n) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Gets the size of the sequence of UTF-8 strings underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \return The count of values in \p strs.
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsSize(strs: *const AMstrs) -> usize {
|
||||||
|
if let Some(strs) = strs.as_ref() {
|
||||||
|
strs.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Creates an iterator over the same sequence of UTF-8 strings as the
|
||||||
|
/// given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \return An `AMstrs` struct.
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsReversed(strs: *const AMstrs) -> AMstrs {
|
||||||
|
if let Some(strs) = strs.as_ref() {
|
||||||
|
strs.reversed()
|
||||||
|
} else {
|
||||||
|
AMstrs::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMstrs
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of UTF-8 strings as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] strs A pointer to an `AMstrs` struct.
|
||||||
|
/// \return An `AMstrs` struct
|
||||||
|
/// \pre \p strs `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// strs must be a valid pointer to an AMstrs
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMstrsRewound(strs: *const AMstrs) -> AMstrs {
|
||||||
|
if let Some(strs) = strs.as_ref() {
|
||||||
|
strs.rewound()
|
||||||
|
} else {
|
||||||
|
AMstrs::default()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
mod have;
|
mod have;
|
||||||
|
mod haves;
|
||||||
mod message;
|
mod message;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
pub(crate) use have::AMsyncHave;
|
|
||||||
pub(crate) use message::{to_sync_message, AMsyncMessage};
|
pub(crate) use message::{to_sync_message, AMsyncMessage};
|
||||||
pub(crate) use state::AMsyncState;
|
pub(crate) use state::AMsyncState;
|
|
@ -1,23 +1,23 @@
|
||||||
use automerge as am;
|
use automerge as am;
|
||||||
|
|
||||||
use crate::result::{to_result, AMresult};
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
|
||||||
/// \struct AMsyncHave
|
/// \struct AMsyncHave
|
||||||
/// \installed_headerfile
|
/// \installed_headerfile
|
||||||
/// \brief A summary of the changes that the sender of a synchronization
|
/// \brief A summary of the changes that the sender of a synchronization
|
||||||
/// message already has.
|
/// message already has.
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct AMsyncHave(am::sync::Have);
|
pub struct AMsyncHave(*const am::sync::Have);
|
||||||
|
|
||||||
impl AMsyncHave {
|
impl AMsyncHave {
|
||||||
pub fn new(have: am::sync::Have) -> Self {
|
pub fn new(have: &am::sync::Have) -> Self {
|
||||||
Self(have)
|
Self(have)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<am::sync::Have> for AMsyncHave {
|
impl AsRef<am::sync::Have> for AMsyncHave {
|
||||||
fn as_ref(&self) -> &am::sync::Have {
|
fn as_ref(&self) -> &am::sync::Have {
|
||||||
&self.0
|
unsafe { &*self.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,18 +25,17 @@ impl AsRef<am::sync::Have> for AMsyncHave {
|
||||||
/// \brief Gets the heads of the sender.
|
/// \brief Gets the heads of the sender.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_have A pointer to an `AMsyncHave` struct.
|
/// \param[in] sync_have A pointer to an `AMsyncHave` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_have `!= NULL`
|
/// \pre \p sync_have `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_have must be a valid pointer to an AMsyncHave
|
/// sync_have must be a valid pointer to an AMsyncHave
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> AMchangeHashes {
|
||||||
to_result(match sync_have.as_ref() {
|
if let Some(sync_have) = sync_have.as_ref() {
|
||||||
Some(sync_have) => sync_have.as_ref().last_sync.as_slice(),
|
AMchangeHashes::new(&sync_have.as_ref().last_sync)
|
||||||
None => Default::default(),
|
} else {
|
||||||
})
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
}
|
}
|
378
automerge-c/src/sync/haves.rs
Normal file
378
automerge-c/src/sync/haves.rs
Normal file
|
@ -0,0 +1,378 @@
|
||||||
|
use automerge as am;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::size_of;
|
||||||
|
|
||||||
|
use crate::sync::have::AMsyncHave;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Detail {
|
||||||
|
len: usize,
|
||||||
|
offset: isize,
|
||||||
|
ptr: *const c_void,
|
||||||
|
storage: *mut c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \note cbindgen won't propagate the value of a `std::mem::size_of<T>()` call
|
||||||
|
/// (https://github.com/eqrion/cbindgen/issues/252) but it will
|
||||||
|
/// propagate the name of a constant initialized from it so if the
|
||||||
|
/// constant's name is a symbolic representation of the value it can be
|
||||||
|
/// converted into a number by post-processing the header it generated.
|
||||||
|
pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::<Detail>();
|
||||||
|
|
||||||
|
impl Detail {
|
||||||
|
fn new(
|
||||||
|
haves: &[am::sync::Have],
|
||||||
|
offset: isize,
|
||||||
|
storage: &mut BTreeMap<usize, AMsyncHave>,
|
||||||
|
) -> Self {
|
||||||
|
let storage: *mut BTreeMap<usize, AMsyncHave> = storage;
|
||||||
|
Self {
|
||||||
|
len: haves.len(),
|
||||||
|
offset,
|
||||||
|
ptr: haves.as_ptr() as *const c_void,
|
||||||
|
storage: storage as *mut c_void,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset = if self.offset < 0 {
|
||||||
|
// It's reversed.
|
||||||
|
let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN);
|
||||||
|
if unclipped >= 0 {
|
||||||
|
// Clip it to the forward stop.
|
||||||
|
len
|
||||||
|
} else {
|
||||||
|
std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX);
|
||||||
|
if unclipped < 0 {
|
||||||
|
// Clip it to the reverse stop.
|
||||||
|
-(len + 1)
|
||||||
|
} else {
|
||||||
|
std::cmp::max(0, std::cmp::min(unclipped, len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_index(&self) -> usize {
|
||||||
|
(self.offset
|
||||||
|
+ if self.offset < 0 {
|
||||||
|
self.len as isize
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[am::sync::Have] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) };
|
||||||
|
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMsyncHave>) };
|
||||||
|
let index = self.get_index();
|
||||||
|
let value = match storage.get_mut(&index) {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
storage.insert(index, AMsyncHave::new(&slice[index]));
|
||||||
|
storage.get_mut(&index).unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.advance(n);
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stopped(&self) -> bool {
|
||||||
|
let len = self.len as isize;
|
||||||
|
self.offset < -len || self.offset == len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||||
|
self.advance(-n);
|
||||||
|
if self.is_stopped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let slice: &[am::sync::Have] =
|
||||||
|
unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) };
|
||||||
|
let storage = unsafe { &mut *(self.storage as *mut BTreeMap<usize, AMsyncHave>) };
|
||||||
|
let index = self.get_index();
|
||||||
|
Some(match storage.get_mut(&index) {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
storage.insert(index, AMsyncHave::new(&slice[index]));
|
||||||
|
storage.get_mut(&index).unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: -(self.offset + 1),
|
||||||
|
ptr: self.ptr,
|
||||||
|
storage: self.storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
len: self.len,
|
||||||
|
offset: if self.offset < 0 { -1 } else { 0 },
|
||||||
|
ptr: self.ptr,
|
||||||
|
storage: self.storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Detail> for [u8; USIZE_USIZE_USIZE_USIZE_] {
|
||||||
|
fn from(detail: Detail) -> Self {
|
||||||
|
unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
(&detail as *const Detail) as *const u8,
|
||||||
|
USIZE_USIZE_USIZE_USIZE_,
|
||||||
|
)
|
||||||
|
.try_into()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \struct AMsyncHaves
|
||||||
|
/// \installed_headerfile
|
||||||
|
/// \brief A random-access iterator over a sequence of synchronization haves.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Eq, PartialEq)]
|
||||||
|
pub struct AMsyncHaves {
|
||||||
|
/// An implementation detail that is intentionally opaque.
|
||||||
|
/// \warning Modifying \p detail will cause undefined behavior.
|
||||||
|
/// \note The actual size of \p detail will vary by platform, this is just
|
||||||
|
/// the one for the platform this documentation was built on.
|
||||||
|
detail: [u8; USIZE_USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AMsyncHaves {
|
||||||
|
pub fn new(haves: &[am::sync::Have], storage: &mut BTreeMap<usize, AMsyncHave>) -> Self {
|
||||||
|
Self {
|
||||||
|
detail: Detail::new(haves, 0, storage).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, n: isize) {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
detail.len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.next(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> {
|
||||||
|
let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) };
|
||||||
|
detail.prev(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reversed(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.reversed().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rewound(&self) -> Self {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
Self {
|
||||||
|
detail: detail.rewound().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[am::sync::Have]> for AMsyncHaves {
|
||||||
|
fn as_ref(&self) -> &[am::sync::Have] {
|
||||||
|
let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) };
|
||||||
|
unsafe { std::slice::from_raw_parts(detail.ptr as *const am::sync::Have, detail.len) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AMsyncHaves {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detail: [0; USIZE_USIZE_USIZE_USIZE_],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Advances an iterator over a sequence of synchronization haves by at
|
||||||
|
/// most \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesAdvance(sync_haves: *mut AMsyncHaves, n: isize) {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||||
|
sync_haves.advance(n);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Tests the equality of two sequences of synchronization haves
|
||||||
|
/// underlying a pair of iterators.
|
||||||
|
///
|
||||||
|
/// \param[in] sync_haves1 A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \param[in] sync_haves2 A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \return `true` if \p sync_haves1 `==` \p sync_haves2 and `false` otherwise.
|
||||||
|
/// \pre \p sync_haves1 `!= NULL`.
|
||||||
|
/// \pre \p sync_haves2 `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves1 must be a valid pointer to an AMsyncHaves
|
||||||
|
/// sync_haves2 must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesEqual(
|
||||||
|
sync_haves1: *const AMsyncHaves,
|
||||||
|
sync_haves2: *const AMsyncHaves,
|
||||||
|
) -> bool {
|
||||||
|
match (sync_haves1.as_ref(), sync_haves2.as_ref()) {
|
||||||
|
(Some(sync_haves1), Some(sync_haves2)) => sync_haves1.as_ref() == sync_haves2.as_ref(),
|
||||||
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Gets the synchronization have at the current position of an iterator
|
||||||
|
/// over a sequence of synchronization haves and then advances it by at
|
||||||
|
/// most \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction.
|
||||||
|
///
|
||||||
|
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMsyncHave` struct that's `NULL` when
|
||||||
|
/// \p sync_haves was previously advanced past its forward/reverse
|
||||||
|
/// limit.
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesNext(
|
||||||
|
sync_haves: *mut AMsyncHaves,
|
||||||
|
n: isize,
|
||||||
|
) -> *const AMsyncHave {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||||
|
if let Some(sync_have) = sync_haves.next(n) {
|
||||||
|
return sync_have;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Advances an iterator over a sequence of synchronization haves by at
|
||||||
|
/// most \p |n| positions where the sign of \p n is relative to the
|
||||||
|
/// iterator's direction and then gets the synchronization have at its
|
||||||
|
/// new position.
|
||||||
|
///
|
||||||
|
/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum
|
||||||
|
/// number of positions to advance.
|
||||||
|
/// \return A pointer to an `AMsyncHave` struct that's `NULL` when
|
||||||
|
/// \p sync_haves is presently advanced past its forward/reverse limit.
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesPrev(
|
||||||
|
sync_haves: *mut AMsyncHaves,
|
||||||
|
n: isize,
|
||||||
|
) -> *const AMsyncHave {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_mut() {
|
||||||
|
if let Some(sync_have) = sync_haves.prev(n) {
|
||||||
|
return sync_have;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Gets the size of the sequence of synchronization haves underlying an
|
||||||
|
/// iterator.
|
||||||
|
///
|
||||||
|
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \return The count of values in \p sync_haves.
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesSize(sync_haves: *const AMsyncHaves) -> usize {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||||
|
sync_haves.len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Creates an iterator over the same sequence of synchronization haves
|
||||||
|
/// as the given one but with the opposite position and direction.
|
||||||
|
///
|
||||||
|
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \return An `AMsyncHaves` struct
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesReversed(sync_haves: *const AMsyncHaves) -> AMsyncHaves {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||||
|
sync_haves.reversed()
|
||||||
|
} else {
|
||||||
|
AMsyncHaves::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \memberof AMsyncHaves
|
||||||
|
/// \brief Creates an iterator at the starting position over the same sequence
|
||||||
|
/// of synchronization haves as the given one.
|
||||||
|
///
|
||||||
|
/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct.
|
||||||
|
/// \return An `AMsyncHaves` struct
|
||||||
|
/// \pre \p sync_haves `!= NULL`.
|
||||||
|
/// \internal
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
/// sync_haves must be a valid pointer to an AMsyncHaves
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn AMsyncHavesRewound(sync_haves: *const AMsyncHaves) -> AMsyncHaves {
|
||||||
|
if let Some(sync_haves) = sync_haves.as_ref() {
|
||||||
|
sync_haves.rewound()
|
||||||
|
} else {
|
||||||
|
AMsyncHaves::default()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,15 +3,18 @@ use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::change::AMchange;
|
use crate::change::AMchange;
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
|
use crate::changes::AMchanges;
|
||||||
use crate::result::{to_result, AMresult};
|
use crate::result::{to_result, AMresult};
|
||||||
use crate::sync::have::AMsyncHave;
|
use crate::sync::have::AMsyncHave;
|
||||||
|
use crate::sync::haves::AMsyncHaves;
|
||||||
|
|
||||||
macro_rules! to_sync_message {
|
macro_rules! to_sync_message {
|
||||||
($handle:expr) => {{
|
($handle:expr) => {{
|
||||||
let handle = $handle.as_ref();
|
let handle = $handle.as_ref();
|
||||||
match handle {
|
match handle {
|
||||||
Some(b) => b,
|
Some(b) => b,
|
||||||
None => return AMresult::error("Invalid `AMsyncMessage*`").into(),
|
None => return AMresult::err("Invalid AMsyncMessage pointer").into(),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -48,54 +51,55 @@ impl AsRef<am::sync::Message> for AMsyncMessage {
|
||||||
/// \brief Gets the changes for the recipient to apply.
|
/// \brief Gets the changes for the recipient to apply.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items.
|
/// \return An `AMchanges` struct.
|
||||||
/// \pre \p sync_message `!= NULL`
|
/// \pre \p sync_message `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> AMchanges {
|
||||||
to_result(match sync_message.as_ref() {
|
if let Some(sync_message) = sync_message.as_ref() {
|
||||||
Some(sync_message) => sync_message.body.changes.as_slice(),
|
AMchanges::new(
|
||||||
None => Default::default(),
|
&sync_message.body.changes,
|
||||||
})
|
&mut sync_message.changes_storage.borrow_mut(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AMchanges::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncMessage
|
/// \memberof AMsyncMessage
|
||||||
/// \brief Decodes an array of bytes into a synchronization message.
|
/// \brief Decodes a sequence of bytes into a synchronization message.
|
||||||
///
|
///
|
||||||
/// \param[in] src A pointer to an array of bytes.
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
/// \param[in] count The count of bytes to decode from the array pointed to by
|
/// \param[in] count The number of bytes in \p src to decode.
|
||||||
/// \p src.
|
/// \return A pointer to an `AMresult` struct containing an `AMsyncMessage`
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_MESSAGE` item.
|
/// struct.
|
||||||
/// \pre \p src `!= NULL`
|
/// \pre \p src `!= NULL`.
|
||||||
/// \pre `sizeof(`\p src `) > 0`
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
/// \pre \p count `<= sizeof(`\p src `)`
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// in order to prevent a memory leak.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// src must be a byte array of length `>= count`
|
/// src must be a byte array of size `>= count`
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
let data = std::slice::from_raw_parts(src, count);
|
let mut data = Vec::new();
|
||||||
to_result(am::sync::Message::decode(data))
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(am::sync::Message::decode(&data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncMessage
|
/// \memberof AMsyncMessage
|
||||||
/// \brief Encodes a synchronization message as an array of bytes.
|
/// \brief Encodes a synchronization message as a sequence of bytes.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item.
|
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||||
/// \pre \p sync_message `!= NULL`
|
/// an `AMbyteSpan` struct.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// \pre \p sync_message `!= NULL`.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -108,40 +112,41 @@ pub unsafe extern "C" fn AMsyncMessageEncode(sync_message: *const AMsyncMessage)
|
||||||
/// \brief Gets a summary of the changes that the sender already has.
|
/// \brief Gets a summary of the changes that the sender already has.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_SYNC_HAVE` items.
|
/// \return An `AMhaves` struct.
|
||||||
/// \pre \p sync_message `!= NULL`
|
/// \pre \p sync_message `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> AMsyncHaves {
|
||||||
to_result(match sync_message.as_ref() {
|
if let Some(sync_message) = sync_message.as_ref() {
|
||||||
Some(sync_message) => sync_message.as_ref().have.as_slice(),
|
AMsyncHaves::new(
|
||||||
None => Default::default(),
|
&sync_message.as_ref().have,
|
||||||
})
|
&mut sync_message.haves_storage.borrow_mut(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AMsyncHaves::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncMessage
|
/// \memberof AMsyncMessage
|
||||||
/// \brief Gets the heads of the sender.
|
/// \brief Gets the heads of the sender.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_message `!= NULL`
|
/// \pre \p sync_message `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> AMchangeHashes {
|
||||||
to_result(match sync_message.as_ref() {
|
if let Some(sync_message) = sync_message.as_ref() {
|
||||||
Some(sync_message) => sync_message.as_ref().heads.as_slice(),
|
AMchangeHashes::new(&sync_message.as_ref().heads)
|
||||||
None => Default::default(),
|
} else {
|
||||||
})
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncMessage
|
/// \memberof AMsyncMessage
|
||||||
|
@ -149,18 +154,17 @@ pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage)
|
||||||
/// by the recipient.
|
/// by the recipient.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
/// \param[in] sync_message A pointer to an `AMsyncMessage` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_message `!= NULL`
|
/// \pre \p sync_message `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_message must be a valid pointer to an AMsyncMessage
|
/// sync_message must be a valid pointer to an AMsyncMessage
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> AMchangeHashes {
|
||||||
to_result(match sync_message.as_ref() {
|
if let Some(sync_message) = sync_message.as_ref() {
|
||||||
Some(sync_message) => sync_message.as_ref().need.as_slice(),
|
AMchangeHashes::new(&sync_message.as_ref().need)
|
||||||
None => Default::default(),
|
} else {
|
||||||
})
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,15 +2,17 @@ use automerge as am;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use crate::change_hashes::AMchangeHashes;
|
||||||
use crate::result::{to_result, AMresult};
|
use crate::result::{to_result, AMresult};
|
||||||
use crate::sync::have::AMsyncHave;
|
use crate::sync::have::AMsyncHave;
|
||||||
|
use crate::sync::haves::AMsyncHaves;
|
||||||
|
|
||||||
macro_rules! to_sync_state {
|
macro_rules! to_sync_state {
|
||||||
($handle:expr) => {{
|
($handle:expr) => {{
|
||||||
let handle = $handle.as_ref();
|
let handle = $handle.as_ref();
|
||||||
match handle {
|
match handle {
|
||||||
Some(b) => b,
|
Some(b) => b,
|
||||||
None => return AMresult::error("Invalid `AMsyncState*`").into(),
|
None => return AMresult::err("Invalid AMsyncState pointer").into(),
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -54,37 +56,36 @@ impl From<AMsyncState> for *mut AMsyncState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
/// \brief Decodes an array of bytes into a synchronization state.
|
/// \brief Decodes a sequence of bytes into a synchronization state.
|
||||||
///
|
///
|
||||||
/// \param[in] src A pointer to an array of bytes.
|
/// \param[in] src A pointer to an array of bytes.
|
||||||
/// \param[in] count The count of bytes to decode from the array pointed to by
|
/// \param[in] count The number of bytes in \p src to decode.
|
||||||
/// \p src.
|
/// \return A pointer to an `AMresult` struct containing an `AMsyncState`
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item.
|
/// struct.
|
||||||
/// \pre \p src `!= NULL`
|
/// \pre \p src `!= NULL`.
|
||||||
/// \pre `sizeof(`\p src `) > 0`
|
/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
|
||||||
/// \pre \p count `<= sizeof(`\p src `)`
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// in order to prevent a memory leak.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// src must be a byte array of length `>= count`
|
/// src must be a byte array of size `>= count`
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult {
|
||||||
let data = std::slice::from_raw_parts(src, count);
|
let mut data = Vec::new();
|
||||||
to_result(am::sync::State::decode(data))
|
data.extend_from_slice(std::slice::from_raw_parts(src, count));
|
||||||
|
to_result(am::sync::State::decode(&data))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
/// \brief Encodes a synchronization state as an array of bytes.
|
/// \brief Encodes a synchronizaton state as a sequence of bytes.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTE_SPAN` item.
|
/// \return A pointer to an `AMresult` struct containing an array of bytes as
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// an `AMbyteSpan` struct.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
@ -99,9 +100,8 @@ pub unsafe extern "C" fn AMsyncStateEncode(sync_state: *const AMsyncState) -> *m
|
||||||
/// \param[in] sync_state1 A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state1 A pointer to an `AMsyncState` struct.
|
||||||
/// \param[in] sync_state2 A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state2 A pointer to an `AMsyncState` struct.
|
||||||
/// \return `true` if \p sync_state1 `==` \p sync_state2 and `false` otherwise.
|
/// \return `true` if \p sync_state1 `==` \p sync_state2 and `false` otherwise.
|
||||||
/// \pre \p sync_state1 `!= NULL`
|
/// \pre \p sync_state1 `!= NULL`.
|
||||||
/// \pre \p sync_state2 `!= NULL`
|
/// \pre \p sync_state2 `!= NULL`.
|
||||||
/// \post `!(`\p sync_state1 `&&` \p sync_state2 `) -> false`
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// #Safety
|
/// #Safety
|
||||||
|
@ -114,17 +114,18 @@ pub unsafe extern "C" fn AMsyncStateEqual(
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match (sync_state1.as_ref(), sync_state2.as_ref()) {
|
match (sync_state1.as_ref(), sync_state2.as_ref()) {
|
||||||
(Some(sync_state1), Some(sync_state2)) => sync_state1.as_ref() == sync_state2.as_ref(),
|
(Some(sync_state1), Some(sync_state2)) => sync_state1.as_ref() == sync_state2.as_ref(),
|
||||||
(None, None) | (None, Some(_)) | (Some(_), None) => false,
|
(None, Some(_)) | (Some(_), None) | (None, None) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
/// \brief Allocates a new synchronization state and initializes it from
|
/// \brief Allocates a new synchronization state and initializes it with
|
||||||
/// default values.
|
/// defaults.
|
||||||
///
|
///
|
||||||
/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item.
|
/// \return A pointer to an `AMresult` struct containing a pointer to an
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// `AMsyncState` struct.
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
|
||||||
|
/// in order to prevent a memory leak.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
|
pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
|
||||||
to_result(am::sync::State::new())
|
to_result(am::sync::State::new())
|
||||||
|
@ -134,36 +135,40 @@ pub extern "C" fn AMsyncStateInit() -> *mut AMresult {
|
||||||
/// \brief Gets the heads that are shared by both peers.
|
/// \brief Gets the heads that are shared by both peers.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> AMchangeHashes {
|
||||||
let sync_state = to_sync_state!(sync_state);
|
if let Some(sync_state) = sync_state.as_ref() {
|
||||||
to_result(sync_state.as_ref().shared_heads.as_slice())
|
AMchangeHashes::new(&sync_state.as_ref().shared_heads)
|
||||||
|
} else {
|
||||||
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
/// \brief Gets the heads that were last sent by this peer.
|
/// \brief Gets the heads that were last sent by this peer.
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState) -> *mut AMresult {
|
pub unsafe extern "C" fn AMsyncStateLastSentHeads(
|
||||||
let sync_state = to_sync_state!(sync_state);
|
sync_state: *const AMsyncState,
|
||||||
to_result(sync_state.as_ref().last_sent_heads.as_slice())
|
) -> AMchangeHashes {
|
||||||
|
if let Some(sync_state) = sync_state.as_ref() {
|
||||||
|
AMchangeHashes::new(&sync_state.as_ref().last_sent_heads)
|
||||||
|
} else {
|
||||||
|
AMchangeHashes::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
|
@ -171,13 +176,11 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
||||||
/// the returned `AMitems` struct is relevant, `false` otherwise.
|
/// the returned `AMhaves` struct is relevant, `false` otherwise.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_HAVE` items.
|
/// \return An `AMhaves` struct.
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// \pre \p has_value `!= NULL`
|
/// \pre \p has_value `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
/// \internal
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
//// \internal
|
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
|
@ -186,15 +189,15 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState
|
||||||
pub unsafe extern "C" fn AMsyncStateTheirHaves(
|
pub unsafe extern "C" fn AMsyncStateTheirHaves(
|
||||||
sync_state: *const AMsyncState,
|
sync_state: *const AMsyncState,
|
||||||
has_value: *mut bool,
|
has_value: *mut bool,
|
||||||
) -> *mut AMresult {
|
) -> AMsyncHaves {
|
||||||
if let Some(sync_state) = sync_state.as_ref() {
|
if let Some(sync_state) = sync_state.as_ref() {
|
||||||
if let Some(haves) = &sync_state.as_ref().their_have {
|
if let Some(haves) = &sync_state.as_ref().their_have {
|
||||||
*has_value = true;
|
*has_value = true;
|
||||||
return to_result(haves.as_slice());
|
return AMsyncHaves::new(haves, &mut sync_state.their_haves_storage.borrow_mut());
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
*has_value = false;
|
*has_value = false;
|
||||||
to_result(Vec::<am::sync::Have>::new())
|
AMsyncHaves::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
|
@ -202,31 +205,29 @@ pub unsafe extern "C" fn AMsyncStateTheirHaves(
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
||||||
/// the returned `AMitems` struct is relevant, `false`
|
/// the returned `AMchangeHashes` struct is relevant, `false`
|
||||||
/// otherwise.
|
/// otherwise.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// \pre \p has_value `!= NULL`
|
/// \pre \p has_value `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
/// has_value must be a valid pointer to a bool
|
/// has_value must be a valid pointer to a bool.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncStateTheirHeads(
|
pub unsafe extern "C" fn AMsyncStateTheirHeads(
|
||||||
sync_state: *const AMsyncState,
|
sync_state: *const AMsyncState,
|
||||||
has_value: *mut bool,
|
has_value: *mut bool,
|
||||||
) -> *mut AMresult {
|
) -> AMchangeHashes {
|
||||||
if let Some(sync_state) = sync_state.as_ref() {
|
if let Some(sync_state) = sync_state.as_ref() {
|
||||||
if let Some(change_hashes) = &sync_state.as_ref().their_heads {
|
if let Some(change_hashes) = &sync_state.as_ref().their_heads {
|
||||||
*has_value = true;
|
*has_value = true;
|
||||||
return to_result(change_hashes.as_slice());
|
return AMchangeHashes::new(change_hashes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*has_value = false;
|
*has_value = false;
|
||||||
to_result(Vec::<am::ChangeHash>::new())
|
AMchangeHashes::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// \memberof AMsyncState
|
/// \memberof AMsyncState
|
||||||
|
@ -234,29 +235,27 @@ pub unsafe extern "C" fn AMsyncStateTheirHeads(
|
||||||
///
|
///
|
||||||
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
/// \param[in] sync_state A pointer to an `AMsyncState` struct.
|
||||||
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
/// \param[out] has_value A pointer to a boolean flag that is set to `true` if
|
||||||
/// the returned `AMitems` struct is relevant, `false`
|
/// the returned `AMchangeHashes` struct is relevant, `false`
|
||||||
/// otherwise.
|
/// otherwise.
|
||||||
/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items.
|
/// \return An `AMchangeHashes` struct.
|
||||||
/// \pre \p sync_state `!= NULL`
|
/// \pre \p sync_state `!= NULL`.
|
||||||
/// \pre \p has_value `!= NULL`
|
/// \pre \p has_value `!= NULL`.
|
||||||
/// \warning The returned `AMresult` struct pointer must be passed to
|
|
||||||
/// `AMresultFree()` in order to avoid a memory leak.
|
|
||||||
/// \internal
|
/// \internal
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// sync_state must be a valid pointer to an AMsyncState
|
/// sync_state must be a valid pointer to an AMsyncState
|
||||||
/// has_value must be a valid pointer to a bool
|
/// has_value must be a valid pointer to a bool.
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn AMsyncStateTheirNeeds(
|
pub unsafe extern "C" fn AMsyncStateTheirNeeds(
|
||||||
sync_state: *const AMsyncState,
|
sync_state: *const AMsyncState,
|
||||||
has_value: *mut bool,
|
has_value: *mut bool,
|
||||||
) -> *mut AMresult {
|
) -> AMchangeHashes {
|
||||||
if let Some(sync_state) = sync_state.as_ref() {
|
if let Some(sync_state) = sync_state.as_ref() {
|
||||||
if let Some(change_hashes) = &sync_state.as_ref().their_need {
|
if let Some(change_hashes) = &sync_state.as_ref().their_need {
|
||||||
*has_value = true;
|
*has_value = true;
|
||||||
return to_result(change_hashes.as_slice());
|
return AMchangeHashes::new(change_hashes);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*has_value = false;
|
*has_value = false;
|
||||||
to_result(Vec::<am::ChangeHash>::new())
|
AMchangeHashes::default()
|
||||||
}
|
}
|
57
automerge-c/test/CMakeLists.txt
Normal file
57
automerge-c/test/CMakeLists.txt
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||||
|
|
||||||
|
find_package(cmocka REQUIRED)
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
test_${LIBRARY_NAME}
|
||||||
|
actor_id_tests.c
|
||||||
|
doc_tests.c
|
||||||
|
group_state.c
|
||||||
|
list_tests.c
|
||||||
|
macro_utils.c
|
||||||
|
main.c
|
||||||
|
map_tests.c
|
||||||
|
stack_utils.c
|
||||||
|
str_utils.c
|
||||||
|
ported_wasm/basic_tests.c
|
||||||
|
ported_wasm/suite.c
|
||||||
|
ported_wasm/sync_tests.c
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(test_${LIBRARY_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||||
|
|
||||||
|
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
|
||||||
|
# contain a non-existent path so its build-time include directory
|
||||||
|
# must be specified for all of its dependent targets instead.
|
||||||
|
target_include_directories(
|
||||||
|
test_${LIBRARY_NAME}
|
||||||
|
PRIVATE "$<BUILD_INTERFACE:${CBINDGEN_INCLUDEDIR}>"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(test_${LIBRARY_NAME} PRIVATE cmocka ${LIBRARY_NAME})
|
||||||
|
|
||||||
|
add_dependencies(test_${LIBRARY_NAME} ${LIBRARY_NAME}_artifacts)
|
||||||
|
|
||||||
|
if(BUILD_SHARED_LIBS AND WIN32)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET test_${LIBRARY_NAME}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
COMMENT "Copying the DLL built by Cargo into the test directory..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME test_${LIBRARY_NAME} COMMAND test_${LIBRARY_NAME})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET test_${LIBRARY_NAME}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_CTEST_COMMAND} --config $<CONFIG> --output-on-failure
|
||||||
|
COMMENT
|
||||||
|
"Running the test(s)..."
|
||||||
|
VERBATIM
|
||||||
|
)
|
105
automerge-c/test/actor_id_tests.c
Normal file
105
automerge-c/test/actor_id_tests.c
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include <math.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* third-party */
|
||||||
|
#include <cmocka.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
#include "str_utils.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* src;
|
||||||
|
char const* str;
|
||||||
|
size_t count;
|
||||||
|
} GroupState;
|
||||||
|
|
||||||
|
static int group_setup(void** state) {
|
||||||
|
GroupState* group_state = test_calloc(1, sizeof(GroupState));
|
||||||
|
group_state->str = "000102030405060708090a0b0c0d0e0f";
|
||||||
|
group_state->count = strlen(group_state->str) / 2;
|
||||||
|
group_state->src = test_malloc(group_state->count);
|
||||||
|
hex_to_bytes(group_state->str, group_state->src, group_state->count);
|
||||||
|
*state = group_state;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int group_teardown(void** state) {
|
||||||
|
GroupState* group_state = *state;
|
||||||
|
test_free(group_state->src);
|
||||||
|
test_free(group_state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMactorIdInit() {
|
||||||
|
AMresult* prior_result = NULL;
|
||||||
|
AMbyteSpan prior_bytes;
|
||||||
|
char const* prior_str = NULL;
|
||||||
|
AMresult* result = NULL;
|
||||||
|
for (size_t i = 0; i != 11; ++i) {
|
||||||
|
result = AMactorIdInit();
|
||||||
|
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||||
|
fail_msg("%s", AMerrorMessage(result));
|
||||||
|
}
|
||||||
|
assert_int_equal(AMresultSize(result), 1);
|
||||||
|
AMvalue const value = AMresultValue(result);
|
||||||
|
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||||
|
AMbyteSpan const bytes = AMactorIdBytes(value.actor_id);
|
||||||
|
char const* const str = AMactorIdStr(value.actor_id);
|
||||||
|
if (prior_result) {
|
||||||
|
size_t const min_count = fmax(bytes.count, prior_bytes.count);
|
||||||
|
assert_memory_not_equal(bytes.src, prior_bytes.src, min_count);
|
||||||
|
assert_string_not_equal(str, prior_str);
|
||||||
|
AMfree(prior_result);
|
||||||
|
}
|
||||||
|
prior_result = result;
|
||||||
|
prior_bytes = bytes;
|
||||||
|
prior_str = str;
|
||||||
|
}
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMactorIdInitBytes(void **state) {
|
||||||
|
GroupState* group_state = *state;
|
||||||
|
AMresult* const result = AMactorIdInitBytes(group_state->src, group_state->count);
|
||||||
|
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||||
|
fail_msg("%s", AMerrorMessage(result));
|
||||||
|
}
|
||||||
|
assert_int_equal(AMresultSize(result), 1);
|
||||||
|
AMvalue const value = AMresultValue(result);
|
||||||
|
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||||
|
AMbyteSpan const bytes = AMactorIdBytes(value.actor_id);
|
||||||
|
assert_int_equal(bytes.count, group_state->count);
|
||||||
|
assert_memory_equal(bytes.src, group_state->src, bytes.count);
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMactorIdInitStr(void **state) {
|
||||||
|
GroupState* group_state = *state;
|
||||||
|
AMresult* const result = AMactorIdInitStr(group_state->str);
|
||||||
|
if (AMresultStatus(result) != AM_STATUS_OK) {
|
||||||
|
fail_msg("%s", AMerrorMessage(result));
|
||||||
|
}
|
||||||
|
assert_int_equal(AMresultSize(result), 1);
|
||||||
|
AMvalue const value = AMresultValue(result);
|
||||||
|
assert_int_equal(value.tag, AM_VALUE_ACTOR_ID);
|
||||||
|
char const* const str = AMactorIdStr(value.actor_id);
|
||||||
|
assert_int_equal(strlen(str), group_state->count * 2);
|
||||||
|
assert_string_equal(str, group_state->str);
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_actor_id_tests(void) {
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_AMactorIdInit),
|
||||||
|
cmocka_unit_test(test_AMactorIdInitBytes),
|
||||||
|
cmocka_unit_test(test_AMactorIdInitStr),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmocka_run_group_tests(tests, group_setup, group_teardown);
|
||||||
|
}
|
202
automerge-c/test/doc_tests.c
Normal file
202
automerge-c/test/doc_tests.c
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* third-party */
|
||||||
|
#include <cmocka.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
#include "group_state.h"
|
||||||
|
#include "stack_utils.h"
|
||||||
|
#include "str_utils.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GroupState* group_state;
|
||||||
|
char const* actor_id_str;
|
||||||
|
uint8_t* actor_id_bytes;
|
||||||
|
size_t actor_id_size;
|
||||||
|
} TestState;
|
||||||
|
|
||||||
|
static int setup(void** state) {
|
||||||
|
TestState* test_state = test_calloc(1, sizeof(TestState));
|
||||||
|
group_setup((void**)&test_state->group_state);
|
||||||
|
test_state->actor_id_str = "000102030405060708090a0b0c0d0e0f";
|
||||||
|
test_state->actor_id_size = strlen(test_state->actor_id_str) / 2;
|
||||||
|
test_state->actor_id_bytes = test_malloc(test_state->actor_id_size);
|
||||||
|
hex_to_bytes(test_state->actor_id_str, test_state->actor_id_bytes, test_state->actor_id_size);
|
||||||
|
*state = test_state;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int teardown(void** state) {
|
||||||
|
TestState* test_state = *state;
|
||||||
|
group_teardown((void**)&test_state->group_state);
|
||||||
|
test_free(test_state->actor_id_bytes);
|
||||||
|
test_free(test_state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMkeys_empty() {
|
||||||
|
AMresultStack* stack = NULL;
|
||||||
|
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
AMstrs forward = AMpush(&stack,
|
||||||
|
AMkeys(doc, AM_ROOT, NULL),
|
||||||
|
AM_VALUE_STRS,
|
||||||
|
cmocka_cb).strs;
|
||||||
|
assert_int_equal(AMstrsSize(&forward), 0);
|
||||||
|
AMstrs reverse = AMstrsReversed(&forward);
|
||||||
|
assert_int_equal(AMstrsSize(&reverse), 0);
|
||||||
|
assert_null(AMstrsNext(&forward, 1));
|
||||||
|
assert_null(AMstrsPrev(&forward, 1));
|
||||||
|
assert_null(AMstrsNext(&reverse, 1));
|
||||||
|
assert_null(AMstrsPrev(&reverse, 1));
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMkeys_list() {
|
||||||
|
AMresultStack* stack = NULL;
|
||||||
|
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
AMfree(AMlistPutInt(doc, AM_ROOT, 0, true, 1));
|
||||||
|
AMfree(AMlistPutInt(doc, AM_ROOT, 1, true, 2));
|
||||||
|
AMfree(AMlistPutInt(doc, AM_ROOT, 2, true, 3));
|
||||||
|
AMstrs forward = AMpush(&stack,
|
||||||
|
AMkeys(doc, AM_ROOT, NULL),
|
||||||
|
AM_VALUE_STRS,
|
||||||
|
cmocka_cb).strs;
|
||||||
|
assert_int_equal(AMstrsSize(&forward), 3);
|
||||||
|
AMstrs reverse = AMstrsReversed(&forward);
|
||||||
|
assert_int_equal(AMstrsSize(&reverse), 3);
|
||||||
|
/* Forward iterator forward. */
|
||||||
|
char const* str = AMstrsNext(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "1@"), str);
|
||||||
|
str = AMstrsNext(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "2@"), str);
|
||||||
|
str = AMstrsNext(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "3@"), str);
|
||||||
|
assert_null(AMstrsNext(&forward, 1));
|
||||||
|
/* Forward iterator reverse. */
|
||||||
|
str = AMstrsPrev(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "3@"), str);
|
||||||
|
str = AMstrsPrev(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "2@"), str);
|
||||||
|
str = AMstrsPrev(&forward, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "1@"), str);
|
||||||
|
assert_null(AMstrsPrev(&forward, 1));
|
||||||
|
/* Reverse iterator forward. */
|
||||||
|
str = AMstrsNext(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "3@"), str);
|
||||||
|
str = AMstrsNext(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "2@"), str);
|
||||||
|
str = AMstrsNext(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "1@"), str);
|
||||||
|
/* Reverse iterator reverse. */
|
||||||
|
assert_null(AMstrsNext(&reverse, 1));
|
||||||
|
str = AMstrsPrev(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "1@"), str);
|
||||||
|
str = AMstrsPrev(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "2@"), str);
|
||||||
|
str = AMstrsPrev(&reverse, 1);
|
||||||
|
assert_ptr_equal(strstr(str, "3@"), str);
|
||||||
|
assert_null(AMstrsPrev(&reverse, 1));
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMkeys_map() {
|
||||||
|
AMresultStack* stack = NULL;
|
||||||
|
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
AMfree(AMmapPutInt(doc, AM_ROOT, "one", 1));
|
||||||
|
AMfree(AMmapPutInt(doc, AM_ROOT, "two", 2));
|
||||||
|
AMfree(AMmapPutInt(doc, AM_ROOT, "three", 3));
|
||||||
|
AMstrs forward = AMpush(&stack,
|
||||||
|
AMkeys(doc, AM_ROOT, NULL),
|
||||||
|
AM_VALUE_STRS,
|
||||||
|
cmocka_cb).strs;
|
||||||
|
assert_int_equal(AMstrsSize(&forward), 3);
|
||||||
|
AMstrs reverse = AMstrsReversed(&forward);
|
||||||
|
assert_int_equal(AMstrsSize(&reverse), 3);
|
||||||
|
/* Forward iterator forward. */
|
||||||
|
assert_string_equal(AMstrsNext(&forward, 1), "one");
|
||||||
|
assert_string_equal(AMstrsNext(&forward, 1), "three");
|
||||||
|
assert_string_equal(AMstrsNext(&forward, 1), "two");
|
||||||
|
assert_null(AMstrsNext(&forward, 1));
|
||||||
|
/* Forward iterator reverse. */
|
||||||
|
assert_string_equal(AMstrsPrev(&forward, 1), "two");
|
||||||
|
assert_string_equal(AMstrsPrev(&forward, 1), "three");
|
||||||
|
assert_string_equal(AMstrsPrev(&forward, 1), "one");
|
||||||
|
assert_null(AMstrsPrev(&forward, 1));
|
||||||
|
/* Reverse iterator forward. */
|
||||||
|
assert_string_equal(AMstrsNext(&reverse, 1), "two");
|
||||||
|
assert_string_equal(AMstrsNext(&reverse, 1), "three");
|
||||||
|
assert_string_equal(AMstrsNext(&reverse, 1), "one");
|
||||||
|
assert_null(AMstrsNext(&reverse, 1));
|
||||||
|
/* Reverse iterator reverse. */
|
||||||
|
assert_string_equal(AMstrsPrev(&reverse, 1), "one");
|
||||||
|
assert_string_equal(AMstrsPrev(&reverse, 1), "three");
|
||||||
|
assert_string_equal(AMstrsPrev(&reverse, 1), "two");
|
||||||
|
assert_null(AMstrsPrev(&reverse, 1));
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMputActor_bytes(void **state) {
|
||||||
|
TestState* test_state = *state;
|
||||||
|
AMactorId const* actor_id = AMpush(&test_state->group_state->stack,
|
||||||
|
AMactorIdInitBytes(
|
||||||
|
test_state->actor_id_bytes,
|
||||||
|
test_state->actor_id_size),
|
||||||
|
AM_VALUE_ACTOR_ID,
|
||||||
|
cmocka_cb).actor_id;
|
||||||
|
AMfree(AMsetActorId(test_state->group_state->doc, actor_id));
|
||||||
|
actor_id = AMpush(&test_state->group_state->stack,
|
||||||
|
AMgetActorId(test_state->group_state->doc),
|
||||||
|
AM_VALUE_ACTOR_ID,
|
||||||
|
cmocka_cb).actor_id;
|
||||||
|
AMbyteSpan const bytes = AMactorIdBytes(actor_id);
|
||||||
|
assert_int_equal(bytes.count, test_state->actor_id_size);
|
||||||
|
assert_memory_equal(bytes.src, test_state->actor_id_bytes, bytes.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMputActor_str(void **state) {
|
||||||
|
TestState* test_state = *state;
|
||||||
|
AMactorId const* actor_id = AMpush(&test_state->group_state->stack,
|
||||||
|
AMactorIdInitStr(test_state->actor_id_str),
|
||||||
|
AM_VALUE_ACTOR_ID,
|
||||||
|
cmocka_cb).actor_id;
|
||||||
|
AMfree(AMsetActorId(test_state->group_state->doc, actor_id));
|
||||||
|
actor_id = AMpush(&test_state->group_state->stack,
|
||||||
|
AMgetActorId(test_state->group_state->doc),
|
||||||
|
AM_VALUE_ACTOR_ID,
|
||||||
|
cmocka_cb).actor_id;
|
||||||
|
char const* const str = AMactorIdStr(actor_id);
|
||||||
|
assert_int_equal(strlen(str), test_state->actor_id_size * 2);
|
||||||
|
assert_string_equal(str, test_state->actor_id_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_AMspliceText() {
|
||||||
|
AMresultStack* stack = NULL;
|
||||||
|
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
AMfree(AMspliceText(doc, AM_ROOT, 0, 0, "one + "));
|
||||||
|
AMfree(AMspliceText(doc, AM_ROOT, 4, 2, "two = "));
|
||||||
|
AMfree(AMspliceText(doc, AM_ROOT, 8, 2, "three"));
|
||||||
|
char const* const text = AMpush(&stack,
|
||||||
|
AMtext(doc, AM_ROOT, NULL),
|
||||||
|
AM_VALUE_STR,
|
||||||
|
cmocka_cb).str;
|
||||||
|
assert_string_equal(text, "one two three");
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_doc_tests(void) {
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_AMkeys_empty),
|
||||||
|
cmocka_unit_test(test_AMkeys_list),
|
||||||
|
cmocka_unit_test(test_AMkeys_map),
|
||||||
|
cmocka_unit_test_setup_teardown(test_AMputActor_bytes, setup, teardown),
|
||||||
|
cmocka_unit_test_setup_teardown(test_AMputActor_str, setup, teardown),
|
||||||
|
cmocka_unit_test(test_AMspliceText),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
}
|
27
automerge-c/test/group_state.c
Normal file
27
automerge-c/test/group_state.c
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/* third-party */
|
||||||
|
#include <cmocka.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include "group_state.h"
|
||||||
|
#include "stack_utils.h"
|
||||||
|
|
||||||
|
int group_setup(void** state) {
|
||||||
|
GroupState* group_state = test_calloc(1, sizeof(GroupState));
|
||||||
|
group_state->doc = AMpush(&group_state->stack,
|
||||||
|
AMcreate(NULL),
|
||||||
|
AM_VALUE_DOC,
|
||||||
|
cmocka_cb).doc;
|
||||||
|
*state = group_state;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int group_teardown(void** state) {
|
||||||
|
GroupState* group_state = *state;
|
||||||
|
AMfreeStack(&group_state->stack);
|
||||||
|
test_free(group_state);
|
||||||
|
return 0;
|
||||||
|
}
|
16
automerge-c/test/group_state.h
Normal file
16
automerge-c/test/group_state.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef GROUP_STATE_H
|
||||||
|
#define GROUP_STATE_H
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
AMresultStack* stack;
|
||||||
|
AMdoc* doc;
|
||||||
|
} GroupState;
|
||||||
|
|
||||||
|
int group_setup(void** state);
|
||||||
|
|
||||||
|
int group_teardown(void** state);
|
||||||
|
|
||||||
|
#endif /* GROUP_STATE_H */
|
379
automerge-c/test/list_tests.c
Normal file
379
automerge-c/test/list_tests.c
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
#include <float.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* third-party */
|
||||||
|
#include <cmocka.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
#include "group_state.h"
|
||||||
|
#include "macro_utils.h"
|
||||||
|
#include "stack_utils.h"
|
||||||
|
|
||||||
|
static void test_AMlistIncrement(void** state) {
|
||||||
|
GroupState* group_state = *state;
|
||||||
|
AMfree(AMlistPutCounter(group_state->doc, AM_ROOT, 0, true, 0));
|
||||||
|
assert_int_equal(AMpush(&group_state->stack,
|
||||||
|
AMlistGet(group_state->doc, AM_ROOT, 0, NULL),
|
||||||
|
AM_VALUE_COUNTER,
|
||||||
|
cmocka_cb).counter, 0);
|
||||||
|
AMfree(AMpop(&group_state->stack));
|
||||||
|
AMfree(AMlistIncrement(group_state->doc, AM_ROOT, 0, 3));
|
||||||
|
assert_int_equal(AMpush(&group_state->stack,
|
||||||
|
AMlistGet(group_state->doc, AM_ROOT, 0, NULL),
|
||||||
|
AM_VALUE_COUNTER,
|
||||||
|
cmocka_cb).counter, 3);
|
||||||
|
AMfree(AMpop(&group_state->stack));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define test_AMlistPut(suffix, mode) test_AMlistPut ## suffix ## _ ## mode
|
||||||
|
|
||||||
|
#define static_void_test_AMlistPut(suffix, mode, member, scalar_value) \
|
||||||
|
static void test_AMlistPut ## suffix ## _ ## mode(void **state) { \
|
||||||
|
GroupState* group_state = *state; \
|
||||||
|
AMfree(AMlistPut ## suffix(group_state->doc, \
|
||||||
|
AM_ROOT, \
|
||||||
|
0, \
|
||||||
|
!strcmp(#mode, "insert"), \
|
||||||
|
scalar_value)); \
|
||||||
|
assert_true(AMpush( \
|
||||||
|
&group_state->stack, \
|
||||||
|
AMlistGet(group_state->doc, AM_ROOT, 0, NULL), \
|
||||||
|
AMvalue_discriminant(#suffix), \
|
||||||
|
cmocka_cb).member == scalar_value); \
|
||||||
|
AMfree(AMpop(&group_state->stack)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define test_AMlistPutBytes(mode) test_AMlistPutBytes ## _ ## mode
|
||||||
|
|
||||||
|
#define static_void_test_AMlistPutBytes(mode, bytes_value) \
|
||||||
|
static void test_AMlistPutBytes_ ## mode(void **state) { \
|
||||||
|
static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \
|
||||||
|
\
|
||||||
|
GroupState* group_state = *state; \
|
||||||
|
AMfree(AMlistPutBytes(group_state->doc, \
|
||||||
|
AM_ROOT, \
|
||||||
|
0, \
|
||||||
|
!strcmp(#mode, "insert"), \
|
||||||
|
bytes_value, \
|
||||||
|
BYTES_SIZE)); \
|
||||||
|
AMbyteSpan const bytes = AMpush( \
|
||||||
|
&group_state->stack, \
|
||||||
|
AMlistGet(group_state->doc, AM_ROOT, 0, NULL), \
|
||||||
|
AM_VALUE_BYTES, \
|
||||||
|
cmocka_cb).bytes; \
|
||||||
|
assert_int_equal(bytes.count, BYTES_SIZE); \
|
||||||
|
assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \
|
||||||
|
AMfree(AMpop(&group_state->stack)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define test_AMlistPutNull(mode) test_AMlistPutNull_ ## mode
|
||||||
|
|
||||||
|
#define static_void_test_AMlistPutNull(mode) \
|
||||||
|
static void test_AMlistPutNull_ ## mode(void **state) { \
|
||||||
|
GroupState* group_state = *state; \
|
||||||
|
AMfree(AMlistPutNull(group_state->doc, \
|
||||||
|
AM_ROOT, \
|
||||||
|
0, \
|
||||||
|
!strcmp(#mode, "insert"))); \
|
||||||
|
AMresult* const result = AMlistGet(group_state->doc, AM_ROOT, 0, NULL); \
|
||||||
|
if (AMresultStatus(result) != AM_STATUS_OK) { \
|
||||||
|
fail_msg("%s", AMerrorMessage(result)); \
|
||||||
|
} \
|
||||||
|
assert_int_equal(AMresultSize(result), 1); \
|
||||||
|
assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); \
|
||||||
|
AMfree(result); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define test_AMlistPutObject(label, mode) test_AMlistPutObject_ ## label ## _ ## mode
|
||||||
|
|
||||||
|
#define static_void_test_AMlistPutObject(label, mode) \
|
||||||
|
static void test_AMlistPutObject_ ## label ## _ ## mode(void **state) { \
|
||||||
|
GroupState* group_state = *state; \
|
||||||
|
AMobjId const* const obj_id = AMpush( \
|
||||||
|
&group_state->stack, \
|
||||||
|
AMlistPutObject(group_state->doc, \
|
||||||
|
AM_ROOT, \
|
||||||
|
0, \
|
||||||
|
!strcmp(#mode, "insert"), \
|
||||||
|
AMobjType_tag(#label)), \
|
||||||
|
AM_VALUE_OBJ_ID, \
|
||||||
|
cmocka_cb).obj_id; \
|
||||||
|
assert_non_null(obj_id); \
|
||||||
|
assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \
|
||||||
|
AMfree(AMpop(&group_state->stack)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define test_AMlistPutStr(mode) test_AMlistPutStr ## _ ## mode
|
||||||
|
|
||||||
|
#define static_void_test_AMlistPutStr(mode, str_value) \
|
||||||
|
static void test_AMlistPutStr_ ## mode(void **state) { \
|
||||||
|
GroupState* group_state = *state; \
|
||||||
|
AMfree(AMlistPutStr(group_state->doc, \
|
||||||
|
AM_ROOT, \
|
||||||
|
0, \
|
||||||
|
!strcmp(#mode, "insert"), \
|
||||||
|
str_value)); \
|
||||||
|
assert_string_equal(AMpush( \
|
||||||
|
&group_state->stack, \
|
||||||
|
AMlistGet(group_state->doc, AM_ROOT, 0, NULL), \
|
||||||
|
AM_VALUE_STR, \
|
||||||
|
cmocka_cb).str, str_value); \
|
||||||
|
AMfree(AMpop(&group_state->stack)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Bool, insert, boolean, true)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Bool, update, boolean, true)
|
||||||
|
|
||||||
|
static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX};
|
||||||
|
|
||||||
|
static_void_test_AMlistPutBytes(insert, BYTES_VALUE)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutBytes(update, BYTES_VALUE)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Counter, insert, counter, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Counter, update, counter, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(F64, insert, f64, DBL_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(F64, update, f64, DBL_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Int, insert, int_, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Int, update, int_, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutNull(insert)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutNull(update)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(List, insert)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(List, update)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(Map, insert)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(Map, update)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(Text, insert)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutObject(Text, update)
|
||||||
|
|
||||||
|
static_void_test_AMlistPutStr(insert, "Hello, world!")
|
||||||
|
|
||||||
|
static_void_test_AMlistPutStr(update, "Hello, world!")
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Timestamp, insert, timestamp, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Timestamp, update, timestamp, INT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Uint, insert, uint, UINT64_MAX)
|
||||||
|
|
||||||
|
static_void_test_AMlistPut(Uint, update, uint, UINT64_MAX)
|
||||||
|
|
||||||
|
static void test_insert_at_index(void** state) {
|
||||||
|
AMresultStack* stack = *state;
|
||||||
|
AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
|
||||||
|
AMobjId const* const list = AMpush(
|
||||||
|
&stack,
|
||||||
|
AMlistPutObject(doc, AM_ROOT, 0, true, AM_OBJ_TYPE_LIST),
|
||||||
|
AM_VALUE_OBJ_ID,
|
||||||
|
cmocka_cb).obj_id;
|
||||||
|
/* Insert both at the same index. */
|
||||||
|
AMfree(AMlistPutUint(doc, list, 0, true, 0));
|
||||||
|
AMfree(AMlistPutUint(doc, list, 0, true, 1));
|
||||||
|
|
||||||
|
assert_int_equal(AMobjSize(doc, list, NULL), 2);
|
||||||
|
AMstrs const keys = AMpush(&stack,
|
||||||
|
AMkeys(doc, list, NULL),
|
||||||
|
AM_VALUE_STRS,
|
||||||
|
cmocka_cb).strs;
|
||||||
|
assert_int_equal(AMstrsSize(&keys), 2);
|
||||||
|
AMlistItems const range = AMpush(&stack,
|
||||||
|
AMlistRange(doc, list, 0, SIZE_MAX, NULL),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_get_list_values(void** state) {
|
||||||
|
AMresultStack* stack = *state;
|
||||||
|
AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc;
|
||||||
|
AMobjId const* const list = AMpush(
|
||||||
|
&stack,
|
||||||
|
AMmapPutObject(doc1, AM_ROOT, "list", AM_OBJ_TYPE_LIST),
|
||||||
|
AM_VALUE_OBJ_ID,
|
||||||
|
cmocka_cb).obj_id;
|
||||||
|
|
||||||
|
/* Insert elements. */
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "First"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Second"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Third"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Fourth"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Fifth"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Sixth"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Seventh"));
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 0, true, "Eighth"));
|
||||||
|
AMfree(AMcommit(doc1, NULL, NULL));
|
||||||
|
|
||||||
|
AMchangeHashes const v1 = AMpush(&stack,
|
||||||
|
AMgetHeads(doc1),
|
||||||
|
AM_VALUE_CHANGE_HASHES,
|
||||||
|
cmocka_cb).change_hashes;
|
||||||
|
AMdoc* const doc2 = AMpush(&stack,
|
||||||
|
AMfork(doc1, NULL),
|
||||||
|
AM_VALUE_DOC,
|
||||||
|
cmocka_cb).doc;
|
||||||
|
|
||||||
|
AMfree(AMlistPutStr(doc1, list, 2, false, "Third V2"));
|
||||||
|
AMfree(AMcommit(doc1, NULL, NULL));
|
||||||
|
|
||||||
|
AMfree(AMlistPutStr(doc2, list, 2, false, "Third V3"));
|
||||||
|
AMfree(AMcommit(doc2, NULL, NULL));
|
||||||
|
|
||||||
|
AMfree(AMmerge(doc1, doc2));
|
||||||
|
|
||||||
|
AMlistItems range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 0, SIZE_MAX, NULL),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), 8);
|
||||||
|
|
||||||
|
AMlistItem const* list_item = NULL;
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL);
|
||||||
|
AMvalue const val2 = AMresultValue(result);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_non_null(AMlistItemObjId(list_item));
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 3, 6, NULL),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
AMlistItems range_back = AMlistItemsReversed(&range);
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), 3);
|
||||||
|
assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3);
|
||||||
|
assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5);
|
||||||
|
|
||||||
|
range = AMlistItemsRewound(&range);
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL);
|
||||||
|
AMvalue const val2 = AMresultValue(result);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_non_null(AMlistItemObjId(list_item));
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 0, SIZE_MAX, &v1),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), 8);
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1);
|
||||||
|
AMvalue const val2 = AMresultValue(result);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_non_null(AMlistItemObjId(list_item));
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 3, 6, &v1),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
range_back = AMlistItemsReversed(&range);
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), 3);
|
||||||
|
assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3);
|
||||||
|
assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5);
|
||||||
|
|
||||||
|
range = AMlistItemsRewound(&range);
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1);
|
||||||
|
AMvalue const val2 = AMresultValue(result);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_non_null(AMlistItemObjId(list_item));
|
||||||
|
AMfree(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 0, SIZE_MAX, NULL),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
AMobjItems values = AMpush(&stack,
|
||||||
|
AMobjValues(doc1, list, NULL),
|
||||||
|
AM_VALUE_OBJ_ITEMS,
|
||||||
|
cmocka_cb).obj_items;
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values));
|
||||||
|
AMobjItem const* value = NULL;
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL &&
|
||||||
|
(value = AMobjItemsNext(&values, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMvalue const val2 = AMobjItemValue(value);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
range = AMpush(&stack,
|
||||||
|
AMlistRange(doc1, list, 0, SIZE_MAX, &v1),
|
||||||
|
AM_VALUE_LIST_ITEMS,
|
||||||
|
cmocka_cb).list_items;
|
||||||
|
values = AMpush(&stack,
|
||||||
|
AMobjValues(doc1, list, &v1),
|
||||||
|
AM_VALUE_OBJ_ITEMS,
|
||||||
|
cmocka_cb).obj_items;
|
||||||
|
assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values));
|
||||||
|
while ((list_item = AMlistItemsNext(&range, 1)) != NULL &&
|
||||||
|
(value = AMobjItemsNext(&values, 1)) != NULL) {
|
||||||
|
AMvalue const val1 = AMlistItemValue(list_item);
|
||||||
|
AMvalue const val2 = AMobjItemValue(value);
|
||||||
|
assert_true(AMvalueEqual(&val1, &val2));
|
||||||
|
assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_list_tests(void) {
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_AMlistIncrement),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Bool, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Bool, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutBytes(insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutBytes(update)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Counter, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Counter, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(F64, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(F64, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Int, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Int, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutNull(insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutNull(update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(List, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(List, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(Map, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(Map, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(Text, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutObject(Text, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPutStr(insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPutStr(update)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Timestamp, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Timestamp, update)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Uint, insert)),
|
||||||
|
cmocka_unit_test(test_AMlistPut(Uint, update)),
|
||||||
|
cmocka_unit_test_setup_teardown(test_insert_at_index, setup_stack, teardown_stack),
|
||||||
|
cmocka_unit_test_setup_teardown(test_get_list_values, setup_stack, teardown_stack),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cmocka_run_group_tests(tests, group_setup, group_teardown);
|
||||||
|
}
|
24
automerge-c/test/macro_utils.c
Normal file
24
automerge-c/test/macro_utils.c
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include "macro_utils.h"
|
||||||
|
|
||||||
|
AMvalueVariant AMvalue_discriminant(char const* suffix) {
|
||||||
|
if (!strcmp(suffix, "Bool")) return AM_VALUE_BOOLEAN;
|
||||||
|
else if (!strcmp(suffix, "Bytes")) return AM_VALUE_BYTES;
|
||||||
|
else if (!strcmp(suffix, "Counter")) return AM_VALUE_COUNTER;
|
||||||
|
else if (!strcmp(suffix, "F64")) return AM_VALUE_F64;
|
||||||
|
else if (!strcmp(suffix, "Int")) return AM_VALUE_INT;
|
||||||
|
else if (!strcmp(suffix, "Null")) return AM_VALUE_NULL;
|
||||||
|
else if (!strcmp(suffix, "Str")) return AM_VALUE_STR;
|
||||||
|
else if (!strcmp(suffix, "Timestamp")) return AM_VALUE_TIMESTAMP;
|
||||||
|
else if (!strcmp(suffix, "Uint")) return AM_VALUE_UINT;
|
||||||
|
else return AM_VALUE_VOID;
|
||||||
|
}
|
||||||
|
|
||||||
|
AMobjType AMobjType_tag(char const* obj_type_label) {
|
||||||
|
if (!strcmp(obj_type_label, "List")) return AM_OBJ_TYPE_LIST;
|
||||||
|
else if (!strcmp(obj_type_label, "Map")) return AM_OBJ_TYPE_MAP;
|
||||||
|
else if (!strcmp(obj_type_label, "Text")) return AM_OBJ_TYPE_TEXT;
|
||||||
|
else return 0;
|
||||||
|
}
|
24
automerge-c/test/macro_utils.h
Normal file
24
automerge-c/test/macro_utils.h
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef MACRO_UTILS_H
|
||||||
|
#define MACRO_UTILS_H
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets the result value discriminant corresponding to a function name
|
||||||
|
* suffix.
|
||||||
|
*
|
||||||
|
* \param[in] suffix A string.
|
||||||
|
* \return An `AMvalue` struct discriminant.
|
||||||
|
*/
|
||||||
|
AMvalueVariant AMvalue_discriminant(char const* suffix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Gets the object type tag corresponding to an object type label.
|
||||||
|
*
|
||||||
|
* \param[in] obj_type_label A string.
|
||||||
|
* \return An `AMobjType` enum tag.
|
||||||
|
*/
|
||||||
|
AMobjType AMobjType_tag(char const* obj_type_label);
|
||||||
|
|
||||||
|
#endif /* MACRO_UTILS_H */
|
|
@ -1,6 +1,6 @@
|
||||||
#include <setjmp.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <setjmp.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* third-party */
|
/* third-party */
|
||||||
|
@ -8,14 +8,8 @@
|
||||||
|
|
||||||
extern int run_actor_id_tests(void);
|
extern int run_actor_id_tests(void);
|
||||||
|
|
||||||
extern int run_byte_span_tests(void);
|
|
||||||
|
|
||||||
extern int run_doc_tests(void);
|
extern int run_doc_tests(void);
|
||||||
|
|
||||||
extern int run_enum_string_tests(void);
|
|
||||||
|
|
||||||
extern int run_item_tests(void);
|
|
||||||
|
|
||||||
extern int run_list_tests(void);
|
extern int run_list_tests(void);
|
||||||
|
|
||||||
extern int run_map_tests(void);
|
extern int run_map_tests(void);
|
||||||
|
@ -23,6 +17,11 @@ extern int run_map_tests(void);
|
||||||
extern int run_ported_wasm_suite(void);
|
extern int run_ported_wasm_suite(void);
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
return (run_actor_id_tests() + run_byte_span_tests() + run_doc_tests() + run_enum_string_tests() +
|
return (
|
||||||
run_item_tests() + run_list_tests() + run_map_tests() + run_ported_wasm_suite());
|
run_actor_id_tests() +
|
||||||
|
run_doc_tests() +
|
||||||
|
run_list_tests() +
|
||||||
|
run_map_tests() +
|
||||||
|
run_ported_wasm_suite()
|
||||||
|
);
|
||||||
}
|
}
|
1164
automerge-c/test/map_tests.c
Normal file
1164
automerge-c/test/map_tests.c
Normal file
File diff suppressed because it is too large
Load diff
1755
automerge-c/test/ported_wasm/basic_tests.c
Normal file
1755
automerge-c/test/ported_wasm/basic_tests.c
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
#include <setjmp.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <setjmp.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
/* third-party */
|
/* third-party */
|
||||||
|
@ -11,5 +11,8 @@ extern int run_ported_wasm_basic_tests(void);
|
||||||
extern int run_ported_wasm_sync_tests(void);
|
extern int run_ported_wasm_sync_tests(void);
|
||||||
|
|
||||||
int run_ported_wasm_suite(void) {
|
int run_ported_wasm_suite(void) {
|
||||||
return (run_ported_wasm_basic_tests() + run_ported_wasm_sync_tests());
|
return (
|
||||||
|
run_ported_wasm_basic_tests() +
|
||||||
|
run_ported_wasm_sync_tests()
|
||||||
|
);
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load diff
30
automerge-c/test/stack_utils.c
Normal file
30
automerge-c/test/stack_utils.c
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* third-party */
|
||||||
|
#include <cmocka.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include "stack_utils.h"
|
||||||
|
|
||||||
|
void cmocka_cb(AMresultStack** stack, uint8_t discriminant) {
|
||||||
|
assert_non_null(stack);
|
||||||
|
assert_non_null(*stack);
|
||||||
|
assert_non_null((*stack)->result);
|
||||||
|
if (AMresultStatus((*stack)->result) != AM_STATUS_OK) {
|
||||||
|
fail_msg("%s", AMerrorMessage((*stack)->result));
|
||||||
|
}
|
||||||
|
assert_int_equal(AMresultValue((*stack)->result).tag, discriminant);
|
||||||
|
}
|
||||||
|
|
||||||
|
int setup_stack(void** state) {
|
||||||
|
*state = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int teardown_stack(void** state) {
|
||||||
|
AMresultStack* stack = *state;
|
||||||
|
AMfreeStack(&stack);
|
||||||
|
return 0;
|
||||||
|
}
|
38
automerge-c/test/stack_utils.h
Normal file
38
automerge-c/test/stack_utils.h
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef STACK_UTILS_H
|
||||||
|
#define STACK_UTILS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* local */
|
||||||
|
#include <automerge-c/automerge.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Reports an error through a cmocka assertion.
|
||||||
|
*
|
||||||
|
* \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
* \param[in] discriminant An `AMvalueVariant` enum tag.
|
||||||
|
* \pre \p stack` != NULL`.
|
||||||
|
*/
|
||||||
|
void cmocka_cb(AMresultStack** stack, uint8_t discriminant);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Allocates a result stack for storing the results allocated during one
|
||||||
|
* or more test cases.
|
||||||
|
*
|
||||||
|
* \param[in,out] state A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
* \pre \p state` != NULL`.
|
||||||
|
* \warning The `AMresultStack` struct returned through \p state must be
|
||||||
|
* deallocated with `teardown_stack()` in order to prevent memory leaks.
|
||||||
|
*/
|
||||||
|
int setup_stack(void** state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Deallocates a result stack after deallocating any results that were
|
||||||
|
* stored in it by one or more test cases.
|
||||||
|
*
|
||||||
|
* \param[in] state A pointer to a pointer to an `AMresultStack` struct.
|
||||||
|
* \pre \p state` != NULL`.
|
||||||
|
*/
|
||||||
|
int teardown_stack(void** state);
|
||||||
|
|
||||||
|
#endif /* STACK_UTILS_H */
|
|
@ -1,5 +1,5 @@
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
/* local */
|
/* local */
|
||||||
#include "str_utils.h"
|
#include "str_utils.h"
|
14
automerge-c/test/str_utils.h
Normal file
14
automerge-c/test/str_utils.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#ifndef STR_UTILS_H
|
||||||
|
#define STR_UTILS_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Converts a hexadecimal string into a sequence of bytes.
|
||||||
|
*
|
||||||
|
* \param[in] hex_str A string.
|
||||||
|
* \param[in] src A pointer to a contiguous sequence of bytes.
|
||||||
|
* \param[in] count The number of bytes to copy to \p src.
|
||||||
|
* \pre \p count `<=` length of \p src.
|
||||||
|
*/
|
||||||
|
void hex_to_bytes(char const* hex_str, uint8_t* src, size_t const count);
|
||||||
|
|
||||||
|
#endif /* STR_UTILS_H */
|
857
automerge-cli/Cargo.lock
generated
Normal file
857
automerge-cli/Cargo.lock
generated
Normal file
|
@ -0,0 +1,857 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "automerge"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"fxhash",
|
||||||
|
"hex",
|
||||||
|
"itertools",
|
||||||
|
"js-sys",
|
||||||
|
"leb128",
|
||||||
|
"nonzero_ext",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"sha2",
|
||||||
|
"smol_str",
|
||||||
|
"thiserror",
|
||||||
|
"tinyvec",
|
||||||
|
"tracing",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"uuid",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "automerge-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"atty",
|
||||||
|
"automerge",
|
||||||
|
"clap",
|
||||||
|
"colored_json",
|
||||||
|
"combine",
|
||||||
|
"duct",
|
||||||
|
"maplit",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"indexmap",
|
||||||
|
"lazy_static",
|
||||||
|
"os_str_bytes",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "3.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored_json"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd32eb54d016e203b7c2600e3a7802c75843a92e38ccc4869aefeca21771a64"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"atty",
|
||||||
|
"libc",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "combine"
|
||||||
|
version = "4.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "duct"
|
||||||
|
version = "0.13.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"os_pipe",
|
||||||
|
"shared_child",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crc32fast",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fxhash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leb128"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nonzero_ext"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44a1290799eababa63ea60af0cbc3f03363e328e58f32fb0294798ed3e85f444"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_pipe"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.136"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shared_child"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smol_str"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
|
@ -13,18 +13,17 @@ bench = false
|
||||||
doc = false
|
doc = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = {version = "~4", features = ["derive"]}
|
clap = {version = "~3.1", features = ["derive"]}
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
atty = "^0.2"
|
||||||
thiserror = "^1.0"
|
thiserror = "^1.0"
|
||||||
combine = "^4.5"
|
combine = "^4.5"
|
||||||
maplit = "^1.0"
|
maplit = "^1.0"
|
||||||
|
colored_json = "^2.1"
|
||||||
tracing-subscriber = "~0.3"
|
tracing-subscriber = "~0.3"
|
||||||
|
|
||||||
automerge = { path = "../automerge" }
|
automerge = { path = "../automerge" }
|
||||||
is-terminal = "0.4.1"
|
|
||||||
termcolor = "1.1.3"
|
|
||||||
serde = "1.0.150"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
duct = "^0.13"
|
duct = "^0.13"
|
|
@ -1,8 +1,6 @@
|
||||||
use automerge as am;
|
use automerge as am;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{color_json::print_colored_json, SkipVerifyFlag};
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ExamineError {
|
pub enum ExamineError {
|
||||||
#[error("Error reading change file: {:?}", source)]
|
#[error("Error reading change file: {:?}", source)]
|
||||||
|
@ -22,18 +20,16 @@ pub enum ExamineError {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn examine(
|
pub fn examine(
|
||||||
mut input: impl std::io::Read,
|
mut input: impl std::io::Read,
|
||||||
mut output: impl std::io::Write,
|
mut output: impl std::io::Write,
|
||||||
skip: SkipVerifyFlag,
|
|
||||||
is_tty: bool,
|
is_tty: bool,
|
||||||
) -> Result<(), ExamineError> {
|
) -> Result<(), ExamineError> {
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
input
|
input
|
||||||
.read_to_end(&mut buf)
|
.read_to_end(&mut buf)
|
||||||
.map_err(|e| ExamineError::ReadingChanges { source: e })?;
|
.map_err(|e| ExamineError::ReadingChanges { source: e })?;
|
||||||
let doc = skip
|
let doc = am::Automerge::load(&buf)
|
||||||
.load(&buf)
|
|
||||||
.map_err(|e| ExamineError::ApplyingInitialChanges { source: e })?;
|
.map_err(|e| ExamineError::ApplyingInitialChanges { source: e })?;
|
||||||
let uncompressed_changes: Vec<_> = doc
|
let uncompressed_changes: Vec<_> = doc
|
||||||
.get_changes(&[])
|
.get_changes(&[])
|
||||||
|
@ -43,7 +39,7 @@ pub(crate) fn examine(
|
||||||
.collect();
|
.collect();
|
||||||
if is_tty {
|
if is_tty {
|
||||||
let json_changes = serde_json::to_value(uncompressed_changes).unwrap();
|
let json_changes = serde_json::to_value(uncompressed_changes).unwrap();
|
||||||
print_colored_json(&json_changes).unwrap();
|
colored_json::write_colored_json(&json_changes, &mut output).unwrap();
|
||||||
writeln!(output).unwrap();
|
writeln!(output).unwrap();
|
||||||
} else {
|
} else {
|
||||||
let json_changes = serde_json::to_string_pretty(&uncompressed_changes).unwrap();
|
let json_changes = serde_json::to_string_pretty(&uncompressed_changes).unwrap();
|
|
@ -1,8 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use automerge as am;
|
use automerge as am;
|
||||||
use automerge::ReadDoc;
|
|
||||||
|
|
||||||
use crate::{color_json::print_colored_json, SkipVerifyFlag};
|
|
||||||
|
|
||||||
pub(crate) fn map_to_json(doc: &am::Automerge, obj: &am::ObjId) -> serde_json::Value {
|
pub(crate) fn map_to_json(doc: &am::Automerge, obj: &am::ObjId) -> serde_json::Value {
|
||||||
let keys = doc.keys(obj);
|
let keys = doc.keys(obj);
|
||||||
|
@ -31,7 +28,7 @@ fn list_to_json(doc: &am::Automerge, obj: &am::ObjId) -> serde_json::Value {
|
||||||
let len = doc.length(obj);
|
let len = doc.length(obj);
|
||||||
let mut array = Vec::new();
|
let mut array = Vec::new();
|
||||||
for i in 0..len {
|
for i in 0..len {
|
||||||
let val = doc.get(obj, i);
|
let val = doc.get(obj, i as usize);
|
||||||
match val {
|
match val {
|
||||||
Ok(Some((am::Value::Object(o), exid)))
|
Ok(Some((am::Value::Object(o), exid)))
|
||||||
if o == am::ObjType::Map || o == am::ObjType::Table =>
|
if o == am::ObjType::Map || o == am::ObjType::Table =>
|
||||||
|
@ -72,23 +69,22 @@ fn scalar_to_json(val: &am::ScalarValue) -> serde_json::Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state_json(input_data: Vec<u8>, skip: SkipVerifyFlag) -> Result<serde_json::Value> {
|
fn get_state_json(input_data: Vec<u8>) -> Result<serde_json::Value> {
|
||||||
let doc = skip.load(&input_data).unwrap(); // FIXME
|
let doc = am::Automerge::load(&input_data).unwrap(); // FIXME
|
||||||
Ok(map_to_json(&doc, &am::ObjId::Root))
|
Ok(map_to_json(&doc, &am::ObjId::Root))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn export_json(
|
pub fn export_json(
|
||||||
mut changes_reader: impl std::io::Read,
|
mut changes_reader: impl std::io::Read,
|
||||||
mut writer: impl std::io::Write,
|
mut writer: impl std::io::Write,
|
||||||
skip: SkipVerifyFlag,
|
|
||||||
is_tty: bool,
|
is_tty: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut input_data = vec![];
|
let mut input_data = vec![];
|
||||||
changes_reader.read_to_end(&mut input_data)?;
|
changes_reader.read_to_end(&mut input_data)?;
|
||||||
|
|
||||||
let state_json = get_state_json(input_data, skip)?;
|
let state_json = get_state_json(input_data)?;
|
||||||
if is_tty {
|
if is_tty {
|
||||||
print_colored_json(&state_json).unwrap();
|
colored_json::write_colored_json(&state_json, &mut writer).unwrap();
|
||||||
writeln!(writer).unwrap();
|
writeln!(writer).unwrap();
|
||||||
} else {
|
} else {
|
||||||
writeln!(
|
writeln!(
|
||||||
|
@ -107,10 +103,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cli_export_with_empty_input() {
|
fn cli_export_with_empty_input() {
|
||||||
assert_eq!(
|
assert_eq!(get_state_json(vec![]).unwrap(), serde_json::json!({}))
|
||||||
get_state_json(vec![], Default::default()).unwrap(),
|
|
||||||
serde_json::json!({})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -124,7 +117,7 @@ mod tests {
|
||||||
let mut backend = initialize_from_json(&initial_state_json).unwrap();
|
let mut backend = initialize_from_json(&initial_state_json).unwrap();
|
||||||
let change_bytes = backend.save();
|
let change_bytes = backend.save();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_state_json(change_bytes, Default::default()).unwrap(),
|
get_state_json(change_bytes).unwrap(),
|
||||||
serde_json::json!({"sparrows": 15.0})
|
serde_json::json!({"sparrows": 15.0})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -151,7 +144,7 @@ mod tests {
|
||||||
*/
|
*/
|
||||||
let change_bytes = backend.save();
|
let change_bytes = backend.save();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_state_json(change_bytes, Default::default()).unwrap(),
|
get_state_json(change_bytes).unwrap(),
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"birds": {
|
"birds": {
|
||||||
"wrens": 3.0,
|
"wrens": 3.0,
|
|
@ -1,15 +1,10 @@
|
||||||
use std::{fs::File, path::PathBuf, str::FromStr};
|
use std::{fs::File, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::{
|
use clap::Parser;
|
||||||
builder::{BoolishValueParser, TypedValueParser, ValueParserFactory},
|
|
||||||
Parser,
|
|
||||||
};
|
|
||||||
use is_terminal::IsTerminal;
|
|
||||||
|
|
||||||
mod color_json;
|
//mod change;
|
||||||
mod examine;
|
mod examine;
|
||||||
mod examine_sync;
|
|
||||||
mod export;
|
mod export;
|
||||||
mod import;
|
mod import;
|
||||||
mod merge;
|
mod merge;
|
||||||
|
@ -21,50 +16,12 @@ struct Opts {
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::ValueEnum, Clone, Debug)]
|
#[derive(Debug)]
|
||||||
enum ExportFormat {
|
enum ExportFormat {
|
||||||
Json,
|
Json,
|
||||||
Toml,
|
Toml,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug)]
|
|
||||||
pub(crate) struct SkipVerifyFlag(bool);
|
|
||||||
|
|
||||||
impl SkipVerifyFlag {
|
|
||||||
fn load(&self, buf: &[u8]) -> Result<automerge::Automerge, automerge::AutomergeError> {
|
|
||||||
if self.0 {
|
|
||||||
automerge::Automerge::load(buf)
|
|
||||||
} else {
|
|
||||||
automerge::Automerge::load_unverified_heads(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct SkipVerifyFlagParser;
|
|
||||||
impl ValueParserFactory for SkipVerifyFlag {
|
|
||||||
type Parser = SkipVerifyFlagParser;
|
|
||||||
|
|
||||||
fn value_parser() -> Self::Parser {
|
|
||||||
SkipVerifyFlagParser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypedValueParser for SkipVerifyFlagParser {
|
|
||||||
type Value = SkipVerifyFlag;
|
|
||||||
|
|
||||||
fn parse_ref(
|
|
||||||
&self,
|
|
||||||
cmd: &clap::Command,
|
|
||||||
arg: Option<&clap::Arg>,
|
|
||||||
value: &std::ffi::OsStr,
|
|
||||||
) -> Result<Self::Value, clap::Error> {
|
|
||||||
BoolishValueParser::new()
|
|
||||||
.parse_ref(cmd, arg, value)
|
|
||||||
.map(SkipVerifyFlag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ExportFormat {
|
impl FromStr for ExportFormat {
|
||||||
type Err = anyhow::Error;
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
@ -86,15 +43,12 @@ enum Command {
|
||||||
format: ExportFormat,
|
format: ExportFormat,
|
||||||
|
|
||||||
/// Path that contains Automerge changes
|
/// Path that contains Automerge changes
|
||||||
|
#[clap(parse(from_os_str))]
|
||||||
changes_file: Option<PathBuf>,
|
changes_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// The file to write to. If omitted assumes stdout
|
/// The file to write to. If omitted assumes stdout
|
||||||
#[clap(long("out"), short('o'))]
|
#[clap(parse(from_os_str), long("out"), short('o'))]
|
||||||
output_file: Option<PathBuf>,
|
output_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// Whether to verify the head hashes of a compressed document
|
|
||||||
#[clap(long, action = clap::ArgAction::SetFalse)]
|
|
||||||
skip_verifying_heads: SkipVerifyFlag,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Import {
|
Import {
|
||||||
|
@ -102,37 +56,69 @@ enum Command {
|
||||||
#[clap(long, short, default_value = "json")]
|
#[clap(long, short, default_value = "json")]
|
||||||
format: ExportFormat,
|
format: ExportFormat,
|
||||||
|
|
||||||
|
#[clap(parse(from_os_str))]
|
||||||
input_file: Option<PathBuf>,
|
input_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// Path to write Automerge changes to
|
/// Path to write Automerge changes to
|
||||||
#[clap(long("out"), short('o'))]
|
#[clap(parse(from_os_str), long("out"), short('o'))]
|
||||||
changes_file: Option<PathBuf>,
|
changes_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Read an automerge document and print a JSON representation of the changes in it to stdout
|
/// Read an automerge document from a file or stdin, perform a change on it and write a new
|
||||||
Examine {
|
/// document to stdout or the specified output file.
|
||||||
|
Change {
|
||||||
|
/// The change script to perform. Change scripts have the form <command> <path> [<JSON value>].
|
||||||
|
/// The possible commands are 'set', 'insert', 'delete', and 'increment'.
|
||||||
|
///
|
||||||
|
/// Paths look like this: $["mapkey"][0]. They always lways start with a '$', then each
|
||||||
|
/// subsequent segment of the path is either a string in double quotes to index a key in a
|
||||||
|
/// map, or an integer index to address an array element.
|
||||||
|
///
|
||||||
|
/// Examples
|
||||||
|
///
|
||||||
|
/// ## set
|
||||||
|
///
|
||||||
|
/// > automerge change 'set $["someobject"] {"items": []}' somefile
|
||||||
|
///
|
||||||
|
/// ## insert
|
||||||
|
///
|
||||||
|
/// > automerge change 'insert $["someobject"]["items"][0] "item1"' somefile
|
||||||
|
///
|
||||||
|
/// ## increment
|
||||||
|
///
|
||||||
|
/// > automerge change 'increment $["mycounter"]'
|
||||||
|
///
|
||||||
|
/// ## delete
|
||||||
|
///
|
||||||
|
/// > automerge change 'delete $["someobject"]["items"]' somefile
|
||||||
|
script: String,
|
||||||
|
|
||||||
|
/// The file to change, if omitted will assume stdin
|
||||||
|
#[clap(parse(from_os_str))]
|
||||||
input_file: Option<PathBuf>,
|
input_file: Option<PathBuf>,
|
||||||
skip_verifying_heads: SkipVerifyFlag,
|
|
||||||
|
/// Path to write Automerge changes to, if omitted will write to stdout
|
||||||
|
#[clap(parse(from_os_str), long("out"), short('o'))]
|
||||||
|
output_file: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Read an automerge sync messaage and print a JSON representation of it
|
/// Read an automerge document and print a JSON representation of the changes in it to stdout
|
||||||
ExamineSync { input_file: Option<PathBuf> },
|
Examine { input_file: Option<PathBuf> },
|
||||||
|
|
||||||
/// Read one or more automerge documents and output a merged, compacted version of them
|
/// Read one or more automerge documents and output a merged, compacted version of them
|
||||||
Merge {
|
Merge {
|
||||||
/// The file to write to. If omitted assumes stdout
|
/// The file to write to. If omitted assumes stdout
|
||||||
#[clap(long("out"), short('o'))]
|
#[clap(parse(from_os_str), long("out"), short('o'))]
|
||||||
output_file: Option<PathBuf>,
|
output_file: Option<PathBuf>,
|
||||||
|
|
||||||
/// The file(s) to compact. If empty assumes stdin
|
/// The file(s) to compact. If empty assumes stdin
|
||||||
input: Vec<PathBuf>,
|
input: Vec<PathBuf>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_file_or_stdin(maybe_path: Option<PathBuf>) -> Result<Box<dyn std::io::Read>> {
|
fn open_file_or_stdin(maybe_path: Option<PathBuf>) -> Result<Box<dyn std::io::Read>> {
|
||||||
if std::io::stdin().is_terminal() {
|
if atty::is(atty::Stream::Stdin) {
|
||||||
if let Some(path) = maybe_path {
|
if let Some(path) = maybe_path {
|
||||||
Ok(Box::new(File::open(path).unwrap()))
|
Ok(Box::new(File::open(&path).unwrap()))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"Must provide file path if not providing input via stdin"
|
"Must provide file path if not providing input via stdin"
|
||||||
|
@ -144,9 +130,9 @@ fn open_file_or_stdin(maybe_path: Option<PathBuf>) -> Result<Box<dyn std::io::Re
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_file_or_stdout(maybe_path: Option<PathBuf>) -> Result<Box<dyn std::io::Write>> {
|
fn create_file_or_stdout(maybe_path: Option<PathBuf>) -> Result<Box<dyn std::io::Write>> {
|
||||||
if std::io::stdout().is_terminal() {
|
if atty::is(atty::Stream::Stdout) {
|
||||||
if let Some(path) = maybe_path {
|
if let Some(path) = maybe_path {
|
||||||
Ok(Box::new(File::create(path).unwrap()))
|
Ok(Box::new(File::create(&path).unwrap()))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Must provide file path if not piping to stdout"))
|
Err(anyhow!("Must provide file path if not piping to stdout"))
|
||||||
}
|
}
|
||||||
|
@ -163,22 +149,16 @@ fn main() -> Result<()> {
|
||||||
changes_file,
|
changes_file,
|
||||||
format,
|
format,
|
||||||
output_file,
|
output_file,
|
||||||
skip_verifying_heads,
|
|
||||||
} => {
|
} => {
|
||||||
let output: Box<dyn std::io::Write> = if let Some(output_file) = output_file {
|
let output: Box<dyn std::io::Write> = if let Some(output_file) = output_file {
|
||||||
Box::new(File::create(output_file)?)
|
Box::new(File::create(&output_file)?)
|
||||||
} else {
|
} else {
|
||||||
Box::new(std::io::stdout())
|
Box::new(std::io::stdout())
|
||||||
};
|
};
|
||||||
match format {
|
match format {
|
||||||
ExportFormat::Json => {
|
ExportFormat::Json => {
|
||||||
let mut in_buffer = open_file_or_stdin(changes_file)?;
|
let mut in_buffer = open_file_or_stdin(changes_file)?;
|
||||||
export::export_json(
|
export::export_json(&mut in_buffer, output, atty::is(atty::Stream::Stdout))
|
||||||
&mut in_buffer,
|
|
||||||
output,
|
|
||||||
skip_verifying_heads,
|
|
||||||
std::io::stdout().is_terminal(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ExportFormat::Toml => unimplemented!(),
|
ExportFormat::Toml => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -195,30 +175,23 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
ExportFormat::Toml => unimplemented!(),
|
ExportFormat::Toml => unimplemented!(),
|
||||||
},
|
},
|
||||||
Command::Examine {
|
Command::Change { ..
|
||||||
input_file,
|
//input_file,
|
||||||
skip_verifying_heads,
|
//output_file,
|
||||||
|
//script,
|
||||||
} => {
|
} => {
|
||||||
|
unimplemented!()
|
||||||
|
/*
|
||||||
let in_buffer = open_file_or_stdin(input_file)?;
|
let in_buffer = open_file_or_stdin(input_file)?;
|
||||||
let out_buffer = std::io::stdout();
|
let mut out_buffer = create_file_or_stdout(output_file)?;
|
||||||
match examine::examine(
|
change::change(in_buffer, &mut out_buffer, script.as_str())
|
||||||
in_buffer,
|
.map_err(|e| anyhow::format_err!("Unable to make changes: {:?}", e))
|
||||||
out_buffer,
|
*/
|
||||||
skip_verifying_heads,
|
|
||||||
std::io::stdout().is_terminal(),
|
|
||||||
) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Command::ExamineSync { input_file } => {
|
Command::Examine { input_file } => {
|
||||||
let in_buffer = open_file_or_stdin(input_file)?;
|
let in_buffer = open_file_or_stdin(input_file)?;
|
||||||
let out_buffer = std::io::stdout();
|
let out_buffer = std::io::stdout();
|
||||||
match examine_sync::examine_sync(in_buffer, out_buffer, std::io::stdout().is_terminal())
|
match examine::examine(in_buffer, out_buffer, atty::is(atty::Stream::Stdout)) {
|
||||||
{
|
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error: {:?}", e);
|
eprintln!("Error: {:?}", e);
|
|
@ -1,6 +1,3 @@
|
||||||
/node_modules
|
/node_modules
|
||||||
/yarn.lock
|
/yarn.lock
|
||||||
dist
|
dist
|
||||||
docs/
|
|
||||||
.vim
|
|
||||||
deno_dist/
|
|
25
automerge-js/README.md
Normal file
25
automerge-js/README.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
## Automerge JS
|
||||||
|
|
||||||
|
This is a reimplementation of Automerge as a JavaScript wrapper around the "automerge-wasm".
|
||||||
|
|
||||||
|
This package is in alpha and feedback in welcome.
|
||||||
|
|
||||||
|
The primary differences between using this package and "automerge" are as follows:
|
||||||
|
|
||||||
|
1. The low level api needs to plugged in via the use function. The only current implementation of "automerge-wasm" but another could used in theory.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import * as Automerge from "automerge-js";
|
||||||
|
import * as wasm_api from "automerge-wasm";
|
||||||
|
|
||||||
|
// browsers require an async wasm load - see automerge-wasm docs
|
||||||
|
Automerge.use(wasm_api);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. There is no front-end back-end split, and no patch format or patch observer. These concepts don't make sense with the wasm implementation.
|
||||||
|
|
||||||
|
3. The basic `Doc<T>` object is now a Proxy object and will behave differently in a repl environment.
|
||||||
|
|
||||||
|
4. The 'Text' class is currently very slow and needs to be re-worked.
|
||||||
|
|
||||||
|
Beyond this please refer to the Automerge [README](http://github.com/automerge/automerge/) for further information.
|
6
automerge-js/config/cjs.json
Normal file
6
automerge-js/config/cjs.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../dist/cjs"
|
||||||
|
}
|
||||||
|
}
|
8
automerge-js/config/mjs.json
Normal file
8
automerge-js/config/mjs.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "es6",
|
||||||
|
"outDir": "../dist/mjs"
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ yarn e2e buildexamples -e webpack
|
||||||
If you're experimenting with a project which is not in the `examples` folder
|
If you're experimenting with a project which is not in the `examples` folder
|
||||||
you'll need a running registry. `run-registry` builds and publishes
|
you'll need a running registry. `run-registry` builds and publishes
|
||||||
`automerge-js` and `automerge-wasm` and then runs the registry at
|
`automerge-js` and `automerge-wasm` and then runs the registry at
|
||||||
`localhost:4873`.
|
`localhost:4873`.
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn e2e run-registry
|
yarn e2e run-registry
|
||||||
|
@ -63,6 +63,7 @@ yarn e2e run-registry
|
||||||
You can now run `yarn install --registry http://localhost:4873` to experiment
|
You can now run `yarn install --registry http://localhost:4873` to experiment
|
||||||
with the built packages.
|
with the built packages.
|
||||||
|
|
||||||
|
|
||||||
## Using the `dev` build of `automerge-wasm`
|
## Using the `dev` build of `automerge-wasm`
|
||||||
|
|
||||||
All the commands above take a `-p` flag which can be either `release` or
|
All the commands above take a `-p` flag which can be either `release` or
|
430
automerge-js/e2e/index.ts
Normal file
430
automerge-js/e2e/index.ts
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
import {once} from "events"
|
||||||
|
import {setTimeout} from "timers/promises"
|
||||||
|
import {spawn, ChildProcess} from "child_process"
|
||||||
|
import * as child_process from "child_process"
|
||||||
|
import {command, subcommands, run, array, multioption, option, Type} from "cmd-ts"
|
||||||
|
import * as path from "path"
|
||||||
|
import * as fsPromises from "fs/promises"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
|
||||||
|
const VERDACCIO_DB_PATH = path.normalize(`${__dirname}/verdacciodb`)
|
||||||
|
const VERDACCIO_CONFIG_PATH = path.normalize(`${__dirname}/verdaccio.yaml`)
|
||||||
|
const AUTOMERGE_WASM_PATH = path.normalize(`${__dirname}/../../automerge-wasm`)
|
||||||
|
const AUTOMERGE_JS_PATH = path.normalize(`${__dirname}/..`)
|
||||||
|
const EXAMPLES_DIR = path.normalize(path.join(__dirname, "../", "examples"))
|
||||||
|
|
||||||
|
// The different example projects in "../examples"
|
||||||
|
type Example = "webpack" | "vite"
|
||||||
|
|
||||||
|
// Type to parse strings to `Example` so the types line up for the `buildExamples` commmand
|
||||||
|
const ReadExample: Type<string, Example> = {
|
||||||
|
async from(str) {
|
||||||
|
if (str === "webpack") {
|
||||||
|
return "webpack"
|
||||||
|
} else if (str === "vite") {
|
||||||
|
return "vite"
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown example type ${str}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Profile = "dev" | "release"
|
||||||
|
|
||||||
|
const ReadProfile: Type<string, Profile> = {
|
||||||
|
async from(str) {
|
||||||
|
if (str === "dev") {
|
||||||
|
return "dev"
|
||||||
|
} else if (str === "release") {
|
||||||
|
return "release"
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown profile ${str}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildjs = command({
|
||||||
|
name: "buildjs",
|
||||||
|
args: {
|
||||||
|
profile: option({
|
||||||
|
type: ReadProfile,
|
||||||
|
long: "profile",
|
||||||
|
short: "p",
|
||||||
|
defaultValue: () => "dev" as Profile
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handler: ({profile}) => {
|
||||||
|
console.log("building js")
|
||||||
|
withPublishedWasm(profile, async (registryUrl: string) => {
|
||||||
|
await buildAndPublishAutomergeJs(registryUrl)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildWasm = command({
|
||||||
|
name: "buildwasm",
|
||||||
|
args: {
|
||||||
|
profile: option({
|
||||||
|
type: ReadProfile,
|
||||||
|
long: "profile",
|
||||||
|
short: "p",
|
||||||
|
defaultValue: () => "dev" as Profile
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handler: ({profile}) => {
|
||||||
|
console.log("building automerge-wasm")
|
||||||
|
withRegistry(
|
||||||
|
buildAutomergeWasm(profile),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildexamples = command({
|
||||||
|
name: "buildexamples",
|
||||||
|
args: {
|
||||||
|
examples: multioption({
|
||||||
|
long: "example",
|
||||||
|
short: "e",
|
||||||
|
type: array(ReadExample),
|
||||||
|
}),
|
||||||
|
profile: option({
|
||||||
|
type: ReadProfile,
|
||||||
|
long: "profile",
|
||||||
|
short: "p",
|
||||||
|
defaultValue: () => "dev" as Profile
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handler: ({examples, profile}) => {
|
||||||
|
if (examples.length === 0) {
|
||||||
|
examples = ["webpack", "vite"]
|
||||||
|
}
|
||||||
|
buildExamples(examples, profile)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const runRegistry = command({
|
||||||
|
name: "run-registry",
|
||||||
|
args: {
|
||||||
|
profile: option({
|
||||||
|
type: ReadProfile,
|
||||||
|
long: "profile",
|
||||||
|
short: "p",
|
||||||
|
defaultValue: () => "dev" as Profile
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handler: ({profile}) => {
|
||||||
|
withPublishedWasm(profile, async (registryUrl: string) => {
|
||||||
|
await buildAndPublishAutomergeJs(registryUrl)
|
||||||
|
console.log("\n************************")
|
||||||
|
console.log(` Verdaccio NPM registry is running at ${registryUrl}`)
|
||||||
|
console.log(" press CTRL-C to exit ")
|
||||||
|
console.log("************************")
|
||||||
|
await once(process, "SIGINT")
|
||||||
|
}).catch(e => {
|
||||||
|
console.error(`Failed: ${e}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const app = subcommands({
|
||||||
|
name: "e2e",
|
||||||
|
cmds: {buildjs, buildexamples, buildwasm: buildWasm, "run-registry": runRegistry}
|
||||||
|
})
|
||||||
|
|
||||||
|
run(app, process.argv.slice(2))
|
||||||
|
|
||||||
|
async function buildExamples(examples: Array<Example>, profile: Profile) {
|
||||||
|
await withPublishedWasm(profile, async (registryUrl) => {
|
||||||
|
printHeader("building and publishing automerge")
|
||||||
|
await buildAndPublishAutomergeJs(registryUrl)
|
||||||
|
for (const example of examples) {
|
||||||
|
printHeader(`building ${example} example`)
|
||||||
|
if (example === "webpack") {
|
||||||
|
const projectPath = path.join(EXAMPLES_DIR, example)
|
||||||
|
await removeExistingAutomerge(projectPath)
|
||||||
|
await fsPromises.rm(path.join(projectPath, "yarn.lock"), {force: true})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", projectPath, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", projectPath, "build"], {stdio: "inherit"})
|
||||||
|
} else if (example === "vite") {
|
||||||
|
const projectPath = path.join(EXAMPLES_DIR, example)
|
||||||
|
await removeExistingAutomerge(projectPath)
|
||||||
|
await fsPromises.rm(path.join(projectPath, "yarn.lock"), {force: true})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", projectPath, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", projectPath, "build"], {stdio: "inherit"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithRegistryAction = (registryUrl: string) => Promise<void>
|
||||||
|
|
||||||
|
async function withRegistry(action: WithRegistryAction, ...actions: Array<WithRegistryAction>) {
|
||||||
|
// First, start verdaccio
|
||||||
|
printHeader("Starting verdaccio NPM server")
|
||||||
|
const verd = await VerdaccioProcess.start()
|
||||||
|
actions.unshift(action)
|
||||||
|
|
||||||
|
for (const action of actions) {
|
||||||
|
try {
|
||||||
|
type Step = "verd-died" | "action-completed"
|
||||||
|
const verdDied: () => Promise<Step> = async () => {
|
||||||
|
await verd.died()
|
||||||
|
return "verd-died"
|
||||||
|
}
|
||||||
|
const actionComplete: () => Promise<Step> = async () => {
|
||||||
|
await action("http://localhost:4873")
|
||||||
|
return "action-completed"
|
||||||
|
}
|
||||||
|
const result = await Promise.race([verdDied(), actionComplete()])
|
||||||
|
if (result === "verd-died") {
|
||||||
|
throw new Error("verdaccio unexpectedly exited")
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
await verd.kill()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await verd.kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function withPublishedWasm(profile: Profile, action: WithRegistryAction) {
|
||||||
|
await withRegistry(
|
||||||
|
buildAutomergeWasm(profile),
|
||||||
|
publishAutomergeWasm,
|
||||||
|
action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildAutomergeWasm(profile: Profile): WithRegistryAction {
|
||||||
|
return async (registryUrl: string) => {
|
||||||
|
printHeader("building automerge-wasm")
|
||||||
|
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_WASM_PATH, "--registry", registryUrl, "install"], {stdio: "inherit"})
|
||||||
|
const cmd = profile === "release" ? "release" : "debug"
|
||||||
|
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_WASM_PATH, cmd], {stdio: "inherit"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function publishAutomergeWasm(registryUrl: string) {
|
||||||
|
printHeader("Publishing automerge-wasm to verdaccio")
|
||||||
|
await fsPromises.rm(path.join(VERDACCIO_DB_PATH, "automerge-wasm"), { recursive: true, force: true} )
|
||||||
|
await yarnPublish(registryUrl, AUTOMERGE_WASM_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildAndPublishAutomergeJs(registryUrl: string) {
|
||||||
|
// Build the js package
|
||||||
|
printHeader("Building automerge")
|
||||||
|
await removeExistingAutomerge(AUTOMERGE_JS_PATH)
|
||||||
|
await removeFromVerdaccio("automerge")
|
||||||
|
await fsPromises.rm(path.join(AUTOMERGE_JS_PATH, "yarn.lock"), {force: true})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_JS_PATH, "install", "--registry", registryUrl, "--check-files"], {stdio: "inherit"})
|
||||||
|
await spawnAndWait("yarn", ["--cwd", AUTOMERGE_JS_PATH, "build"], {stdio: "inherit"})
|
||||||
|
await yarnPublish(registryUrl, AUTOMERGE_JS_PATH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A running verdaccio process
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class VerdaccioProcess {
|
||||||
|
child: ChildProcess
|
||||||
|
stdout: Array<Buffer>
|
||||||
|
stderr: Array<Buffer>
|
||||||
|
|
||||||
|
constructor(child: ChildProcess) {
|
||||||
|
this.child = child
|
||||||
|
|
||||||
|
// Collect stdout/stderr otherwise the subprocess gets blocked writing
|
||||||
|
this.stdout = []
|
||||||
|
this.stderr = []
|
||||||
|
this.child.stdout && this.child.stdout.on("data", (data) => this.stdout.push(data))
|
||||||
|
this.child.stderr && this.child.stderr.on("data", (data) => this.stderr.push(data))
|
||||||
|
|
||||||
|
const errCallback = (e: any) => {
|
||||||
|
console.error("!!!!!!!!!ERROR IN VERDACCIO PROCESS!!!!!!!!!")
|
||||||
|
console.error(" ", e)
|
||||||
|
if (this.stdout.length > 0) {
|
||||||
|
console.log("\n**Verdaccio stdout**")
|
||||||
|
const stdout = Buffer.concat(this.stdout)
|
||||||
|
process.stdout.write(stdout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stderr.length > 0) {
|
||||||
|
console.log("\n**Verdaccio stderr**")
|
||||||
|
const stdout = Buffer.concat(this.stderr)
|
||||||
|
process.stdout.write(stdout)
|
||||||
|
}
|
||||||
|
process.exit(-1)
|
||||||
|
}
|
||||||
|
this.child.on("error", errCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a verdaccio process and wait for it to respond succesfully to http requests
|
||||||
|
*
|
||||||
|
* The returned `VerdaccioProcess` can be used to control the subprocess
|
||||||
|
*/
|
||||||
|
static async start() {
|
||||||
|
const child = spawn("yarn", ["verdaccio", "--config", VERDACCIO_CONFIG_PATH], {env: { ...process.env, FORCE_COLOR: "true"}})
|
||||||
|
|
||||||
|
// Forward stdout and stderr whilst waiting for startup to complete
|
||||||
|
const stdoutCallback = (data: Buffer) => process.stdout.write(data)
|
||||||
|
const stderrCallback = (data: Buffer) => process.stderr.write(data)
|
||||||
|
child.stdout && child.stdout.on("data", stdoutCallback)
|
||||||
|
child.stderr && child.stderr.on("data", stderrCallback)
|
||||||
|
|
||||||
|
const healthCheck = async () => {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch("http://localhost:4873")
|
||||||
|
if (resp.status === 200) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
console.log(`Healthcheck failed: bad status ${resp.status}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Healthcheck failed: ${e}`)
|
||||||
|
}
|
||||||
|
await setTimeout(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await withTimeout(healthCheck(), 10000)
|
||||||
|
|
||||||
|
// Stop forwarding stdout/stderr
|
||||||
|
child.stdout && child.stdout.off("data", stdoutCallback)
|
||||||
|
child.stderr && child.stderr.off("data", stderrCallback)
|
||||||
|
return new VerdaccioProcess(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a SIGKILL to the process and wait for it to stop
|
||||||
|
*/
|
||||||
|
async kill() {
|
||||||
|
this.child.stdout && this.child.stdout.destroy()
|
||||||
|
this.child.stderr && this.child.stderr.destroy()
|
||||||
|
this.child.kill();
|
||||||
|
try {
|
||||||
|
await withTimeout(once(this.child, "close"), 500)
|
||||||
|
} catch (e) {
|
||||||
|
console.error("unable to kill verdaccio subprocess, trying -9")
|
||||||
|
this.child.kill(9)
|
||||||
|
await withTimeout(once(this.child, "close"), 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A promise which resolves if the subprocess exits for some reason
|
||||||
|
*/
|
||||||
|
async died(): Promise<number | null> {
|
||||||
|
const [exit, _signal] = await once(this.child, "exit")
|
||||||
|
return exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printHeader(header: string) {
|
||||||
|
console.log("\n===============================")
|
||||||
|
console.log(` ${header}`)
|
||||||
|
console.log("===============================")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the automerge, automerge-wasm, and automerge-js packages from
|
||||||
|
* `$packageDir/node_modules`
|
||||||
|
*
|
||||||
|
* This is useful to force refreshing a package by use in combination with
|
||||||
|
* `yarn install --check-files`, which checks if a package is present in
|
||||||
|
* `node_modules` and if it is not forces a reinstall.
|
||||||
|
*
|
||||||
|
* @param packageDir - The directory containing the package.json of the target project
|
||||||
|
*/
|
||||||
|
async function removeExistingAutomerge(packageDir: string) {
|
||||||
|
await fsPromises.rm(path.join(packageDir, "node_modules", "automerge-wasm"), {recursive: true, force: true})
|
||||||
|
await fsPromises.rm(path.join(packageDir, "node_modules", "automerge"), {recursive: true, force: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpawnResult = {
|
||||||
|
stdout?: Buffer,
|
||||||
|
stderr?: Buffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
async function spawnAndWait(cmd: string, args: Array<string>, options: child_process.SpawnOptions): Promise<SpawnResult> {
|
||||||
|
const child = spawn(cmd, args, options)
|
||||||
|
let stdout = null
|
||||||
|
let stderr = null
|
||||||
|
if (child.stdout) {
|
||||||
|
stdout = []
|
||||||
|
child.stdout.on("data", data => stdout.push(data))
|
||||||
|
}
|
||||||
|
if (child.stderr) {
|
||||||
|
stderr = []
|
||||||
|
child.stderr.on("data", data => stderr.push(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
const [exit, _signal] = await once(child, "exit")
|
||||||
|
if (exit && exit !== 0) {
|
||||||
|
throw new Error("nonzero exit code")
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
stderr: stderr? Buffer.concat(stderr) : null,
|
||||||
|
stdout: stdout ? Buffer.concat(stdout) : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a package from the verdaccio registry. This is necessary because we
|
||||||
|
* often want to _replace_ a version rather than update the version number.
|
||||||
|
* Obviously this is very bad and verboten in normal circumastances, but the
|
||||||
|
* whole point here is to be able to test the entire packaging story so it's
|
||||||
|
* okay I Promise.
|
||||||
|
*/
|
||||||
|
async function removeFromVerdaccio(packageName: string) {
|
||||||
|
await fsPromises.rm(path.join(VERDACCIO_DB_PATH, packageName), {force: true, recursive: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function yarnPublish(registryUrl: string, cwd: string) {
|
||||||
|
await spawnAndWait(
|
||||||
|
"yarn",
|
||||||
|
[
|
||||||
|
"--registry",
|
||||||
|
registryUrl,
|
||||||
|
"--cwd",
|
||||||
|
cwd,
|
||||||
|
"publish",
|
||||||
|
"--non-interactive",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
stdio: "inherit",
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
FORCE_COLOR: "true",
|
||||||
|
// This is a fake token, it just has to be the right format
|
||||||
|
npm_config__auth: "//localhost:4873/:_authToken=Gp2Mgxm4faa/7wp0dMSuRA=="
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a given delay to resolve a promise, throwing an error if the
|
||||||
|
* promise doesn't resolve with the timeout
|
||||||
|
*
|
||||||
|
* @param promise - the promise to wait for @param timeout - the delay in
|
||||||
|
* milliseconds to wait before throwing
|
||||||
|
*/
|
||||||
|
async function withTimeout<T>(promise: Promise<T>, timeout: number): Promise<T> {
|
||||||
|
type Step = "timed-out" | {result: T}
|
||||||
|
const timedOut: () => Promise<Step> = async () => {
|
||||||
|
await setTimeout(timeout)
|
||||||
|
return "timed-out"
|
||||||
|
}
|
||||||
|
const succeeded: () => Promise<Step> = async () => {
|
||||||
|
const result = await promise
|
||||||
|
return {result}
|
||||||
|
}
|
||||||
|
const result = await Promise.race([timedOut(), succeeded()])
|
||||||
|
if (result === "timed-out") {
|
||||||
|
throw new Error("timed out")
|
||||||
|
} else {
|
||||||
|
return result.result
|
||||||
|
}
|
||||||
|
}
|
6
automerge-js/e2e/tsconfig.json
Normal file
6
automerge-js/e2e/tsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"module": "nodenext"
|
||||||
|
}
|
25
automerge-js/e2e/verdaccio.yaml
Normal file
25
automerge-js/e2e/verdaccio.yaml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
storage: "./verdacciodb"
|
||||||
|
auth:
|
||||||
|
htpasswd:
|
||||||
|
file: ./htpasswd
|
||||||
|
publish:
|
||||||
|
allow_offline: true
|
||||||
|
logs: {type: stdout, format: pretty, level: info}
|
||||||
|
packages:
|
||||||
|
"automerge-wasm":
|
||||||
|
access: "$all"
|
||||||
|
publish: "$all"
|
||||||
|
"automerge-js":
|
||||||
|
access: "$all"
|
||||||
|
publish: "$all"
|
||||||
|
"*":
|
||||||
|
access: "$all"
|
||||||
|
publish: "$all"
|
||||||
|
proxy: npmjs
|
||||||
|
"@*/*":
|
||||||
|
access: "$all"
|
||||||
|
publish: "$all"
|
||||||
|
proxy: npmjs
|
||||||
|
uplinks:
|
||||||
|
npmjs:
|
||||||
|
url: https://registry.npmjs.org/
|
|
@ -1,15 +1,15 @@
|
||||||
import * as Automerge from "/node_modules/.vite/deps/automerge-js.js?v=6e973f28"
|
import * as Automerge from "/node_modules/.vite/deps/automerge-js.js?v=6e973f28";
|
||||||
console.log(Automerge)
|
console.log(Automerge);
|
||||||
let doc = Automerge.init()
|
let doc = Automerge.init();
|
||||||
doc = Automerge.change(doc, d => (d.hello = "from automerge-js"))
|
doc = Automerge.change(doc, (d) => d.hello = "from automerge-js");
|
||||||
console.log(doc)
|
console.log(doc);
|
||||||
const result = JSON.stringify(doc)
|
const result = JSON.stringify(doc);
|
||||||
if (typeof document !== "undefined") {
|
if (typeof document !== "undefined") {
|
||||||
const element = document.createElement("div")
|
const element = document.createElement("div");
|
||||||
element.innerHTML = JSON.stringify(result)
|
element.innerHTML = JSON.stringify(result);
|
||||||
document.body.appendChild(element)
|
document.body.appendChild(element);
|
||||||
} else {
|
} else {
|
||||||
console.log("node:", result)
|
console.log("node:", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL2FsZXgvUHJvamVjdHMvYXV0b21lcmdlL2F1dG9tZXJnZS1ycy9hdXRvbWVyZ2UtanMvZXhhbXBsZXMvdml0ZS9zcmMvbWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBBdXRvbWVyZ2UgZnJvbSBcImF1dG9tZXJnZS1qc1wiXG5cbi8vIGhlbGxvIHdvcmxkIGNvZGUgdGhhdCB3aWxsIHJ1biBjb3JyZWN0bHkgb24gd2ViIG9yIG5vZGVcblxuY29uc29sZS5sb2coQXV0b21lcmdlKVxubGV0IGRvYyA9IEF1dG9tZXJnZS5pbml0KClcbmRvYyA9IEF1dG9tZXJnZS5jaGFuZ2UoZG9jLCAoZDogYW55KSA9PiBkLmhlbGxvID0gXCJmcm9tIGF1dG9tZXJnZS1qc1wiKVxuY29uc29sZS5sb2coZG9jKVxuY29uc3QgcmVzdWx0ID0gSlNPTi5zdHJpbmdpZnkoZG9jKVxuXG5pZiAodHlwZW9mIGRvY3VtZW50ICE9PSAndW5kZWZpbmVkJykge1xuICAgIC8vIGJyb3dzZXJcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgZWxlbWVudC5pbm5lckhUTUwgPSBKU09OLnN0cmluZ2lmeShyZXN1bHQpXG4gICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChlbGVtZW50KTtcbn0gZWxzZSB7XG4gICAgLy8gc2VydmVyXG4gICAgY29uc29sZS5sb2coXCJub2RlOlwiLCByZXN1bHQpXG59XG5cbiJdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWSxlQUFlO0FBSTNCLFFBQVEsSUFBSSxTQUFTO0FBQ3JCLElBQUksTUFBTSxVQUFVLEtBQUs7QUFDekIsTUFBTSxVQUFVLE9BQU8sS0FBSyxDQUFDLE1BQVcsRUFBRSxRQUFRLG1CQUFtQjtBQUNyRSxRQUFRLElBQUksR0FBRztBQUNmLE1BQU0sU0FBUyxLQUFLLFVBQVUsR0FBRztBQUVqQyxJQUFJLE9BQU8sYUFBYSxhQUFhO0FBRWpDLFFBQU0sVUFBVSxTQUFTLGNBQWMsS0FBSztBQUM1QyxVQUFRLFlBQVksS0FBSyxVQUFVLE1BQU07QUFDekMsV0FBUyxLQUFLLFlBQVksT0FBTztBQUNyQyxPQUFPO0FBRUgsVUFBUSxJQUFJLFNBQVMsTUFBTTtBQUMvQjsiLCJuYW1lcyI6W119
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi9ob21lL2FsZXgvUHJvamVjdHMvYXV0b21lcmdlL2F1dG9tZXJnZS1ycy9hdXRvbWVyZ2UtanMvZXhhbXBsZXMvdml0ZS9zcmMvbWFpbi50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBBdXRvbWVyZ2UgZnJvbSBcImF1dG9tZXJnZS1qc1wiXG5cbi8vIGhlbGxvIHdvcmxkIGNvZGUgdGhhdCB3aWxsIHJ1biBjb3JyZWN0bHkgb24gd2ViIG9yIG5vZGVcblxuY29uc29sZS5sb2coQXV0b21lcmdlKVxubGV0IGRvYyA9IEF1dG9tZXJnZS5pbml0KClcbmRvYyA9IEF1dG9tZXJnZS5jaGFuZ2UoZG9jLCAoZDogYW55KSA9PiBkLmhlbGxvID0gXCJmcm9tIGF1dG9tZXJnZS1qc1wiKVxuY29uc29sZS5sb2coZG9jKVxuY29uc3QgcmVzdWx0ID0gSlNPTi5zdHJpbmdpZnkoZG9jKVxuXG5pZiAodHlwZW9mIGRvY3VtZW50ICE9PSAndW5kZWZpbmVkJykge1xuICAgIC8vIGJyb3dzZXJcbiAgICBjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7XG4gICAgZWxlbWVudC5pbm5lckhUTUwgPSBKU09OLnN0cmluZ2lmeShyZXN1bHQpXG4gICAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChlbGVtZW50KTtcbn0gZWxzZSB7XG4gICAgLy8gc2VydmVyXG4gICAgY29uc29sZS5sb2coXCJub2RlOlwiLCByZXN1bHQpXG59XG5cbiJdLCJtYXBwaW5ncyI6IkFBQUEsWUFBWSxlQUFlO0FBSTNCLFFBQVEsSUFBSSxTQUFTO0FBQ3JCLElBQUksTUFBTSxVQUFVLEtBQUs7QUFDekIsTUFBTSxVQUFVLE9BQU8sS0FBSyxDQUFDLE1BQVcsRUFBRSxRQUFRLG1CQUFtQjtBQUNyRSxRQUFRLElBQUksR0FBRztBQUNmLE1BQU0sU0FBUyxLQUFLLFVBQVUsR0FBRztBQUVqQyxJQUFJLE9BQU8sYUFBYSxhQUFhO0FBRWpDLFFBQU0sVUFBVSxTQUFTLGNBQWMsS0FBSztBQUM1QyxVQUFRLFlBQVksS0FBSyxVQUFVLE1BQU07QUFDekMsV0FBUyxLQUFLLFlBQVksT0FBTztBQUNyQyxPQUFPO0FBRUgsVUFBUSxJQUFJLFNBQVMsTUFBTTtBQUMvQjsiLCJuYW1lcyI6W119
|
|
@ -9,7 +9,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automerge/automerge": "2.0.0-alpha.7"
|
"automerge": "1.0.1-preview.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.6.4",
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -4,6 +4,6 @@ export function setupCounter(element: HTMLButtonElement) {
|
||||||
counter = count
|
counter = count
|
||||||
element.innerHTML = `count is ${counter}`
|
element.innerHTML = `count is ${counter}`
|
||||||
}
|
}
|
||||||
element.addEventListener("click", () => setCounter(++counter))
|
element.addEventListener('click', () => setCounter(++counter))
|
||||||
setCounter(0)
|
setCounter(0)
|
||||||
}
|
}
|
18
automerge-js/examples/vite/src/main.ts
Normal file
18
automerge-js/examples/vite/src/main.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import * as Automerge from "automerge"
|
||||||
|
|
||||||
|
// hello world code that will run correctly on web or node
|
||||||
|
|
||||||
|
let doc = Automerge.init()
|
||||||
|
doc = Automerge.change(doc, (d: any) => d.hello = "from automerge-js")
|
||||||
|
const result = JSON.stringify(doc)
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
// browser
|
||||||
|
const element = document.createElement('div');
|
||||||
|
element.innerHTML = JSON.stringify(result)
|
||||||
|
document.body.appendChild(element);
|
||||||
|
} else {
|
||||||
|
// server
|
||||||
|
console.log("node:", result)
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue