Merge branch 'io' into gtk

This commit is contained in:
TheJackiMonster 2021-05-17 00:21:57 +02:00
commit 47820061c7
No known key found for this signature in database
GPG key ID: D850A5F772E880F9
25 changed files with 1701 additions and 0 deletions

28
bin/test_io.py Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
realpath = os.path.realpath(__file__)
sys.path.insert(1, os.path.join(os.path.dirname(realpath), '..'))
import manuskript.data as data
import manuskript.load_save.version_1 as v1
path = os.path.join(sys.path[1], "sample-projects/book-of-acts")
import time
start = time.time()
project = data.Project(path + ".msk")
project.load()
end = time.time()
duration = end - start
print(duration)
project.save()

View file

@ -0,0 +1,15 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from manuskript.data.characters import Characters, Character
from manuskript.data.color import Color
from manuskript.data.goal import GoalKind, Goal
from manuskript.data.info import Info
from manuskript.data.labels import LabelHost, Label
from manuskript.data.outline import Outline, OutlineFolder, OutlineText
from manuskript.data.plots import Plots, PlotLine, PlotStep
from manuskript.data.project import Project
from manuskript.data.revisions import Revisions
from manuskript.data.settings import Settings
from manuskript.data.status import StatusHost, Status
from manuskript.data.unique_id import UniqueIDHost, UniqueID

View file

@ -0,0 +1,121 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from collections import OrderedDict
from manuskript.data.color import Color
from manuskript.data.unique_id import UniqueIDHost
from manuskript.io.mmdFile import MmdFile
class Character:
def __init__(self, path, characters):
self.file = MmdFile(path, 21)
self.characters = characters
self.UID = None
self.name = None
self.importance = None
self.POV = None
self.motivation = None
self.goal = None
self.conflict = None
self.epiphany = None
self.summarySentence = None
self.summaryParagraph = None
self.summaryFull = None
self.notes = None
self.color = None
self.details = dict()
def allowPOV(self) -> bool:
return True if self.POV is None else self.POV
@classmethod
def loadAttribute(cls, metadata: dict, name: str, defaultValue=None):
if name in metadata:
return metadata.pop(name)
else:
return defaultValue
def load(self):
metadata, _ = self.file.loadMMD(True)
ID = Character.loadAttribute(metadata, "ID")
if ID is None:
raise IOError("Character is missing ID!")
self.UID = self.characters.host.loadID(int(ID))
self.name = Character.loadAttribute(metadata, "Name", None)
self.importance = Character.loadAttribute(metadata, "Importance", None)
self.POV = Character.loadAttribute(metadata, "POV", None)
self.motivation = Character.loadAttribute(metadata, "Motivation", None)
self.goal = Character.loadAttribute(metadata, "Goal", None)
self.conflict = Character.loadAttribute(metadata, "Conflict", None)
self.epiphany = Character.loadAttribute(metadata, "Epiphany", None)
self.summarySentence = Character.loadAttribute(metadata, "Phrase Summary", None)
self.summaryParagraph = Character.loadAttribute(metadata, "Paragraph Summary", None)
self.summaryFull = Character.loadAttribute(metadata, "Full Summary", None)
self.notes = Character.loadAttribute(metadata, "Notes", None)
self.color = Color.parse(Character.loadAttribute(metadata, "Color", None))
self.details.clear()
for (key, value) in metadata.items():
self.details[key] = value
def save(self):
metadata = OrderedDict()
metadata["Name"] = self.name
metadata["ID"] = str(self.UID.value)
metadata["Importance"] = self.importance
metadata["POV"] = self.POV
metadata["Motivation"] = self.motivation
metadata["Goal"] = self.goal
metadata["Conflict"] = self.conflict
metadata["Epiphany"] = self.epiphany
metadata["Phrase Summary"] = self.summarySentence
metadata["Paragraph Summary"] = self.summaryParagraph
metadata["Full Summary"] = self.summaryFull
metadata["Notes"] = self.notes
metadata["Color"] = self.color
for (key, value) in self.details.items():
if not (key in metadata):
metadata[key] = value
self.file.save((metadata, None))
class Characters:
def __init__(self, path):
self.dir_path = os.path.join(path, "characters")
self.host = UniqueIDHost()
self.characters = list()
def load(self):
self.characters.clear()
for name in os.listdir(self.dir_path):
path = os.path.join(self.dir_path, name)
if not os.path.isfile(path):
continue
character = Character(path, self)
try:
character.load()
except FileNotFoundError:
continue
self.characters.append(character)
def save(self):
for character in self.characters:
character.save()

26
manuskript/data/color.py Normal file
View file

