Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/issue 2 initial setup (#1) #4

Merged
merged 1 commit into from
Jan 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/main/python/Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
lxml = "*"
pytest = "*"

[dev-packages]

[requires]
python_version = "3.11"
144 changes: 144 additions & 0 deletions src/main/python/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/main/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# pydita Python scripts and modules

These modules and scripts provide general purpose DITA processing.
3 changes: 3 additions & 0 deletions src/main/python/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
.. include:: README.md
"""
30 changes: 30 additions & 0 deletions src/main/python/ditalib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Python DITA support library

Provides support for processing DITA content with full key
and DITAVAL awareness.

Provides the following services:

* Map resolution: resolve trees of maps into a single in-memory XML document with all submap information preserved and (optionally) with metadata propagated per the DITA 1.3/2.0 rules.
* Key space construction and management: Construct DITA 1.3/2.0 key spaces and make them available for key resolution and key space reporting.
* DITA processing utilities, including DITA @class value checking, topicref type checking (topichead, topicgroup, map reference, etc.), and reference resolution.
* DITAVAL filtering and reporting: Construct DITA filters that can be used to filter DITA elements and report on the details of a filter (conditions, actions, etc.)
* General error collection and reporting facilities beyond Python's built-in logging.
* Generic XML processing utilities, including configuring parsers with Open Toolkit-managed entity resolution libraries.

## Configuration

In order to create parsers initialized with an Open Toolkit entity catalog you need to tell ditalib where to find the Open Toolkit, which you can do in several ways:

* Create a file `.build.properties` in your home directory (`$HOME`) with the entry `dita.ot.dir`:
```
dita.ot.dir=${user.home}/ditaot/ditaot-3_7_4
```

This file can have other properties.
* Set the environment variable `DITA_OT_DIR` with the path to the OT:
```
% export DITA_OT_DIR="{HOME}/ditaot/ditaot-3_7_4"
```

* (TBD) Run the `
15 changes: 15 additions & 0 deletions src/main/python/ditalib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
.. include:: README.md
"""

import os, sys

# Add the python dir to the import path
# so local module imports will work

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
libDir = os.path.abspath(os.path.join(SCRIPT_DIR, '../..'))
sys.path.append(libDir)

print('### sys.path:')
print(sys.path)
53 changes: 53 additions & 0 deletions src/main/python/ditalib/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Manages and provides access to configuration information needed to do DITA processing.
"""

import os, sys
from io import IOBase

homeDir: str = os.environ.get("HOME")
buildPropertiesFile: str = ".build.properties"
buildPropertiesPath: str = os.path.join(homeDir, buildPropertiesFile)
otDirProperty: str = "dita.ot.dir"
otDirEnvVariable: str = "DITA_OT_DIR"

def readPropertiesFile(filePath: str) -> dict[str, str]:
"""Reads the specified Java properties file (name=value) into a
dictionary.

Args:

filePath (str): Path to the properties file to load.

Returns:

dict[str, str]: Dictionary where keys are the property names and values are the property values.
"""
props: dict[str, str] = {}
if os.path.exists(filePath):
f: IOBase = open(filePath,'r')
for line in f.readlines():
if line.startswith('#'):
continue
(name, value) = line.split('=')
props[name] = value.strip()
f.close()
else:
raise FileNotFoundError(filePath)
return props

def getDitaOtPath() -> str:
"""Gets the configured DITA OT path if it can find it.

Returns:

str: The absolute path to the configured DITA OT or None if
the configuration is not found.
"""

otPath: str = os.environ.get(otDirEnvVariable)
if otPath is None:
properties: dict[str, str] = readPropertiesFile(buildPropertiesPath)
otPath: str = properties.get(otDirProperty)

return otPath

17 changes: 17 additions & 0 deletions src/main/python/ditalib/ditacontext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Object that maintains DITA-related context for use in DITA-aware
processing of DITA elements.
"""

from lxml import etree
from lxml.etree import Element

from ditalib.logging import Errors

class DitaContext:
"""Provides access to current context needed to do DITA processing.
"""

def __init__(self):
self._mapcontext: Element = None
self._ditavalFilter: DitavalFilter = DitavalFilter()
self._errors: Errors = Errors()
83 changes: 83 additions & 0 deletions src/main/python/ditalib/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Error capture and logging utilities beyond built-in logging facilities.
"""

from typing import Union
from datetime import datetime
from copy import copy, deepcopy

# Serverity levels:
INFO: str = "info"
ERROR: str = "error"
FATAL: str = "fatal"
WARN: str = "warning"

class ErrorRecord():
"""Captures the details about an error
"""

def __init__(self, key: str, err: Exception, severity:str=ERROR,timestamp:datetime=datetime.now()):
"""A single error record

Args:

key (str): The key for the record, such as a file name.

err (Exception): The exception that describes the error being recorded.

severity (str, optional): The error's severity. Defaults to ERROR.

timestamp (datetime, optional): The time the error occurred. Defaults to datetime.now().
"""
self._key = key
self._err = Exception
self._severity = severity
self._timestamp: datetime = timestamp

def getKey(self) -> str:
return self._key

def getException(self) -> Exception:
return self._err

def getSeverity(self) -> str:
return self._severity

class Errors(dict):
"""Maintains a dictionary of named things to ErrorRecord objects.
"""

def __init__(self):
super().__init__()

def logErrorRecord(self, error: ErrorRecord) -> None:
"""Logs an error record.

Args:

error (ErrorRecord): The error record to be logged.
"""
errors: list[ErrorRecord] = self.get(error.getKey())
if errors is None:
errors = []
self[error.getKey()] = errors
errors.append(error)

def logError(self, key: str, err: Union[str, Exception], severity="error") -> None:
"""Log an error.

Args:

key (str): Key to associate with the error, such as filename.

err (Exception): Message or exception that describes the error being logged.

severity (str, optional): _description_. Defaults to "error".
"""
exception: Exception = None
if isinstance(err, Exception):
exception = err
else:
exception = Exception(err)

error: ErrorRecord = ErrorRecord(key, exception, severity=severity)
self.logErrorRecord(error)
Loading