"""Modular processing of JSON data by trees and branches, pointers and patches.
"""
import sys
import os
import filesysobjects.userdata
import filesysobjects.osdata
__author__ = 'Arno-Can Uestuensoez'
__maintainer__ = 'Arno-Can Uestuensoez'
__license__ = "Artistic-License-2.0 + Forced-Fairplay-Constraints"
__copyright__ = "Copyright (C) 2015-2016 Arno-Can Uestuensoez" \
                " @Ingenieurbuero Arno-Can Uestuensoez"
__version__ = '0.2.21'
__uuid__ = '63b597d6-4ada-4880-9f99-f5e0961351fb'
#
# used for default names
appname = 'jsondata'
[docs]class JSONDataError(Exception):
    """ base Exception."""
    def __init__(self, *arg):
        """To be replaced by derived Exceptions.
        Fetch standard parameters and forward message to base class 'Exception'.
        """
        self.fetch(*arg)
        Exception.__init__(self, self.s)
    def fetch(self, *arg):
        """Fetch arguments.
        Args:
            *args:
                The following order is expected:
                0. Reason of exception.
                1. Name of object that caused the exception.
                2. Value of the object.
        Returns:
            None.
        Raises:
            None.
        """
        self.s = ""
        for a in arg:
            self.s += ":" + str(a)
        self.s = self.s[1:]
    def __repr__(self):
        """Cause: <reason>:<object>:<value>"""
        return self.s
    def __str__(self):
        """Cause with additional header text."""
        return "ERROR::" + self.s 
# Sets display for inetractive JSON/JSONschema design.
_interactive = False
V3K = False  #: Python3.5+
if sys.version_info[:2] > (3, 4,):
    V3K = True
    ISSTR = (str,)
    """string and unicode"""
elif sys.version_info[:2] > (2, 6,) and sys.version_info[:2][0] < 3:
    ISSTR = (str, unicode,)  # @UndefinedVariable
    """string and unicode"""
else:
    raise JSONDataError(
        "Requires Python 2.7+, or 3.5+:" +
        str(sys.version_info[:2]))
#
# generic exceptions
#
[docs]class JSONDataIndexError(JSONDataError, IndexError):
    """ Error on key."""
    def __init__(self, *arg):
        JSONDataError.__init__(self, *arg)
        JSONDataError.fetch(self, *arg)
        IndexError.__init__(self, self.s)
    def __str__(self):
        return "JSONDataIndexError:" + self.s 
[docs]class JSONDataKeyError(JSONDataError, KeyError):
    """ Error on key."""
    def __init__(self, *arg):
        JSONDataError.__init__(self, *arg)
        JSONDataError.fetch(self, *arg)
        KeyError.__init__(self, self.s)
    def __str__(self):
        return "JSONDataKeyError:" + self.s 
[docs]class JSONDataPathError(JSONDataError, KeyError):
    """ Error on key."""
    def __init__(self, *arg):
        JSONDataError.__init__(self, *arg)
        JSONDataError.fetch(self, *arg)
        KeyError.__init__(self, self.s)
    def __str__(self):
        return "JSONDataPathError:" + self.s 
class JSONDataNodeError(JSONDataError):
    """ Error on node, slightly different from key."""
    def __str__(self):
        return "JSONDataNodeError:" + self.s
[docs]class JSONDataNodeTypeError(JSONDataError):
    """ Error on NodeTypes."""
    def __str__(self):
        return "JSONDataNodeTypeError:" + self.s 
[docs]class JSONDataParameterError(JSONDataError):
    """ Erroneous parameters."""
    def __str__(self):
        return "JSONDataParameterError:" + self.s 
[docs]class JSONDataSourceFileError(JSONDataError):
    """ Error on read of a source file."""
    def __str__(self):
        return "JSONDataSourceFileError:" + self.s 