@ -0,0 +1,26 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import re
class Color:
def __init__(self, red: int, green: int, blue: int):
self.red = red
self.green = green
self.blue = blue
def __str__(self):
return "#%02x%02x%02x" % (self.red, self.green, self.blue)
@classmethod
def parse(cls, string: str):
colorPattern = re.compile(r"\#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})")
m = colorPattern.match(string)
if m is None:
return None
return Color(int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16))

38
manuskript/data/goal.py Normal file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from enum import Enum, unique
@unique
class GoalKind(Enum):
WORDS = 0
CHARACTERS = 1
class Goal:
def __init__(self, value: int = 0, kind: GoalKind = GoalKind.WORDS):
self.value = value
self.kind = kind
def __str__(self):
if self.kind != GoalKind.WORDS:
return str(self.value) + " " + self.kind.name.lower()
else:
return str(self.value)
@classmethod
def parse(cls, string: str):
if string is None:
return None
parts = string.split(" ")
try:
value = int(parts[0])
kind = GoalKind[parts[1].upper()] if len(parts) > 1 else GoalKind.WORDS
except ValueError:
return None
return Goal(value, kind)

50
manuskript/data/info.py Normal file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.io.mmdFile import MmdFile
class Info:
def __init__(self, path):
self.file = MmdFile(os.path.join(path, "infos.txt"), 16)
self.title = None
self.subtitle = None
self.serie = None
self.volume = None
self.genre = None
self.license = None
self.author = None
self.email = None
def load(self):
try:
metadata, _ = self.file.loadMMD(True)
except FileNotFoundError:
metadata = dict()
self.title = metadata.get("Title", None)
self.subtitle = metadata.get("Subtitle", None)
self.serie = metadata.get("Serie", None)
self.volume = metadata.get("Volume", None)
self.genre = metadata.get("Genre", None)
self.license = metadata.get("License", None)
self.author = metadata.get("Author", None)
self.email = metadata.get("Email", None)
def save(self):
metadata = dict()
metadata["Title"] = self.title
metadata["Subtitle"] = self.subtitle
metadata["Serie"] = self.serie
metadata["Volume"] = self.volume
metadata["Genre"] = self.genre
metadata["License"] = self.license
metadata["Author"] = self.author
metadata["Email"] = self.email
self.file.save((metadata, None))

72
manuskript/data/labels.py Normal file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.data.color import Color
from manuskript.io.mmdFile import MmdFile
class Label:
def __init__(self, host, name: str, color: Color):
self.host = host
self.name = name
self.color = color
def __str__(self):
return self.name
def load(self):
self.host.load()
def save(self):
self.host.save()
class LabelHost:
def __init__(self, path):
self.file = MmdFile(os.path.join(path, "labels.txt"), 21)
self.labels = dict()
def addLabel(self, name: str, color: Color):
self.labels[name] = Label(self, name, color)
def removeLabel(self, name: str):
self.labels.pop(name)
def renameLabel(self, oldName: str, newName: str):
label = self.labels.get(oldName)
label.name = newName
self.labels[newName] = label
self.labels.pop(oldName)
def getLabel(self, name: str):
return self.labels.get(name)
def __iter__(self):
return self.labels.values().__iter__()
def load(self):
try:
metadata, _ = self.file.loadMMD(True)
self.labels.clear()
except FileNotFoundError:
self.labels.clear()
return
for (name, value) in metadata.items():
if value is None:
continue
self.addLabel(name, Color.parse(value))
def save(self):
metadata = dict()
for (name, label) in self.labels.items():
metadata[name] = label.color
self.file.save((metadata, None))

224
manuskript/data/outline.py Normal file
View file

