mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-10-01 09:46:30 +13:00
Merge branch 'io' into gtk
This commit is contained in:
commit
47820061c7
25 changed files with 1701 additions and 0 deletions
28
bin/test_io.py
Normal file
28
bin/test_io.py
Normal 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()
|
15
manuskript/data/__init__.py
Normal file
15
manuskript/data/__init__.py
Normal 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
|
121
manuskript/data/characters.py
Normal file
121
manuskript/data/characters.py
Normal 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
26
manuskript/data/color.py
Normal 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
38
manuskript/data/goal.py
Normal 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
50
manuskript/data/info.py
Normal 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
72
manuskript/data/labels.py
Normal 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
224
manuskript/data/outline.py
Normal 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
191
manuskript/data/plots.py
Normal 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)
|
84
manuskript/data/project.py
Normal file
84
manuskript/data/project.py
Normal 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)
|
87
manuskript/data/revisions.py
Normal file
87
manuskript/data/revisions.py
Normal 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
131
manuskript/data/settings.py
Normal 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
70
manuskript/data/status.py
Normal 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")
|
38
manuskript/data/summary.py
Normal file
38
manuskript/data/summary.py
Normal 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))
|
43
manuskript/data/unique_id.py
Normal file
43
manuskript/data/unique_id.py
Normal 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
116
manuskript/data/world.py
Normal 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
10
manuskript/io/__init__.py
Normal 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
|
17
manuskript/io/abstractFile.py
Normal file
17
manuskript/io/abstractFile.py
Normal 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
14
manuskript/io/jsonFile.py
Normal 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
83
manuskript/io/mmdFile.py
Normal 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
69
manuskript/io/mskFile.py
Normal 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
75
manuskript/io/opmlFile.py
Normal 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
21
manuskript/io/textFile.py
Normal 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
22
manuskript/io/xmlFile.py
Normal 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
56
manuskript/io/zipFile.py
Normal 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)
|
Loading…
Reference in a new issue