[docs]class JSONDataModeError(JSONDataError):
    """ Type error of source file content."""
    def __str__(self):
        return "JSONDataModeError:" + self.s 
[docs]class JSONDataTargetFileError(JSONDataError):
    """ Error on writing a file."""
    def __str__(self):
        return "JSONDataTargetFileError:" + self.s 
[docs]class JSONDataValueError(JSONDataError):
    """ Error on a value."""
    def __str__(self):
        return "JSONDataValueError:" + self.s 
[docs]class JSONDataAmbiguityError(Exception):
    """ Error ambiguity of provided parameters."""
    def __init__(self, requested, *sources):
        if _interactive:
            self.s = "Ambiguious input for:\n  " + str(requested)
            for sx in sources:
                self.s += "\n    " + str(sx)
        else:
            self.s = "Ambiguious input for:" + str(requested)
            for sx in sources:
                self.s += ":" + str(sx)
        Exception.__init__(self, self.s)
    def __str__(self):
        return "JSONDataAmbiguityError:" + self.s 
[docs]class JSONPointerError(JSONDataError):
    """ Pointer error."""
    pass 
class JSONPointerTypeError(JSONDataError):
    """ Pointer type error, the JSON pointer syntax does not represent a valid pointer."""
    pass
class JSONTypeError(JSONDataError):
    """ Pointer error."""
    pass
class JSONDiffError(JSONDataError):
    """Error in JSONDiff."""
    pass
class JSONSearchError(JSONDataError):
    """Error in JSONSearch."""
    pass
[docs]class JSONDataPatchError(JSONDataError):
    pass 
[docs]class JSONDataPatchItemError(JSONDataPatchError):
    pass 