@ -0,0 +1,224 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from collections import OrderedDict
from enum import Enum, unique
from manuskript.data.goal import Goal
from manuskript.data.unique_id import UniqueIDHost
from manuskript.io.mmdFile import MmdFile
@unique
class OutlineState(Enum):
UNDEFINED = 0,
OPTIMIZED = 1,
COMPLETE = 2
class OutlineItem:
def __init__(self, path, outline):
self.file = MmdFile(path)
self.outline = outline
self.state = OutlineState.UNDEFINED
self.UID = None
self.title = ""
self.type = ""
self.summarySentence = None
self.summaryFull = None
self.POV = None
self.notes = None
self.label = None
self.status = None
self.compile = True
self.goal = None
@classmethod
def loadMetadata(cls, item, metadata: dict):
ID = metadata.get("ID")
if ID is None:
return
item.UID = item.outline.host.loadID(int(ID))
item.title = metadata.get("title", None)
item.type = metadata.get("type", "md")
item.summarySentence = metadata.get("summarySentence", None)
item.summaryFull = metadata.get("summaryFull", None)
item.POV = metadata.get("POV", None)
item.notes = metadata.get("notes", None)
item.label = metadata.get("label", None)
item.status = metadata.get("status", None)
item.compile = metadata.get("compile")
item.goal = Goal.parse(metadata.get("setGoal", None))
@classmethod
def saveMetadata(cls, item):
metadata = OrderedDict()
if item.UID is None:
return metadata
metadata["title"] = item.title
metadata["ID"] = str(item.UID.value)
metadata["type"] = item.type
metadata["summarySentence"] = item.summarySentence
metadata["summaryFull"] = item.summaryFull
metadata["POV"] = item.POV
metadata["notes"] = item.notes
metadata["label"] = item.label
metadata["status"] = item.status
metadata["compile"] = item.compile
metadata["setGoal"] = item.goal
return metadata
def load(self, optimized: bool = True):
raise IOError('Loading undefined!')
def save(self):
raise IOError('Saving undefined!')
class OutlineText(OutlineItem):
def __init__(self, path, outline):
OutlineItem.__init__(self, path, outline)
self.text = ""
def load(self, optimized: bool = True):
metadata, body = self.file.loadMMD(optimized)
OutlineItem.loadMetadata(self, metadata)
if not optimized:
self.text = body
self.state = OutlineState.COMPLETE
elif self.state == OutlineState.UNDEFINED:
self.state = OutlineState.OPTIMIZED
def save(self):
if self.state == OutlineState.OPTIMIZED:
self.outline.host.removeID(self.UID)
self.load(False)
metadata = OutlineItem.saveMetadata(self)
self.file.save((metadata, self.text))
class OutlineFolder(OutlineItem):
def __init__(self, path, outline):
self.dir_path = path
self.items = list()
OutlineItem.__init__(self, os.path.join(self.dir_path, "folder.txt"), outline)
def __iter__(self):
return self.items.__iter__()
@classmethod
def loadItems(cls, outline, folder, recursive: bool = True):
folder.items.clear()
names = os.listdir(folder.dir_path)
names.remove("folder.txt")
for name in names:
path = os.path.join(folder.dir_path, name)
if os.path.isdir(path):
item = OutlineFolder(path, outline)
else:
item = OutlineText(path, outline)
try:
item.load()
except FileNotFoundError:
continue
folder.items.append(item)
if recursive:
for item in folder.items:
if type(item) is OutlineFolder:
cls.loadItems(outline, item, recursive)
def load(self, _: bool = True):
metadata, _ = self.file.loadMMD(True)
OutlineItem.loadMetadata(self, metadata)
self.state = OutlineState.COMPLETE
@classmethod
def saveItems(cls, folder, recursive: bool = True):
for item in folder.items:
item.save()
if recursive:
for item in folder.items:
if type(item) is OutlineFolder:
cls.saveItems(item, recursive)
def save(self):
self.type = "folder"
metadata = OutlineItem.saveMetadata(self)
self.file.save((metadata, "\n"))
class Outline:
def __init__(self, path):
self.dir_path = os.path.join(path, "outline")
self.host = UniqueIDHost()
self.items = list()
def __iter__(self):
return self.items.__iter__()
def all(self):
result = list()
queue = list(self.items)
while len(queue) > 0:
item = queue.pop()
if type(item) is OutlineFolder:
for child in item:
queue.append(child)
result.append(item)
return result
def load(self):
self.items.clear()
for name in os.listdir(self.dir_path):
path = os.path.join(self.dir_path, name)
if os.path.isdir(path):
item = OutlineFolder(path, self)
else:
item = OutlineText(path, self)
try:
item.load()
except FileNotFoundError:
continue
self.items.append(item)
for item in self.items:
if type(item) is OutlineFolder:
OutlineFolder.loadItems(self, item, True)
def save(self):
for item in self.items:
item.save()
for item in self.items:
if type(item) is OutlineFolder:
OutlineFolder.saveItems(item, True)

191
manuskript/data/plots.py Normal file
View file

