#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# **************************************************************************** #
# This file is part of the pdpy project: https://github.com/pdpy-org
# Copyright (C) 2021 Fede Camara Halac
# **************************************************************************** #
"""
Translator
==========
"""
from pathlib import Path
from json import load as json_load
from json import loads as json_loads
from pickle import dump as pickle_dump
from pickle import load as pickle_load
from pickle import HIGHEST_PROTOCOL as PICKLE_HIGHEST_PROTOCOL
from types import SimpleNamespace
from ..core.base import Base
from ..patching.pdpy import PdPy
from ..encoding.pdpyencoder import PdPyEncoder
from ..parse.pdpyparser import PdPyParser
from ..utilities.default import getFormat
from ..utilities.exceptions import ArgumentException
from ..utilities.utils import log, parsePdFileLines, loadPdFile, parsePdBinBuf
__all__ = [ 'Translator' ]
[docs]class Translator(Base):
r""" This class maintains and translates a `pdpy` Obj in memory.
This class loads a file in `.pd` or `.json` formats and keeps an
internal mirror (aka, translation) between the two. The direction
of the translation depends on the input file type. Use the `save_*`
functions to write translations to disk. Alternatively, you can load
a `.pkl` (aka, pickle) file containing a `pdpy` object.
Parameters
----------
json: :class:`dict`
A dictionary of arguments with the following keys:
* ``to``: the target format. Can be `json`, `xml`, `pd`, or `pkl`.
* ``fro``: the source format. Can be `json`, `xml`, `pd`, or `pkl`.
* ``input``: An input file Path, will be formated using `pathlib.Path`
* ``output``: An output file Path, will be formated using `pathlib.Path`
* ``encoding`` (`str`, defaults: 'utf-8'): Encoding of the input file
* ``source`` (`str`, inferred from `input_file`): Source file type
* ``reflect`` (`bool`): If set to `True`, performs a reflected translation
"""
def __init__(self, json):
super().__populate__(self, json)
self.source = getFormat(self.fro) if self.fro else None
self.target = getFormat(self.to) if self.to else None
# grab the source extension from the input file
if self.source is None:
self.source = self.input_file.suffix
if self.target == self.source:
raise ArgumentException("Source and target are the same, skipping translation")
if self.target is None:
raise ArgumentException("To or fro are missing or malformed")
else:
self.input_file = Path(self.input)
if self.input_file.suffix != "." + self.source:
log(2, self.source, self.target, self.input_file)
raise ArgumentException("Input file suffix does not match with -f argument")
if not self.input_file.exists():
raise ArgumentException("File" + " " + self.input_file + " " + "does not exist.")
if self.output is None:
self.output_file = self.input_file.with_suffix("." + self.target)
log(1, "Using" + " " + self.output_file.as_posix() + " " + "as output file")
else:
self.output_file = Path(self.output)
if self.output_file.suffix != "." + self.target:
raise ArgumentException("Input file suffix does not match with -f argument")
# store an object containing a pd object database
if self.internals is not None:
r""" Attempt to load PDDB manager
Fall back to json if the package is not there.
Get it here: `<https://github.com/pdpy-org/pddb>`_
"""
import sys
pddb_path = Path(self.internals)
if pddb_path.exists():
try:
sys.path.append((pddb_path.parent / 'src').as_posix())
from pddb import PDDB
self.pddb = PDDB(dbname=pddb_path.resolve(), listen=False)
except ImportError:
print("Could not import pddb. Loading json instead.")
try:
with open(self.internals, "r", encoding=self.encoding) as fp:
self.pddb=json_load(fp,object_hook=lambda o:SimpleNamespace(**o))
except Exception as e:
print("Could not load pddb.json. See error below.")
print(e)
else:
raise ArgumentException("PDDB:" + " " + pddb_path.as_posix() + " " + "is missing.")
# Load the source file
if self.source == "pd":
pd_file_path = self.input_file.as_posix()
pd_file = loadPdFile(pd_file_path, self.encoding)
pd_lines = parsePdFileLines(pd_file)
self.pdpy = PdPy(
name = self.input_file.name,
encoding = self.encoding,
pd_lines = pd_lines
)
elif self.source == "json":
with open(self.input_file, "r", encoding=self.encoding) as fp:
self.pdpy = json_load(fp, object_hook = PdPyEncoder())
self.pdpy.__jsontree__()
elif self.source == "pkl":
with open(self.input_file, "rb") as fp:
data = pickle_load(fp, encoding=self.encoding)
self.pdpy = json_loads(data, object_hook = PdPyEncoder())
self.pdpy.__jsontree__()
elif self.source == "pdpy":
with open(self.input_file, "r", encoding=self.encoding) as fp:
self.pdpy = PdPyParser(
fp.readlines(), # pdpy_file_pointer
self.pddb,
name = self.input_file.name,
encoding = self.encoding
)
elif self.source == "xml":
with open(self.input_file, "r", encoding=self.encoding) as fp:
self.pdpy = PdPy(
name = self.input_file.name,
encoding = self.encoding,
xml = fp
)
else:
raise ValueError("Unknown source type: {}".format(self.source))
def __call__(self, target=None, out=None):
if target is None:
target = self.target
if out is None:
out = self.output_file
if not isinstance(out, Path):
out = Path(out)
if target == 'json' or target == 'pkl':
# return a json string representation from pdpy
self.json = self.pdpy.__json__()
if self.json is not None:
if target == 'json':
ofname = out.with_suffix(".json")
with open(ofname, 'w', encoding=self.encoding) as fp:
fp.write(self.json)
elif target == "pkl":
ofname = out.with_suffix(".pkl")
with open(out.with_suffix(".pkl"), "wb") as fp:
pickle_dump(self.json, fp, PICKLE_HIGHEST_PROTOCOL)
# the Pd reflection logic when json is the target
if self.reflect:
self.pd_ref = PdPy(
name = self.input_file.name,
encoding = self.encoding,
json = self.json
).__pd__()
if self.pd_ref is not None:
out = out.parent / (out.stem + '_ref')
ofname = out.with_suffix(".pd")
with open(ofname, 'w', encoding=self.encoding) as fp:
fp.write(self.pd_ref)
else:
log(2, "No JSON representation available")
if target == "pd" and self.pdpy is not None:
# get the pd representation
self.pd = self.pdpy.__pd__()
if self.pd is not None:
with open(out, 'w', encoding=self.encoding) as fp:
fp.write(self.pd)
# the Json reflection logic when Pd is the target
if self.reflect:
self.json_ref = PdPy(
name = self.input_file.name,
encoding = self.encoding,
pd_lines = parsePdBinBuf(self.pd)
).__json__()
if self.json_ref is not None:
out = out.parent / (out.stem + '_ref')
ofname = out.with_suffix(".json")
with open(ofname, 'w', encoding=self.encoding) as fp:
fp.write(self.json_ref)
else:
log(2, "No Pd representation available")
if target == "xml" and self.pdpy is not None:
# get the xml representation
self.xml = self.pdpy.__xml__()
if self.xml is not None:
ofname = out.with_suffix(".xml")
self.xml.write(ofname.as_posix(), encoding=self.encoding)
# the Pd reflection logic when xml is the target
if self.reflect:
self.xml_ref = PdPy(
name = self.input_file.name,
encoding = self.encoding,
xml = self.xml.to_string() # give it an xml string
).__pd__()
if self.xml_ref is not None:
out = out.parent / (out.stem + '_ref')
ofname = out.with_suffix(".pd")
with open(ofname, 'w', encoding=self.encoding) as fp:
fp.write(self.xml_ref)
else:
log(2, "No XML representation available")
# self.xml = JsonToXml(self.pdpy)
# if self.xml is not None:
# ofname = out.with_suffix(".xml")
# with open(ofname, 'w') as fp:
# fp.write(self.xml.to_string())
# if self.reflect:
# self.xml_ref = JsonToXml(self.pdpy)
# end def __call__