collab-common 0.0.7

Code shared by collab's client and server
Documentation
use core::marker::PhantomData;
use core::mem;
use std::ffi::{OsStr, OsString};

use cola::{EncodedReplica, Replica};
use serde::{Deserialize, Serialize};

use crate::{Cursor, Directory, Document, Either, FileId, FileKind, PeerId};

/// TODO: docs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct File {
    /// TODO: docs
    id: FileId,

    /// TODO: docs
    name: OsString,

    /// TODO: docs
    kind: FileKind,
}

impl File {
    /// TODO: docs
    #[inline(always)]
    pub fn build_directory() -> FileBuilder<Directory, WantsFileId> {
        FileBuilder::new(Directory::new())
    }

    /// TODO: docs
    #[inline(always)]
    pub fn build_document() -> FileBuilder<BuildDocument, WantsFileId> {
        FileBuilder::new(BuildDocument::uninit())
    }

    /// Returns the [`FileId`] of this file.
    #[inline(always)]
    pub fn id(&self) -> FileId {
        self.id
    }

    /// Returns the [`FileKind`] of this file.
    #[inline(always)]
    pub fn kind(&self) -> &FileKind {
        &self.kind
    }

    /// Returns the name of this file.
    #[inline(always)]
    pub fn name(&self) -> &OsStr {
        &self.name
    }

    /// TODO: docs
    #[inline(always)]
    fn new<S>(id: FileId, name: S, kind: FileKind) -> Self
    where
        S: Into<OsString>,
    {
        Self { id, name: name.into(), kind }
    }

    #[inline(always)]
    pub(crate) fn uninit() -> Self {
        Self::new(
            FileId::new(0),
            OsString::default(),
            FileKind::Directory(Directory::new()),
        )
    }
}

/// An identifier for a file that remains stable across moves and renames.
pub struct FileBuilder<T, S> {
    id: FileId,
    name: OsString,
    kind: T,
    _state: PhantomData<S>,
}

impl<T, S> FileBuilder<T, S> {
    /// TODO: docs
    #[inline(always)]
    fn change_state<NewState>(&mut self) -> &mut FileBuilder<T, NewState> {
        // SAFETY: the only difference is a `PhantomData`.
        unsafe { core::mem::transmute(self) }
    }

    /// TODO: docs
    #[inline(always)]
    fn new(kind: T) -> Self {
        Self {
            id: FileId::new(0),
            name: OsString::default(),
            kind,
            _state: PhantomData,
        }
    }
}

impl<T> FileBuilder<T, WantsFileId> {
    /// TODO: docs
    #[inline(always)]
    pub fn file_id(
        &mut self,
        file_id: FileId,
    ) -> &mut FileBuilder<T, WantsName> {
        self.id = file_id;
        self.change_state()
    }
}

impl FileBuilder<Directory, WantsName> {
    /// TODO: docs
    #[inline(always)]
    pub fn name<S>(
        &mut self,
        name: S,
    ) -> &mut FileBuilder<Directory, WithChild>
    where
        S: Into<OsString>,
    {
        self.name = name.into();
        self.change_state()
    }
}

impl FileBuilder<Directory, WithChild> {
    /// TODO: docs
    #[inline(always)]
    pub fn build(&mut self) -> File {
        let name = core::mem::take(&mut self.name);
        let directory = core::mem::replace(&mut self.kind, Directory::new());
        File::new(self.id, name, FileKind::Directory(directory))
    }

    /// TODO: docs
    #[inline(always)]
    pub fn child(&mut self, file: File) -> &mut Self {
        self.kind.push(file);
        self
    }
}

impl FileBuilder<BuildDocument, WantsName> {
    /// TODO: docs
    #[inline(always)]
    pub fn name<S>(
        &mut self,
        name: S,
    ) -> &mut FileBuilder<BuildDocument, WantsPeerIdOrReplica>
    where
        S: Into<OsString>,
    {
        self.name = name.into();
        self.change_state()
    }
}

impl FileBuilder<BuildDocument, WantsPeerIdOrReplica> {
    /// TODO: docs
    #[inline(always)]
    pub fn peer_id(
        &mut self,
        peer_id: PeerId,
    ) -> &mut FileBuilder<BuildDocument, WantsText> {
        self.kind.kind = Either::Left(peer_id);
        self.change_state()
    }

    /// TODO: docs
    #[inline(always)]
    pub fn replica(
        &mut self,
        replica: &Replica,
    ) -> &mut FileBuilder<BuildDocument, WantsText> {
        self.kind.kind = Either::Right(replica.encode());
        self.change_state()
    }
}

impl FileBuilder<BuildDocument, WantsText> {
    /// TODO: docs
    #[inline(always)]
    pub fn text<S>(&mut self, text: S) -> &mut FileBuilder<BuildDocument, Done>
    where
        S: Into<String>,
    {
        self.kind.text = text.into();
        self.change_state()
    }
}

impl FileBuilder<BuildDocument, Done> {
    /// TODO: docs
    #[inline(always)]
    pub fn build(&mut self) -> File {
        let name = core::mem::take(&mut self.name);
        let kind = &mut self.kind;
        let cursors = core::mem::take(&mut kind.cursors);
        let text = core::mem::take(&mut kind.text);
        let kind = mem::replace(&mut kind.kind, Either::Left(PeerId::new(1)));
        let replica = match kind {
            Either::Left(peer_id) => {
                Replica::new(peer_id.as_u64(), text.len()).encode()
            },
            Either::Right(replica) => replica,
        };
        let document = Document::new(cursors, replica, text);
        File::new(self.id, name, FileKind::Document(document))
    }

    /// TODO: docs
    #[inline(always)]
    pub fn cursor(&mut self, cursor: Cursor) -> &mut Self {
        self.kind.cursors.push(cursor);
        self
    }

    /// TODO: docs
    #[inline(always)]
    pub fn cursors<C>(&mut self, cursors: C) -> &mut Self
    where
        C: IntoIterator<Item = Cursor>,
    {
        self.kind.cursors.extend(cursors);
        self
    }
}

use typestate::*;

mod typestate {
    use super::*;

    /// TODO: docs
    pub struct BuildDocument {
        pub(super) cursors: Vec<Cursor>,
        pub(super) kind: Either<PeerId, EncodedReplica>,
        pub(super) text: String,
    }

    impl BuildDocument {
        #[inline(always)]
        pub(super) fn uninit() -> Self {
            Self {
                cursors: Vec::new(),
                kind: Either::Left(PeerId::new(1)),
                text: String::new(),
            }
        }
    }

    /// TODO: docs
    pub struct Done;

    /// TODO: docs
    pub struct WantsFileId;

    /// TODO: docs
    pub struct WantsName;

    /// TODO: docs
    pub struct WithChild;

    /// TODO: docs
    pub struct WantsPeerIdOrReplica;

    /// TODO: docs
    pub struct WantsText;
}