@ -0,0 +1,191 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from lxml import etree
from enum import Enum, unique
from manuskript.data.unique_id import UniqueIDHost, UniqueID
from manuskript.io.xmlFile import XmlFile
@unique
class PlotImportance(Enum):
MINOR = 0
SECONDARY = 1
MAIN = 2
class PlotStep:
def __init__(self, plot, UID: UniqueID, name: str, meta: str = "", summary: str = ""):
self.plot = plot
self.UID = UID
self.name = name
self.meta = meta
self.summary = summary
def save(self):
self.plot.save()
class PlotLine:
def __init__(self, plots, UID: UniqueID, name: str, importance: PlotImportance = PlotImportance.MINOR):
self.plots = plots
self.host = UniqueIDHost()
self.UID = UID
self.name = name
self.importance = importance
self.characters = list()
self.description = ""
self.result = ""
self.steps = list()
def addStep(self, name: str, meta: str = "", summary: str = ""):
step = PlotStep(self, self.host.newID(), name, meta, summary)
self.steps.append(step)
return step
def loadStep(self, ID: int, name: str, meta: str = "", summary: str = ""):
step = PlotStep(self, self.host.loadID(ID), name, meta, summary)
self.steps.append(step)
return step
def removeStep(self, step: PlotStep):
self.host.removeID(step.UID)
self.steps.remove(step)
def __iter__(self):
return self.steps.__iter__()
def save(self):
self.plots.save()
class Plots:
def __init__(self, path):
self.file = XmlFile(os.path.join(path, "plots.xml"))
self.host = UniqueIDHost()
self.lines = dict()
def addLine(self, name: str, importance: PlotImportance = PlotImportance.MINOR):
line = PlotLine(self, self.host.newID(), name, importance)
self.lines[line.UID.value] = line
return line
def loadLine(self, ID: int, name: str, importance: PlotImportance = PlotImportance.MINOR):
line = PlotLine(self, self.host.loadID(ID), name, importance)
self.lines[line.UID.value] = line
return line
def removeLine(self, line: PlotLine):
self.host.removeID(line.UID)
self.lines.pop(line.UID.value)
def __iter__(self):
return self.lines.values().__iter__()
@classmethod
def loadPlotStep(cls, line: PlotLine, element: etree.Element):
ID = element.get("ID")
if ID is None:
return
line.loadStep(
int(ID),
element.get("name"),
element.get("meta"),
element.get("summary")
)
@classmethod
def loadPlotLine(cls, plots, element: etree.Element):
ID = element.get("ID")
if ID is None:
return
importance = PlotImportance(int(element.get("importance", 0)))
line = plots.loadLine(int(ID), element.get("name"), importance)
line.description = element.get("description")
line.result = element.get("result")
for characterID in element.get("characters", "").split(','):
#TODO: Character loadings/adding should link to models!
try:
line.characters.append(int(characterID))
except ValueError:
continue
for child in element.findall("step"):
cls.loadPlotStep(line, child)
@classmethod
def loadPlots(cls, plots, root: etree.Element):
plots.host.reset()
plots.lines.clear()
for element in root.findall("plot"):
cls.loadPlotLine(plots, element)
def load(self):
try:
tree = self.file.load()
Plots.loadPlots(self, tree.getroot())
except FileNotFoundError:
self.host.reset()
self.lines.clear()
@classmethod
def saveElementAttribute(cls, element: etree.Element, name: str, value):
if value is None:
return
str_value = str(value)
if len(str_value) > 0:
element.set(name, str_value)
@classmethod
def savePlotStep(cls, step: PlotStep, parent: etree.Element):
element = etree.SubElement(parent, "step")
cls.saveElementAttribute(element, "name", step.name)
cls.saveElementAttribute(element, "ID", step.UID.value)
cls.saveElementAttribute(element, "meta", step.meta)
cls.saveElementAttribute(element, "summary", step.summary)
@classmethod
def savePlotLine(cls, line: PlotLine, root: etree.Element):
element = etree.SubElement(root, "plot")
characters = ",".join([str(characterID) for characterID in line.characters])
cls.saveElementAttribute(element, "name", line.name)
cls.saveElementAttribute(element, "ID", line.UID.value)
cls.saveElementAttribute(element, "importance", line.importance.value)
cls.saveElementAttribute(element, "characters", characters)
cls.saveElementAttribute(element, "description", line.description)
cls.saveElementAttribute(element, "result", line.result)
for step in line:
cls.savePlotStep(step, element)
@classmethod
def savePlots(cls, plots):
root = etree.Element("root")
for line in plots.lines.values():
cls.savePlotLine(line, root)
return root
def save(self):
tree = etree.ElementTree(Plots.savePlots(self))
self.file.save(tree)

View file