#
# mode of operations
#
MJ_RFC4627 = 1  #: The first JSON RFC.
MJ_RFC7493 = 2  #: The IJSON RFC.
MJ_RFC7159 = 2  #: The JSON RFC by 'now'.
MJ_RFC8259 = 4  #: The JSON RFC by 'now'.
MJ_ECMA404 = 16  #: The first JSON EMCMA standard.
MJ_RFC6901 = 32  #: JSONPointer first IETF RFC.
MJ_RELPOINTERD1 = 64  #: JSONPointer - relative pointer Draft-1.
MJ_RFC6902 = 128  #: JSONPatch first IETF RFC.
MJ_DEFAULT = MJ_RFC7159
#
# validation of schemes
#
MS_OFF = 40  #: No validation.
MS_DRAFT3 = 43  #: The first supported JSONSchema IETF-Draft.
MS_DRAFT4 = 44  #: The current supported JSONSchema IETF-Draft.
MS_ON = MS_DRAFT4  #: The current when the default is activated.
MODE_SCHEMA_DEFAULT = MS_OFF  #: The current default validation mode.
str2mj = {
    "rfc4627": MJ_RFC4627,
    "rfc7493": MJ_RFC7493,
    "rfc7159": MJ_RFC7159,
    "rfc8259": MJ_RFC8259,
    "relpointerD1": MJ_RELPOINTERD1,
    "ecma404": MJ_ECMA404,
    "rfc6901": MJ_RFC6901,
    "rfc6902": MJ_RFC6902,
    "oss": MS_OFF,
    str(MJ_RFC4627): MJ_RFC4627,
    str(MJ_RFC7493): MJ_RFC7493,
    str(MJ_RFC7159): MJ_RFC7159,
    str(MJ_RFC8259): MJ_RFC8259,
    str(MJ_ECMA404): MJ_ECMA404,
    str(MJ_RFC6901): MJ_RFC6901,
    str(MJ_RELPOINTERD1): MJ_RELPOINTERD1,
    str(MJ_RFC6902): MJ_RFC6902,
    str(MS_OFF): MS_OFF,
    MJ_RFC4627: MJ_RFC4627,
    MJ_RFC7493: MJ_RFC7493,
    MJ_RFC7159: MJ_RFC7159,
    MJ_ECMA404: MJ_ECMA404,
    MJ_RFC6901: MJ_RFC6901,
    MJ_RFC6902: MJ_RFC6902,
    MS_OFF: MS_OFF,
}
mj2str = {
    MJ_RFC4627: "rfc4627",
    MJ_RFC7493: "rfc7493",
    MJ_RFC7159: "rfc7159",
    MJ_RFC8259: "rfc8259",
    MJ_RELPOINTERD1: "relpointerD1",
    MJ_ECMA404: "ecma404",
    MJ_RFC6901: "rfc6901",
    MJ_RFC6902: "rfc6902",
    MS_OFF: "off",
}
#
# match criteria for node comparison
#
MATCH_INSERT = 0  #: for dicts
MATCH_NO = 1  #: negates the whole set
MATCH_KEY = 2  #: for dicts
MATCH_CHLDATTR = 3  #: for dicts and lists
MATCH_INDEX = 4  #: for lists
MATCH_MEM = 5  #: for dicts(value) and lists
MATCH_NEW = 6  #: If not present create a new, else ignore and keep present untouched.
MATCH_PRESENT = 7  #: Check all are present, else fails.
#
# application constraints and types for branch operations ::
#   res = a OP b
#
B_ALL = 0  #: OP-On-Branches: process in any case.
B_AND = 1  #: OP-On-Branches: process only when both present.
B_OR = 2  #: OP-On-Branches: process if one at all is present.
B_XOR = 4  #: OP-On-Branches: process if only one is present.
B_ADD = 8  #: OP-On-Branches: add in accordance to RFC6902
B_MOD = 16  #: OP-On-Branches: modulo of branches.
B_SUB = 32  #: OP-On-Branches: subtract branches.
#
# copy property
#
C_REF = 0  #: OP-Copy: Copy reference.
C_DEEP = 1  #: OP-Copy: Copy deep.
C_SHALLOW = 2  #: OP-Copy: Copy shallow.
C_DEFAULT = C_REF  #: Default value.
#
# application-scope
#
SC_DATA = 0  #: OP-Scope: the managed JSON data only
SC_SCHEMA = 1  #: OP-Scope: the managed JSON schema only
SC_JSON = 2  #: OP-Scope: the managed JSON data and schema only.
SC_OBJ = 3  #: OP-Scope: the attributes of current instance.
SC_ALL = 4  #: OP-Scope: the complete object, including data.
#
# data-scope
#
SD_BOTH = 0  #: Apply on mixed input and output data.
SD_INPUT = 1  #: Apply on input data.
SD_OUTPUT = 2  #: Apply on output data.
#
# sort order
#
S_NONE = 0  # no sort
S_SIMPLE = 1  # goups upper lower
#
# return types
#
R_OBJ = 0  #: Return object of type self.
R_DATA = 1  #: Return self.data.
R_JDATA = 2  #: Return object of type JSONData.
#
# types of return values
#
RT_STR = 1  #: string - 'str' or 'unicode'
RT_DICT = 2  #: dict
RT_LST = 4  #: list
RT_JSONPOINTER = 8  #: JSONPointer
RT_JSONPATCH= 16  #: JSONPatch
RT_JSONDATA = 32  #: JSONData
RT_DEFAULT = RT_JSONPOINTER  #: default
rtypes2num = {
    'RT_DICT': RT_DICT,
    'RT_JSONDATA': RT_JSONDATA,
    'RT_JSONPATCH': RT_JSONPATCH,
    'RT_JSONPOINTER': RT_JSONPOINTER,
    'RT_LST': RT_LST,
    'RT_STR': RT_STR,
    'default': RT_DEFAULT,
    'dict': RT_DICT,
    'jdata': RT_JSONPATCH,
    'jpatch': RT_JSONDATA,
    'jpointer': RT_JSONPOINTER,
    'list': RT_LST,
    'str': RT_STR,
    None: RT_DEFAULT,
    RT_DEFAULT: RT_DEFAULT,
    RT_DICT: RT_DICT,
    RT_JSONDATA: RT_JSONDATA,
    RT_JSONPATCH: RT_JSONPATCH,
    RT_JSONPOINTER: RT_JSONPOINTER,
    RT_LST: RT_LST,
    RT_STR: RT_STR,
}
#
# match sets
#
M_FIRST = 1  #: First match only.
M_LAST = 2  #: Last match only.
M_ALL = 4  #: All matches.
#
# verify pointers against jsondata
#
V_NONE = 1  #: no checks at all
V_FINAL = 2  #: checks final result only
V_STEPS = 4  #: checks each intermediate directory
V_DEFAULT = V_NONE  #: default
verify2num = {
    'V_DEFAULT': V_DEFAULT,
    'V_FINAL': V_FINAL,
    'V_NONE': V_NONE,
    'V_STEPS': V_STEPS,
    'default': V_DEFAULT,
    'final': V_FINAL,
    'none': V_NONE,
    'steps': V_STEPS,
    None: V_DEFAULT,
    V_DEFAULT: V_DEFAULT,
    V_FINAL: V_FINAL,
    V_NONE: V_NONE,
    V_STEPS: V_STEPS,
}
#
# display formats for JSONDiff
#
DF_SUMUP = 0   # short list
DF_CSV = 1     # csv, for now semicolon only
DF_JSON = 3    # JSON struture
DF_TABLE = 4   # table, for now fixed
DF_REVIEW = 5  # short for quick review format
DF_REPR = 6    # repr() - raw string, Python syntax
DF_STR = 7     # str() - formatted string, Python syntax
str2df = {
    'sumup': DF_SUMUP,
    'csv': DF_CSV,
    'json': DF_JSON,
    'review': DF_REVIEW,
    'repr': DF_REPR,
    'str': DF_STR,
    'tabble': DF_TABLE,
    str(DF_SUMUP): DF_SUMUP,
    str(DF_CSV): DF_CSV,
    str(DF_JSON): DF_JSON,
    str(DF_REVIEW): DF_REVIEW,
    str(DF_REPR): DF_REPR,
    str(DF_STR): DF_STR,
    str(DF_TABLE): DF_TABLE,
    DF_SUMUP: DF_SUMUP,
    DF_CSV: DF_CSV,
    DF_JSON: DF_JSON,
    DF_REVIEW: DF_REVIEW,
    DF_REPR: DF_REPR,
    DF_STR: DF_STR,
    DF_TABLE: DF_TABLE,
}
df2str = {
    DF_SUMUP: 'sumup',
    DF_CSV: 'csv',
    DF_JSON: 'json',
    DF_REVIEW: 'review',
    DF_REPR: 'repr',
    DF_STR: 'str',
    DF_TABLE: 'tabble',
}
#
# display formats for JSONData
#
PJ_TREE = 0     #: tree view JSON syntax
PJ_FLAT = 1     #: flat print JSON syntax
PJ_PYTREE = 2   #: tree view Python syntax
PJ_PYFLAT = 3   #: flat print Python syntax
PJ_REPR = 4     #: repr() - raw string, Python syntax
PJ_STR = 5      #: str() - formatted string, Python syntax
str2pj = {
    'tree': PJ_TREE,
    'flat': PJ_FLAT,
    'pytree': PJ_PYTREE,
    'pyflat': PJ_PYFLAT,
    'repr': PJ_REPR,
    'str': PJ_STR,
    str(PJ_TREE): PJ_TREE,
    str(PJ_FLAT): PJ_FLAT,
    str(PJ_PYTREE): PJ_PYTREE,
    str(PJ_PYFLAT): PJ_PYFLAT,
    str(PJ_REPR): PJ_REPR,
    str(PJ_STR): PJ_STR,
    PJ_TREE: PJ_TREE,
    PJ_FLAT: PJ_FLAT,
    PJ_PYTREE: PJ_PYTREE,
    PJ_PYFLAT: PJ_PYFLAT,
    PJ_REPR: PJ_REPR,
    PJ_STR: PJ_STR,
}
pj2str = {
    PJ_TREE: 'tree',
    PJ_FLAT: 'flat',
    PJ_PYTREE: 'pytree',
    PJ_PYFLAT: 'pyflat',
    PJ_REPR: 'repr',
    PJ_STR: 'str',
}
#
# Notation at the API - in/out.
#
NOTATION_NATIVE = 0  #: JSON notation in accordance to RFC7159
NOTATION_JSON = 1  #: JSON notation in accordance to RFC7159
NOTATION_JSON_REL = 2  #: JSON notation as relative pointer
NOTATION_HTTP_FRAGMENT = 3  #: JSON notation in accordance to RFC7159 with RFC3986.
#
# character display
#
CHARS_RAW = 0  #: display character set as raw
CHARS_STR = 1  #: display character set as str
CHARS_UTF = 2  #: display character set as utf
#
# line handling for overflow
#
LINE_CUT = 0  #: force line fit
LINE_WRAP = 1  #: wrap line in order to fit to length
#
# search parameters
#
SEARCH_FIRST = 0  #: Break display after first match.
SEARCH_ALL = 1  #: List all matches.
#
# pointer style
#
PT_PATH = 0  #: Displays a list of items.
PT_RFC6901 = 1  #: Displays rfc6901 strings.
PT_NODE = 2  #: Displays the node.
#
# json syntax
#
JSYN_NATIVE = 1  #: Literally in accordance to standards.
JSYN_PYTHON = 2  #: Python in-memory syntax representation.
# maps match strings and enums onto match-enums
match2match ={
    'child_attr_list': MATCH_CHLDATTR,
    'index': MATCH_INDEX,
    'key': MATCH_KEY,
    'mem': MATCH_MEM,
    'new': MATCH_NEW,
    'no': MATCH_NO,
    'present': MATCH_PRESENT,
    MATCH_CHLDATTR: MATCH_CHLDATTR,
    MATCH_INDEX: MATCH_INDEX,
    MATCH_KEY: MATCH_KEY,
    MATCH_MEM: MATCH_MEM,
    MATCH_NEW: MATCH_NEW,
    MATCH_NO: MATCH_NO,
    MATCH_PRESENT: MATCH_PRESENT,
}
# maps mode strings and enums onto mde-enums
mode2mj = {
    'default': MJ_RFC7159,
    'ecma404': MJ_RFC8259,
    'rfc4627': MJ_RFC4627,
    'rfc7159': MJ_RFC7159,
    'rfc7493': MJ_RFC7493,
    'rfc8259': MJ_RFC8259,
    MJ_ECMA404: MJ_RFC8259,
    MJ_RFC4627: MJ_RFC4627,
    MJ_RFC7159: MJ_RFC7159,
    MJ_RFC7493: MJ_RFC7493,
    MJ_RFC8259: MJ_RFC8259,
}
# maps validator strings and enums onto validator-enums
validator2ms = {
    'default': MS_DRAFT4,
    'draft3': MS_DRAFT3,
    'off': MS_OFF,
    MS_DRAFT3: MS_DRAFT3,
    MS_DRAFT4: MS_DRAFT4,
    MS_OFF: MS_OFF,
}
# maps copy strings and enums onto copy-enums
copy2c = {
    'deep': C_DEEP,
    'default': C_REF,
    'ref': C_REF,
    'shallow': C_SHALLOW,
    C_DEEP: C_DEEP,
    C_REF: C_REF,
    C_SHALLOW: C_SHALLOW,
}
#
# for now hard-coded
#
consolewidth = 80
#
# misc
#
_verbose = 0  # common verbose variable
_debug = 0  # common verbose variable