Reads characters

This commit is contained in:
Olivier Keshavjee 2016-03-10 11:45:40 +01:00
parent 10fdb89eef
commit b2a51e1a09
3 changed files with 350 additions and 30 deletions

View file

@ -12,10 +12,15 @@ import manuskript.load_save.version_1 as v1
def saveProject(version=None): def saveProject(version=None):
# While debugging, we don't save the project
return
if version == 0: if version == 0:
v0.saveProject() v0.saveProject()
else: else:
v1.saveProject(zip=True) v1.saveProject()
# FIXME: add settings to chose between saving as zip or not.
def loadProject(project): def loadProject(project):
@ -44,9 +49,10 @@ def loadProject(project):
with open(project, "r") as f: with open(project, "r") as f:
version = int(f.read()) version = int(f.read())
print("Loading:", project)
print("Detected file format version:", version) print("Detected file format version:", version)
if version == 0 or True: if version == 0:
v0.loadProject(project) v0.loadProject(project)
else: else:
v1.loadProject(project) v1.loadProject(project, zip=isZip)

View file

@ -7,18 +7,22 @@
# versioning and third-partty editing. # versioning and third-partty editing.
import os import os
import re
import shutil import shutil
import string import string
import zipfile import zipfile
from collections import OrderedDict
from PyQt5.QtCore import Qt, QModelIndex from PyQt5.QtCore import Qt, QModelIndex
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor, QStandardItem
from manuskript import settings from manuskript import settings
from manuskript.enums import Character, World, Plot, PlotStep, Outline from manuskript.enums import Character, World, Plot, PlotStep, Outline
from manuskript.functions import mainWindow, iconColor from manuskript.functions import mainWindow, iconColor, iconFromColorString
from lxml import etree as ET from lxml import etree as ET
from manuskript.load_save.version_0 import loadFilesFromZip
from manuskript.models.characterModel import CharacterInfo
try: try:
import zlib # Used with zipfile for compression import zlib # Used with zipfile for compression
@ -30,6 +34,31 @@ except:
cache = {} cache = {}
characterMap = OrderedDict([
(Character.name, "Name"),
(Character.ID, "ID"),
(Character.importance, "Importance"),
(Character.motivation, "Motivation"),
(Character.goal, "Goal"),
(Character.conflict, "Conflict"),
(Character.summarySentence, "Phrase Summary"),
(Character.summaryPara, "Paragraph Summary"),
(Character.summaryFull, "Full Summary"),
(Character.notes, "Notes"),
])
# characterMap = {
# Character.name: "Name",
# Character.ID: "ID",
# Character.importance: "Importance",
# Character.motivation: "Motivation",
# Character.goal: "Goal",
# Character.conflict: "Conflict",
# Character.summarySentence: "Phrase Summary",
# Character.summaryPara: "Paragraph Summary",
# Character.summaryFull: "Full Summary",
# Character.notes: "Notes",
# }
def formatMetaData(name, value, tabLength=10): def formatMetaData(name, value, tabLength=10):
# Multiline formatting # Multiline formatting
@ -177,18 +206,6 @@ def saveProject(zip=None):
# In a character folder # In a character folder
path = os.path.join("characters", "{name}.txt") path = os.path.join("characters", "{name}.txt")
_map = [
(Character.name, "Name"),
(Character.ID, "ID"),
(Character.importance, "Importance"),
(Character.motivation, "Motivation"),
(Character.goal, "Goal"),
(Character.conflict, "Conflict"),
(Character.summarySentence, "Phrase Summary"),
(Character.summaryPara, "Paragraph Summary"),
(Character.summaryFull, "Full Summary"),
(Character.notes, "Notes"),
]
mdl = mw.mdlCharacter mdl = mw.mdlCharacter
# Review characters # Review characters
@ -196,11 +213,15 @@ def saveProject(zip=None):
# Generates file's content # Generates file's content
content = "" content = ""
for m, name in _map: for m in characterMap:
val = mdl.data(c.index(m.value)).strip() val = mdl.data(c.index(m.value)).strip()
if val: if val:
content += formatMetaData(name, val, 20) content += formatMetaData(characterMap[m], val, 20)
# Character's color:
content += formatMetaData("Color", c.color().name(QColor.HexRgb), 20)
# Character's infos
for info in c.infos: for info in c.infos:
content += formatMetaData(info.description, info.value, 20) content += formatMetaData(info.description, info.value, 20)
@ -334,9 +355,7 @@ def saveProject(zip=None):
filename = os.path.join(dir, folder, path) filename = os.path.join(dir, folder, path)
os.makedirs(os.path.dirname(filename), exist_ok=True) os.makedirs(os.path.dirname(filename), exist_ok=True)
# TODO: the first time it saves, it will overwrite everything, since it's not yet in cache. # Check if content is in cache, and write if necessary
# Or we have to cache while loading.
if path not in cache or cache[path] != content: if path not in cache or cache[path] != content:
log("* Writing file", path) log("* Writing file", path)
mode = "w" + ("b" if type(content) == bytes else "") mode = "w" + ("b" if type(content) == bytes else "")
@ -344,10 +363,6 @@ def saveProject(zip=None):
f.write(content) f.write(content)
cache[path] = content cache[path] = content
else:
pass
# log(" In cache, and identical. Do nothing.")
# Removing phantoms # Removing phantoms
for path in [p for p in cache if p not in [p for p, c in files]]: for path in [p for p in cache if p not in [p for p, c in files]]:
filename = os.path.join(dir, folder, path) filename = os.path.join(dir, folder, path)
@ -569,15 +584,313 @@ def outlineToMMD(item):
return content return content
########################################################################################################################
# LOAD
########################################################################################################################
def loadProject(project): def loadProject(project, zip=None):
""" """
Loads a project. Loads a project.
@param project: the filename of the project to open. @param project: the filename of the project to open.
@param zip: whether the project is a zipped or not.
@return: an array of errors, empty if None. @return: an array of errors, empty if None.
""" """
# Don't forget to cache everything that is loaded # FIXME: Don't forget to cache everything that is loaded
# In order to save only what has changed. # In order to save only what has changed.
pass mw = mainWindow()
errors = []
####################################################################################################################
# Read and store everything in a dict
log("\nLoading {} ({})".format(project, "ZIP" if zip else "not zip"))
if zip:
files = loadFilesFromZip(project)
# Decode files
for f in files:
if f[-4:] not in [".xml", "opml"]:
files[f] = files[f].decode("utf-8")
else:
# Project path
dir = os.path.dirname(project)
# Folder containing file: name of the project file (without .msk extension)
folder = os.path.splitext(os.path.basename(project))[0]
# The full path towards the folder containing files
path = os.path.join(dir, folder, "")
files = {}
for dirpath, dirnames, filenames in os.walk(path):
p = dirpath.replace(path, "")
for f in filenames:
mode = "r" + ("b" if f[-4:] in [".xml", "opml"] else "")
with open(os.path.join(dirpath, f), mode) as fo:
files[os.path.join(p, f)] = fo.read()
####################################################################################################################
# Settings
if "settings.txt" in files:
settings.load(files["settings.txt"], fromString=True, protocol=0)
else:
errors.append("settings.txt")
####################################################################################################################
# Labels
mdl = mw.mdlLabels
mdl.appendRow(QStandardItem("")) # Empty = No labels
if "labels.txt" in files:
log("\nReading labels:")
for s in files["labels.txt"].split("\n"):
if not s:
continue
m = re.search(r"^(.*?):\s*(.*)$", s)
txt = m.group(1)
col = m.group(2)
log("* Add status: {} ({})".format(txt, col))
icon = iconFromColorString(col)
mdl.appendRow(QStandardItem(icon, txt))
else:
errors.append("labels.txt")
####################################################################################################################
# Status
mdl = mw.mdlStatus
mdl.appendRow(QStandardItem("")) # Empty = No status
if "status.txt" in files:
log("\nReading Status:")
for s in files["status.txt"].split("\n"):
if not s:
continue
log("* Add status:", s)
mdl.appendRow(QStandardItem(s))
else:
errors.append("status.txt")
####################################################################################################################
# Infos
mdl = mw.mdlFlatData
if "infos.txt" in files:
md, body = parseMMDFile(files["infos.txt"], asDict=True)
row = []
for name in ["Title", "Subtitle", "Serie", "Volume", "Genre", "License", "Author", "Email"]:
row.append(QStandardItem(md.get(name, "")))
mdl.appendRow(row)
else:
errors.append("infos.txt")
####################################################################################################################
# Summary
mdl = mw.mdlFlatData
if "summary.txt" in files:
md, body = parseMMDFile(files["summary.txt"], asDict=True)
row = []
for name in ["Situation", "Sentence", "Paragraph", "Page", "Full"]:
row.append(QStandardItem(md.get(name, "")))
mdl.appendRow(row)
else:
errors.append("summary.txt")
####################################################################################################################
# Plots
mdl = mw.mdlPlots
if "plots.xml" in files:
log("\nReading plots:")
# xml = bytearray(files["plots.xml"], "utf-8")
root = ET.fromstring(files["plots.xml"])
for plot in root:
# Create row
row = getStandardItemRowFromXMLEnum(plot, Plot)
# Log
log("* Add plot: ", row[0].text())
# Characters
if row[Plot.characters.value].text():
IDs = row[Plot.characters.value].text().split(",")
item = QStandardItem()
for ID in IDs:
item.appendRow(QStandardItem(ID.strip()))
row[Plot.characters.value] = item
# Subplots
for step in plot:
row[Plot.steps.value].appendRow(
getStandardItemRowFromXMLEnum(step, PlotStep)
)
# Add row to the model
mdl.appendRow(row)
else:
errors.append("plots.xml")
####################################################################################################################
# World
mdl = mw.mdlWorld
if "world.opml" in files:
log("\nReading World:")
# xml = bytearray(files["plots.xml"], "utf-8")
root = ET.fromstring(files["world.opml"])
body = root.find("body")
for outline in body:
row = getOutlineItem(outline, World)
mdl.appendRow(row)
else:
errors.append("world.opml")
####################################################################################################################
# Characters
mdl = mw.mdlCharacter
log("\nReading Characters:")
for f in [f for f in files if "characters" in f]:
md, body = parseMMDFile(files[f])
c = mdl.addCharacter()
color = False
for desc, val in md:
# Base infos
if desc in characterMap.values():
key = [key for key, value in characterMap.items() if value == desc][0]
index = c.index(key.value)
mdl.setData(index, val)
# Character color
elif desc == "Color" and not color:
c.setColor(QColor(val))
# We remember the first time we found "Color": it is the icon color.
# If "Color" comes a second time, it is a Character's info.
color = True
# Character's infos
else:
c.infos.append(CharacterInfo(c, desc, val))
log("* Adds {} ({})".format(c.name(), c.ID()))
# if "perso.xml" in files:
# loadStandardItemModelXMLForCharacters(mw.mdlCharacter, files["perso.xml"])
# else:
# errors.append("perso.xml")
#
#
# if "outline.xml" in files:
# mw.mdlOutline.loadFromXML(files["outline.xml"], fromString=True)
# else:
# errors.append("outline.xml")
#
# return errors
def getOutlineItem(item, enum):
row = getStandardItemRowFromXMLEnum(item, enum)
log("* Add worldItem:", row[0].text())
for child in item:
sub = getOutlineItem(child, enum)
row[0].appendRow(sub)
return row
def getStandardItemRowFromXMLEnum(item, enum):
"""
Reads and etree item and creates a row of QStandardItems by cross-referencing an enum.
Returns a list of QStandardItems that can be added to a QStandardItemModel by appendRow.
@param item: the etree item
@param enum: the enum
@return: list of QStandardItems
"""
row = []
for i in range(len(enum)):
row.append(QStandardItem(""))
for name in item.attrib:
if name in enum.__members__:
row[enum[name].value] = QStandardItem(item.attrib[name])
return row
def parseMMDFile(text, asDict=False):
"""
Takes the content of a MultiMarkDown file (str) and returns:
1. A list containing metadatas: (description, value) if asDict is False.
If asDict is True, returns metadatas as an OrderedDict. Be aware that if multiple metadatas have the same description
(which is stupid, but hey), they will be lost except the last one.
2. The body of the file
@param text: the content of the file
@return: (list, str) or (OrderedDict, str)
"""
md = []
mdd = OrderedDict()
body = []
descr = ""
val = ""
inBody = False
for s in text.split("\n"):
if not inBody:
m = re.match(r"^(.*?):\s*(.*)$", s)
if m:
# Commit last metadata
if descr:
if descr == "None":
descr = ""
md.append((descr, val))
mdd[descr] = val
descr = ""
val = ""
# Store new values
descr = m.group(1)
val = m.group(2)
elif s[:4] == " ":
val += "\n" + s.strip()
elif s == "":
# End of metadatas
inBody = True
# Commit last metadata
if descr:
if descr == "None":
descr = ""
md.append((descr, val))
mdd[descr] = val
else:
body.append[s]
# We remove the second empty line (since we save with two empty lines)
if body and body[0] == "":
body = body[1:]
body = "\n".join(body)
if not asDict:
return md, body
else:
return mdd, body

View file

@ -156,12 +156,13 @@ class characterModel(QAbstractItemModel):
def addCharacter(self): def addCharacter(self):
""" """
Creates a new character Creates a new character
@return: nothing @return: the character
""" """
c = Character(model=self, name=self.tr("New character")) c = Character(model=self, name=self.tr("New character"))
self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters)) self.beginInsertRows(QModelIndex(), len(self.characters), len(self.characters))
self.characters.append(c) self.characters.append(c)
self.endInsertRows() self.endInsertRows()
return c
def removeCharacter(self, ID): def removeCharacter(self, ID):
""" """