@ -0,0 +1,84 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from zipfile import BadZipFile
from manuskript.data.info import Info
from manuskript.data.summary import Summary
from manuskript.data.labels import LabelHost
from manuskript.data.status import StatusHost
from manuskript.data.settings import Settings
from manuskript.data.characters import Characters
from manuskript.data.plots import Plots
from manuskript.data.world import World
from manuskript.data.outline import Outline
from manuskript.data.revisions import Revisions
from manuskript.io.mskFile import MskFile
class Project:
def __init__(self, path):
self.file = MskFile(path)
self.info = Info(self.file.dir_path)
self.summary = Summary(self.file.dir_path)
self.labels = LabelHost(self.file.dir_path)
self.statuses = StatusHost(self.file.dir_path)
self.settings = Settings(self.file.dir_path)
self.characters = Characters(self.file.dir_path)
self.plots = Plots(self.file.dir_path)
self.world = World(self.file.dir_path)
self.outline = Outline(self.file.dir_path)
self.revisions = Revisions(self.file.dir_path)
def __del__(self):
del self.file
def getName(self):
parts = os.path.split(self.file.path)
name = parts[-1]
if name.endswith('.msk'):
name = name[:-4]
return name
def load(self):
try:
self.file.load()
except BadZipFile:
return
except FileNotFoundError:
return
self.info.load()
self.summary.load()
self.labels.load()
self.statuses.load()
self.settings.load()
self.characters.load()
self.plots.load()
self.world.load()
self.outline.load()
self.revisions.load()
self.file.setZipFile(self.settings.isEnabled("saveToZip"))
def save(self):
saveToZip = self.settings.isEnabled("saveToZip")
self.file.setZipFile(saveToZip)
self.info.save()
self.summary.save()
self.labels.save()
self.statuses.save()
self.settings.save()
self.characters.save()
self.plots.save()
self.world.save()
self.outline.save()
#self.revisions.save()
self.file.save(saveToZip)

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from lxml import etree
from manuskript.io.xmlFile import XmlFile
class RevisionEntry:
def __init__(self, outline, timestamp: int, text: str):
self.outline = outline
self.timestamp = timestamp
self.text = text
def load(self):
self.outline.load()
class RevisionOutline:
def __init__(self, revisions, ID: int):
self.revisions = revisions
self.ID = ID
self.entries = list()
def __iter__(self):
return self.entries.__iter__()
def load(self):
self.revisions.load()
class Revisions:
def __init__(self, path):
self.file = XmlFile(os.path.join(path, "revisions.xml"))
self.outline = dict()
def __iter__(self):
return self.outline.values().__iter__()
@classmethod
def loadRevisionEntry(cls, revisions, ID: int, element: etree.Element):
timestamp = element.get("timestamp")
text = element.get("text")
if (timestamp is None) or (text is None):
return
revOutline = revisions.outline.get(ID, None)
if revOutline is None:
revOutline = RevisionOutline(revisions, ID)
revisions.outline[ID] = revOutline
revOutline.entries.append(RevisionEntry(revOutline, timestamp, text))
@classmethod
def loadRevisionOutline(cls, revisions, element: etree.Element, parent: etree.Element):
if element.tag == "revision":
if parent is None:
return
ID = int(parent.get("ID"))
cls.loadRevisionEntry(revisions, ID, element)
elif element.tag == "outlineItem":
ID = element.get("ID")
if ID is None:
return
for child in element:
cls.loadRevisionOutline(revisions, child, element)
def load(self):
try:
tree = self.file.load()
self.outline.clear()
Revisions.loadRevisionOutline(self, tree.getroot(), None)
except FileNotFoundError:
self.outline.clear()

131
manuskript/data/settings.py Normal file
View file

@ -0,0 +1,131 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.io.jsonFile import JsonFile
class Settings:
def __init__(self, path, initDefault: bool = True):
self.file = JsonFile(os.path.join(path, "settings.txt"))
self.properties = dict()
if initDefault:
Settings.loadDefaultSettings(self)
def get(self, key: str):
return self.properties.get(key)
def isEnabled(self, key: str) -> bool:
return self.properties.get(key, False) is True
def set(self, key: str, value):
self.properties[key] = value
def __iter__(self):
return self.properties.__iter__()
@classmethod
def loadDefaultSettings(cls, settings):
settings.properties = {
'autoSave': False,
'autoSaveDelay': 5,
'autoSaveNoChanges': True,
'autoSaveNoChangesDelay': 5,
'corkBackground': {
'color': '#926239',
'image': 'writingdesk.jpg'
},
'corkSizeFactor': 84,
'corkStyle': 'new',
'defaultTextType': 'md',
'dict': 'en_US',
'dontShowDeleteWarning': False,
'folderView': 'cork',
'frequencyAnalyzer': {
'phraseMax': 5,
'phraseMin': 2,
'wordExclude': 'a, and, or',
'wordMin': 1
},
'fullScreenTheme': 'gentleblues',
'lastTab': 6,
'openIndexes': [None],
'outlineViewColumns': [0, 8, 9, 11, 12, 13, 7],
'revisions': {
'keep': True,
'rules': {
'2592000': 86400,
'3600': 600,
'600': 60,
'86400': 3600,
'null': 604800},
'smartremove': True
},
'saveOnQuit': True,
'saveToZip': False,
'spellcheck': False,
'textEditor': {
'background': '#fff',
'backgroundTransparent': False,
'cursorNotBlinking': False,
'cursorWidth': 1,
'font': 'DejaVu Sans,10,-1,5,50,0,0,0,0,0',
'fontColor': '#000',
'indent': True,
'lineSpacing': 100,
'marginsLR': 0,
'marginsTB': 0,
'maxWidth': 0,
'misspelled': '#F00',
'spacingAbove': 5,
'spacingBelow': 5,
'tabWidth': 20,
'textAlignment': 0
},
'viewMode': 'fiction',
'viewSettings': {
'Cork': {
'Background': 'Nothing',
'Border': 'Nothing',
'Corner': 'Label',
'Icon': 'Nothing',
'Text': 'Nothing'
},
'Outline': {
'Background': 'Nothing',
'Icon': 'Nothing',
'Text': 'Compile'
},
'Tree': {
'Background': 'Nothing',
'Icon': 'Nothing',
'InfoFolder': 'Summary',
'InfoText': 'Nothing',
'Text': 'Compile',
'iconSize': 24
}
}
}
def load(self):
try:
self.properties = self.file.load()
except FileNotFoundError:
Settings.loadDefaultSettings(self)
def save(self):
self.file.save(self.properties)

