import json
import os
import shutil
from documentmodel.document import Document
from documentmodel.docparagraph import DocParagraph
from documentmodel.documentwriter import DocumentParser, DocumentWriter
from documentmodel.randutils import random_id
from typing import Dict, Generic, List, Optional
[docs]class Clipboard:
def __init__(self, files_root: str):
self.files_root = files_root
[docs] def get_path(self):
return os.path.join(self.files_root, 'clipboard')
[docs] def get(self, user_id: int):
return Clipboard.UserClipboard(self, user_id)
[docs] def clear_all(self):
path = self.get_path()
if os.path.exists(path):
shutil.rmtree(path)
[docs] class UserClipboard:
def __init__(self, parent: 'Clipboard', user_id: int):
self.user_id = user_id
self.path = os.path.join(parent.get_path(), str(self.user_id))
[docs] def get_clipfilename(self) -> str:
return os.path.join(self.path, 'content')
[docs] def get_reffilename(self) -> str:
return os.path.join(self.path, 'ref-content')
[docs] def get_parreffilename(self) -> str:
return os.path.join(self.path, 'ref-parcontent')
[docs] def clear(self):
for name in (self.get_clipfilename(), self.get_reffilename(), self.get_parreffilename()):
if os.path.isfile(name):
os.remove(name)
[docs] def clear_refs(self):
for name in (self.get_reffilename(), self.get_parreffilename()):
if os.path.isfile(name):
os.remove(name)
[docs] def read(self, as_ref: Optional[bool] = False, force_parrefs: Optional[bool] = False)\
-> Optional[List[Dict[str, str]]]:
if as_ref:
clipfilename = self.get_parreffilename() if force_parrefs else self.get_reffilename()
else:
clipfilename = self.get_clipfilename()
if not os.path.isfile(clipfilename):
return None
with open(clipfilename, 'rt', encoding='utf-8') as clipfile:
content = clipfile.read()
return DocumentParser(content).validate_structure(is_whole_document=False).get_blocks()
[docs] def write(self, pars: List[Dict[str, Generic]]):
os.makedirs(self.path, exist_ok=True)
text = DocumentWriter(pars).get_text()
with open(self.get_clipfilename(), 'wt', encoding='utf-8') as clipfile:
clipfile.write(text)
[docs] def write_refs(self, pars: List[DocParagraph], area_name: Optional[str]):
os.makedirs(self.path, exist_ok=True)
ref_pars = [p.create_reference(p.doc).dict() for p in pars]
reftext = DocumentWriter(ref_pars).get_text()
with open(self.get_parreffilename(), 'wt', encoding='utf-8') as reffile:
reffile.write(reftext)
if area_name and len(pars) > 0:
os.makedirs(self.path, exist_ok=True)
ref_pars = [DocParagraph.create_area_reference(pars[0].doc, area_name).dict()]
reftext = DocumentWriter(ref_pars).get_text()
with open(self.get_reffilename(), 'wt', encoding='utf-8') as reffile:
reffile.write(reftext)
else:
shutil.copy(self.get_parreffilename(), self.get_reffilename())
[docs] def delete_from_source(self):
pars = self.read(as_ref=True, force_parrefs=True)
if pars is None:
return
doc = None
for par in pars:
if doc is None or doc.doc_id != par['attrs']['rd']:
doc = Document(par['attrs']['rd'])
doc.delete_paragraph(par['attrs']['rp'])
[docs] def cut_pars(self, doc: Document, par_start: str, par_end: str,
area_name: Optional[str] = None) -> List[DocParagraph]:
pars = self.copy_pars(doc, par_start, par_end, area_name, doc, disable_ref=True)
self.delete_from_source()
return pars
[docs] def copy_pars(self, doc: Document, par_start: str, par_end: str, area_name: Optional[str] = None,
ref_doc: Optional[Document] = None, disable_ref: bool = False) -> List[DocParagraph]:
copying = False
par_objs = []
pars = []
if ref_doc is None:
ref_doc = doc
# todo: make the iterator accept ranges
i = doc.__iter__()
try:
while True:
par = next(i)
if not copying and par.get_id() == par_start:
copying = True
if copying:
par_objs.append(par)
pars.append(par.dict())
if par.get_id() == par_end:
raise StopIteration
except StopIteration:
pass
finally:
i.close()
self.write_metadata(area_name=area_name, disable_ref=disable_ref)
self.write(pars)
self.write_refs(par_objs, area_name)
return par_objs
[docs] def paste_before(self, doc: Document, par_id: str, as_ref: bool = False) -> List[DocParagraph]:
pars = self.read(as_ref)
if pars is None:
return
metadata = self.read_metadata()
if not as_ref and metadata.get('area_name') is not None and doc.named_section_exists(metadata['area_name']):
new_area_name = metadata['area_name'] + '_' + random_id()
pars[0]['attrs']['area'] = new_area_name
pars[len(pars) - 1]['attrs']['area_end'] = new_area_name
doc_pars = []
par_before = par_id
for par in reversed(pars):
# We need to reverse the sequence because we're inserting before, not after
new_par_id = par['id'] if not doc.has_paragraph(par['id']) else random_id()
new_par = doc.insert_paragraph(par['md'], insert_before_id=par_before, par_id=new_par_id,
attrs=par.get('attrs'), properties=par.get('properties'))
doc_pars = [new_par] + doc_pars
par_before = new_par.get_id()
return doc_pars
[docs] def paste_after(self, doc: Document, par_id: str, as_ref: bool = False) -> List[DocParagraph]:
par_before = None
# todo: make the iterator accept ranges
i = doc.__iter__()
try:
while True:
if next(i).get_id() == par_id:
par_before = next(i).get_id()
raise StopIteration
except StopIteration:
pass
finally:
i.close()
return self.paste_before(doc, par_before, as_ref)