From 5239598a9115ae7160cb376468a74bf7a71c0974 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Tue, 4 May 2021 15:28:47 +0200 Subject: [PATCH 01/13] Added simple classes to handle file extension related io Signed-off-by: TheJackiMonster --- manuskript/io/__init__.py | 15 +++++ manuskript/io/abstractFile.py | 14 +++++ manuskript/io/jsonFile.py | 14 +++++ manuskript/io/opmlFile.py | 61 ++++++++++++++++++ manuskript/io/plotsFile.py | 114 ++++++++++++++++++++++++++++++++++ manuskript/io/textFile.py | 15 +++++ manuskript/io/xmlFile.py | 16 +++++ 7 files changed, 249 insertions(+) create mode 100644 manuskript/io/__init__.py create mode 100644 manuskript/io/abstractFile.py create mode 100644 manuskript/io/jsonFile.py create mode 100644 manuskript/io/opmlFile.py create mode 100644 manuskript/io/plotsFile.py create mode 100644 manuskript/io/textFile.py create mode 100644 manuskript/io/xmlFile.py diff --git a/manuskript/io/__init__.py b/manuskript/io/__init__.py new file mode 100644 index 00000000..8a72b285 --- /dev/null +++ b/manuskript/io/__init__.py @@ -0,0 +1,15 @@ +#!/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.plotsFile import PlotsFile + +extensions = { + ".txt": TextFile, + ".json": JsonFile, + ".xml": XmlFile, + ".opml": OpmlFile +} diff --git a/manuskript/io/abstractFile.py b/manuskript/io/abstractFile.py new file mode 100644 index 00000000..679890ba --- /dev/null +++ b/manuskript/io/abstractFile.py @@ -0,0 +1,14 @@ +#!/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!') diff --git a/manuskript/io/jsonFile.py b/manuskript/io/jsonFile.py new file mode 100644 index 00000000..16a90c00 --- /dev/null +++ b/manuskript/io/jsonFile.py @@ -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)) diff --git a/manuskript/io/opmlFile.py b/manuskript/io/opmlFile.py new file mode 100644 index 00000000..f5675f49 --- /dev/null +++ b/manuskript/io/opmlFile.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from lxml import etree +from manuskript.io.xmlFile import XmlFile + + +class OpmlOutlineItem: + + def __init__(self, tag="outline"): + self.tag = tag + self.attributes = dict() + self.children = [] + + def keys(self): + return self.attributes.keys() + + def values(self): + return self.attributes.values() + + +class OpmlFile(XmlFile): + + @classmethod + def loadOutline(cls, element): + outline = OpmlOutlineItem(element.tag) + + 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() + + return OpmlFile.loadOutline(root) + + @classmethod + def saveOutline(cls, outline, parent=None): + if parent is None: + element = etree.Element(outline.tag) + else: + element = etree.SubElement(parent, outline.tag) + + for key in outline.keys(): + element.attrib[key] = outline.attributes[key] + + for child in outline.children: + cls.saveOutline(child, element) + + return element + + def save(self, content): + root = OpmlFile.saveOutline(content) + tree = etree.ElementTree(root) + + XmlFile.save(self, tree) diff --git a/manuskript/io/plotsFile.py b/manuskript/io/plotsFile.py new file mode 100644 index 00000000..03a0152f --- /dev/null +++ b/manuskript/io/plotsFile.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from lxml import etree +from manuskript.io.xmlFile import XmlFile + + +class PlotStep: + + def __init__(self, name, ID, meta="", summary=""): + self.name = name + self.ID = ID + self.meta = meta + self.summary = summary + + +class PlotItem: + + def __init__(self, name, ID, importance, description, result): + self.name = name + self.ID = ID + self.importance = importance + self.characters = [] + self.description = description + self.result = result + self.steps = [] + + +class PlotsFile(XmlFile): + + @classmethod + def loadPlot(cls, element): + plotID = element.get("ID") + + if plotID is None: + return None + + plot = PlotItem( + element.get("name"), + int(plotID), + int(element.get("importance", 0)), + element.get("description"), + element.get("result") + ) + + for characterID in element.get("characters", "").split(','): + try: + plot.characters.append(int(characterID)) + except ValueError: + continue + + for child in element.findall("step"): + stepID = child.get("ID") + + if stepID is None: + continue + + step = PlotStep( + child.get("name"), + int(stepID), + child.get("meta"), + child.get("summary") + ) + + plot.steps.append(step) + + return plot + + def load(self): + tree = XmlFile.load(self) + root = tree.getroot() + + plots = [] + + for element in root.findall("plot"): + plots.append(PlotsFile.loadPlot(element)) + + return plots + + @classmethod + def saveElementAttribute(cls, element, name, value): + if value is None: + return + + element.set(name, str(value)) + + @classmethod + def savePlot(cls, parent, plot): + element = etree.SubElement(parent, "plot") + + cls.saveElementAttribute(element, "name", plot.name) + cls.saveElementAttribute(element, "ID", plot.ID) + cls.saveElementAttribute(element, "importance", plot.importance) + cls.saveElementAttribute(element, "characters", ",".join([str(characterID) for characterID in plot.characters])) + cls.saveElementAttribute(element, "description", plot.description) + cls.saveElementAttribute(element, "result", plot.result) + + for step in plot.steps: + child = etree.SubElement(element, "step") + + cls.saveElementAttribute(child, "name", step.name) + cls.saveElementAttribute(child, "ID", step.ID) + cls.saveElementAttribute(child, "meta", step.meta) + cls.saveElementAttribute(child, "summary", step.summary) + + def save(self, plots): + root = etree.Element("root") + + for plot in plots: + PlotsFile.savePlot(root, plot) + + tree = etree.ElementTree(root) + + XmlFile.save(self, tree) diff --git a/manuskript/io/textFile.py b/manuskript/io/textFile.py new file mode 100644 index 00000000..05c3ec77 --- /dev/null +++ b/manuskript/io/textFile.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.io.abstractFile import AbstractFile + + +class TextFile(AbstractFile): + + def load(self): + with open(self.path, 'rb') as file: + return file.read() + + def save(self, content): + with open(self.path, 'wb') as file: + file.write(content) diff --git a/manuskript/io/xmlFile.py b/manuskript/io/xmlFile.py new file mode 100644 index 00000000..bbe33f41 --- /dev/null +++ b/manuskript/io/xmlFile.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +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) From dfe6cdece2a66fa7bebcdd8c247c8421250ed123 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Tue, 4 May 2021 23:04:53 +0200 Subject: [PATCH 02/13] Added new data models for plots with use of new file io Signed-off-by: TheJackiMonster --- bin/test_io.py | 24 +++++ manuskript/data/__init__.py | 5 + manuskript/data/plots.py | 185 +++++++++++++++++++++++++++++++++++ manuskript/data/unique_id.py | 43 ++++++++ manuskript/io/__init__.py | 1 - manuskript/io/plotsFile.py | 114 --------------------- 6 files changed, 257 insertions(+), 115 deletions(-) create mode 100644 bin/test_io.py create mode 100644 manuskript/data/__init__.py create mode 100644 manuskript/data/plots.py create mode 100644 manuskript/data/unique_id.py delete mode 100644 manuskript/io/plotsFile.py diff --git a/bin/test_io.py b/bin/test_io.py new file mode 100644 index 00000000..32237d6c --- /dev/null +++ b/bin/test_io.py @@ -0,0 +1,24 @@ +#!/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.io as io +import manuskript.data as data + +path = os.path.join(sys.path[1], "sample-projects/book-of-acts/settings.txt") + +settings = io.JsonFile(path) +result = settings.load() + +print(result) + +plots = data.Plots(os.path.join(sys.path[1], "sample-projects/book-of-acts/plots.xml")) + +plots.load() +plots.save() diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py new file mode 100644 index 00000000..7d270571 --- /dev/null +++ b/manuskript/data/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from manuskript.data.unique_id import UniqueIDHost, UniqueID +from manuskript.data.plots import Plots, PlotLine, PlotStep diff --git a/manuskript/data/plots.py b/manuskript/data/plots.py new file mode 100644 index 00000000..ba3060f3 --- /dev/null +++ b/manuskript/data/plots.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +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(path) + 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): + tree = self.file.load() + Plots.loadPlots(self, tree.getroot()) + + @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) diff --git a/manuskript/data/unique_id.py b/manuskript/data/unique_id.py new file mode 100644 index 00000000..ddeb5f04 --- /dev/null +++ b/manuskript/data/unique_id.py @@ -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) diff --git a/manuskript/io/__init__.py b/manuskript/io/__init__.py index 8a72b285..cf1cb0c7 100644 --- a/manuskript/io/__init__.py +++ b/manuskript/io/__init__.py @@ -5,7 +5,6 @@ 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.plotsFile import PlotsFile extensions = { ".txt": TextFile, diff --git a/manuskript/io/plotsFile.py b/manuskript/io/plotsFile.py deleted file mode 100644 index 03a0152f..00000000 --- a/manuskript/io/plotsFile.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python -# --!-- coding: utf8 --!-- - -from lxml import etree -from manuskript.io.xmlFile import XmlFile - - -class PlotStep: - - def __init__(self, name, ID, meta="", summary=""): - self.name = name - self.ID = ID - self.meta = meta - self.summary = summary - - -class PlotItem: - - def __init__(self, name, ID, importance, description, result): - self.name = name - self.ID = ID - self.importance = importance - self.characters = [] - self.description = description - self.result = result - self.steps = [] - - -class PlotsFile(XmlFile): - - @classmethod - def loadPlot(cls, element): - plotID = element.get("ID") - - if plotID is None: - return None - - plot = PlotItem( - element.get("name"), - int(plotID), - int(element.get("importance", 0)), - element.get("description"), - element.get("result") - ) - - for characterID in element.get("characters", "").split(','): - try: - plot.characters.append(int(characterID)) - except ValueError: - continue - - for child in element.findall("step"): - stepID = child.get("ID") - - if stepID is None: - continue - - step = PlotStep( - child.get("name"), - int(stepID), - child.get("meta"), - child.get("summary") - ) - - plot.steps.append(step) - - return plot - - def load(self): - tree = XmlFile.load(self) - root = tree.getroot() - - plots = [] - - for element in root.findall("plot"): - plots.append(PlotsFile.loadPlot(element)) - - return plots - - @classmethod - def saveElementAttribute(cls, element, name, value): - if value is None: - return - - element.set(name, str(value)) - - @classmethod - def savePlot(cls, parent, plot): - element = etree.SubElement(parent, "plot") - - cls.saveElementAttribute(element, "name", plot.name) - cls.saveElementAttribute(element, "ID", plot.ID) - cls.saveElementAttribute(element, "importance", plot.importance) - cls.saveElementAttribute(element, "characters", ",".join([str(characterID) for characterID in plot.characters])) - cls.saveElementAttribute(element, "description", plot.description) - cls.saveElementAttribute(element, "result", plot.result) - - for step in plot.steps: - child = etree.SubElement(element, "step") - - cls.saveElementAttribute(child, "name", step.name) - cls.saveElementAttribute(child, "ID", step.ID) - cls.saveElementAttribute(child, "meta", step.meta) - cls.saveElementAttribute(child, "summary", step.summary) - - def save(self, plots): - root = etree.Element("root") - - for plot in plots: - PlotsFile.savePlot(root, plot) - - tree = etree.ElementTree(root) - - XmlFile.save(self, tree) From f8ad2a912148c763f591214f3ec6afc80ef62054 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Tue, 4 May 2021 23:37:41 +0200 Subject: [PATCH 03/13] Added new data model for settings with new file io Signed-off-by: TheJackiMonster --- bin/test_io.py | 12 ++-- manuskript/data/__init__.py | 1 + manuskript/data/plots.py | 4 +- manuskript/data/settings.py | 119 ++++++++++++++++++++++++++++++++++++ manuskript/io/jsonFile.py | 2 +- manuskript/io/textFile.py | 4 +- 6 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 manuskript/data/settings.py diff --git a/bin/test_io.py b/bin/test_io.py index 32237d6c..fec84a42 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -11,14 +11,16 @@ sys.path.insert(1, os.path.join(os.path.dirname(realpath), '..')) import manuskript.io as io import manuskript.data as data -path = os.path.join(sys.path[1], "sample-projects/book-of-acts/settings.txt") +path = os.path.join(sys.path[1], "sample-projects/book-of-acts") -settings = io.JsonFile(path) -result = settings.load() +settings = data.Settings(path) -print(result) +settings.load() +settings.save() -plots = data.Plots(os.path.join(sys.path[1], "sample-projects/book-of-acts/plots.xml")) +print(settings.properties) + +plots = data.Plots(path) plots.load() plots.save() diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 7d270571..b0981c98 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -3,3 +3,4 @@ from manuskript.data.unique_id import UniqueIDHost, UniqueID from manuskript.data.plots import Plots, PlotLine, PlotStep +from manuskript.data.settings import Settings diff --git a/manuskript/data/plots.py b/manuskript/data/plots.py index ba3060f3..d863c036 100644 --- a/manuskript/data/plots.py +++ b/manuskript/data/plots.py @@ -1,6 +1,8 @@ #!/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 @@ -66,7 +68,7 @@ class PlotLine: class Plots: def __init__(self, path): - self.file = XmlFile(path) + self.file = XmlFile(os.path.join(path, "plots.xml")) self.host = UniqueIDHost() self.lines = dict() diff --git a/manuskript/data/settings.py b/manuskript/data/settings.py new file mode 100644 index 00000000..4314e960 --- /dev/null +++ b/manuskript/data/settings.py @@ -0,0 +1,119 @@ +#!/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 __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): + self.properties = self.file.load() + + def save(self): + self.file.save(self.properties) diff --git a/manuskript/io/jsonFile.py b/manuskript/io/jsonFile.py index 16a90c00..95213e09 100644 --- a/manuskript/io/jsonFile.py +++ b/manuskript/io/jsonFile.py @@ -11,4 +11,4 @@ class JsonFile(TextFile): return json.loads(TextFile.load(self)) def save(self, content): - TextFile.save(self, json.dumps(content)) + TextFile.save(self, json.dumps(content, indent=4, sort_keys=True)) diff --git a/manuskript/io/textFile.py b/manuskript/io/textFile.py index 05c3ec77..a1ed9d52 100644 --- a/manuskript/io/textFile.py +++ b/manuskript/io/textFile.py @@ -8,8 +8,8 @@ class TextFile(AbstractFile): def load(self): with open(self.path, 'rb') as file: - return file.read() + return file.read().decode('utf-8') def save(self, content): with open(self.path, 'wb') as file: - file.write(content) + file.write(content.encode('utf-8')) From 08220eda421e1bcfed14ba1fd676ffa87ddaa2a3 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Wed, 5 May 2021 14:59:07 +0200 Subject: [PATCH 04/13] Added new data model for old revisions and statuses Signed-off-by: TheJackiMonster --- bin/test_io.py | 13 ++++++ manuskript/data/__init__.py | 5 ++- manuskript/data/revisions.py | 82 ++++++++++++++++++++++++++++++++++++ manuskript/data/status.py | 66 +++++++++++++++++++++++++++++ 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 manuskript/data/revisions.py create mode 100644 manuskript/data/status.py diff --git a/bin/test_io.py b/bin/test_io.py index fec84a42..a0fdbb79 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -24,3 +24,16 @@ plots = data.Plots(path) plots.load() plots.save() + +revs = data.Revisions(path) + +revs.load() + +statusHost = data.StatusHost(path) + +statusHost.load() + +for status in statusHost: + print("--" + str(status)) + +statusHost.save() diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index b0981c98..9e5457da 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -from manuskript.data.unique_id import UniqueIDHost, UniqueID + from manuskript.data.plots import Plots, PlotLine, PlotStep +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 diff --git a/manuskript/data/revisions.py b/manuskript/data/revisions.py new file mode 100644 index 00000000..3648e982 --- /dev/null +++ b/manuskript/data/revisions.py @@ -0,0 +1,82 @@ +#!/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): + tree = self.file.load() + Revisions.loadRevisionOutline(self, tree.getroot(), None) diff --git a/manuskript/data/status.py b/manuskript/data/status.py new file mode 100644 index 00000000..46ebca50 --- /dev/null +++ b/manuskript/data/status.py @@ -0,0 +1,66 @@ +#!/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): + text = self.file.load() + self.statuses.clear() + + 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") From 2faac4efa87e7c7fad6ee9919d85b7b1cb492ad0 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Wed, 5 May 2021 17:53:58 +0200 Subject: [PATCH 05/13] Added io handling for zip archives and project files Signed-off-by: TheJackiMonster --- bin/test_io.py | 24 +++++---------- manuskript/data/__init__.py | 2 +- manuskript/data/plots.py | 8 +++-- manuskript/data/project.py | 48 +++++++++++++++++++++++++++++ manuskript/data/revisions.py | 9 ++++-- manuskript/data/settings.py | 14 ++++++++- manuskript/data/status.py | 8 +++-- manuskript/io/__init__.py | 9 ++---- manuskript/io/mskFile.py | 60 ++++++++++++++++++++++++++++++++++++ manuskript/io/zipFile.py | 51 ++++++++++++++++++++++++++++++ 10 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 manuskript/data/project.py create mode 100644 manuskript/io/mskFile.py create mode 100644 manuskript/io/zipFile.py diff --git a/bin/test_io.py b/bin/test_io.py index a0fdbb79..5da09a80 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -13,27 +13,19 @@ import manuskript.data as data path = os.path.join(sys.path[1], "sample-projects/book-of-acts") -settings = data.Settings(path) +project = data.Project(path + ".msk") +project.load() -settings.load() -settings.save() +settings = project.settings print(settings.properties) -plots = data.Plots(path) +plots = project.plots -plots.load() -plots.save() +revs = project.revisions -revs = data.Revisions(path) - -revs.load() - -statusHost = data.StatusHost(path) - -statusHost.load() - -for status in statusHost: +for status in project.statuses: print("--" + str(status)) -statusHost.save() +settings.set("saveToZip", True) +project.save() diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 9e5457da..01591ff4 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- - 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 diff --git a/manuskript/data/plots.py b/manuskript/data/plots.py index d863c036..ae439b37 100644 --- a/manuskript/data/plots.py +++ b/manuskript/data/plots.py @@ -135,8 +135,12 @@ class Plots: cls.loadPlotLine(plots, element) def load(self): - tree = self.file.load() - Plots.loadPlots(self, tree.getroot()) + 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): diff --git a/manuskript/data/project.py b/manuskript/data/project.py new file mode 100644 index 00000000..33a70221 --- /dev/null +++ b/manuskript/data/project.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +from zipfile import BadZipFile +from manuskript.data.plots import Plots +from manuskript.data.revisions import Revisions +from manuskript.data.settings import Settings +from manuskript.data.status import StatusHost +from manuskript.io.mskFile import MskFile + + +class Project: + + def __init__(self, path): + self.file = MskFile(path) + + self.statuses = StatusHost(self.file.dir_path) + self.settings = Settings(self.file.dir_path) + self.plots = Plots(self.file.dir_path) + self.revisions = Revisions(self.file.dir_path) + + def __del__(self): + del self.file + + def load(self): + try: + self.file.load() + except BadZipFile: + return + except FileNotFoundError: + return + + self.statuses.load() + self.settings.load() + self.plots.load() + self.revisions.load() + + self.file.setZipFile(self.settings.isEnabled("saveToZip")) + + def save(self): + print("Save project: " + str(self.file.path) + " " + str(self.file.dir_path)) + + self.statuses.save() + self.settings.save() + self.plots.save() + #self.revisions.save() + + self.file.save(self.settings.isEnabled("saveToZip")) diff --git a/manuskript/data/revisions.py b/manuskript/data/revisions.py index 3648e982..c02db7ac 100644 --- a/manuskript/data/revisions.py +++ b/manuskript/data/revisions.py @@ -78,5 +78,10 @@ class Revisions: cls.loadRevisionOutline(revisions, child, element) def load(self): - tree = self.file.load() - Revisions.loadRevisionOutline(self, tree.getroot(), None) + try: + tree = self.file.load() + self.outline.clear() + + Revisions.loadRevisionOutline(self, tree.getroot(), None) + except FileNotFoundError: + self.outline.clear() diff --git a/manuskript/data/settings.py b/manuskript/data/settings.py index 4314e960..7e14b8a1 100644 --- a/manuskript/data/settings.py +++ b/manuskript/data/settings.py @@ -15,6 +15,15 @@ class Settings: 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__() @@ -113,7 +122,10 @@ class Settings: } def load(self): - self.properties = self.file.load() + try: + self.properties = self.file.load() + except FileNotFoundError: + Settings.loadDefaultSettings(self) def save(self): self.file.save(self.properties) diff --git a/manuskript/data/status.py b/manuskript/data/status.py index 46ebca50..74def9ae 100644 --- a/manuskript/data/status.py +++ b/manuskript/data/status.py @@ -48,8 +48,12 @@ class StatusHost: return self.statuses.values().__iter__() def load(self): - text = self.file.load() - self.statuses.clear() + try: + text = self.file.load() + self.statuses.clear() + except FileNotFoundError: + self.statuses.clear() + return if len(text) <= 1: return diff --git a/manuskript/io/__init__.py b/manuskript/io/__init__.py index cf1cb0c7..7a5a8292 100644 --- a/manuskript/io/__init__.py +++ b/manuskript/io/__init__.py @@ -5,10 +5,5 @@ from manuskript.io.textFile import TextFile from manuskript.io.jsonFile import JsonFile from manuskript.io.xmlFile import XmlFile from manuskript.io.opmlFile import OpmlFile - -extensions = { - ".txt": TextFile, - ".json": JsonFile, - ".xml": XmlFile, - ".opml": OpmlFile -} +from manuskript.io.zipFile import ZipFile +from manuskript.io.mskFile import MskFile diff --git a/manuskript/io/mskFile.py b/manuskript/io/mskFile.py new file mode 100644 index 00000000..0d9a1780 --- /dev/null +++ b/manuskript/io/mskFile.py @@ -0,0 +1,60 @@ +#!/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] + 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") diff --git a/manuskript/io/zipFile.py b/manuskript/io/zipFile.py new file mode 100644 index 00000000..ce583ae9 --- /dev/null +++ b/manuskript/io/zipFile.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +import tempfile +import shutil + +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) From b97c5366d3501e35cd282f304b9da1445383189b Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Thu, 6 May 2021 16:57:30 +0200 Subject: [PATCH 06/13] Fixed differences in saving project with new io Signed-off-by: TheJackiMonster --- bin/test_io.py | 3 +++ manuskript/data/project.py | 5 ++++- manuskript/io/mskFile.py | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bin/test_io.py b/bin/test_io.py index 5da09a80..07aac8fb 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -29,3 +29,6 @@ for status in project.statuses: settings.set("saveToZip", True) project.save() + +settings.set("saveToZip", False) +project.save() diff --git a/manuskript/data/project.py b/manuskript/data/project.py index 33a70221..d5320ae2 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -40,9 +40,12 @@ class Project: def save(self): print("Save project: " + str(self.file.path) + " " + str(self.file.dir_path)) + saveToZip = self.settings.isEnabled("saveToZip") + self.file.setZipFile(saveToZip) + self.statuses.save() self.settings.save() self.plots.save() #self.revisions.save() - self.file.save(self.settings.isEnabled("saveToZip")) + self.file.save(saveToZip) diff --git a/manuskript/io/mskFile.py b/manuskript/io/mskFile.py index 0d9a1780..8604bfbf 100644 --- a/manuskript/io/mskFile.py +++ b/manuskript/io/mskFile.py @@ -34,7 +34,10 @@ class MskFile(TextFile, ZipFile): if not zipFile: self.dir_path = os.path.splitext(self.path)[0] - os.mkdir(self.dir_path) + + if not os.path.isdir(self.dir_path): + os.mkdir(self.dir_path) + ZipFile.load(self) self.zipFile = zipFile From 54aa28414a94e9f146218f9c71d3cc3964476ab7 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Thu, 6 May 2021 18:51:23 +0200 Subject: [PATCH 07/13] Added io handling for multimarkdown files with accelerated metadata loading Signed-off-by: TheJackiMonster --- bin/test_io.py | 37 ++++++++++++++------- manuskript/io/__init__.py | 1 + manuskript/io/mmdFile.py | 69 +++++++++++++++++++++++++++++++++++++++ manuskript/io/textFile.py | 8 ++--- 4 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 manuskript/io/mmdFile.py diff --git a/bin/test_io.py b/bin/test_io.py index 07aac8fb..364765a3 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -13,22 +13,35 @@ import manuskript.data as data path = os.path.join(sys.path[1], "sample-projects/book-of-acts") -project = data.Project(path + ".msk") -project.load() +#project = data.Project(path + ".msk") +#project.load() -settings = project.settings +#settings = project.settings -print(settings.properties) +#print(settings.properties) -plots = project.plots +#plots = project.plots -revs = project.revisions +#revs = project.revisions -for status in project.statuses: - print("--" + str(status)) +#for status in project.statuses: +# print("--" + str(status)) -settings.set("saveToZip", True) -project.save() +#settings.set("saveToZip", True) +#project.save() -settings.set("saveToZip", False) -project.save() +#settings.set("saveToZip", False) +#project.save() + +mmd = io.MmdFile(path + "/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md") + +meta, _ = mmd.loadMMD() + +print(meta) + +meta, body = mmd.load() + +print(meta) +print(body) + +mmd.save((meta, body)) diff --git a/manuskript/io/__init__.py b/manuskript/io/__init__.py index 7a5a8292..5a66973d 100644 --- a/manuskript/io/__init__.py +++ b/manuskript/io/__init__.py @@ -5,5 +5,6 @@ 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 diff --git a/manuskript/io/mmdFile.py b/manuskript/io/mmdFile.py new file mode 100644 index 00000000..ed56ef34 --- /dev/null +++ b/manuskript/io/mmdFile.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +import re + +from manuskript.io.abstractFile import AbstractFile + + +class MmdFile(AbstractFile): + + 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": + if not (metaKey is None): + metadata[metaKey] = metaValue + + break + + if not ignoreBody: + body = file.read() + + return metadata, body + + def load(self): + return self.loadMMD(False) + + def save(self, content): + metadata, body = content + metaSpacing = 0 + + for key in metadata.keys(): + metaSpacing = max(metaSpacing, len(key) + 2) + + with open(self.path, 'wt', encoding='utf-8') as file: + for (key, value) in metadata.items(): + spacing = metaSpacing - (len(key) + 2) + lines = value.split("\n") + + file.write(key + ": " + spacing * " " + lines[0] + "\n") + + for line in lines[1:]: + file.write(metaSpacing * " " + line + "\n") + + file.write("\n") + if not (body is None): + file.write(body) diff --git a/manuskript/io/textFile.py b/manuskript/io/textFile.py index a1ed9d52..56738ca8 100644 --- a/manuskript/io/textFile.py +++ b/manuskript/io/textFile.py @@ -7,9 +7,9 @@ from manuskript.io.abstractFile import AbstractFile class TextFile(AbstractFile): def load(self): - with open(self.path, 'rb') as file: - return file.read().decode('utf-8') + with open(self.path, 'rt', encoding='utf-8') as file: + return file.read() def save(self, content): - with open(self.path, 'wb') as file: - file.write(content.encode('utf-8')) + with open(self.path, 'wt', encoding='utf-8') as file: + file.write(content) From caf428592a3ce01f18f22c977fac26b4d77b9e7e Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Sun, 9 May 2021 00:39:12 +0200 Subject: [PATCH 08/13] Added io handling for outline, summary and goals Signed-off-by: TheJackiMonster --- bin/test_io.py | 32 ++---- manuskript/data/__init__.py | 2 + manuskript/data/goal.py | 35 ++++++ manuskript/data/outline.py | 222 ++++++++++++++++++++++++++++++++++++ manuskript/data/project.py | 16 ++- manuskript/data/summary.py | 35 ++++++ manuskript/io/mmdFile.py | 19 ++- 7 files changed, 329 insertions(+), 32 deletions(-) create mode 100644 manuskript/data/goal.py create mode 100644 manuskript/data/outline.py create mode 100644 manuskript/data/summary.py diff --git a/bin/test_io.py b/bin/test_io.py index 364765a3..3e5dcadd 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -13,35 +13,25 @@ import manuskript.data as data path = os.path.join(sys.path[1], "sample-projects/book-of-acts") -#project = data.Project(path + ".msk") -#project.load() +project = data.Project(path + ".msk") +project.load() -#settings = project.settings +for item in project.outline.all(): + print(str(item.title) + " " + str(item.goal)) -#print(settings.properties) +settings = project.settings -#plots = project.plots +print(settings.properties) -#revs = project.revisions +plots = project.plots -#for status in project.statuses: -# print("--" + str(status)) +revs = project.revisions + +for status in project.statuses: + print("--" + str(status)) #settings.set("saveToZip", True) #project.save() #settings.set("saveToZip", False) #project.save() - -mmd = io.MmdFile(path + "/outline/0-Jerusalem/0-Chapter_1/0-Introduction.md") - -meta, _ = mmd.loadMMD() - -print(meta) - -meta, body = mmd.load() - -print(meta) -print(body) - -mmd.save((meta, body)) diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 01591ff4..526a9d5d 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -3,6 +3,8 @@ from manuskript.data.plots import Plots, PlotLine, PlotStep from manuskript.data.project import Project +from manuskript.data.goal import GoalKind, Goal +from manuskript.data.outline import Outline, OutlineFolder, OutlineText from manuskript.data.revisions import Revisions from manuskript.data.settings import Settings from manuskript.data.status import StatusHost, Status diff --git a/manuskript/data/goal.py b/manuskript/data/goal.py new file mode 100644 index 00000000..4a406f6f --- /dev/null +++ b/manuskript/data/goal.py @@ -0,0 +1,35 @@ +#!/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 = 0 + self.kind = kind + + def __str__(self): + return str(self.value) + " " + self.kind.name.lower() + + @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) diff --git a/manuskript/data/outline.py b/manuskript/data/outline.py new file mode 100644 index 00000000..c2509d76 --- /dev/null +++ b/manuskript/data/outline.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +import os + +from enum import Enum, unique +from manuskript.data.goal import Goal +from manuskript.data.unique_id import UniqueIDHost, UniqueID +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.title = "" + self.UID = None + 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 = dict() + + if item.UID is None: + return metadata + + metadata["ID"] = str(item.UID.value) + metadata["title"] = item.title + 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"] = str(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.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, "")) + + +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) diff --git a/manuskript/data/project.py b/manuskript/data/project.py index d5320ae2..c7211535 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -2,10 +2,12 @@ # --!-- coding: utf8 --!-- from zipfile import BadZipFile -from manuskript.data.plots import Plots -from manuskript.data.revisions import Revisions -from manuskript.data.settings import Settings +from manuskript.data.summary import Summary from manuskript.data.status import StatusHost +from manuskript.data.settings import Settings +from manuskript.data.plots import Plots +from manuskript.data.outline import Outline +from manuskript.data.revisions import Revisions from manuskript.io.mskFile import MskFile @@ -14,9 +16,11 @@ class Project: def __init__(self, path): self.file = MskFile(path) + self.summary = Summary(self.file.dir_path) self.statuses = StatusHost(self.file.dir_path) self.settings = Settings(self.file.dir_path) self.plots = Plots(self.file.dir_path) + self.outline = Outline(self.file.dir_path) self.revisions = Revisions(self.file.dir_path) def __del__(self): @@ -30,22 +34,24 @@ class Project: except FileNotFoundError: return + self.summary.load() self.statuses.load() self.settings.load() self.plots.load() + self.outline.load() self.revisions.load() self.file.setZipFile(self.settings.isEnabled("saveToZip")) def save(self): - print("Save project: " + str(self.file.path) + " " + str(self.file.dir_path)) - saveToZip = self.settings.isEnabled("saveToZip") self.file.setZipFile(saveToZip) + self.summary.load() self.statuses.save() self.settings.save() self.plots.save() + self.outline.save() #self.revisions.save() self.file.save(saveToZip) diff --git a/manuskript/data/summary.py b/manuskript/data/summary.py new file mode 100644 index 00000000..05a57723 --- /dev/null +++ b/manuskript/data/summary.py @@ -0,0 +1,35 @@ +#!/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): + metadata, _ = self.file.loadMMD(True) + + 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)) diff --git a/manuskript/io/mmdFile.py b/manuskript/io/mmdFile.py index ed56ef34..1ca4356d 100644 --- a/manuskript/io/mmdFile.py +++ b/manuskript/io/mmdFile.py @@ -8,6 +8,11 @@ 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 @@ -34,11 +39,11 @@ class MmdFile(AbstractFile): if not (m is None): metaValue += "\n" + m.group(2) elif line == "\n": - if not (metaKey is None): - metadata[metaKey] = metaValue - break + if not (metaKey is None): + metadata[metaKey] = metaValue + if not ignoreBody: body = file.read() @@ -49,13 +54,16 @@ class MmdFile(AbstractFile): def save(self, content): metadata, body = content - metaSpacing = 0 + metaSpacing = self.metaSpacing for key in metadata.keys(): 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 = value.split("\n") @@ -64,6 +72,5 @@ class MmdFile(AbstractFile): for line in lines[1:]: file.write(metaSpacing * " " + line + "\n") - file.write("\n") if not (body is None): - file.write(body) + file.write("\n" + body) From f9ad8d214c867b4be094e11485854e99ca3cb35f Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Sun, 9 May 2021 01:15:14 +0200 Subject: [PATCH 09/13] Added io handling for project info, color and labels Signed-off-by: TheJackiMonster --- bin/test_io.py | 20 ----------- manuskript/data/__init__.py | 4 ++- manuskript/data/color.py | 26 ++++++++++++++ manuskript/data/info.py | 50 ++++++++++++++++++++++++++ manuskript/data/labels.py | 72 +++++++++++++++++++++++++++++++++++++ manuskript/data/project.py | 10 +++++- manuskript/data/summary.py | 5 ++- 7 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 manuskript/data/color.py create mode 100644 manuskript/data/info.py create mode 100644 manuskript/data/labels.py diff --git a/bin/test_io.py b/bin/test_io.py index 3e5dcadd..02323eab 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -15,23 +15,3 @@ path = os.path.join(sys.path[1], "sample-projects/book-of-acts") project = data.Project(path + ".msk") project.load() - -for item in project.outline.all(): - print(str(item.title) + " " + str(item.goal)) - -settings = project.settings - -print(settings.properties) - -plots = project.plots - -revs = project.revisions - -for status in project.statuses: - print("--" + str(status)) - -#settings.set("saveToZip", True) -#project.save() - -#settings.set("saveToZip", False) -#project.save() diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 526a9d5d..36d07c88 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -6,6 +6,8 @@ from manuskript.data.project import Project from manuskript.data.goal import GoalKind, Goal from manuskript.data.outline import Outline, OutlineFolder, OutlineText from manuskript.data.revisions import Revisions -from manuskript.data.settings import Settings +from manuskript.data.color import Color +from manuskript.data.labels import LabelHost, Label from manuskript.data.status import StatusHost, Status +from manuskript.data.settings import Settings from manuskript.data.unique_id import UniqueIDHost, UniqueID diff --git a/manuskript/data/color.py b/manuskript/data/color.py new file mode 100644 index 00000000..12c5f2af --- /dev/null +++ b/manuskript/data/color.py @@ -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)) diff --git a/manuskript/data/info.py b/manuskript/data/info.py new file mode 100644 index 00000000..3758733e --- /dev/null +++ b/manuskript/data/info.py @@ -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)) diff --git a/manuskript/data/labels.py b/manuskript/data/labels.py new file mode 100644 index 00000000..6c691e78 --- /dev/null +++ b/manuskript/data/labels.py @@ -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 label in self.labels: + metadata[label.name] = str(label.color) + + self.file.save((metadata, None)) diff --git a/manuskript/data/project.py b/manuskript/data/project.py index c7211535..7b6953d1 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -2,7 +2,9 @@ # --!-- coding: utf8 --!-- 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.plots import Plots @@ -16,7 +18,9 @@ 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.plots = Plots(self.file.dir_path) @@ -34,7 +38,9 @@ class Project: except FileNotFoundError: return + self.info.load() self.summary.load() + self.labels.load() self.statuses.load() self.settings.load() self.plots.load() @@ -47,7 +53,9 @@ class Project: saveToZip = self.settings.isEnabled("saveToZip") self.file.setZipFile(saveToZip) - self.summary.load() + self.info.save() + self.summary.save() + self.labels.save() self.statuses.save() self.settings.save() self.plots.save() diff --git a/manuskript/data/summary.py b/manuskript/data/summary.py index 05a57723..88aabda6 100644 --- a/manuskript/data/summary.py +++ b/manuskript/data/summary.py @@ -17,7 +17,10 @@ class Summary: self.full = None def load(self): - metadata, _ = self.file.loadMMD(True) + try: + metadata, _ = self.file.loadMMD(True) + except FileNotFoundError: + metadata = dict() self.sentence = metadata.get("Sentence", None) self.paragraph = metadata.get("Paragraph", None) From 41b3f23facbbe5376e5a53fdccdabe55228dacd3 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Sun, 9 May 2021 01:51:14 +0200 Subject: [PATCH 10/13] Added basic io handling for characters Signed-off-by: TheJackiMonster --- bin/test_io.py | 1 - manuskript/data/__init__.py | 12 ++-- manuskript/data/characters.py | 116 ++++++++++++++++++++++++++++++++++ manuskript/data/labels.py | 2 +- manuskript/data/outline.py | 4 +- manuskript/data/project.py | 4 ++ 6 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 manuskript/data/characters.py diff --git a/bin/test_io.py b/bin/test_io.py index 02323eab..cf203a1e 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -8,7 +8,6 @@ realpath = os.path.realpath(__file__) sys.path.insert(1, os.path.join(os.path.dirname(realpath), '..')) -import manuskript.io as io import manuskript.data as data path = os.path.join(sys.path[1], "sample-projects/book-of-acts") diff --git a/manuskript/data/__init__.py b/manuskript/data/__init__.py index 36d07c88..0ce564b9 100644 --- a/manuskript/data/__init__.py +++ b/manuskript/data/__init__.py @@ -1,13 +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.goal import GoalKind, Goal -from manuskript.data.outline import Outline, OutlineFolder, OutlineText from manuskript.data.revisions import Revisions -from manuskript.data.color import Color -from manuskript.data.labels import LabelHost, Label -from manuskript.data.status import StatusHost, Status from manuskript.data.settings import Settings +from manuskript.data.status import StatusHost, Status from manuskript.data.unique_id import UniqueIDHost, UniqueID diff --git a/manuskript/data/characters.py b/manuskript/data/characters.py new file mode 100644 index 00000000..e7d1f634 --- /dev/null +++ b/manuskript/data/characters.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# --!-- coding: utf8 --!-- + +import os + +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 = True + 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() + + @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", True) + 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 = dict() + + for (key, value) in self.details.items(): + metadata[key] = value + + metadata["ID"] = str(self.UID.value) + metadata["Name"] = self.name + 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 + + 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() diff --git a/manuskript/data/labels.py b/manuskript/data/labels.py index 6c691e78..dea3ff71 100644 --- a/manuskript/data/labels.py +++ b/manuskript/data/labels.py @@ -67,6 +67,6 @@ class LabelHost: metadata = dict() for label in self.labels: - metadata[label.name] = str(label.color) + metadata[label.name] = label.color self.file.save((metadata, None)) diff --git a/manuskript/data/outline.py b/manuskript/data/outline.py index c2509d76..a58f2572 100644 --- a/manuskript/data/outline.py +++ b/manuskript/data/outline.py @@ -5,7 +5,7 @@ import os from enum import Enum, unique from manuskript.data.goal import Goal -from manuskript.data.unique_id import UniqueIDHost, UniqueID +from manuskript.data.unique_id import UniqueIDHost from manuskript.io.mmdFile import MmdFile @@ -23,8 +23,8 @@ class OutlineItem: self.outline = outline self.state = OutlineState.UNDEFINED - self.title = "" self.UID = None + self.title = "" self.type = "" self.summarySentence = None self.summaryFull = None diff --git a/manuskript/data/project.py b/manuskript/data/project.py index 7b6953d1..c3be32bd 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -7,6 +7,7 @@ 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.outline import Outline from manuskript.data.revisions import Revisions @@ -23,6 +24,7 @@ class Project: 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.outline = Outline(self.file.dir_path) self.revisions = Revisions(self.file.dir_path) @@ -43,6 +45,7 @@ class Project: self.labels.load() self.statuses.load() self.settings.load() + self.characters.load() self.plots.load() self.outline.load() self.revisions.load() @@ -58,6 +61,7 @@ class Project: self.labels.save() self.statuses.save() self.settings.save() + self.characters.save() self.plots.save() self.outline.save() #self.revisions.save() From 674cebdea2559e3b22cf999c3a2f02a84eadad8f Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Sun, 9 May 2021 02:14:36 +0200 Subject: [PATCH 11/13] Fixed differences between new data models and sample data Signed-off-by: TheJackiMonster --- bin/test_io.py | 2 ++ manuskript/data/characters.py | 21 +++++++++++++-------- manuskript/data/goal.py | 7 +++++-- manuskript/data/labels.py | 4 ++-- manuskript/data/outline.py | 10 ++++++---- manuskript/io/mmdFile.py | 9 ++++++--- 6 files changed, 34 insertions(+), 19 deletions(-) diff --git a/bin/test_io.py b/bin/test_io.py index cf203a1e..94ce9981 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -14,3 +14,5 @@ path = os.path.join(sys.path[1], "sample-projects/book-of-acts") project = data.Project(path + ".msk") project.load() + +project.save() diff --git a/manuskript/data/characters.py b/manuskript/data/characters.py index e7d1f634..a78ac18b 100644 --- a/manuskript/data/characters.py +++ b/manuskript/data/characters.py @@ -3,6 +3,7 @@ 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 @@ -17,7 +18,7 @@ class Character: self.UID = None self.name = None self.importance = None - self.pov = True + self.POV = None self.motivation = None self.goal = None self.conflict = None @@ -29,6 +30,9 @@ class Character: 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: @@ -47,7 +51,7 @@ class Character: 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", True) + 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) @@ -64,15 +68,12 @@ class Character: self.details[key] = value def save(self): - metadata = dict() + metadata = OrderedDict() - for (key, value) in self.details.items(): - metadata[key] = value - - metadata["ID"] = str(self.UID.value) metadata["Name"] = self.name + metadata["ID"] = str(self.UID.value) metadata["Importance"] = self.importance - metadata["POV"] = self.pov + metadata["POV"] = self.POV metadata["Motivation"] = self.motivation metadata["Goal"] = self.goal metadata["Conflict"] = self.conflict @@ -83,6 +84,10 @@ class Character: 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)) diff --git a/manuskript/data/goal.py b/manuskript/data/goal.py index 4a406f6f..8a128136 100644 --- a/manuskript/data/goal.py +++ b/manuskript/data/goal.py @@ -13,11 +13,14 @@ class GoalKind(Enum): class Goal: def __init__(self, value: int = 0, kind: GoalKind = GoalKind.WORDS): - self.value = 0 + self.value = value self.kind = kind def __str__(self): - return str(self.value) + " " + self.kind.name.lower() + if self.kind != GoalKind.WORDS: + return str(self.value) + " " + self.kind.name.lower() + else: + return str(self.value) @classmethod def parse(cls, string: str): diff --git a/manuskript/data/labels.py b/manuskript/data/labels.py index dea3ff71..556f6041 100644 --- a/manuskript/data/labels.py +++ b/manuskript/data/labels.py @@ -66,7 +66,7 @@ class LabelHost: def save(self): metadata = dict() - for label in self.labels: - metadata[label.name] = label.color + for (name, label) in self.labels.items(): + metadata[name] = label.color self.file.save((metadata, None)) diff --git a/manuskript/data/outline.py b/manuskript/data/outline.py index a58f2572..e3df7c57 100644 --- a/manuskript/data/outline.py +++ b/manuskript/data/outline.py @@ -3,6 +3,7 @@ import os +from collections import OrderedDict from enum import Enum, unique from manuskript.data.goal import Goal from manuskript.data.unique_id import UniqueIDHost @@ -56,13 +57,13 @@ class OutlineItem: @classmethod def saveMetadata(cls, item): - metadata = dict() + metadata = OrderedDict() if item.UID is None: return metadata - metadata["ID"] = str(item.UID.value) metadata["title"] = item.title + metadata["ID"] = str(item.UID.value) metadata["type"] = item.type metadata["summarySentence"] = item.summarySentence metadata["summaryFull"] = item.summaryFull @@ -71,7 +72,7 @@ class OutlineItem: metadata["label"] = item.label metadata["status"] = item.status metadata["compile"] = item.compile - metadata["setGoal"] = str(item.goal) + metadata["setGoal"] = item.goal return metadata @@ -101,6 +102,7 @@ class OutlineText(OutlineItem): def save(self): if self.state == OutlineState.OPTIMIZED: + self.outline.host.removeID(self.UID) self.load(False) metadata = OutlineItem.saveMetadata(self) @@ -163,7 +165,7 @@ class OutlineFolder(OutlineItem): def save(self): self.type = "folder" metadata = OutlineItem.saveMetadata(self) - self.file.save((metadata, "")) + self.file.save((metadata, "\n")) class Outline: diff --git a/manuskript/io/mmdFile.py b/manuskript/io/mmdFile.py index 1ca4356d..076c7e1c 100644 --- a/manuskript/io/mmdFile.py +++ b/manuskript/io/mmdFile.py @@ -8,7 +8,7 @@ from manuskript.io.abstractFile import AbstractFile class MmdFile(AbstractFile): - def __init__(self, path, metaSpacing = 16): + def __init__(self, path, metaSpacing=16): AbstractFile.__init__(self, path) self.metaSpacing = metaSpacing @@ -56,7 +56,10 @@ class MmdFile(AbstractFile): metadata, body = content metaSpacing = self.metaSpacing - for key in metadata.keys(): + 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: @@ -65,7 +68,7 @@ class MmdFile(AbstractFile): continue spacing = metaSpacing - (len(key) + 2) - lines = value.split("\n") + lines = str(value).split("\n") file.write(key + ": " + spacing * " " + lines[0] + "\n") From ee881bdb173f24c7dd8872a608c78752848240dd Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Sun, 9 May 2021 17:07:30 +0200 Subject: [PATCH 12/13] Added data model for world and its items Signed-off-by: TheJackiMonster --- bin/test_io.py | 10 ++++ manuskript/data/project.py | 4 ++ manuskript/data/world.py | 116 +++++++++++++++++++++++++++++++++++++ manuskript/io/opmlFile.py | 46 ++++++++++----- 4 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 manuskript/data/world.py diff --git a/bin/test_io.py b/bin/test_io.py index 94ce9981..808c83c9 100644 --- a/bin/test_io.py +++ b/bin/test_io.py @@ -9,10 +9,20 @@ 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() diff --git a/manuskript/data/project.py b/manuskript/data/project.py index c3be32bd..cbc063e6 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -9,6 +9,7 @@ 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 @@ -26,6 +27,7 @@ class Project: 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) @@ -47,6 +49,7 @@ class Project: self.settings.load() self.characters.load() self.plots.load() + self.world.load() self.outline.load() self.revisions.load() @@ -63,6 +66,7 @@ class Project: self.settings.save() self.characters.save() self.plots.save() + self.world.save() self.outline.save() #self.revisions.save() diff --git a/manuskript/data/world.py b/manuskript/data/world.py new file mode 100644 index 00000000..49d6e28b --- /dev/null +++ b/manuskript/data/world.py @@ -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) diff --git a/manuskript/io/opmlFile.py b/manuskript/io/opmlFile.py index f5675f49..3a86095a 100644 --- a/manuskript/io/opmlFile.py +++ b/manuskript/io/opmlFile.py @@ -7,10 +7,12 @@ from manuskript.io.xmlFile import XmlFile class OpmlOutlineItem: - def __init__(self, tag="outline"): - self.tag = tag + def __init__(self): self.attributes = dict() - self.children = [] + self.children = list() + + def __iter__(self): + return self.children.__iter__() def keys(self): return self.attributes.keys() @@ -23,7 +25,7 @@ class OpmlFile(XmlFile): @classmethod def loadOutline(cls, element): - outline = OpmlOutlineItem(element.tag) + outline = OpmlOutlineItem() for key in element.keys(): outline.attributes[key] = element.get(key) @@ -37,25 +39,37 @@ class OpmlFile(XmlFile): tree = XmlFile.load(self) root = tree.getroot() - return OpmlFile.loadOutline(root) + 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=None): - if parent is None: - element = etree.Element(outline.tag) - else: - element = etree.SubElement(parent, outline.tag) + def saveOutline(cls, outline, parent): + element = etree.SubElement(parent, "outline") - for key in outline.keys(): - element.attrib[key] = outline.attributes[key] + 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) - return element - def save(self, content): - root = OpmlFile.saveOutline(content) - tree = etree.ElementTree(root) + 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) From 9078079241332affb5e675424f46accfdf8896b2 Mon Sep 17 00:00:00 2001 From: TheJackiMonster Date: Fri, 14 May 2021 19:46:39 +0200 Subject: [PATCH 13/13] Added remove function for file types Signed-off-by: TheJackiMonster --- manuskript/data/project.py | 11 +++++++++++ manuskript/io/abstractFile.py | 3 +++ manuskript/io/mmdFile.py | 6 +++++- manuskript/io/mskFile.py | 6 ++++++ manuskript/io/textFile.py | 6 ++++++ manuskript/io/xmlFile.py | 6 ++++++ manuskript/io/zipFile.py | 7 ++++++- 7 files changed, 43 insertions(+), 2 deletions(-) diff --git a/manuskript/data/project.py b/manuskript/data/project.py index cbc063e6..a9a80ec2 100644 --- a/manuskript/data/project.py +++ b/manuskript/data/project.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- +import os + from zipfile import BadZipFile from manuskript.data.info import Info from manuskript.data.summary import Summary @@ -34,6 +36,15 @@ class Project: 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() diff --git a/manuskript/io/abstractFile.py b/manuskript/io/abstractFile.py index 679890ba..574cf73a 100644 --- a/manuskript/io/abstractFile.py +++ b/manuskript/io/abstractFile.py @@ -12,3 +12,6 @@ class AbstractFile: def save(self, content): raise IOError('Saving undefined!') + + def remove(self): + raise IOError('Removing undefined!') diff --git a/manuskript/io/mmdFile.py b/manuskript/io/mmdFile.py index 076c7e1c..e881dcb6 100644 --- a/manuskript/io/mmdFile.py +++ b/manuskript/io/mmdFile.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- - +import os import re from manuskript.io.abstractFile import AbstractFile @@ -77,3 +77,7 @@ class MmdFile(AbstractFile): if not (body is None): file.write("\n" + body) + + def remove(self): + if os.path.exists(self.path): + os.remove(self.path) diff --git a/manuskript/io/mskFile.py b/manuskript/io/mskFile.py index 8604bfbf..5cb6e81e 100644 --- a/manuskript/io/mskFile.py +++ b/manuskript/io/mskFile.py @@ -61,3 +61,9 @@ class MskFile(TextFile, 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) diff --git a/manuskript/io/textFile.py b/manuskript/io/textFile.py index 56738ca8..a51d255d 100644 --- a/manuskript/io/textFile.py +++ b/manuskript/io/textFile.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- +import os + from manuskript.io.abstractFile import AbstractFile @@ -13,3 +15,7 @@ class TextFile(AbstractFile): 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) diff --git a/manuskript/io/xmlFile.py b/manuskript/io/xmlFile.py index bbe33f41..d4a11503 100644 --- a/manuskript/io/xmlFile.py +++ b/manuskript/io/xmlFile.py @@ -1,6 +1,8 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- +import os + from lxml import etree from manuskript.io.abstractFile import AbstractFile @@ -14,3 +16,7 @@ class XmlFile(AbstractFile): 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) diff --git a/manuskript/io/zipFile.py b/manuskript/io/zipFile.py index ce583ae9..1457e636 100644 --- a/manuskript/io/zipFile.py +++ b/manuskript/io/zipFile.py @@ -1,8 +1,9 @@ #!/usr/bin/env python # --!-- coding: utf8 --!-- -import tempfile +import os import shutil +import tempfile from zipfile import ZipFile as _ZipFile from manuskript.io.abstractFile import AbstractFile @@ -49,3 +50,7 @@ class ZipFile(AbstractFile): 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)