70
manuskript/data/status.py Normal file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.io.textFile import TextFile
class Status:
def __init__(self, host, name: str):
self.host = host
self.name = name
def __str__(self):
return self.name
def load(self):
self.host.load()
def save(self):
self.host.save()
class StatusHost:
def __init__(self, path):
self.file = TextFile(os.path.join(path, "status.txt"))
self.statuses = dict()
def addStatus(self, name: str):
self.statuses[name] = Status(self, name)
def removeStatus(self, name: str):
self.statuses.pop(name)
def renameStatus(self, oldName: str, newName: str):
status = self.statuses.get(oldName)
status.name = newName
self.statuses[newName] = status
self.statuses.pop(oldName)
def getStatus(self, name: str):
return self.statuses.get(name)
def __iter__(self):
return self.statuses.values().__iter__()
def load(self):
try:
text = self.file.load()
self.statuses.clear()
except FileNotFoundError:
self.statuses.clear()
return
if len(text) <= 1:
return
text = text[:-1]
if len(text) <= 0:
return
for name in text.split("\n"):
self.addStatus(name)
def save(self):
self.file.save("\n".join(self.statuses.keys()) + "\n")

View file

@ -0,0 +1,38 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.io.mmdFile import MmdFile
class Summary:
def __init__(self, path):
self.file = MmdFile(os.path.join(path, "summary.txt"), 13)
self.sentence = None
self.paragraph = None
self.page = None
self.full = None
def load(self):
try:
metadata, _ = self.file.loadMMD(True)
except FileNotFoundError:
metadata = dict()
self.sentence = metadata.get("Sentence", None)
self.paragraph = metadata.get("Paragraph", None)
self.page = metadata.get("Page", None)
self.full = metadata.get("Full", None)
def save(self):
metadata = dict()
metadata["Sentence"] = self.sentence
metadata["Paragraph"] = self.paragraph
metadata["Page"] = self.page
metadata["Full"] = self.full
self.file.save((metadata, None))

View file

@ -0,0 +1,43 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
class UniqueID:
def __init__(self, host, value: int):
self.host = host
self.value = value
def __str__(self):
return str(self.value)
class UniqueIDHost:
def __init__(self):
self.counter = 0
self.uids = dict()
def reset(self):
self.counter = 0
self.uids.clear()
def newID(self):
uid = UniqueID(self, self.counter)
self.counter = self.counter + 1
self.uids[uid.value] = uid
return uid
def loadID(self, value: int):
if value in self.uids:
raise ValueError("ID not unique: " + str(value))
uid = UniqueID(self, value)
self.counter = max(self.counter, uid.value + 1)
self.uids[uid.value] = uid
return uid
def removeID(self, uid: UniqueID):
if uid.host != self:
raise ValueError("ID not bound to host!")
self.uids.pop(uid.value)

116
manuskript/data/world.py Normal file
View file

