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};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct File {
id: FileId,
name: OsString,
kind: FileKind,
}
impl File {
#[inline(always)]
pub fn build_directory() -> FileBuilder<Directory, WantsFileId> {
FileBuilder::new(Directory::new())
}
#[inline(always)]
pub fn build_document() -> FileBuilder<BuildDocument, WantsFileId> {
FileBuilder::new(BuildDocument::uninit())
}
#[inline(always)]
pub fn id(&self) -> FileId {
self.id
}
#[inline(always)]
pub fn kind(&self) -> &FileKind {
&self.kind
}
#[inline(always)]
pub fn name(&self) -> &OsStr {
&self.name
}
#[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()),
)
}
}
pub struct FileBuilder<T, S> {
id: FileId,
name: OsString,
kind: T,
_state: PhantomData<S>,
}
impl<T, S> FileBuilder<T, S> {
#[inline(always)]
fn change_state<NewState>(&mut self) -> &mut FileBuilder<T, NewState> {
unsafe { core::mem::transmute(self) }
}
#[inline(always)]
fn new(kind: T) -> Self {
Self {
id: FileId::new(0),
name: OsString::default(),
kind,
_state: PhantomData,
}
}
}
impl<T> FileBuilder<T, WantsFileId> {
#[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> {
#[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> {
#[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))
}
#[inline(always)]
pub fn child(&mut self, file: File) -> &mut Self {
self.kind.push(file);
self
}
}
impl FileBuilder<BuildDocument, WantsName> {
#[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> {
#[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()
}
#[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> {
#[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> {
#[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))
}
#[inline(always)]
pub fn cursor(&mut self, cursor: Cursor) -> &mut Self {
self.kind.cursors.push(cursor);
self
}
#[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::*;
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(),
}
}
}
pub struct Done;
pub struct WantsFileId;
pub struct WantsName;
pub struct WithChild;
pub struct WantsPeerIdOrReplica;
pub struct WantsText;
}