Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8e83310ea3 | ||
|
149ab50b3e | ||
|
f06a3e3928 | ||
|
3bb4bd79b9 | ||
|
a09de2302b |
9 changed files with 349 additions and 292 deletions
|
@ -1,6 +1,6 @@
|
|||
extern crate web_sys;
|
||||
use automerge as am;
|
||||
use automerge::{Change, ChangeHash, Prop, Value};
|
||||
use automerge::{Change, ChangeHash, Prop, Value, ExId};
|
||||
use js_sys::{Array, Object, Reflect, Uint8Array};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
@ -151,9 +151,9 @@ impl Automerge {
|
|||
pub fn keys(&mut self, obj: JsValue, heads: JsValue) -> Result<Array, JsValue> {
|
||||
let obj = self.import(obj)?;
|
||||
let result = if let Some(heads) = get_heads(heads) {
|
||||
self.0.keys_at(obj, &heads)
|
||||
self.0.keys_at(&obj, &heads)
|
||||
} else {
|
||||
self.0.keys(obj)
|
||||
self.0.keys(&obj)
|
||||
}
|
||||
.iter()
|
||||
.map(|s| JsValue::from_str(s))
|
||||
|
@ -164,9 +164,9 @@ impl Automerge {
|
|||
pub fn text(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
|
||||
let obj = self.import(obj)?;
|
||||
if let Some(heads) = get_heads(heads) {
|
||||
self.0.text_at(obj, &heads)
|
||||
self.0.text_at(&obj, &heads)
|
||||
} else {
|
||||
self.0.text(obj)
|
||||
self.0.text(&obj)
|
||||
}
|
||||
.map_err(to_js_err)
|
||||
.map(|t| t.into())
|
||||
|
@ -185,7 +185,7 @@ impl Automerge {
|
|||
let mut vals = vec![];
|
||||
if let Some(t) = text.as_string() {
|
||||
self.0
|
||||
.splice_text(obj, start, delete_count, &t)
|
||||
.splice_text(&obj, start, delete_count, &t)
|
||||
.map_err(to_js_err)?;
|
||||
} else {
|
||||
if let Ok(array) = text.dyn_into::<Array>() {
|
||||
|
@ -201,7 +201,7 @@ impl Automerge {
|
|||
}
|
||||
}
|
||||
self.0
|
||||
.splice(obj, start, delete_count, vals)
|
||||
.splice(&obj, start, delete_count, vals)
|
||||
.map_err(to_js_err)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -223,9 +223,12 @@ impl Automerge {
|
|||
let value = self.import_value(value, datatype)?;
|
||||
let opid = self
|
||||
.0
|
||||
.insert(obj, index as usize, value)
|
||||
.insert(&obj, index as usize, value)
|
||||
.map_err(to_js_err)?;
|
||||
Ok(self.export(opid))
|
||||
match opid {
|
||||
Some(opid) => Ok(self.export(opid)),
|
||||
None => Ok(JsValue::null()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
|
@ -238,7 +241,7 @@ impl Automerge {
|
|||
let obj = self.import(obj)?;
|
||||
let prop = self.import_prop(prop)?;
|
||||
let value = self.import_value(value, datatype)?;
|
||||
let opid = self.0.set(obj, prop, value).map_err(to_js_err)?;
|
||||
let opid = self.0.set(&obj, prop, value).map_err(to_js_err)?;
|
||||
match opid {
|
||||
Some(opid) => Ok(self.export(opid)),
|
||||
None => Ok(JsValue::null()),
|
||||
|
@ -252,7 +255,7 @@ impl Automerge {
|
|||
.as_f64()
|
||||
.ok_or("inc needs a numberic value")
|
||||
.map_err(to_js_err)?;
|
||||
self.0.inc(obj, prop, value as i64).map_err(to_js_err)?;
|
||||
self.0.inc(&obj, prop, value as i64).map_err(to_js_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -263,9 +266,9 @@ impl Automerge {
|
|||
let heads = get_heads(heads);
|
||||
if let Ok(prop) = prop {
|
||||
let value = if let Some(h) = heads {
|
||||
self.0.value_at(obj, prop, &h)
|
||||
self.0.value_at(&obj, prop, &h)
|
||||
} else {
|
||||
self.0.value(obj, prop)
|
||||
self.0.value(&obj, prop)
|
||||
}
|
||||
.map_err(to_js_err)?;
|
||||
match value {
|
||||
|
@ -289,9 +292,9 @@ impl Automerge {
|
|||
let prop = to_prop(arg);
|
||||
if let Ok(prop) = prop {
|
||||
let values = if let Some(heads) = get_heads(heads) {
|
||||
self.0.values_at(obj, prop, &heads)
|
||||
self.0.values_at(&obj, prop, &heads)
|
||||
} else {
|
||||
self.0.values(obj, prop)
|
||||
self.0.values(&obj, prop)
|
||||
}
|
||||
.map_err(to_js_err)?;
|
||||
for value in values {
|
||||
|
@ -318,16 +321,16 @@ impl Automerge {
|
|||
pub fn length(&mut self, obj: JsValue, heads: JsValue) -> Result<JsValue, JsValue> {
|
||||
let obj = self.import(obj)?;
|
||||
if let Some(heads) = get_heads(heads) {
|
||||
Ok((self.0.length_at(obj, &heads) as f64).into())
|
||||
Ok((self.0.length_at(&obj, &heads) as f64).into())
|
||||
} else {
|
||||
Ok((self.0.length(obj) as f64).into())
|
||||
Ok((self.0.length(&obj) as f64).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn del(&mut self, obj: JsValue, prop: JsValue) -> Result<(), JsValue> {
|
||||
let obj = self.import(obj)?;
|
||||
let prop = to_prop(prop)?;
|
||||
self.0.del(obj, prop).map_err(to_js_err)?;
|
||||
self.0.del(&obj, prop).map_err(to_js_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -442,11 +445,11 @@ impl Automerge {
|
|||
}
|
||||
}
|
||||
|
||||
fn export<E: automerge::Exportable>(&self, val: E) -> JsValue {
|
||||
self.0.export(val).into()
|
||||
fn export(&self, val: ExId) -> JsValue {
|
||||
val.to_string().into()
|
||||
}
|
||||
|
||||
fn import<I: automerge::Importable>(&self, id: JsValue) -> Result<I, JsValue> {
|
||||
fn import(&self, id: JsValue) -> Result<ExId, JsValue> {
|
||||
let id_str = id
|
||||
.as_string()
|
||||
.ok_or("invalid opid/objid/elemid")
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::encoding::{Encodable, DEFLATE_MIN_SIZE};
|
|||
use crate::legacy as amp;
|
||||
use crate::{
|
||||
ActorId, AutomergeError, ElemId, IndexedCache, Key, ObjId, Op, OpId, OpType, Transaction, HEAD,
|
||||
ROOT,
|
||||
};
|
||||
use core::ops::Range;
|
||||
use flate2::{
|
||||
|
@ -417,7 +416,7 @@ fn increment_range_map(ranges: &mut HashMap<u32, Range<usize>>, len: usize) {
|
|||
}
|
||||
|
||||
fn export_objid(id: &ObjId, actors: &IndexedCache<ActorId>) -> amp::ObjectId {
|
||||
if id.0 == ROOT {
|
||||
if id == &ObjId::root() {
|
||||
amp::ObjectId::Root
|
||||
} else {
|
||||
export_opid(&id.0, actors).into()
|
||||
|
|
|
@ -11,7 +11,6 @@ use std::{
|
|||
str,
|
||||
};
|
||||
|
||||
use crate::ROOT;
|
||||
use crate::{ActorId, ElemId, Key, ObjId, ObjType, OpId, OpType, ScalarValue};
|
||||
|
||||
use crate::legacy as amp;
|
||||
|
@ -846,7 +845,7 @@ impl ObjEncoder {
|
|||
|
||||
fn append(&mut self, obj: &ObjId, actors: &[usize]) {
|
||||
match obj.0 {
|
||||
ROOT => {
|
||||
OpId(ctr, _) if ctr == 0 => {
|
||||
self.actor.append_null();
|
||||
self.ctr.append_null();
|
||||
}
|
||||
|
@ -951,7 +950,7 @@ impl ChangeEncoder {
|
|||
index_by_hash.insert(hash, index);
|
||||
}
|
||||
self.actor
|
||||
.append_value(actors.lookup(change.actor_id.clone()).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap());
|
||||
.append_value(actors.lookup(&change.actor_id).unwrap()); //actors.iter().position(|a| a == &change.actor_id).unwrap());
|
||||
self.seq.append_value(change.seq);
|
||||
// FIXME iterops.count is crazy slow
|
||||
self.max_op
|
||||
|
|
|
@ -17,6 +17,8 @@ pub enum AutomergeError {
|
|||
InvalidSeq(u64),
|
||||
#[error("index {0} is out of bounds")]
|
||||
InvalidIndex(usize),
|
||||
#[error("generic automerge error")]
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AutomergeError {
|
||||
|
|
|
@ -2,10 +2,11 @@ use itertools::Itertools;
|
|||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Index;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct IndexedCache<T> {
|
||||
pub cache: Vec<T>,
|
||||
pub cache: Vec<Rc<T>>,
|
||||
lookup: HashMap<T, usize>,
|
||||
}
|
||||
|
||||
|
@ -25,14 +26,14 @@ where
|
|||
*n
|
||||
} else {
|
||||
let n = self.cache.len();
|
||||
self.cache.push(item.clone());
|
||||
self.cache.push(Rc::new(item.clone()));
|
||||
self.lookup.insert(item, n);
|
||||
n
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(&self, item: T) -> Option<usize> {
|
||||
self.lookup.get(&item).cloned()
|
||||
pub fn lookup(&self, item: &T) -> Option<usize> {
|
||||
self.lookup.get(item).cloned()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
|
@ -48,7 +49,7 @@ where
|
|||
self.cache.iter().sorted().cloned().for_each(|item| {
|
||||
let n = sorted.cache.len();
|
||||
sorted.cache.push(item.clone());
|
||||
sorted.lookup.insert(item, n);
|
||||
sorted.lookup.insert(item.as_ref().clone(), n);
|
||||
});
|
||||
sorted
|
||||
}
|
||||
|
@ -63,7 +64,7 @@ where
|
|||
}
|
||||
|
||||
impl<T> IntoIterator for IndexedCache<T> {
|
||||
type Item = T;
|
||||
type Item = Rc<T>;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
|
|
|
@ -46,12 +46,14 @@ mod query;
|
|||
mod types;
|
||||
mod value;
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::fmt;
|
||||
use change::{encode_document, export_change};
|
||||
use clock::Clock;
|
||||
use indexed_cache::IndexedCache;
|
||||
use op_set::OpSet;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use types::{ElemId, Key, ObjId, Op, HEAD};
|
||||
use types::{ElemId, Key, ObjId, Op, OpId, HEAD, Export, Exportable };
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub use change::{decode_change, Change};
|
||||
|
@ -59,8 +61,7 @@ pub use error::AutomergeError;
|
|||
pub use legacy::Change as ExpandedChange;
|
||||
pub use sync::{BloomFilter, SyncHave, SyncMessage, SyncState};
|
||||
pub use types::{
|
||||
ActorId, ChangeHash, Export, Exportable, Importable, ObjType, OpId, OpType, Patch, Peer, Prop,
|
||||
ROOT,
|
||||
ActorId, ChangeHash, ObjType, OpType, Patch, Peer, Prop,
|
||||
};
|
||||
pub use value::{ScalarValue, Value};
|
||||
|
||||
|
@ -78,6 +79,35 @@ pub struct Automerge {
|
|||
transaction: Option<Transaction>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ExId {
|
||||
Root,
|
||||
Id(u64, Rc<ActorId>, usize),
|
||||
}
|
||||
|
||||
impl PartialEq for ExId {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(ExId::Root, ExId::Root) => true,
|
||||
(ExId::Id(ctr1,actor1,_), ExId::Id(ctr2,actor2,_)) if ctr1 == ctr2 && actor1 == actor2 => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ExId {}
|
||||
|
||||
impl fmt::Display for ExId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ExId::Root => write!(f, "_root"),
|
||||
ExId::Id(ctr,actor,_) => write!(f, "{}@{}", ctr, actor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const ROOT : ExId = ExId::Root;
|
||||
|
||||
impl Automerge {
|
||||
pub fn new() -> Self {
|
||||
Automerge {
|
||||
|
@ -262,24 +292,40 @@ impl Automerge {
|
|||
// PropAt::()
|
||||
// NthAt::()
|
||||
|
||||
pub fn keys(&self, obj: OpId) -> Vec<String> {
|
||||
pub fn keys(&self, obj: &ExId) -> Vec<String> {
|
||||
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||
let q = self.ops.search(obj.into(), query::Keys::new());
|
||||
q.keys.iter().map(|k| self.export(*k)).collect()
|
||||
q.keys.iter().map(|k| self.to_string(*k)).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys_at(&self, obj: OpId, heads: &[ChangeHash]) -> Vec<String> {
|
||||
pub fn keys_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Vec<String> {
|
||||
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||
let clock = self.clock_at(heads);
|
||||
let q = self.ops.search(obj.into(), query::KeysAt::new(clock));
|
||||
q.keys.iter().map(|k| self.export(*k)).collect()
|
||||
q.keys.iter().map(|k| self.to_string(*k)).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn length(&self, obj: OpId) -> usize {
|
||||
pub fn length(&self, obj: &ExId) -> usize {
|
||||
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||
self.ops.search(obj.into(), query::Len::new(obj.into())).len
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn length_at(&self, obj: OpId, heads: &[ChangeHash]) -> usize {
|
||||
pub fn length_at(&self, obj: &ExId, heads: &[ChangeHash]) -> usize {
|
||||
if let Ok(obj) = self.exid_to_obj(obj) {
|
||||
let clock = self.clock_at(heads);
|
||||
self.ops.search(obj.into(), query::LenAt::new(clock)).len
|
||||
self.ops.search(obj, query::LenAt::new(clock)).len
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// set(obj, prop, value) - value can be scalar or objtype
|
||||
|
@ -302,32 +348,73 @@ impl Automerge {
|
|||
/// - The key does not exist in the object
|
||||
pub fn set<P: Into<Prop>, V: Into<Value>>(
|
||||
&mut self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
value: V,
|
||||
) -> Result<Option<OpId>, AutomergeError> {
|
||||
) -> Result<Option<ExId>, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
let value = value.into();
|
||||
self.local_op(obj.into(), prop.into(), value.into())
|
||||
if let Some(id) = self.local_op(obj, prop.into(), value.into())? {
|
||||
Ok(Some(self.id_to_exid(id)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn exid_to_obj(&self, id: &ExId) -> Result<ObjId,AutomergeError> {
|
||||
match id {
|
||||
ExId::Root => Ok(ObjId::root()),
|
||||
ExId::Id(ctr,actor,idx) => {
|
||||
// do a direct get here b/c this could be foriegn and not be within the array
|
||||
// bounds
|
||||
if self.ops.m.actors.cache.get(*idx) == Some(actor) {
|
||||
Ok(ObjId(OpId(*ctr,*idx)))
|
||||
} else {
|
||||
// FIXME - make a real error
|
||||
let idx = self.ops.m.actors.lookup(actor).ok_or(AutomergeError::Fail)?;
|
||||
Ok(ObjId(OpId(*ctr,idx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn id_to_exid(&self, id: OpId) -> ExId {
|
||||
ExId::Id(id.0, self.ops.m.actors.cache[id.1].clone(), id.1)
|
||||
}
|
||||
|
||||
pub fn insert<V: Into<Value>>(
|
||||
&mut self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
index: usize,
|
||||
value: V,
|
||||
) -> Result<OpId, AutomergeError> {
|
||||
let obj = obj.into();
|
||||
) -> Result<Option<ExId>, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
if let Some(id) = self.do_insert(obj, index, value)? {
|
||||
Ok(Some(self.id_to_exid(id)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn do_insert<V: Into<Value>>(
|
||||
&mut self,
|
||||
obj: ObjId,
|
||||
index: usize,
|
||||
value: V,
|
||||
) -> Result<Option<OpId>, AutomergeError> {
|
||||
let id = self.next_id();
|
||||
|
||||
let query = self.ops.search(obj, query::InsertNth::new(index));
|
||||
|
||||
let key = query.key()?;
|
||||
let value = value.into();
|
||||
let action = value.into();
|
||||
let is_make = matches!(&action, OpType::Make(_));
|
||||
|
||||
let op = Op {
|
||||
change: self.history.len(),
|
||||
id,
|
||||
action: value.into(),
|
||||
action,
|
||||
obj,
|
||||
key,
|
||||
succ: Default::default(),
|
||||
|
@ -338,60 +425,63 @@ impl Automerge {
|
|||
self.ops.insert(query.pos, op.clone());
|
||||
self.tx().operations.push(op);
|
||||
|
||||
Ok(id)
|
||||
if is_make {
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inc<P: Into<Prop>>(
|
||||
&mut self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
value: i64,
|
||||
) -> Result<OpId, AutomergeError> {
|
||||
match self.local_op(obj.into(), prop.into(), OpType::Inc(value))? {
|
||||
Some(opid) => Ok(opid),
|
||||
None => {
|
||||
panic!("increment should always create a new op")
|
||||
}
|
||||
}
|
||||
) -> Result<(), AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
self.local_op(obj.into(), prop.into(), OpType::Inc(value))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn del<P: Into<Prop>>(&mut self, obj: OpId, prop: P) -> Result<OpId, AutomergeError> {
|
||||
// TODO: Should we also no-op multiple delete operations?
|
||||
match self.local_op(obj.into(), prop.into(), OpType::Del)? {
|
||||
Some(opid) => Ok(opid),
|
||||
None => {
|
||||
panic!("delete should always create a new op")
|
||||
}
|
||||
}
|
||||
pub fn del<P: Into<Prop>>(&mut self, obj: &ExId, prop: P) -> Result<(), AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
self.local_op(obj.into(), prop.into(), OpType::Del)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert
|
||||
/// the new elements
|
||||
pub fn splice(
|
||||
&mut self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
mut pos: usize,
|
||||
del: usize,
|
||||
vals: Vec<Value>,
|
||||
) -> Result<Vec<OpId>, AutomergeError> {
|
||||
) -> Result<Vec<ExId>, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
for _ in 0..del {
|
||||
self.del(obj, pos)?;
|
||||
// del()
|
||||
self.local_op(obj.into(), pos.into(), OpType::Del)?;
|
||||
}
|
||||
let mut result = Vec::with_capacity(vals.len());
|
||||
let mut results = Vec::new();
|
||||
for v in vals {
|
||||
result.push(self.insert(obj, pos, v)?);
|
||||
// insert()
|
||||
let id = self.do_insert(obj, pos, v)?;
|
||||
if let Some(id) = id {
|
||||
results.push(self.id_to_exid(id));
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
Ok(result)
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn splice_text(
|
||||
& mut self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
pos: usize,
|
||||
del: usize,
|
||||
text: &str,
|
||||
) -> Result<Vec<OpId>, AutomergeError> {
|
||||
) -> Result<Vec<ExId>, AutomergeError> {
|
||||
let mut vals = vec![];
|
||||
for c in text.to_owned().graphemes(true) {
|
||||
vals.push(c.into());
|
||||
|
@ -399,8 +489,8 @@ impl Automerge {
|
|||
self.splice(obj, pos, del, vals)
|
||||
}
|
||||
|
||||
pub fn text(&self, obj: OpId) -> Result<String, AutomergeError> {
|
||||
let obj = obj.into();
|
||||
pub fn text(&self, obj: &ExId) -> Result<String, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
let query = self.ops.search(obj, query::ListVals::new(obj));
|
||||
let mut buffer = String::new();
|
||||
for q in &query.ops {
|
||||
|
@ -411,9 +501,9 @@ impl Automerge {
|
|||
Ok(buffer)
|
||||
}
|
||||
|
||||
pub fn text_at(&self, obj: OpId, heads: &[ChangeHash]) -> Result<String, AutomergeError> {
|
||||
pub fn text_at(&self, obj: &ExId, heads: &[ChangeHash]) -> Result<String, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
let clock = self.clock_at(heads);
|
||||
let obj = obj.into();
|
||||
let query = self.ops.search(obj, query::ListValsAt::new(clock));
|
||||
let mut buffer = String::new();
|
||||
for q in &query.ops {
|
||||
|
@ -429,36 +519,36 @@ impl Automerge {
|
|||
// Something better?
|
||||
pub fn value<P: Into<Prop>>(
|
||||
&self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
) -> Result<Option<(Value, OpId)>, AutomergeError> {
|
||||
) -> Result<Option<(Value, ExId)>, AutomergeError> {
|
||||
Ok(self.values(obj, prop.into())?.first().cloned())
|
||||
}
|
||||
|
||||
pub fn value_at<P: Into<Prop>>(
|
||||
&self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
heads: &[ChangeHash],
|
||||
) -> Result<Option<(Value, OpId)>, AutomergeError> {
|
||||
) -> Result<Option<(Value, ExId)>, AutomergeError> {
|
||||
Ok(self.values_at(obj, prop, heads)?.first().cloned())
|
||||
}
|
||||
|
||||
pub fn values<P: Into<Prop>>(
|
||||
&self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
) -> Result<Vec<(Value, OpId)>, AutomergeError> {
|
||||
let obj = obj.into();
|
||||
) -> Result<Vec<(Value, ExId)>, AutomergeError> {
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
let result = match prop.into() {
|
||||
Prop::Map(p) => {
|
||||
let prop = self.ops.m.props.lookup(p);
|
||||
let prop = self.ops.m.props.lookup(&p);
|
||||
if let Some(p) = prop {
|
||||
self.ops
|
||||
.search(obj, query::Prop::new(obj, p))
|
||||
.ops
|
||||
.into_iter()
|
||||
.map(|o| o.into())
|
||||
.map(|o| (o.value(), self.id_to_exid(o.id)))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
|
@ -469,7 +559,7 @@ impl Automerge {
|
|||
.search(obj, query::Nth::new(n))
|
||||
.ops
|
||||
.into_iter()
|
||||
.map(|o| o.into())
|
||||
.map(|o| (o.value(), self.id_to_exid(o.id)))
|
||||
.collect(),
|
||||
};
|
||||
Ok(result)
|
||||
|
@ -477,22 +567,22 @@ impl Automerge {
|
|||
|
||||
pub fn values_at<P: Into<Prop>>(
|
||||
&self,
|
||||
obj: OpId,
|
||||
obj: &ExId,
|
||||
prop: P,
|
||||
heads: &[ChangeHash],
|
||||
) -> Result<Vec<(Value, OpId)>, AutomergeError> {
|
||||
) -> Result<Vec<(Value, ExId)>, AutomergeError> {
|
||||
let prop = prop.into();
|
||||
let obj = obj.into();
|
||||
let obj = self.exid_to_obj(obj)?;
|
||||
let clock = self.clock_at(heads);
|
||||
let result = match prop {
|
||||
Prop::Map(p) => {
|
||||
let prop = self.ops.m.props.lookup(p);
|
||||
let prop = self.ops.m.props.lookup(&p);
|
||||
if let Some(p) = prop {
|
||||
self.ops
|
||||
.search(obj, query::PropAt::new(p, clock))
|
||||
.ops
|
||||
.into_iter()
|
||||
.map(|o| o.into())
|
||||
.map(|o| (o.value(), self.id_to_exid(o.id)))
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
|
@ -503,7 +593,7 @@ impl Automerge {
|
|||
.search(obj, query::NthAt::new(n, clock))
|
||||
.ops
|
||||
.into_iter()
|
||||
.map(|o| o.into())
|
||||
.map(|o| (o.value(), self.id_to_exid(o.id)))
|
||||
.collect(),
|
||||
};
|
||||
Ok(result)
|
||||
|
@ -576,20 +666,11 @@ impl Automerge {
|
|||
let prop = self.ops.m.props.cache(prop);
|
||||
let query = self.ops.search(obj, query::Prop::new(obj, prop));
|
||||
|
||||
match (&query.ops[..], &action) {
|
||||
// If there are no conflicts for this value and the old operation and the new operation are
|
||||
// both setting the same value then we do nothing.
|
||||
(
|
||||
&[Op {
|
||||
action: OpType::Set(ref old_v),
|
||||
..
|
||||
}],
|
||||
OpType::Set(new_v),
|
||||
) if old_v == new_v => {
|
||||
if query.ops.len() == 1 && query.ops[0].is_noop(&action) {
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let is_make = matches!(&action, OpType::Make(_));
|
||||
|
||||
let pred = query.ops.iter().map(|op| op.id).collect();
|
||||
|
||||
|
@ -606,7 +687,11 @@ impl Automerge {
|
|||
|
||||
self.insert_local_op(op, query.pos, &query.ops_pos);
|
||||
|
||||
if is_make {
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn local_list_op(
|
||||
|
@ -621,20 +706,11 @@ impl Automerge {
|
|||
let pred = query.ops.iter().map(|op| op.id).collect();
|
||||
let key = query.key()?;
|
||||
|
||||
match (&query.ops[..], &action) {
|
||||
// If there are no conflicts for this value and the old operation and the new operation are
|
||||
// both setting the same value then we do nothing.
|
||||
(
|
||||
&[Op {
|
||||
action: OpType::Set(ref old_v),
|
||||
..
|
||||
}],
|
||||
OpType::Set(new_v),
|
||||
) if old_v == new_v => {
|
||||
if query.ops.len() == 1 && query.ops[0].is_noop(&action) {
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let is_make = matches!(&action, OpType::Make(_));
|
||||
|
||||
let op = Op {
|
||||
change: self.history.len(),
|
||||
|
@ -649,7 +725,11 @@ impl Automerge {
|
|||
|
||||
self.insert_local_op(op, query.pos, &query.ops_pos);
|
||||
|
||||
if is_make {
|
||||
Ok(Some(id))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_causally_ready(&self, change: &Change) -> bool {
|
||||
|
@ -677,20 +757,19 @@ impl Automerge {
|
|||
.map(|(i, c)| {
|
||||
let actor = self.ops.m.actors.cache(change.actor_id().clone());
|
||||
let id = OpId(change.start_op + i as u64, actor);
|
||||
// FIXME dont need to_string()
|
||||
let obj: ObjId = self.import(&c.obj.to_string()).unwrap();
|
||||
let obj = match c.obj {
|
||||
legacy::ObjectId::Root => ObjId::root(),
|
||||
legacy::ObjectId::Id(id) => ObjId(OpId(id.0, self.ops.m.actors.cache(id.1))),
|
||||
};
|
||||
let pred = c
|
||||
.pred
|
||||
.iter()
|
||||
.map(|i| self.import(&i.to_string()).unwrap())
|
||||
.map(|i| OpId(i.0, self.ops.m.actors.cache(i.1.clone())))
|
||||
.collect();
|
||||
let key = match &c.key {
|
||||
legacy::Key::Map(n) => Key::Map(self.ops.m.props.cache(n.to_string())),
|
||||
legacy::Key::Seq(legacy::ElementId::Head) => Key::Seq(HEAD),
|
||||
// FIXME dont need to_string()
|
||||
legacy::Key::Seq(legacy::ElementId::Id(i)) => {
|
||||
Key::Seq(self.import(&i.to_string()).unwrap())
|
||||
}
|
||||
legacy::Key::Seq(legacy::ElementId::Id(i)) => Key::Seq(ElemId(OpId(i.0, self.ops.m.actors.cache(i.1.clone()))))
|
||||
};
|
||||
Op {
|
||||
change: change_id,
|
||||
|
@ -724,11 +803,13 @@ impl Automerge {
|
|||
let c: Vec<_> = self.history.iter().map(|c| c.decode()).collect();
|
||||
let ops: Vec<_> = self.ops.iter().cloned().collect();
|
||||
// TODO - can we make encode_document error free
|
||||
let tmp : Vec<_> = self.ops.m.props.cache.iter().map(|a| a.as_ref().clone()).collect();
|
||||
// FIXME : tmp
|
||||
let bytes = encode_document(
|
||||
&c,
|
||||
ops.as_slice(),
|
||||
&self.ops.m.actors,
|
||||
&self.ops.m.props.cache,
|
||||
&tmp
|
||||
);
|
||||
if bytes.is_ok() {
|
||||
self.saved = self.get_heads().iter().copied().collect();
|
||||
|
@ -930,7 +1011,7 @@ impl Automerge {
|
|||
to_see.push(*h);
|
||||
}
|
||||
}
|
||||
let actor = self.ops.m.actors.lookup(c.actor_id().clone()).unwrap();
|
||||
let actor = self.ops.m.actors.lookup(c.actor_id()).unwrap();
|
||||
clock.include(actor, c.max_op());
|
||||
seen.insert(hash);
|
||||
}
|
||||
|
@ -1023,9 +1104,9 @@ impl Automerge {
|
|||
self.deps.insert(change.hash);
|
||||
}
|
||||
|
||||
pub fn import<I: Importable>(&self, s: &str) -> Result<I, AutomergeError> {
|
||||
if let Some(x) = I::from(s) {
|
||||
Ok(x)
|
||||
pub fn import(&self, s: &str) -> Result<ExId, AutomergeError> {
|
||||
if s == "_root" {
|
||||
Ok(ExId::Root)
|
||||
} else {
|
||||
let n = s
|
||||
.find('@')
|
||||
|
@ -1038,13 +1119,13 @@ impl Automerge {
|
|||
.ops
|
||||
.m
|
||||
.actors
|
||||
.lookup(actor)
|
||||
.lookup(&actor)
|
||||
.ok_or_else(|| AutomergeError::InvalidOpId(s.to_owned()))?;
|
||||
Ok(I::wrap(OpId(counter, actor)))
|
||||
Ok(ExId::Id(counter, self.ops.m.actors.cache[actor].clone(), actor))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export<E: Exportable>(&self, id: E) -> String {
|
||||
fn to_string<E: Exportable>(&self, id: E) -> String {
|
||||
match id.export() {
|
||||
Export::Id(id) => format!("{}@{}", id.counter(), self.ops.m.actors[id.actor()]),
|
||||
Export::Prop(index) => self.ops.m.props[index].clone(),
|
||||
|
@ -1063,11 +1144,11 @@ impl Automerge {
|
|||
"succ"
|
||||
);
|
||||
for i in self.ops.iter() {
|
||||
let id = self.export(i.id);
|
||||
let obj = self.export(i.obj);
|
||||
let id = self.to_string(i.id);
|
||||
let obj = self.to_string(i.obj);
|
||||
let key = match i.key {
|
||||
Key::Map(n) => self.ops.m.props[n].clone(),
|
||||
Key::Seq(n) => self.export(n),
|
||||
Key::Seq(n) => self.to_string(n),
|
||||
};
|
||||
let value: String = match &i.action {
|
||||
OpType::Set(value) => format!("{}", value),
|
||||
|
@ -1075,8 +1156,8 @@ impl Automerge {
|
|||
OpType::Inc(obj) => format!("inc{}", obj),
|
||||
OpType::Del => format!("del{}", 0),
|
||||
};
|
||||
let pred: Vec<_> = i.pred.iter().map(|id| self.export(*id)).collect();
|
||||
let succ: Vec<_> = i.succ.iter().map(|id| self.export(*id)).collect();
|
||||
let pred: Vec<_> = i.pred.iter().map(|id| self.to_string(*id)).collect();
|
||||
let succ: Vec<_> = i.succ.iter().map(|id| self.to_string(*id)).collect();
|
||||
log!(
|
||||
" {:12} {:12} {:12} {} {:?} {:?}",
|
||||
id,
|
||||
|
@ -1123,9 +1204,9 @@ mod tests {
|
|||
fn insert_op() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
doc.set_actor(ActorId::random());
|
||||
doc.set(ROOT, "hello", "world")?;
|
||||
doc.set(&ROOT, "hello", "world")?;
|
||||
assert!(doc.pending_ops() == 1);
|
||||
doc.value(ROOT, "hello")?;
|
||||
doc.value(&ROOT, "hello")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1133,18 +1214,18 @@ mod tests {
|
|||
fn test_list() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
doc.set_actor(ActorId::random());
|
||||
let list_id = doc.set(ROOT, "items", Value::list())?.unwrap();
|
||||
doc.set(ROOT, "zzz", "zzzval")?;
|
||||
assert!(doc.value(ROOT, "items")?.unwrap().1 == list_id);
|
||||
doc.insert(list_id, 0, "a")?;
|
||||
doc.insert(list_id, 0, "b")?;
|
||||
doc.insert(list_id, 2, "c")?;
|
||||
doc.insert(list_id, 1, "d")?;
|
||||
assert!(doc.value(list_id, 0)?.unwrap().0 == "b".into());
|
||||
assert!(doc.value(list_id, 1)?.unwrap().0 == "d".into());
|
||||
assert!(doc.value(list_id, 2)?.unwrap().0 == "a".into());
|
||||
assert!(doc.value(list_id, 3)?.unwrap().0 == "c".into());
|
||||
assert!(doc.length(list_id) == 4);
|
||||
let list_id = doc.set(&ROOT, "items", Value::list())?.unwrap();
|
||||
doc.set(&ROOT, "zzz", "zzzval")?;
|
||||
assert!(doc.value(&ROOT, "items")?.unwrap().1 == list_id);
|
||||
doc.insert(&list_id, 0, "a")?;
|
||||
doc.insert(&list_id, 0, "b")?;
|
||||
doc.insert(&list_id, 2, "c")?;
|
||||
doc.insert(&list_id, 1, "d")?;
|
||||
assert!(doc.value(&list_id, 0)?.unwrap().0 == "b".into());
|
||||
assert!(doc.value(&list_id, 1)?.unwrap().0 == "d".into());
|
||||
assert!(doc.value(&list_id, 2)?.unwrap().0 == "a".into());
|
||||
assert!(doc.value(&list_id, 3)?.unwrap().0 == "c".into());
|
||||
assert!(doc.length(&list_id) == 4);
|
||||
doc.save()?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1153,22 +1234,22 @@ mod tests {
|
|||
fn test_del() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
doc.set_actor(ActorId::random());
|
||||
doc.set(ROOT, "xxx", "xxx")?;
|
||||
assert!(!doc.values(ROOT, "xxx")?.is_empty());
|
||||
doc.del(ROOT, "xxx")?;
|
||||
assert!(doc.values(ROOT, "xxx")?.is_empty());
|
||||
doc.set(&ROOT, "xxx", "xxx")?;
|
||||
assert!(!doc.values(&ROOT, "xxx")?.is_empty());
|
||||
doc.del(&ROOT, "xxx")?;
|
||||
assert!(doc.values(&ROOT, "xxx")?.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inc() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
let id = doc.set(ROOT, "counter", Value::counter(10))?.unwrap();
|
||||
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(10), id)));
|
||||
doc.inc(ROOT, "counter", 10)?;
|
||||
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(20), id)));
|
||||
doc.inc(ROOT, "counter", -5)?;
|
||||
assert!(doc.value(ROOT, "counter")? == Some((Value::counter(15), id)));
|
||||
doc.set(&ROOT, "counter", Value::counter(10))?;
|
||||
assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(10));
|
||||
doc.inc(&ROOT, "counter", 10)?;
|
||||
assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(20));
|
||||
doc.inc(&ROOT, "counter", -5)?;
|
||||
assert!(doc.value(&ROOT, "counter")?.unwrap().0 == Value::counter(15));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1176,15 +1257,15 @@ mod tests {
|
|||
fn test_save_incremental() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
|
||||
doc.set(ROOT, "foo", 1)?;
|
||||
doc.set(&ROOT, "foo", 1)?;
|
||||
|
||||
let save1 = doc.save().unwrap();
|
||||
|
||||
doc.set(ROOT, "bar", 2)?;
|
||||
doc.set(&ROOT, "bar", 2)?;
|
||||
|
||||
let save2 = doc.save_incremental();
|
||||
|
||||
doc.set(ROOT, "baz", 3)?;
|
||||
doc.set(&ROOT, "baz", 3)?;
|
||||
|
||||
let save3 = doc.save_incremental();
|
||||
|
||||
|
@ -1202,7 +1283,7 @@ mod tests {
|
|||
let mut doc_a = Automerge::load(&save_a)?;
|
||||
let mut doc_b = Automerge::load(&save_b)?;
|
||||
|
||||
assert!(doc_a.values(ROOT, "baz")? == doc_b.values(ROOT, "baz")?);
|
||||
assert!(doc_a.values(&ROOT, "baz")? == doc_b.values(&ROOT, "baz")?);
|
||||
|
||||
assert!(doc_a.save().unwrap() == doc_b.save().unwrap());
|
||||
|
||||
|
@ -1212,17 +1293,17 @@ mod tests {
|
|||
#[test]
|
||||
fn test_save_text() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
let text = doc.set(ROOT, "text", Value::text())?.unwrap();
|
||||
let text = doc.set(&ROOT, "text", Value::text())?.unwrap();
|
||||
let heads1 = doc.commit(None, None);
|
||||
doc.splice_text(text, 0, 0, "hello world")?;
|
||||
doc.splice_text(&text, 0, 0, "hello world")?;
|
||||
let heads2 = doc.commit(None, None);
|
||||
doc.splice_text(text, 6, 0, "big bad ")?;
|
||||
doc.splice_text(&text, 6, 0, "big bad ")?;
|
||||
let heads3 = doc.commit(None, None);
|
||||
|
||||
assert!(&doc.text(text)? == "hello big bad world");
|
||||
assert!(&doc.text_at(text, &heads1)?.is_empty());
|
||||
assert!(&doc.text_at(text, &heads2)? == "hello world");
|
||||
assert!(&doc.text_at(text, &heads3)? == "hello big bad world");
|
||||
assert!(&doc.text(&text)? == "hello big bad world");
|
||||
assert!(&doc.text_at(&text, &heads1)?.is_empty());
|
||||
assert!(&doc.text_at(&text, &heads2)? == "hello world");
|
||||
assert!(&doc.text_at(&text, &heads3)? == "hello big bad world");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1231,50 +1312,50 @@ mod tests {
|
|||
fn test_props_vals_at() -> Result<(), AutomergeError> {
|
||||
let mut doc = Automerge::new();
|
||||
doc.set_actor("aaaa".try_into().unwrap());
|
||||
doc.set(ROOT, "prop1", "val1")?;
|
||||
doc.set(&ROOT, "prop1", "val1")?;
|
||||
doc.commit(None, None);
|
||||
let heads1 = doc.get_heads();
|
||||
doc.set(ROOT, "prop1", "val2")?;
|
||||
doc.set(&ROOT, "prop1", "val2")?;
|
||||
doc.commit(None, None);
|
||||
let heads2 = doc.get_heads();
|
||||
doc.set(ROOT, "prop2", "val3")?;
|
||||
doc.set(&ROOT, "prop2", "val3")?;
|
||||
doc.commit(None, None);
|
||||
let heads3 = doc.get_heads();
|
||||
doc.del(ROOT, "prop1")?;
|
||||
doc.del(&ROOT, "prop1")?;
|
||||
doc.commit(None, None);
|
||||
let heads4 = doc.get_heads();
|
||||
doc.set(ROOT, "prop3", "val4")?;
|
||||
doc.set(&ROOT, "prop3", "val4")?;
|
||||
doc.commit(None, None);
|
||||
let heads5 = doc.get_heads();
|
||||
assert!(doc.keys_at(ROOT, &heads1) == vec!["prop1".to_owned()]);
|
||||
assert!(doc.value_at(ROOT, "prop1", &heads1)?.unwrap().0 == Value::str("val1"));
|
||||
assert!(doc.value_at(ROOT, "prop2", &heads1)? == None);
|
||||
assert!(doc.value_at(ROOT, "prop3", &heads1)? == None);
|
||||
assert!(doc.keys_at(&ROOT, &heads1) == vec!["prop1".to_owned()]);
|
||||
assert!(doc.value_at(&ROOT, "prop1", &heads1)?.unwrap().0 == Value::str("val1"));
|
||||
assert!(doc.value_at(&ROOT, "prop2", &heads1)? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop3", &heads1)? == None);
|
||||
|
||||
assert!(doc.keys_at(ROOT, &heads2) == vec!["prop1".to_owned()]);
|
||||
assert!(doc.value_at(ROOT, "prop1", &heads2)?.unwrap().0 == Value::str("val2"));
|
||||
assert!(doc.value_at(ROOT, "prop2", &heads2)? == None);
|
||||
assert!(doc.value_at(ROOT, "prop3", &heads2)? == None);
|
||||
assert!(doc.keys_at(&ROOT, &heads2) == vec!["prop1".to_owned()]);
|
||||
assert!(doc.value_at(&ROOT, "prop1", &heads2)?.unwrap().0 == Value::str("val2"));
|
||||
assert!(doc.value_at(&ROOT, "prop2", &heads2)? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop3", &heads2)? == None);
|
||||
|
||||
assert!(doc.keys_at(ROOT, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]);
|
||||
assert!(doc.value_at(ROOT, "prop1", &heads3)?.unwrap().0 == Value::str("val2"));
|
||||
assert!(doc.value_at(ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(ROOT, "prop3", &heads3)? == None);
|
||||
assert!(doc.keys_at(&ROOT, &heads3) == vec!["prop1".to_owned(), "prop2".to_owned()]);
|
||||
assert!(doc.value_at(&ROOT, "prop1", &heads3)?.unwrap().0 == Value::str("val2"));
|
||||
assert!(doc.value_at(&ROOT, "prop2", &heads3)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(&ROOT, "prop3", &heads3)? == None);
|
||||
|
||||
assert!(doc.keys_at(ROOT, &heads4) == vec!["prop2".to_owned()]);
|
||||
assert!(doc.value_at(ROOT, "prop1", &heads4)? == None);
|
||||
assert!(doc.value_at(ROOT, "prop2", &heads4)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(ROOT, "prop3", &heads4)? == None);
|
||||
assert!(doc.keys_at(&ROOT, &heads4) == vec!["prop2".to_owned()]);
|
||||
assert!(doc.value_at(&ROOT, "prop1", &heads4)? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop2", &heads4)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(&ROOT, "prop3", &heads4)? == None);
|
||||
|
||||
assert!(doc.keys_at(ROOT, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]);
|
||||
assert!(doc.value_at(ROOT, "prop1", &heads5)? == None);
|
||||
assert!(doc.value_at(ROOT, "prop2", &heads5)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(ROOT, "prop3", &heads5)?.unwrap().0 == Value::str("val4"));
|
||||
assert!(doc.keys_at(&ROOT, &heads5) == vec!["prop2".to_owned(), "prop3".to_owned()]);
|
||||
assert!(doc.value_at(&ROOT, "prop1", &heads5)? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop2", &heads5)?.unwrap().0 == Value::str("val3"));
|
||||
assert!(doc.value_at(&ROOT, "prop3", &heads5)?.unwrap().0 == Value::str("val4"));
|
||||
|
||||
assert!(doc.keys_at(ROOT, &[]).is_empty());
|
||||
assert!(doc.value_at(ROOT, "prop1", &[])? == None);
|
||||
assert!(doc.value_at(ROOT, "prop2", &[])? == None);
|
||||
assert!(doc.value_at(ROOT, "prop3", &[])? == None);
|
||||
assert!(doc.keys_at(&ROOT, &[]).is_empty());
|
||||
assert!(doc.value_at(&ROOT, "prop1", &[])? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop2", &[])? == None);
|
||||
assert!(doc.value_at(&ROOT, "prop3", &[])? == None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1283,47 +1364,47 @@ mod tests {
|
|||
let mut doc = Automerge::new();
|
||||
doc.set_actor("aaaa".try_into().unwrap());
|
||||
|
||||
let list = doc.set(ROOT, "list", Value::list())?.unwrap();
|
||||
let list = doc.set(&ROOT, "list", Value::list())?.unwrap();
|
||||
let heads1 = doc.commit(None, None);
|
||||
|
||||
doc.insert(list, 0, Value::int(10))?;
|
||||
doc.insert(&list, 0, Value::int(10))?;
|
||||
let heads2 = doc.commit(None, None);
|
||||
|
||||
doc.set(list, 0, Value::int(20))?;
|
||||
doc.insert(list, 0, Value::int(30))?;
|
||||
doc.set(&list, 0, Value::int(20))?;
|
||||
doc.insert(&list, 0, Value::int(30))?;
|
||||
let heads3 = doc.commit(None, None);
|
||||
|
||||
doc.set(list, 1, Value::int(40))?;
|
||||
doc.insert(list, 1, Value::int(50))?;
|
||||
doc.set(&list, 1, Value::int(40))?;
|
||||
doc.insert(&list, 1, Value::int(50))?;
|
||||
let heads4 = doc.commit(None, None);
|
||||
|
||||
doc.del(list, 2)?;
|
||||
doc.del(&list, 2)?;
|
||||
let heads5 = doc.commit(None, None);
|
||||
|
||||
doc.del(list, 0)?;
|
||||
doc.del(&list, 0)?;
|
||||
let heads6 = doc.commit(None, None);
|
||||
|
||||
assert!(doc.length_at(list, &heads1) == 0);
|
||||
assert!(doc.value_at(list, 0, &heads1)?.is_none());
|
||||
assert!(doc.length_at(&list, &heads1) == 0);
|
||||
assert!(doc.value_at(&list, 0, &heads1)?.is_none());
|
||||
|
||||
assert!(doc.length_at(list, &heads2) == 1);
|
||||
assert!(doc.value_at(list, 0, &heads2)?.unwrap().0 == Value::int(10));
|
||||
assert!(doc.length_at(&list, &heads2) == 1);
|
||||
assert!(doc.value_at(&list, 0, &heads2)?.unwrap().0 == Value::int(10));
|
||||
|
||||
assert!(doc.length_at(list, &heads3) == 2);
|
||||
assert!(doc.value_at(list, 0, &heads3)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(list, 1, &heads3)?.unwrap().0 == Value::int(20));
|
||||
assert!(doc.length_at(&list, &heads3) == 2);
|
||||
assert!(doc.value_at(&list, 0, &heads3)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(&list, 1, &heads3)?.unwrap().0 == Value::int(20));
|
||||
|
||||
assert!(doc.length_at(list, &heads4) == 3);
|
||||
assert!(doc.value_at(list, 0, &heads4)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(list, 1, &heads4)?.unwrap().0 == Value::int(50));
|
||||
assert!(doc.value_at(list, 2, &heads4)?.unwrap().0 == Value::int(40));
|
||||
assert!(doc.length_at(&list, &heads4) == 3);
|
||||
assert!(doc.value_at(&list, 0, &heads4)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(&list, 1, &heads4)?.unwrap().0 == Value::int(50));
|
||||
assert!(doc.value_at(&list, 2, &heads4)?.unwrap().0 == Value::int(40));
|
||||
|
||||
assert!(doc.length_at(list, &heads5) == 2);
|
||||
assert!(doc.value_at(list, 0, &heads5)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(list, 1, &heads5)?.unwrap().0 == Value::int(50));
|
||||
assert!(doc.length_at(&list, &heads5) == 2);
|
||||
assert!(doc.value_at(&list, 0, &heads5)?.unwrap().0 == Value::int(30));
|
||||
assert!(doc.value_at(&list, 1, &heads5)?.unwrap().0 == Value::int(50));
|
||||
|
||||
assert!(doc.length_at(list, &heads6) == 1);
|
||||
assert!(doc.value_at(list, 0, &heads6)?.unwrap().0 == Value::int(50));
|
||||
assert!(doc.length_at(&list, &heads6) == 1);
|
||||
assert!(doc.value_at(&list, 0, &heads6)?.unwrap().0 == Value::int(50));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::error;
|
||||
use crate::legacy as amp;
|
||||
use crate::ScalarValue;
|
||||
use crate::{ Value, ScalarValue };
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Eq;
|
||||
use std::convert::TryFrom;
|
||||
|
@ -10,7 +10,7 @@ use std::str::FromStr;
|
|||
use tinyvec::{ArrayVec, TinyVec};
|
||||
|
||||
pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0));
|
||||
pub const ROOT: OpId = OpId(0, 0);
|
||||
pub(crate) const ROOT: OpId = OpId(0, 0);
|
||||
|
||||
const ROOT_STR: &str = "_root";
|
||||
const HEAD_STR: &str = "_head";
|
||||
|
@ -161,23 +161,16 @@ pub enum OpType {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Export {
|
||||
pub(crate) enum Export {
|
||||
Id(OpId),
|
||||
Special(String),
|
||||
Prop(usize),
|
||||
}
|
||||
|
||||
pub trait Exportable {
|
||||
pub(crate) trait Exportable {
|
||||
fn export(&self) -> Export;
|
||||
}
|
||||
|
||||
pub trait Importable {
|
||||
fn wrap(id: OpId) -> Self;
|
||||
fn from(s: &str) -> Option<Self>
|
||||
where
|
||||
Self: std::marker::Sized;
|
||||
}
|
||||
|
||||
impl OpId {
|
||||
#[inline]
|
||||
pub fn counter(&self) -> u64 {
|
||||
|
@ -234,45 +227,6 @@ impl Exportable for Key {
|
|||
}
|
||||
}
|
||||
|
||||
impl Importable for ObjId {
|
||||
fn wrap(id: OpId) -> Self {
|
||||
ObjId(id)
|
||||
}
|
||||
fn from(s: &str) -> Option<Self> {
|
||||
if s == ROOT_STR {
|
||||
Some(ROOT.into())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Importable for OpId {
|
||||
fn wrap(id: OpId) -> Self {
|
||||
id
|
||||
}
|
||||
fn from(s: &str) -> Option<Self> {
|
||||
if s == ROOT_STR {
|
||||
Some(ROOT)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Importable for ElemId {
|
||||
fn wrap(id: OpId) -> Self {
|
||||
ElemId(id)
|
||||
}
|
||||
fn from(s: &str) -> Option<Self> {
|
||||
if s == HEAD_STR {
|
||||
Some(HEAD)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OpId> for ObjId {
|
||||
fn from(o: OpId) -> Self {
|
||||
ObjId(o)
|
||||
|
@ -352,11 +306,17 @@ impl Key {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, Ord, Eq, PartialEq, Copy, Hash, Default)]
|
||||
pub struct OpId(pub u64, pub usize);
|
||||
pub(crate) struct OpId(pub u64, pub usize);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
|
||||
pub(crate) struct ObjId(pub OpId);
|
||||
|
||||
impl ObjId {
|
||||
pub fn root() -> Self {
|
||||
ObjId(OpId(0,0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)]
|
||||
pub(crate) struct ElemId(pub OpId);
|
||||
|
||||
|
@ -374,7 +334,11 @@ pub(crate) struct Op {
|
|||
|
||||
impl Op {
|
||||
pub fn is_del(&self) -> bool {
|
||||
matches!(self.action, OpType::Del)
|
||||
matches!(&self.action, OpType::Del)
|
||||
}
|
||||
|
||||
pub fn is_noop(&self, action: &OpType) -> bool {
|
||||
matches!((&self.action, action), (OpType::Set(n), OpType::Set(m)) if n == m)
|
||||
}
|
||||
|
||||
pub fn overwrites(&self, other: &Op) -> bool {
|
||||
|
@ -389,6 +353,14 @@ impl Op {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn value(&self) -> Value {
|
||||
match &self.action {
|
||||
OpType::Make(obj_type) => Value::Object(*obj_type),
|
||||
OpType::Set(scalar) => Value::Scalar(scalar.clone()),
|
||||
_ => panic!("cant convert op into a value - {:?}", self),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn dump(&self) -> String {
|
||||
match &self.action {
|
||||
|
|
|
@ -5,9 +5,9 @@ use std::fs;
|
|||
fn replay_trace(commands: Vec<(usize, usize, Vec<Value>)>) -> Automerge {
|
||||
let mut doc = Automerge::new();
|
||||
|
||||
let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
|
||||
let text = doc.set(&ROOT, "text", Value::text()).unwrap().unwrap();
|
||||
for (pos, del, vals) in commands {
|
||||
doc.splice(text, pos, del, vals).unwrap();
|
||||
doc.splice(&text, pos, del, vals).unwrap();
|
||||
}
|
||||
doc.commit(None, None);
|
||||
doc
|
||||
|
|
|
@ -19,12 +19,12 @@ fn main() -> Result<(), AutomergeError> {
|
|||
let mut doc = Automerge::new();
|
||||
|
||||
let now = Instant::now();
|
||||
let text = doc.set(ROOT, "text", Value::text()).unwrap().unwrap();
|
||||
let text = doc.set( &ROOT, "text", Value::text()).unwrap().unwrap();
|
||||
for (i, (pos, del, vals)) in commands.into_iter().enumerate() {
|
||||
if i % 1000 == 0 {
|
||||
println!("Processed {} edits in {} ms", i, now.elapsed().as_millis());
|
||||
}
|
||||
doc.splice(text, pos, del, vals)?;
|
||||
doc.splice(&text, pos, del, vals)?;
|
||||
}
|
||||
let _ = doc.save();
|
||||
println!("Done in {} ms", now.elapsed().as_millis());
|
||||
|
|
Loading…
Reference in a new issue