@ -0,0 +1,116 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.data.unique_id import UniqueIDHost, UniqueID
from manuskript.io.opmlFile import OpmlFile, OpmlOutlineItem
class WorldItem:
def __init__(self, world, UID: UniqueID, name: str):
self.world = world
self.UID = UID
self.name = name
self.description = None
self.passion = None
self.conflict = None
self.children = list()
def __iter__(self):
return self.children.__iter__()
def load(self):
self.world.load()
class World:
def __init__(self, path):
self.file = OpmlFile(os.path.join(path, "world.opml"))
self.host = UniqueIDHost()
self.items = dict()
self.top = list()
def addItem(self, name: str) -> WorldItem:
item = WorldItem(self, self.host.newID(), name)
self.items[item.UID.value] = item
return item
def loadItem(self, ID: int, name: str) -> WorldItem:
item = WorldItem(self, self.host.loadID(ID), name)
self.items[item.UID.value] = item
return item
def removeItem(self, item: WorldItem):
self.host.removeID(item.UID)
self.items.pop(item.UID.value)
def __iter__(self):
return self.items.values().__iter__()
@classmethod
def loadWorldItem(cls, world, outline: OpmlOutlineItem):
ID = outline.attributes.get("ID", None)
if ID is None:
return None
item = world.loadItem(int(ID), outline.attributes.get("name", None))
item.description = outline.attributes.get("description", None)
item.passion = outline.attributes.get("passion", None)
item.conflict = outline.attributes.get("conflict", None)
for child in outline.children:
childItem = cls.loadWorldItem(world, child)
if childItem is None:
continue
item.children.append(childItem)
return item
def load(self):
try:
outlines = self.file.load()
self.items.clear()
self.top.clear()
for outline in outlines:
item = World.loadWorldItem(self, outline)
if item is None:
continue
self.top.append(item)
except FileNotFoundError:
self.items.clear()
self.top.clear()
@classmethod
def saveWorldItem(cls, item: WorldItem):
outline = OpmlOutlineItem()
outline.attributes["name"] = item.name
outline.attributes["ID"] = str(item.UID.value)
outline.attributes["description"] = item.description
outline.attributes["passion"] = item.passion
outline.attributes["conflict"] = item.conflict
for childItem in item.children:
outline.children.append(cls.saveWorldItem(childItem))
return outline
def save(self):
outlines = list()
for item in self.top:
outlines.append(World.saveWorldItem(item))
self.file.save(outlines)

10
manuskript/io/__init__.py Normal file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from manuskript.io.textFile import TextFile
from manuskript.io.jsonFile import JsonFile
from manuskript.io.xmlFile import XmlFile
from manuskript.io.opmlFile import OpmlFile
from manuskript.io.mmdFile import MmdFile
from manuskript.io.zipFile import ZipFile
from manuskript.io.mskFile import MskFile

View file

@ -0,0 +1,17 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
class AbstractFile:
def __init__(self, path):
self.path = path
def load(self):
raise IOError('Loading undefined!')
def save(self, content):
raise IOError('Saving undefined!')
def remove(self):
raise IOError('Removing undefined!')

14
manuskript/io/jsonFile.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import json
from manuskript.io.textFile import TextFile
class JsonFile(TextFile):
def load(self):
return json.loads(TextFile.load(self))
def save(self, content):
TextFile.save(self, json.dumps(content, indent=4, sort_keys=True))

83
manuskript/io/mmdFile.py Normal file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import re
from manuskript.io.abstractFile import AbstractFile
class MmdFile(AbstractFile):
def __init__(self, path, metaSpacing=16):
AbstractFile.__init__(self, path)
self.metaSpacing = metaSpacing
def loadMMD(self, ignoreBody: bool = True):
metadata = dict()
body = None
metaPattern = re.compile(r"^([^\s].*?):\s*(.*)\n$")
metaValuePattern = re.compile(r"^(\s+)(.*)\n$")
metaKey = None
metaValue = None
with open(self.path, 'rt', encoding='utf-8') as file:
for line in file:
m = metaPattern.match(line)
if not (m is None):
if not (metaKey is None):
metadata[metaKey] = metaValue
metaKey = m.group(1)
metaValue = m.group(2)
continue
m = metaValuePattern.match(line)
if not (m is None):
metaValue += "\n" + m.group(2)
elif line == "\n":
break
if not (metaKey is None):
metadata[metaKey] = metaValue
if not ignoreBody:
body = file.read()
return metadata, body
def load(self):
return self.loadMMD(False)
def save(self, content):
metadata, body = content
metaSpacing = self.metaSpacing
for (key, value) in metadata.items():
if value is None:
continue
metaSpacing = max(metaSpacing, len(key) + 2)
with open(self.path, 'wt', encoding='utf-8') as file:
for (key, value) in metadata.items():
if value is None:
continue
spacing = metaSpacing - (len(key) + 2)
lines = str(value).split("\n")
file.write(key + ": " + spacing * " " + lines[0] + "\n")
for line in lines[1:]:
file.write(metaSpacing * " " + line + "\n")
if not (body is None):
file.write("\n" + body)
def remove(self):
if os.path.exists(self.path):
os.remove(self.path)

69
manuskript/io/mskFile.py Normal file
View file

