3 # The software in this package is distributed under the GNU General
4 # Public License version 2 (with a special exception described below).
6 # A copy of GNU General Public License (GPL) is included in this distribution,
7 # in the file COPYING.GPL.
9 # As a special exception, if other files instantiate templates or use macros
10 # or inline functions from this file, or you compile this file and link it
11 # with other works to produce a work based on this file, this file
12 # does not by itself cause the resulting work to be covered
13 # by the GNU General Public License.
15 # However the source code for this file must still be made available
16 # in accordance with section (3) of the GNU General Public License.
18 # This exception does not invalidate any other reasons why a work based
19 # on this file might be covered by the GNU General Public License.
21 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
26 -------------------------------------------------------------------------------
27 Represent CNF_VARs as recursive structures.
29 .. note:: DEPRECATED! Please do not extend this or add new uses of this module,
30 use :py:mod:`pyi2ncommon.arnied_api` or :py:mod:`pyi2ncommon.cnfvar`
33 Copyright: 2014-2017 Intra2net AG
38 -------------------------------------------------------------------------------
40 This module provides read and write functionality for the Intra2net *CNF*
41 format. Two different syntaxes are available: classical *CNF* and a JSON
42 representation. Both versions are commonly understood by Intra2net software.
44 On the command line, raw CNF is accepted if the option ``-`` is given: ::
46 $ get_cnf routing 2 |python3 cnfvar.py - <<ENOUGH
47 1 ROUTING,2: "192.168.55.0"
48 2 (1) ROUTING_COMMENT,0: ""
49 3 (1) ROUTING_DNS_RELAYING_ALLOWED,0: "1"
50 4 (1) ROUTING_EMAIL_RELAYING_ALLOWED,0: "1"
51 5 (1) ROUTING_FIREWALL_RULESET_REF,0: "9"
52 6 (1) ROUTING_GATEWAY,0: "10.0.254.1"
53 7 (1) ROUTING_NAT_INTO,0: "0"
54 8 (1) ROUTING_NETMASK,0: "255.255.255.0"
55 9 (1) ROUTING_PROXY_PROFILE_REF,0: "2"
58 1 ROUTING,2: "192.168.55.0"
59 2 (1) ROUTING_COMMENT,0: ""
60 3 (1) ROUTING_DNS_RELAYING_ALLOWED,0: "1"
61 4 (1) ROUTING_EMAIL_RELAYING_ALLOWED,0: "1"
62 5 (1) ROUTING_FIREWALL_RULESET_REF,0: "9"
63 6 (1) ROUTING_GATEWAY,0: "10.0.254.1"
64 7 (1) ROUTING_NAT_INTO,0: "0"
65 8 (1) ROUTING_NETMASK,0: "255.255.255.0"
66 9 (1) ROUTING_PROXY_PROFILE_REF,0: "2"
68 The input takes one round-trip through the parsers and will error out on
69 problematic lines. Thus, ``cnfvar.py`` can be used to syntax-check CNF data.
71 Note that line numbers may be arbitrarily reassigned in the process. Of course,
72 parent references and the relative ordering of lines will be preserved in this
76 Decide on some facility for automatic fixup of line number values. The
77 internal representation is recursive so line numbers are not needed to
78 establish a variable hierarchy. They might as well be omitted from the json
79 input and only added when writing cnf. For the time being a lack of the
80 "number" field is interpreted as an error. Though it should at least
81 optionally be possible to omit the numbers entirely and have a function add
82 them after the fact. This might as well be added to :py:func:`is_cnf`
83 though it would be counterintuitive to have a predicate mutate its
84 argument. So maybe it could return a second argument to indicate a valid
85 structure that needs fixup or something like that.
88 The variable values of get_cnf seems to be encoded in latin1, and set_cnf
89 seems to assume latin1-encoded values (not var names). Function
90 :py:func:`read_cnf` converts this to unicode and functions
91 :py:func:`dump_cnf_string`, :py:func:`print_cnf`, and :py:func:`write_cnf`
92 convert unicode back to latin1.
95 notes on Python 3 conversion
96 -------------------------------------------------------------------------------
98 Since the original *CNF* format assumes latin-1 encoded data pretty much
99 exclusively, we preserve the original encoding while parsing the file.
100 When assembling the data structures returned to the user, values are then
101 converted to strings so they can be used naturally at the Python end.
104 -------------------------------------------------------------------------------
114 ###############################################################################
116 ###############################################################################
119 CNF_FIELD_MANDATORY = set ([ "varname", "data", "instance" ])
120 CNF_FIELD_OPTIONAL = set ([ "parent", "children", "comment", "number" ])
121 CNF_FIELD_KNOWN = CNF_FIELD_MANDATORY | CNF_FIELD_OPTIONAL
123 grab_parent_pattern = re.compile(b"""
125 \s* # optional spaces
132 base_line_pattern = re.compile(b"""
134 \s* # optional spaces
137 ([A-Z][A-Z0-9_]*) # varname
138 \s* # optional spaces
140 \s* # optional spaces
142 \s* # optional spaces
144 \s* # optional spaces
145 \"( # quoted string (data)
146 (?: \\\" # (of escaped dquote
147 |[^\"])* # or anything not a
149 \s* # optional spaces
152 \s* # optional spaces
153 .* # string (comment)
154 )? # egroup, optional
159 child_line_pattern = re.compile(b"""
161 \s* # optional spaces
166 ([A-Z][A-Z0-9_]*) # varname
167 \s* # optional spaces
169 \s* # optional spaces
171 \s* # optional spaces
173 \s* # optional spaces
174 \"([^\"]*)\" # quoted string (data)
175 \s* # optional spaces
178 \s* # optional spaces
179 .* # string (comment)
180 )? # egroup, optional
186 ###############################################################################
188 ###############################################################################
192 # Sadly, the Intranator is still stuck with one leg in the 90s.
195 """Take given unicode str and convert it to a latin1-encoded `bytes`."""
196 return s.encode("latin-1")
200 """Take given latin1-encoded `bytes` value and convert it to `str`."""
201 return s.decode("latin-1")
205 # Conversion functions
208 def marshal_in_number(number):
212 def marshal_in_parent(parent):
216 def marshal_in_instance(instance):
220 def marshal_in_varname(varname):
221 return from_latin1(varname).lower()
224 def marshal_in_data(data):
225 return from_latin1(data) if data is not None else ""
228 def marshal_in_comment(comment):
229 return comment and from_latin1(comment[1:].strip()) or None
237 return isinstance(s, str)
240 ###############################################################################
242 ###############################################################################
245 class InvalidCNF(Exception):
247 def __init__(self, msg):
251 return "Malformed CNF_VAR: \"%s\"" % self.msg
254 class MalformedCNF(Exception):
256 def __init__(self, msg):
260 return "Malformed CNF file: \"%s\"" % self.msg
263 ###############################################################################
265 ###############################################################################
277 raise InvalidCNF("CNF_VAR lacks a name.")
278 elif not is_string(varname):
279 raise InvalidCNF("Varname field of CNF_VAR \"%s\" is not a string."
282 raise InvalidCNF("Varname field of CNF_VAR is the empty string.")
284 if comment is not None:
285 if not is_string(comment):
286 raise InvalidCNF("Comment field of CNF_VAR \"%s\" is not a string."
290 raise InvalidCNF("Data field of CNF_VAR \"%s\" is empty."
292 elif not is_string(data):
293 raise InvalidCNF("Data field of CNF_VAR \"%s\" is not a string."
297 raise InvalidCNF("Instance field of CNF_VAR \"%s\" is empty."
299 elif not isinstance(instance, int):
300 raise InvalidCNF("Instance field of CNF_VAR \"%s\" is not an integer."
304 raise InvalidCNF("Number field of CNF_VAR \"%s\" is empty."
306 elif not isinstance(number, int):
307 raise InvalidCNF("Number field of CNF_VAR \"%s\" is not an integer."
310 raise InvalidCNF("Number field of CNF_VAR \"%s\" must be positive, not %d."
313 other = acc.get(number, None)
314 if other is not None: # already in use
315 raise InvalidCNF("Number field of CNF_VAR \"%s\" already used by variable %s."
317 acc[number] = varname
321 raise InvalidCNF("Parent field of nested CNF_VAR \"%s\" is empty."
323 elif not isinstance(parent, int):
324 raise InvalidCNF("Parent field of CNF_VAR \"%s\" is not an integer."
327 if parent is not None:
328 raise InvalidCNF("Flat CNF_VAR \"%s\" has nonsensical parent field \"%s\"."
335 is_cnf -- Predicate testing "CNF_VAR-ness". Folds the :py:func:`is_valid`
336 predicate over the argument which can be either a well-formed CNF
337 dictionary or a list of CNF_VARs.
339 :type root: cnfvar or cnf list
342 Not that if it returns at all, ``is_cnf()`` returns ``True``. Any non
343 well-formed member of the argument will cause the predicate to bail out
344 with an exception during traversal.
348 raise InvalidCNF(root)
349 return walk_cnf(cnf, False, is_valid, {}) is not None
354 Check whether a dictionary is a valid CNF.
356 :param dict obj: dictionary to check
357 :returns: True if the dictionary has all the mandatory fields and no
358 unknown fields, False otherwise
361 assert isinstance (obj, dict)
363 for f in CNF_FIELD_MANDATORY:
364 if obj.get(f, None) is None:
368 if f not in CNF_FIELD_KNOWN:
374 ###############################################################################
376 ###############################################################################
380 # JSON reader for get_cnf -j (the easy part)
383 def make_varname_lowercase(cnfvar):
385 Custom hook for json decoder: convert variable name to lowercase.
387 Since variable names are case insensitive, :py:func:`read_cnf` converts
388 them all to lower case. Downstream users of :py:func:`read_cnf_json` (e.g.
389 :py:class:`simple_cnf.SimpleCnf`) rely on lowercase variable names.
391 :param dict cnfvar: JSON "object" converted into a dict
392 :returns: same as input but if field `varname` is present, its value is
393 converted to lower case
394 :rtype: dict with str keys
397 cnfvar['varname'] = cnfvar['varname'].lower()
398 except KeyError: # there is no "varname" field
400 except AttributeError: # cnfvar['varname'] is not a string
405 def read_cnf_json(cnfdata):
407 Read json data from cnf data bytes.
409 :param bytes cnfdata: config data
410 :return: the parsed json data
413 .. note:: The JSON module does not decode data for all versions
414 of Python 3 so we handle the decoding ourselves.
416 if isinstance (cnfdata, bytes) is True:
417 cnfdata = from_latin1 (cnfdata)
418 cnf_json = json.loads(cnfdata, object_hook=make_varname_lowercase)
419 if is_cnf(cnf_json) is False:
420 raise TypeError("Invalid CNF_VAR.")
425 # CNF reader (the moderately more complicated part)
427 # Parsing usually starts from the `read_cnf`, which accepts a string containing
428 # the variables to parse in the same structure as returned by `get_cnf`.
430 # In the `prepare` function the string is split into lines, and a 3-element
431 # tuple is built. The first (named `current`) and second (named `next`)
432 # elements of this tuple are respectively the first and second non-empty lines
433 # of the input, while the third is a list of the remaining lines. This tuple is
434 # named `state` in the implementation below, and it is passed around during
435 # parsing. The `get` and `peek` functions are used to easily retrieve the
436 # `current` and `next` items from the "state".
438 # When we "advance" the state, we actually drop the "current" element,
439 # replacing it with the "next", while a new "next" is popped from the list of
440 # remaining lines. Parsing is done this way because we need to look ahead at
441 # the next line -- if it is a child it needs to be appended to the `children`
442 # property of the current line.
444 # Regular expressions are used to extract important information from the CNF
445 # lines. Finally, once parsing is completed, a dictionary is returned. The dict
446 # has the same structure as the serialized JSON output returned by
453 Read cnf data from data bytes.
455 :param data: raw data
456 :type data: str or bytes
457 :return: the parsed cnf data
458 :rtype: {str, {str, str or int}}
460 if isinstance(data, str):
461 data = to_latin1(data)
462 state = prepare(data)
464 raise InvalidCNF("Empty input string.")
466 cnf = parse_cnf_root(state)
467 if is_cnf(cnf) is False:
468 raise TypeError("Invalid CNF_VAR.")
474 Build 3-element iterable from a CNF string dump.
476 :param raw: string content as returned by `get_cnf`
478 :returns: 3-element tuple, where the first two elements are the first two
479 lines of the output and the third is a list containing the rest
480 of the lines in reverse.
481 :rtype: (str * str option * str list) option
483 lines = raw.splitlines()
495 first = first.strip()
497 return advance((first, second, lines))
499 return (first, second, lines)
504 Pop the next line from the stream, advancing the tuple.
506 :param cns: a 3-element tuple containing two CNF lines and a list of the
508 :type cnd: (str, str, [str])
509 :returns: a new tuple with a new item popped from the list of lines
510 :rtype cnd: (str, str, [str])
512 current, next, stream = cns
513 if next is None: # reached end of stream
523 if current == "": # skip blank lines
524 return advance((current, next, stream))
525 return (current, next, stream)
530 Get the current line from the state without advancing it.
532 :param cns: a 3-element tuple containing two CNF lines and a list of the
534 :type cnd: (str, str, [str])
535 :returns: the CNF line stored as `current`
544 Get the next line from the state without advancing it.
546 :param cns: a 3-element tuple containing two CNF lines and a list of the
548 :type cnd: (str, str, [str])
549 :returns: the CNF line stored as `next`
556 def parse_cnf_root(state):
558 Iterate over and parse a list of CNF lines.
560 :param state: a 3-element tuple containing two lines and a list of the
562 :type state: (str, str, [str])
563 :returns: a list of parsed CNF variables
566 The function will parse the first element from the `state` tuple, then read
567 the next line to see if it is a child variable. If it is, it will be
568 appended to the last parsed CNF, otherwise top-level parsing is done
574 cnf_line = read_base_line(current)
575 if cnf_line is not None:
576 lines.append(cnf_line)
577 state = advance(state)
578 if state is None: # -> nothing left to do
581 parent = get_parent(current) # peek at next line
582 if parent is not None: # -> recurse into children
583 (state, children, _parent) = parse_cnf_children(state, parent)
584 cnf_line["children"] = children
589 state = advance(state)
596 def parse_cnf_children(state, parent):
598 Read and parse child CNFs of a given parent until there is none left.
600 :param state: a 3-element tuple containing two lines and a list of the
602 :type state: (str, str, [str])
603 :param parent: id of the parent whose children we are looking for
605 :returns: a 3-element tuple with the current state, a list of children of
606 the given parent and the parent ID
607 :rtype: (tuple, [str], int)
609 The function will recursively parse child lines from the `state` tuple
610 until one of these conditions is satisfied:
612 1. the input is exhausted
614 2.1. is a toplevel line
615 2.2. is a child line whose parent has a lower parent number
617 Conceptually, 2.1 is a very similar to 2.2 but due to the special status of
618 toplevel lines in CNF we need to handle them separately.
620 Note that since nesting of CNF vars is achieved via parent line numbers,
621 lines with different parents could appear out of order. libcnffile will
622 happily parse those and still assign children to the specified parent:
626 1 USER,1337: "l33t_h4x0r"
627 2 (1) USER_GROUP_MEMBER_REF,0: "2"
628 4 USER,1701: "picard"
629 5 (4) USER_GROUP_MEMBER_REF,0: "2"
630 6 (4) USER_PASSWORD,0: "engage"
631 3 (1) USER_PASSWORD,0: "hacktheplanet"
634 1 USER,1337: "l33t_h4x0r"
635 2 (1) USER_GROUP_MEMBER_REF,0: "2"
636 3 (1) USER_PASSWORD,0: "hacktheplanet"
638 1 USER,1701: "picard"
639 2 (1) USER_GROUP_MEMBER_REF,0: "2"
640 3 (1) USER_PASSWORD,0: "engage"
642 It is a limitation of ``cnfvar.py`` that it cannot parse CNF data
643 structured like the above example: child lists are only populated from
644 subsequent CNF vars using the parent number solely to track nesting levels.
645 The parser does not keep track of line numbers while traversing the input
646 so it doesn’t support retroactively assigning a child to anything else but
647 the immediate parent.
652 cnf_line = read_child_line(current)
653 if cnf_line is not None:
654 lines.append(cnf_line)
655 state = advance(state)
659 new_parent = get_parent(current)
660 if new_parent is None:
662 return (state, lines, None)
663 if new_parent > parent:
664 # parent is further down in hierarchy -> new level
665 (state, children, new_parent) = \
666 parse_cnf_children (state, new_parent)
669 cnf_line["children"] = children
671 new_parent = get_parent(current)
672 if new_parent is None:
674 return (state, lines, None)
675 if new_parent < parent:
676 # parent is further up in hierarchy -> pop level
677 return (state, lines, new_parent)
678 # new_parent == parent -> continue parsing on same level
679 return (state, lines, parent)
682 def get_parent(line):
684 Extract the ID of the parent for a given CNF line.
686 :param str line: CNF line
687 :returns: parent ID or None if no parent is found
690 match = re.match(grab_parent_pattern, line)
691 if match is None: # -> no parent
693 return int(match.groups()[0])
696 def read_base_line(line):
698 Turn one top-level CNF line into a dictionary.
700 :param str line: CNF line
703 This performs the necessary decoding on values to obtain proper Python
704 strings from 8-bit encoded CNF data.
706 The function only operates on individual lines. Argument strings that
707 contain data for multiple lines – this includes child lines of the current
708 CNF var! – will trigger a parsing exception.
710 if len(line.strip()) == 0:
711 return None # ignore empty lines
713 return None # ignore comments
715 match = re.match(base_line_pattern, line)
717 raise MalformedCNF("Syntax error in line \"\"\"%s\"\"\"" % line)
718 number, varname, instance, data, comment = match.groups()
720 "number" : marshal_in_number (number),
721 "varname" : marshal_in_varname (varname),
722 "instance" : marshal_in_instance (instance),
723 "data" : marshal_in_data (data),
724 "comment" : marshal_in_comment (comment),
728 def read_child_line(line):
730 Turn one child CNF line into a dictionary.
732 :param str line: CNF line
735 This function only operates on individual lines. If the argument string is
736 syntactically valid but contains input representing multiple CNF vars, a
737 parse error will be thrown.
739 if len(line.strip()) == 0:
740 return None # ignore empty lines
742 return None # ignore comments
744 match = re.match(child_line_pattern, line)
746 raise MalformedCNF("Syntax error in child line \"\"\"%s\"\"\""
747 % from_latin1 (line))
748 number, parent, varname, instance, data, comment = match.groups()
750 "number" : marshal_in_number (number),
751 "parent" : marshal_in_parent (parent),
752 "varname" : marshal_in_varname (varname),
753 "instance" : marshal_in_instance (instance),
754 "data" : marshal_in_data (data),
755 "comment" : marshal_in_comment (comment),
759 ###############################################################################
761 ###############################################################################
764 cnf_line_nest_indent = " "
765 cnf_line_base_fmt = "%d %s,%d: \"%s\""
766 cnf_line_child_fmt = "%d %s(%d) %s,%d: \"%s\""
769 def format_cnf_vars(da, var):
771 Return a list of formatted cnf_line byte strings.
773 :param da: a tuple where the first element is the depth (0 = top-level,
774 >1 = child CNF) and the second is the string being built.
776 :param var: the CNF element to convert to string in the current iteration
778 :returns: a tuple like `da`, where the second element should contain all
782 This function is meant to be passed to the :py:func:`functools.reduce`
785 The variable names are uppercased unconditionally because while ``get_cnf``
786 is case-indifferent for variable names, ``set_cnf`` isn’t.
791 line = cnf_line_child_fmt \
793 cnf_line_nest_indent * depth,
795 var["varname"].upper(),
799 line = cnf_line_base_fmt \
801 var["varname"].upper(),
805 comment = var.get("comment", None)
806 if comment and len(comment) != 0:
807 line = line + (" # %s" % comment)
809 acc.append(to_latin1(line))
811 children = var.get("children", None)
812 if children is not None:
813 (_, acc) = functools.reduce(format_cnf_vars, children, (depth + 1, acc))
820 Extract a list of CNFs from a given structure.
822 :param root: list of CNFs or a CNF dictionary
823 :type root: [dict] or dict
824 :raises: :py:class:`TypeError` if no CNFs can be extracted
825 :returns: list with one or more CNF objects
828 Output varies depending on a few conditions:
829 - If `root` is a list, return it right away
830 - If `root` is a dict corresponding to a valid CNF value, return it wrapped
832 - If `root` is a dict with a `cnf` key containg a list (as the JSON
833 returned by `get_cnf -j`), return the value
834 - Otherwise, raise an error
836 if isinstance(root, list):
838 if not isinstance(root, dict):
840 "Expected dictionary of CNF_VARs, got %s." % type(root))
843 cnf = root.get("cnf", None)
844 if not isinstance(cnf, list):
845 raise TypeError("Expected list of CNF_VARs, got %s." % type(cnf))
849 def normalize_cnf(cnf):
851 Ensure the output conforms to set_cnf()’s expectations.
853 :param cnf: list of CNF objects to normalize
855 :returns: normalized list
858 if isinstance(cnf, list) is False:
859 raise MalformedCNF("expected list of CNF_VARs, got [%s]" % type(cnf))
862 { "number" : var ["number"]
863 , "varname" : var ["varname"].upper()
864 , "instance" : var ["instance"]
865 , "data" : var ["data"]
868 children = var.get("children", None)
869 if children is not None:
870 vvar ["children"] = normalize_cnf(children)
872 parent = var.get("parent", None)
873 if parent is not None:
874 vvar ["parent"] = var["parent"]
876 comment = var.get("comment", None)
877 if comment is not None:
878 vvar ["comment"] = var["comment"]
882 return [norm(var) for var in cnf]
885 ###############################################################################
887 ###############################################################################
890 def walk_cnf(cnf, nested, fun, acc):
892 Depth-first traversal of a CNF tree.
896 :type fun: 'a -> bool -> (cnf stuff) -> 'a
900 Executes ``fun`` recursively for each node in the tree. The function
901 receives the accumulator ``acc`` which can be of an arbitrary type as first
902 argument. The second argument is a flag indicating whether the current
903 CNF var is a child (if ``True``) or a parent var. CNF member fields are
904 passed via named optional arguments.
909 comment=var.get("comment", None),
910 data=var.get("data", None),
911 instance=var.get("instance", None),
912 number=var.get("number", None),
913 parent=var.get("parent", None),
914 varname=var.get("varname", None))
915 children = var.get("children", None)
916 if children is not None:
917 acc = walk_cnf(children, True, fun, acc)
921 def renumber_vars(root, parent=None, toplevel=False):
923 Number cnfvars linearly.
925 If *parent* is specified, numbering will start at this offset. Also, the
926 VAR *root* will be assigned this number as a parent lineno unless
927 *toplevel* is set (the root var in a CNF tree obviously can’t have a
930 The *toplevel* parameter is useful when renumbering an existing variable
931 starting at a given offset without at the same time having that offset
932 assigned as a parent.
934 root = cnf_root (root)
939 if toplevel is False and parent is not None:
940 var["parent"] = parent
941 children = var.get("children", None)
942 if children is not None:
943 i = renumber_vars(children, parent=i, toplevel=False)
947 def count_vars(root):
949 Traverse the cnf structure recursively, counting VAR objects (CNF lines).
953 raise InvalidCNF(root)
954 return walk_cnf(cnf, True, lambda n, _nested, **_kwa: n + 1, 0)
961 def get_vars(cnf, data=None, instance=None):
963 get_vars -- Query a CNF_VAR structure. Skims the *toplevel* of the CNF_VAR
964 structure for entries with a matching `data` or `instance` field.
970 :returns: The structure containing only references to the
971 matching variables. Containing an empty list of
972 variables in case there is no match.
974 Values are compared literally. If both ``instance`` and ``data`` are
975 specified, vars will be compared against both.
979 criterion = lambda _var: False
982 criterion = lambda var: var[
983 "data"] == data and var["instance"] == instance
985 criterion = lambda var: var["data"] == data
987 criterion = lambda var: var["instance"] == instance
989 return {"cnf": [var for var in cnf if criterion(var) is True]}
994 ###############################################################################
996 ###############################################################################
1000 # Print/dump raw CNF values
1003 def output_cnf(root, out, renumber=False):
1005 Dump a textual representation of given CNF VAR structure to given stream.
1007 Runs :py:func:`format_cnf_vars` on the input (`root`) and then writes that
1008 to the given file-like object (out).
1010 :param root: a CNF_VAR structure
1011 :type root: dict or list or anything that :py:func:`cnf_root` accepts
1012 :param out: file-like object or something with a `write(str)` function
1013 :param bool renumber: Whether to renumber cnfvars first
1015 Files are converted to the 8-bit format expected by CNF so they can be fed
1016 directly into libcnffile.
1018 cnf = cnf_root(root)
1019 if renumber is True:
1020 _count = renumber_vars(root)
1021 if is_cnf(cnf) is True:
1022 (_, lines) = functools.reduce(format_cnf_vars, cnf, (0, []))
1023 if isinstance(out, (io.RawIOBase, io.BufferedIOBase)):
1024 out.write (b"\n".join (lines))
1026 else: # either subclass of io.TextIOBase or unknown
1027 out.write ("\n".join (map (from_latin1, lines)))
1031 def dump_cnf_bytes (root, renumber=False):
1033 Serialize CNF var structure, returning the result as a byte sequence.
1035 cnf = cnf_root(root)
1037 output_cnf(root, out, renumber=renumber)
1038 res = out.getvalue()
1043 def dump_cnf_string(root, renumber=False):
1045 Serialize CNF var structure, returning a latin1-encode byte string.
1047 .. todo::this is identical to :py:func:`dump_cnf_bytes`!
1049 cnf = cnf_root(root)
1051 output_cnf(root, out, renumber=renumber)
1052 res = out.getvalue()
1057 def print_cnf(root, out=None, renumber=False):
1059 Print given CNF_VAR structure to stdout (or other file-like object).
1061 Note that per default the config is printed to sys.stdout using the shell's
1062 preferred encoding. If the shell cannot handle unicode this might raise
1065 All params forwarded to :py:func:`output_cnf`. See args there.
1067 if root is not None:
1068 output_cnf(root, out or sys.stdout, renumber=renumber)
1071 def write_cnf(*argv, **kw_argv):
1072 """Alias for :py:func:`print_cnf`."""
1073 print_cnf(*argv, **kw_argv)
1076 def print_cnf_raw(root, out=None):
1077 """`if root is not None: out.write(root)`."""
1078 if root is not None:
1082 def write_cnf_raw(*argv, **kw_argv):
1083 """Alias for :py:func:`print_cnf_raw`."""
1084 print_cnf_raw(*argv, **kw_argv)
1088 # Print/dump CNF values in JSON format
1092 def output_json(root, out, renumber=False):
1094 Dump CNF_VAR structure to file-like object in json format.
1096 :param root: CNF_VAR structure
1097 :type root: dict or list or anything that :py:func:`cnf_root` accepts
1098 :param out: file-like object, used as argument to :py:func:`json.dumps` so
1099 probably has to accept `str` (as opposed to `bytes`).
1100 :param bool renumber: whether to renumber variables before dupming.
1102 # if not isinstance(out, file):
1103 #raise TypeError("%s (%s) is not a stream." % (out, type(out)))
1104 if renumber is True:
1105 _count = renumber_vars(root)
1106 if is_cnf(root) is True:
1107 root ["cnf"] = normalize_cnf(cnf_root (root))
1108 data = json.dumps(root)
1111 # TODO: else raise value error?
1114 def dump_json_string(root, renumber=False):
1116 Serialize CNF var structure as JSON, returning the result as a string.
1119 output_json(root, out, renumber=renumber)
1120 res = out.getvalue()
1125 def print_cnf_json(root, out=None, renumber=False):
1127 Print CNF_VAR structure in json format to stdout.
1129 Calls :py:func:`output_json` with `sys.stdout` if `out` is not given or
1132 if root is not None:
1133 output_json(root, out or sys.stdout, renumber=renumber)
1136 def write_cnf_json(*argv, **kw_argv):
1137 """Alias for :py:func:`print_cnf_json`."""
1138 print_cnf_json(*argv, **kw_argv)
1142 ###############################################################################
1143 # ENTRY POINT FOR DEVELOPMENT
1144 ###############################################################################
1148 print("usage: cnfvar.py -" , file=sys.stderr)
1149 print("" , file=sys.stderr)
1150 print(" Read CNF from stdin.", file=sys.stderr)
1151 print("" , file=sys.stderr)
1158 cnf = read_cnf(sys.stdin.buffer.read())
1161 elif first == "test":
1162 cnf = read_cnf(sys.stdin.buffer.read())
1163 cnff = get_vars(cnf, instance=2, data="FAX")
1170 if __name__ == "__main__":
1171 sys.exit(main(sys.argv))