Skip to content

Commit

Permalink
feat: wip: Export document settings & attributes to xlsx
Browse files Browse the repository at this point in the history
  • Loading branch information
robtaylor committed Oct 25, 2024
1 parent a4eaf5f commit 229ac25
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 45 deletions.
101 changes: 56 additions & 45 deletions doorstop/core/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import yaml

from doorstop import common, settings
from doorstop import common, settings as dsettings
from doorstop.common import (
DoorstopError,
DoorstopInfo,
Expand Down Expand Up @@ -135,7 +135,7 @@ def new(
"""
# Check separator
if sep and sep not in settings.SEP_CHARS:
if sep and sep not in dsettings.SEP_CHARS:
raise DoorstopError("invalid UID separator '{}'".format(sep))

config = os.path.join(path, Document.CONFIG)
Expand Down Expand Up @@ -193,7 +193,23 @@ def include(self, node):
return self._load(text, yamlfile, loader=IncludeLoader)

@property
def set_properties(self, data):
def settings(self):
"""
Document settings as a dict
"""
sets = {}
for key, value in self._data.items():
if key == "prefix":
sets[key] = str(value)
elif key == "parent":
if value:
sets[key] = value
else:
sets[key] = value
return sets

@settings.setter
def settings(self, settings):
def fill_key(key, value):
match key:
case "prefix":
Expand All @@ -210,17 +226,29 @@ def fill_key(key, value):
msg = f"unexpected document setting '{key}' in: {self.config}"
raise DoorstopError(msg)

# Store parsed data
sets = data.get("settings", {})
for key, value in sets.items():
for key, value in settings.items():
try:
fill_key(key, value)
except (AttributeError, TypeError, ValueError):
msg = f"invalid value for '{key}' in: {self.config}"
raise DoorstopError(msg)

@property
def attributes(self):
"""
Document attributes as a dict
"""
# Save the attributes
attributes = {}
if self._attribute_defaults:
attributes["defaults"] = self._attribute_defaults
if self._extended_reviewed:
attributes["reviewed"] = self._extended_reviewed
return attributes

@attributes.setter
def attributes(self, attributes):
# Store parsed attributes
attributes = data.get("attributes", {})
for key, value in attributes.items():
if key == "defaults":
self._attribute_defaults = value
Expand All @@ -234,49 +262,32 @@ def fill_key(key, value):
)
raise DoorstopError(msg)

self.extensions = data.get("extensions", {})

def load(self, reload=False):
"""Load the document's properties from its file."""
if self._loaded and not reload:
return
log.debug("loading {}...".format(repr(self)))
data = self._load_with_include(self.config)
self._load_from_dict(data, reload)
self.settings = data["settings"] if "settings" in data else {}
self.attributes = data["attributes"] if "attributes" in data else {}
self.extensions = data.get("extensions", {})
# Set meta attributes
self._loaded = True
if reload:
list(self._iter(reload=reload))

@property
def properties(self):
data = {}
sets = {}
for key, value in self._data.items():
if key == "prefix":
sets[key] = str(value)
elif key == "parent":
if value:
sets[key] = value
else:
sets[key] = value
data["settings"] = sets
# Save the attributes
attributes = {}
if self._attribute_defaults:
attributes["defaults"] = self._attribute_defaults
if self._extended_reviewed:
attributes["reviewed"] = self._extended_reviewed
if attributes:
data["attributes"] = attributes
return data

@edit_document
def save(self):
"""Save the document's properties to its file."""
log.debug("saving {}...".format(repr(self)))
# Dump the data to YAML
text = self._dump(self.properties)

data = {}
if self.settings:
data["settings"] = self.settings,
if self.attributes:
data["attributes"] = self.attributes

text = self._dump(data)
# Save the YAML to file
self._write(text, self.config)
# Set meta attributes
Expand Down Expand Up @@ -322,7 +333,7 @@ def _iter(self, reload=False):
except Exception:
log.error("Unable to load: %s", item)
raise
if settings.CACHE_ITEMS and self.tree:
if dsettings.CACHE_ITEMS and self.tree:
self.tree._item_cache[ # pylint: disable=protected-access
item.uid
] = item
Expand Down Expand Up @@ -401,7 +412,7 @@ def sep(self):
def sep(self, value):
"""Set the prefix-number separator to use for new item UIDs."""
# TODO: raise a specific exception for invalid separator characters?
assert not value or value in settings.SEP_CHARS
assert not value or value in dsettings.SEP_CHARS
self._data["sep"] = value.strip()
# TODO: should the new separator be applied to all items?

Expand Down Expand Up @@ -489,7 +500,7 @@ def index(self, value):
path = os.path.join(self.path, Document.INDEX)
log.info("creating {} index...".format(self))
common.write_lines(
self._lines_index(self.items), path, end=settings.WRITE_LINESEPERATOR
self._lines_index(self.items), path, end=dsettings.WRITE_LINESEPERATOR
)

@index.deleter
Expand Down Expand Up @@ -525,7 +536,7 @@ def add_item(self, number=None, level=None, reorder=True, defaults=None, name=No
name, self.prefix
)
raise DoorstopError(msg)
if self.sep not in settings.SEP_CHARS:
if self.sep not in dsettings.SEP_CHARS:
msg = "cannot add item with name '{}' to document '{}' with an invalid separator '{}'".format(
name, self.prefix, self.sep
)
Expand Down Expand Up @@ -617,13 +628,13 @@ def reorder(self, manual=True, automatic=True, start=None, keep=None, _items=Non
@staticmethod
def _lines_index(items):
"""Generate (pseudo) YAML lines for the document index."""
yield "#" * settings.MAX_LINE_LENGTH
yield "#" * dsettings.MAX_LINE_LENGTH
yield "# THIS TEMPORARY FILE WILL BE DELETED AFTER DOCUMENT REORDERING"
yield "# MANUALLY INDENT, DEDENT, & MOVE ITEMS TO THEIR DESIRED LEVEL"
yield "# A NEW ITEM WILL BE ADDED FOR ANY UNKNOWN IDS, i.e. - new: "
yield "# THE COMMENT WILL BE USED AS THE ITEM TEXT FOR NEW ITEMS"
yield "# CHANGES WILL BE REFLECTED IN THE ITEM FILES AFTER CONFIRMATION"
yield "#" * settings.MAX_LINE_LENGTH
yield "#" * dsettings.MAX_LINE_LENGTH
yield ""
yield "initial: {}".format(items[0].level if items else 1.0)
yield "outline:"
Expand All @@ -632,8 +643,8 @@ def _lines_index(items):
lines = item.text.strip().splitlines()
comment = lines[0].replace("\\", "\\\\") if lines else ""
line = space + "- {u}: # {c}".format(u=item.uid, c=comment)
if len(line) > settings.MAX_LINE_LENGTH:
line = line[: settings.MAX_LINE_LENGTH - 3] + "..."
if len(line) > dsettings.MAX_LINE_LENGTH:
line = line[: dsettings.MAX_LINE_LENGTH - 3] + "..."
yield line

@staticmethod
Expand Down Expand Up @@ -863,9 +874,9 @@ def get_issues(
return

# Reorder or check item levels
if settings.REORDER:
if dsettings.REORDER:
self.reorder(_items=items)
elif settings.CHECK_LEVELS:
elif dsettings.CHECK_LEVELS:
yield from self._get_issues_level(items)

item_validator = ItemValidator()
Expand Down
23 changes: 23 additions & 0 deletions doorstop/core/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,22 @@ def _file_xlsx(obj, path, auto=False):
return path


def _add_properties_sheet(wb, document_properties):
sheet = wb.create_sheet(title="Document Properties")
sheet.append([
"prefix",
"settings key",
"settings value",
"attributes key",
"attributes value",
])
for prefix, data in document_properties.items():
for set_k, set_v in data["settings"].items():
sheet.append([prefix, repr(set_k), repr(set_v)])
for attr_k, attr_v in data["attributes"].items():
sheet.append([prefix, "", "", repr(attr_k), repr(attr_v)])


def _get_xlsx(obj, path, auto):
# Create a new workbook
workbook = openpyxl.Workbook()
Expand All @@ -272,10 +288,17 @@ def _get_xlsx(obj, path, auto):

first_sheet = None
if is_tree(obj):
document_properties = {}
log.debug("xlsx export: exporting tree")
for obj2, path2 in iter_documents(obj, path, ".xlsx"):
sheet = _add_xlsx_sheet(workbook, obj2, auto)
first_sheet = sheet or first_sheet
document_properties[obj2.prefix] = {
"settings": obj2.settings,
"attributes": obj2.attributes
}
if document_properties:
_add_properties_sheet(workbook, document_properties)
else:
log.debug("xlsx export: exporting single Item/Document")
first_sheet = _add_xlsx_sheet(workbook, obj, auto)
Expand Down

0 comments on commit 229ac25

Please sign in to comment.