@ -0,0 +1,69 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import shutil
from manuskript.io.textFile import TextFile
from manuskript.io.zipFile import ZipFile
class MskFile(TextFile, ZipFile):
def __init__(self, path):
dir_path = os.path.splitext(path)[0]
if (not os.path.isdir(dir_path)) or (os.path.getsize(path) > 1):
dir_path = None
self.zipFile = dir_path is None
ZipFile.__init__(self, path, dir_path)
def __del__(self):
ZipFile.__del__(self)
if self.isZipFile() and (self.tmp is None) and not (self.dir_path is None):
shutil.rmtree(self.dir_path)
def isZipFile(self) -> bool:
return self.zipFile
def setZipFile(self, zipFile: bool):
if zipFile is self.zipFile:
return
if not zipFile:
self.dir_path = os.path.splitext(self.path)[0]
if not os.path.isdir(self.dir_path):
os.mkdir(self.dir_path)
ZipFile.load(self)
self.zipFile = zipFile
def load(self):
if self.zipFile:
ZipFile.load(self)
else:
value = TextFile.load(self)
if value == "1":
self.setZipFile(False)
return self.zipFile
def save(self, content=None):
if not (content is None):
self.setZipFile(content)
if self.zipFile:
ZipFile.save(self)
else:
TextFile.save(self, "1")
def remove(self):
if os.path.isdir(self.dir_path):
shutil.rmtree(self.dir_path)
ZipFile.remove(self)

75
manuskript/io/opmlFile.py Normal file
View file

@ -0,0 +1,75 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
from lxml import etree
from manuskript.io.xmlFile import XmlFile
class OpmlOutlineItem:
def __init__(self):
self.attributes = dict()
self.children = list()
def __iter__(self):
return self.children.__iter__()
def keys(self):
return self.attributes.keys()
def values(self):
return self.attributes.values()
class OpmlFile(XmlFile):
@classmethod
def loadOutline(cls, element):
outline = OpmlOutlineItem()
for key in element.keys():
outline.attributes[key] = element.get(key)
for child in element.getchildren():
outline.children.append(cls.loadOutline(child))
return outline
def load(self):
tree = XmlFile.load(self)
root = tree.getroot()
if root.tag != "opml":
raise IOError("No valid OPML!")
body = root.find("body")
if body is None:
return []
return [OpmlFile.loadOutline(element) for element in body.getchildren()]
@classmethod
def saveOutline(cls, outline, parent):
element = etree.SubElement(parent, "outline")
for (key, value) in outline.attributes.items():
if value is None:
continue
element.attrib[key] = value
for child in outline.children:
cls.saveOutline(child, element)
def save(self, content):
root = etree.Element("opml")
root.set("version", "1.0")
body = etree.SubElement(root, "body")
for outline in content:
OpmlFile.saveOutline(outline, body)
tree = etree.ElementTree(root)
XmlFile.save(self, tree)

21
manuskript/io/textFile.py Normal file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from manuskript.io.abstractFile import AbstractFile
class TextFile(AbstractFile):
def load(self):
with open(self.path, 'rt', encoding='utf-8') as file:
return file.read()
def save(self, content):
with open(self.path, 'wt', encoding='utf-8') as file:
file.write(content)
def remove(self):
if os.path.exists(self.path):
os.remove(self.path)

22
manuskript/io/xmlFile.py Normal file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
from lxml import etree
from manuskript.io.abstractFile import AbstractFile
class XmlFile(AbstractFile):
def load(self):
with open(self.path, 'rb') as file:
return etree.parse(file)
def save(self, content):
with open(self.path, 'wb') as file:
content.write(file, encoding="utf-8", xml_declaration=True, pretty_print=True)
def remove(self):
if os.path.exists(self.path):
os.remove(self.path)

56
manuskript/io/zipFile.py Normal file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--
import os
import shutil
import tempfile
from zipfile import ZipFile as _ZipFile
from manuskript.io.abstractFile import AbstractFile
class ZipFile(AbstractFile):
def __init__(self, path, dir_path=None):
AbstractFile.__init__(self, path)
if dir_path is None:
self.tmp = tempfile.TemporaryDirectory()
dir_path = self.tmp.name
else:
self.tmp = None
self.dir_path = dir_path
def __del__(self):
if not (self.tmp is None):
self.tmp.cleanup()
def load(self):
if self.dir_path is None:
self.tmp = tempfile.TemporaryDirectory()
self.dir_path = self.tmp.name
archive = _ZipFile(self.path)
archive.extractall(self.dir_path)
return self.dir_path
def save(self, content=None):
if not (content is None):
if not (self.tmp is None):
self.tmp.cleanup()
self.tmp = None
self.dir_path = content
elif self.dir_path is None:
if self.tmp is None:
self.tmp = tempfile.TemporaryDirectory()
self.dir_path = self.tmp.name
shutil.make_archive(self.path, 'zip', self.dir_path)
shutil.move(self.path + ".zip", self.path)
def remove(self):
if os.path.exists(self.path):
os.remove(self.path)