Merge crates

This commit is contained in:
Andras Schmelczer 2025-06-15 11:30:07 +01:00
parent 82e77eec89
commit bcbac03228
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
60 changed files with 73 additions and 248 deletions

42
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -206,11 +200,14 @@ dependencies = [
name = "reconcile"
version = "0.4.0"
dependencies = [
"console_error_panic_hook",
"insta",
"pretty_assertions",
"serde",
"serde_yaml",
"test-case",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
@ -290,19 +287,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_lib"
version = "0.4.0"
dependencies = [
"base64",
"console_error_panic_hook",
"insta",
"reconcile",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-test",
]
[[package]]
name = "test-case"
version = "3.3.1"
@ -336,26 +320,6 @@ dependencies = [
"test-case-core",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"

View file

@ -1,21 +1,38 @@
[workspace]
resolver = "2"
members = [
"reconcile",
"sync_lib"
]
[workspace.package]
rust-version = "1.83"
[package]
name = "reconcile"
rust-version = "1.85"
authors = ["Andras Schmelczer <andras@schmelczer.dev>"]
edition = "2024"
license = "MIT"
repository = "https://github.com/schmelczer/vault-link"
repository = "https://github.com/schmelczer/reconcile"
version = "0.4.0"
[workspace.dependencies]
serde = { version = "1.0.219", default-features = false, features = ["derive"] }
thiserror = { version = "2.0.12", default-features = false }
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
serde = { version = "1.0.219", optional = true, features = ["derive"] }
wasm-bindgen = { version = "0.2.99", optional = true }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
[features]
default = ["serde", "wasm"]
serde = [ "dep:serde" ]
wasm = [ "dep:wasm-bindgen", "dep:console_error_panic_hook" ]
[dev-dependencies]
insta = "1.42.2"
pretty_assertions = "1.4.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_yaml ="0.9.34"
test-case = "3.3.1"
wasm-bindgen-test = "0.3.49"
[profile.release]
codegen-units = 1

View file

@ -1,23 +0,0 @@
[package]
name = "reconcile"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
serde = { version = "1.0.219", optional = true, features = ["derive"] }
[features]
serde = [ "dep:serde" ]
[dev-dependencies]
insta = "1.42.2"
pretty_assertions = "1.4.1"
serde = { version = "1.0.219", features = ["derive"] }
serde_yaml ="0.9.34"
test-case = "3.3.1"
[lints]
workspace = true

View file

@ -8,3 +8,6 @@ pub use operation_transformation::{
reconcile_with_tokenizer,
};
pub use tokenizer::{Tokenizer, token::Token, word_tokenizer::word_tokenizer};
#[cfg(feature = "wasm")]
pub mod wasm;

2
src/wasm.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod cursor;
pub mod lib;

View file

@ -3,27 +3,27 @@ use wasm_bindgen::prelude::*;
/// Wrapper type to expose `TextWithCursors` to JS.
#[wasm_bindgen]
#[derive(Debug, Clone, PartialEq)]
pub struct TextWithCursors {
pub struct JsTextWithCursors {
text: String,
cursors: Vec<CursorPosition>,
cursors: Vec<JsCursorPosition>,
}
#[wasm_bindgen]
impl TextWithCursors {
impl JsTextWithCursors {
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new(text: String, cursors: Vec<CursorPosition>) -> Self { Self { text, cursors } }
pub fn new(text: String, cursors: Vec<JsCursorPosition>) -> Self { Self { text, cursors } }
#[must_use]
pub fn text(&self) -> String { self.text.clone() }
#[must_use]
pub fn cursors(&self) -> Vec<CursorPosition> { self.cursors.clone() }
pub fn cursors(&self) -> Vec<JsCursorPosition> { self.cursors.clone() }
}
impl From<TextWithCursors> for reconcile::TextWithCursors<'_> {
fn from(owned: TextWithCursors) -> Self {
reconcile::TextWithCursors::new_owned(
impl From<JsTextWithCursors> for crate::TextWithCursors<'_> {
fn from(owned: JsTextWithCursors) -> Self {
crate::TextWithCursors::new_owned(
owned.text.to_string(),
owned
.cursors
@ -34,9 +34,9 @@ impl From<TextWithCursors> for reconcile::TextWithCursors<'_> {
}
}
impl From<reconcile::TextWithCursors<'_>> for TextWithCursors {
fn from(text_with_cursors: reconcile::TextWithCursors<'_>) -> Self {
TextWithCursors {
impl From<crate::TextWithCursors<'_>> for JsTextWithCursors {
fn from(text_with_cursors: crate::TextWithCursors<'_>) -> Self {
JsTextWithCursors {
text: text_with_cursors.text.into_owned(),
cursors: text_with_cursors
.cursors
@ -50,13 +50,13 @@ impl From<reconcile::TextWithCursors<'_>> for TextWithCursors {
/// Wrapper type to expose `CursorPosition` to JS.
#[wasm_bindgen]
#[derive(Debug, Clone, PartialEq)]
pub struct CursorPosition {
pub struct JsCursorPosition {
id: usize,
char_index: usize,
}
#[wasm_bindgen]
impl CursorPosition {
impl JsCursorPosition {
#[wasm_bindgen(constructor)]
#[must_use]
pub fn new(id: usize, char_index: usize) -> Self { Self { id, char_index } }
@ -69,18 +69,18 @@ impl CursorPosition {
pub fn char_index(&self) -> usize { self.char_index }
}
impl From<CursorPosition> for reconcile::CursorPosition {
fn from(owned: CursorPosition) -> Self {
reconcile::CursorPosition {
impl From<JsCursorPosition> for crate::CursorPosition {
fn from(owned: JsCursorPosition) -> Self {
crate::CursorPosition {
id: owned.id,
char_index: owned.char_index,
}
}
}
impl From<reconcile::CursorPosition> for CursorPosition {
fn from(cursor: reconcile::CursorPosition) -> Self {
CursorPosition {
impl From<crate::CursorPosition> for JsCursorPosition {
fn from(cursor: crate::CursorPosition) -> Self {
JsCursorPosition {
id: cursor.id,
char_index: cursor.char_index,
}

View file

@ -11,56 +11,9 @@
use core::str;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use cursor::TextWithCursors;
use errors::SyncLibError;
use wasm_bindgen::prelude::*;
pub mod cursor;
pub mod errors;
/// Encode binary data for easy transport over HTTP. Inverse of
/// `base64_to_bytes`.
///
/// # Arguments
///
/// - `input`: The binary data to encode.
///
/// # Returns
///
/// The base64-encoded string.
///
/// # Panics
///
/// If the input is not valid UTF-8.
#[wasm_bindgen(js_name = bytesToBase64)]
#[must_use]
pub fn bytes_to_base64(input: &[u8]) -> String {
set_panic_hook();
STANDARD.encode(input)
}
/// Inverse of `bytes_to_base64`.
/// Decode base64-encoded data into binary data.
///
/// # Arguments
///
/// - `input`: The base64-encoded string.
///
/// # Returns
///
/// The decoded binary data.
///
/// # Errors
///
/// If the input is not valid base64.
#[wasm_bindgen(js_name = base64ToBytes)]
pub fn base64_to_bytes(input: &str) -> Result<Vec<u8>, SyncLibError> {
set_panic_hook();
STANDARD.decode(input).map_err(SyncLibError::from)
}
use crate::wasm::cursor::JsTextWithCursors;
/// Merge two documents with a common parent. Relies on `reconcile::reconcile`
/// for texts and returns the right document as-is if either of the updated
@ -87,7 +40,7 @@ pub fn merge(parent: &[u8], left: &[u8], right: &[u8]) -> Vec<u8> {
if is_binary(parent) || is_binary(left) || is_binary(right) {
right.to_vec()
} else {
reconcile::reconcile(
crate::reconcile(
str::from_utf8(parent).expect("parent must be valid UTF-8 because it's not binary"),
str::from_utf8(left).expect("left must be valid UTF-8 because it's not binary"),
str::from_utf8(right).expect("right must be valid UTF-8 because it's not binary"),
@ -96,13 +49,13 @@ pub fn merge(parent: &[u8], left: &[u8], right: &[u8]) -> Vec<u8> {
}
}
/// WASM wrapper around `reconcile::reconcile` for merging text.
/// WASM wrapper around `crate::reconcile` for merging text.
#[wasm_bindgen(js_name = mergeText)]
#[must_use]
pub fn merge_text(parent: &str, left: &str, right: &str) -> String {
set_panic_hook();
reconcile::reconcile(parent, left, right)
crate::reconcile(parent, left, right)
}
/// WASM wrapper around `reconcile::reconcile_with_cursors` for merging text.
@ -110,12 +63,12 @@ pub fn merge_text(parent: &str, left: &str, right: &str) -> String {
#[must_use]
pub fn merge_text_with_cursors(
parent: &str,
left: TextWithCursors,
right: TextWithCursors,
) -> TextWithCursors {
left: JsTextWithCursors,
right: JsTextWithCursors,
) -> JsTextWithCursors {
set_panic_hook();
reconcile::reconcile_with_cursors(parent, left.into(), right.into()).into()
crate::reconcile_with_cursors(parent, left.into(), right.into()).into()
}
/// Heuristically determine if the given data is a binary or a text file's

View file

@ -1,32 +0,0 @@
[package]
name = "sync_lib"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
base64 = "0.22.1"
reconcile = { path = "../reconcile" }
wasm-bindgen = "0.2.99"
thiserror = { workspace = true }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.49"
insta = "1.42.2"
[features]
default = ["console_error_panic_hook"]
[lints]
workspace = true

View file

@ -1,29 +0,0 @@
use base64::DecodeError;
use thiserror::Error;
use wasm_bindgen::JsValue;
#[derive(Error, Debug)]
pub enum SyncLibError {
#[error("Base64 decoding error because of {}", .reason)]
Base64DecodingError { reason: String },
}
impl From<DecodeError> for SyncLibError {
fn from(e: DecodeError) -> Self {
SyncLibError::Base64DecodingError {
reason: e.to_string(),
}
}
}
impl From<std::string::FromUtf8Error> for SyncLibError {
fn from(e: std::string::FromUtf8Error) -> Self {
SyncLibError::Base64DecodingError {
reason: e.to_string(),
}
}
}
impl From<SyncLibError> for JsValue {
fn from(val: SyncLibError) -> Self { JsValue::from_str(&val.to_string()) }
}

View file

@ -1,10 +0,0 @@
---
source: sync_lib/tests/web.rs
expression: base64_to_bytes(input)
snapshot_kind: text
---
Err(
Base64DecodingError {
reason: "Invalid symbol 61, offset 0.",
},
)

View file

@ -1,30 +1,10 @@
use insta::assert_debug_snapshot;
use sync_lib::{
cursor::{CursorPosition, TextWithCursors},
*,
use reconcile::wasm::{
cursor::{JsCursorPosition, JsTextWithCursors},
lib::{is_binary, is_file_type_mergable, merge, merge_text, merge_text_with_cursors},
};
use wasm_bindgen_test::*;
#[wasm_bindgen_test(unsupported = test)]
fn test_bytes_to_base64() {
let input = b"hello";
let expected = "aGVsbG8=";
assert_eq!(bytes_to_base64(input), expected);
}
#[wasm_bindgen_test(unsupported = test)]
fn test_base64_to_bytes() {
let input = "aGVsbG8=";
let expected = b"hello".to_vec();
assert_eq!(base64_to_bytes(input).unwrap(), expected);
}
#[test] // insta doesn't support wasm-bindgen-test
fn test_base64_to_bytes_error() {
let input = "===";
assert_debug_snapshot!(base64_to_bytes(input));
}
#[wasm_bindgen_test(unsupported = test)]
fn test_merge() {
let left = b"hello ";
@ -50,18 +30,18 @@ fn test_merge_text() {
fn test_merge_text_with_cursors() {
let result = merge_text_with_cursors(
"hi",
TextWithCursors::new("hi world".to_owned(), vec![]),
TextWithCursors::new(
JsTextWithCursors::new("hi world".to_owned(), vec![]),
JsTextWithCursors::new(
"hi".to_owned(),
vec![CursorPosition::new(0, 1), CursorPosition::new(1, 2)],
vec![JsCursorPosition::new(0, 1), JsCursorPosition::new(1, 2)],
),
);
assert_eq!(
result,
TextWithCursors::new(
JsTextWithCursors::new(
"hi world".to_owned(),
vec![CursorPosition::new(0, 1), CursorPosition::new(1, 2)]
vec![JsCursorPosition::new(0, 1), JsCursorPosition::new(1, 2)]
),
);
}