""" Handles all write accesses to the Ontologies and kif files. The
SyntaxController's main purpose is to act as an intermediary between the user
and the Ontology.
This module contains:
- SyntaxController: The interface to the parser/serializer.
"""
import logging
from io import StringIO, BytesIO
from os import listdir, fdopen, remove
from os.path import basename, isdir, join
from pkg_resources import resource_filename
from tempfile import mkstemp
from subprocess import Popen, PIPE, DEVNULL
import pysumo
from .logger import actionlog
from . import parser
[docs]def get_ontologies(lpath=None):
""" Returns a set of all ontologies provided by pysumo as well as local ontologies. """
ret = set()
pdata = resource_filename('pysumo', 'data')
for f in listdir(resource_filename('pysumo', 'data')):
if f.endswith(".kif"):
ret.add(Ontology(join(pdata, f), lpath=lpath))
if isdir(pysumo.CONFIG_PATH):
for f in listdir(pysumo.CONFIG_PATH):
if f.endswith(".kif"):
ret.add(Ontology(join(pysumo.CONFIG_PATH, f), lpath=lpath))
return ret
[docs]class SyntaxController:
""" The high-level class containing the interface to all parsing/serialization operations.
All operations that can modify the Ontology or kif-file are passed through the SyntaxController.
The SyntaxController acts as a moderator between user-side widgets and the low-level API
ensuring that all requests are correct, that higher objects never gain direct access to
internal objects and that all changes to the Ontology are atomic.
Methods:
- parse_partial: Checks a code block for syntax errors.
- parse_patch: Checks a code for correctness and adds it to the Ontology.
- add_ontology: Adds an Ontology to the in-memory Ontology.
- remove_ontology: Removes an Ontology from the in-memory Ontology.
- undo: Undoes the most recent change to the ontology.
- redo: Redoes the most recent change to the ontology.
"""
def __init__(self, index):
""" Initializes the SyntaxController object. """
self.index = index
self.log = logging.getLogger('.' + __name__)
[docs] def parse_partial(self, code_block, ontology=None):
""" Tells self.parser to check code_block for syntactical correctness.
Arguments:
- code_block: the partial code block that will be checked
Raises:
- ParseError
"""
f = StringIO(code_block)
ast = parser.kifparse(f, ontology)
f.close()
return ast
[docs] def parse_patch(self, ontology, patch):
""" Apply a patch to the last version of the ontology and parse this new version
Arguments:
- ontology: the ontlogy which is patched
- patch: the patch to add to the ontology
Raises:
- ParseError
"""
(tempfile, tempfilepath) = mkstemp(text=True)
o = self.index.get_ontology_file(ontology)
tempfile = fdopen(tempfile, 'wt')
for l in o:
print(l, end='', file=tempfile)
tempfile.close()
p = Popen(["patch", "-u", tempfilepath], stdin=PIPE, stdout=DEVNULL)
p.communicate(patch.encode())
with open(tempfilepath) as f:
pos = f.tell()
num = ontology.action_log.queue_log(BytesIO(f.read().encode()))
f.seek(pos)
newast = parser.kifparse(f, ontology, ast=self.index.root)
try:
self.remove_ontology(ontology)
newast = parser.astmerge((self.index.root, newast))
except AttributeError:
pass
newast.ontology = None
self.index.update_index(newast)
self.index.ontologies.add(ontology)
ontology.action_log.ok_log_item(num)
remove(tempfilepath)
[docs] def add_ontology(self, ontology, newversion=None):
""" Adds ontology to the current in-memory Ontology.
Arguments:
- ontology: the ontology that will be added
- newversion: a string witch represent the new verison of the ontology
Raises:
- ParseError
"""
if newversion == None:
with open(ontology.path, errors='replace') as f:
pos = f.tell()
num = ontology.action_log.queue_log(BytesIO(f.read().encode()))
f.seek(pos)
newast = parser.kifparse(f, ontology, ast=self.index.root)
else:
num = ontology.action_log.queue_log(BytesIO(newversion.encode()))
f = StringIO(newversion)
newast = parser.kifparse(StringIO(newversion), ontology, ast=self.index.root)
try:
self.remove_ontology(ontology)
newast = parser.astmerge((self.index.root, newast))
except AttributeError:
pass
newast.ontology = None
self.index.update_index(newast)
self.index.ontologies.add(ontology)
ontology.action_log.ok_log_item(num)
[docs] def remove_ontology(self, ontology):
""" Removes ontology from the current in-memory Ontology.
Arguments:
- ontology: the ontology that will be removed
Raises:
- NoSuchOntologyError
"""
offset = 0
for n, c in enumerate(list(self.index.root.children)):
if c.ontology == ontology:
self.index.root.children.pop(n - offset)
offset = offset + 1
self.index.update_index(self.index.root)
self.index.ontologies.discard(ontology)
[docs] def undo(self, ontology):
""" Undoes the last action in ontology """
self.log.info('Undoing change to %s' % str(ontology))
kif = ontology.action_log.undo().getvalue().decode('utf8')
self._update_asts(ontology, kif)
[docs] def redo(self, ontology):
""" Redoes the last action in ontology """
self.log.info('Redoing change to %s' % str(ontology))
kif = ontology.action_log.redo().getvalue().decode('utf8')
self._update_asts(ontology, kif)
def _update_asts(self, ontology, kif):
ast = self.parse_partial(kif, ontology)
self.remove_ontology(ontology)
newast = parser.astmerge((self.index.root, ast))
self.index.update_index(newast)
[docs]class Ontology:
""" Contains basic information about a KIF file. This class is used to
maintain separation between different Ontology-files so the user can choose
which are active and where each Ontology should be saved.
Variables:
- name: The name of the Ontology.
- path: The location of the Ontology in the filesystem.
- url: The URL from which the Ontology can be updated.
- active: Whether or not the Ontology is currently loaded.
"""
def __init__(self, path, name=None, url=None, lpath=None):
""" Initializes an Ontology and instantiates variables. """
if name is None:
self.name = basename(path)
else:
self.name = name
self.action_log = actionlog.ActionLog(self.name, lpath)
with open(path, 'r+b') as f:
self.action_log.current = BytesIO(f.read())
self.path = path
self.url = url
self.active = False
[docs] def save(self):
""" Saves all pending changes in self to self.path. """
with open(self.path, 'w+b') as f:
f.write(self.action_log.current.getbuffer())
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
return self.name < other.name
def __hash__(self):
return hash(self.name)
def __repr__(self):
return self.name