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 DEPRECATED! Please do not extend this or add new uses of this module, use
30 :py:mod:`pyi2ncommon.arnied_api` or :py:mod:`pyi2ncommon.cnfvar` instead!
32 Copyright: 2014-2017 Intra2net AG
37 -------------------------------------------------------------------------------
39 This module provides read and write functionality for the Intra2net *CNF*
40 format. Two different syntaxes are available: classical *CNF* and a JSON
41 representation. Both versions are commonly understood by Intra2net software.
43 On the command line, raw CNF is accepted if the option ``-`` is given: ::
45 $ get_cnf routing 2 |python3 cnfvar.py - <<ENOUGH
46 1 ROUTING,2: "192.168.55.0"
47 2 (1) ROUTING_COMMENT,0: ""
48 3 (1) ROUTING_DNS_RELAYING_ALLOWED,0: "1"
49 4 (1) ROUTING_EMAIL_RELAYING_ALLOWED,0: "1"
50 5 (1) ROUTING_FIREWALL_RULESET_REF,0: "9"
51 6 (1) ROUTING_GATEWAY,0: "10.0.254.1"
52 7 (1) ROUTING_NAT_INTO,0: "0"
53 8 (1) ROUTING_NETMASK,0: "255.255.255.0"
54 9 (1) ROUTING_PROXY_PROFILE_REF,0: "2"
57 1 ROUTING,2: "192.168.55.0"
58 2 (1) ROUTING_COMMENT,0: ""
59 3 (1) ROUTING_DNS_RELAYING_ALLOWED,0: "1"
60 4 (1) ROUTING_EMAIL_RELAYING_ALLOWED,0: "1"
61 5 (1) ROUTING_FIREWALL_RULESET_REF,0: "9"
62 6 (1) ROUTING_GATEWAY,0: "10.0.254.1"
63 7 (1) ROUTING_NAT_INTO,0: "0"
64 8 (1) ROUTING_NETMASK,0: "255.255.255.0"
65 9 (1) ROUTING_PROXY_PROFILE_REF,0: "2"
67 The input takes one round-trip through the parsers and will error out on
68 problematic lines. Thus, ``cnfvar.py`` can be used to syntax-check CNF data.
70 Note that line numbers may be arbitrarily reassigned in the process. Of course,
71 parent references and the relative ordering of lines will be preserved in this
75 Decide on some facility for automatic fixup of line number values. The
76 internal representation is recursive so line numbers are not needed to
77 establish a variable hierarchy. They might as well be omitted from the json
78 input and only added when writing cnf. For the time being a lack of the
79 "number" field is interpreted as an error. Though it should at least
80 optionally be possible to omit the numbers entirely and have a function add
81 them after the fact. This might as well be added to :py:func:`is_cnf`
82 though it would be counterintuitive to have a predicate mutate its
83 argument. So maybe it could return a second argument to indicate a valid
84 structure that needs fixup or something like that.
87 The variable values of get_cnf seems to be encoded in latin1, and set_cnf
88 seems to assume latin1-encoded values (not var names). Function
89 :py:func:`read_cnf` converts this to unicode and functions
90 :py:func:`dump_cnf_string`, :py:func:`print_cnf`, and :py:func:`write_cnf`
91 convert unicode back to latin1.
94 notes on Python 3 conversion
95 -------------------------------------------------------------------------------
97 Since the original *CNF* format assumes latin-1 encoded data pretty much
98 exclusively, we preserve the original encoding while parsing the file.
99 When assembling the data structures returned to the user, values are then
100 converted to strings so they can be used naturally at the Python end.
103 -------------------------------------------------------------------------------
113 ###############################################################################
115 ###############################################################################
118 CNF_FIELD_MANDATORY = set ([ "varname", "data", "instance" ])
119 CNF_FIELD_OPTIONAL = set ([ "parent", "children", "comment", "number" ])
120 CNF_FIELD_KNOWN = CNF_FIELD_MANDATORY | CNF_FIELD_OPTIONAL
122 grab_parent_pattern = re.compile(b"""
124 \s* # optional spaces
131 base_line_pattern = re.compile(b"""
133 \s* # optional spaces
136 ([A-Z][A-Z0-9_]*) # varname
137 \s* # optional spaces
139 \s* # optional spaces
141 \s* # optional spaces
143 \s* # optional spaces
144 \"( # quoted string (data)
145 (?: \\\" # (of escaped dquote
146 |[^\"])* # or anything not a
148 \s* # optional spaces
151 \s* # optional spaces
152 .* # string (comment)
153 )? # egroup, optional
158 child_line_pattern = re.compile(b"""
160 \s* # optional spaces
165 ([A-Z][A-Z0-9_]*) # varname
166 \s* # optional spaces
168 \s* # optional spaces
170 \s* # optional spaces
172 \s* # optional spaces
173 \"([^\"]*)\" # quoted string (data)
174 \s* # optional spaces
177 \s* # optional spaces
178 .* # string (comment)
179 )? # egroup, optional
185 ###############################################################################
187 ###############################################################################
191 # Sadly, the Intranator is still stuck with one leg in the 90s.
194 """Take given unicode str and convert it to a latin1-encoded `bytes`."""
195 return s.encode("latin-1")
199 """Take given latin1-encoded `bytes` value and convert it to `str`."""
200 return s.decode("latin-1")
204 # Conversion functions
207 def marshal_in_number(number):
211 def marshal_in_parent(parent):
215 def marshal_in_instance(instance):
219 def marshal_in_varname(varname):
220 return from_latin1(varname).lower()
223 def marshal_in_data(data):
224 return from_latin1(data) if data is not None else ""
227 def marshal_in_comment(comment):
228 return comment and from_latin1(comment[1:].strip()) or None
236 return isinstance(s, str)
239 ###############################################################################
241 ###############################################################################
244 class InvalidCNF(Exception):
246 def __init__(self, msg):
250 return "Malformed CNF_VAR: \"%s\"" % self.msg
253 class MalformedCNF(Exception):
255 def __init__(self, msg):
259 return "Malformed CNF file: \"%s\"" % self.msg
262 ###############################################################################
264 ###############################################################################
276 raise InvalidCNF("CNF_VAR lacks a name.")
277 elif not is_string(varname):
278 raise InvalidCNF("Varname field of CNF_VAR \"%s\" is not a string."
281 raise InvalidCNF("Varname field of CNF_VAR is the empty string.")
283 if comment is not None:
284 if not is_string(comment):
285 raise InvalidCNF("Comment field of CNF_VAR \"%s\" is not a string."
289 raise InvalidCNF("Data field of CNF_VAR \"%s\" is empty."
291 elif not is_string(data):
292 raise InvalidCNF("Data field of CNF_VAR \"%s\" is not a string."
296 raise InvalidCNF("Instance field of CNF_VAR \"%s\" is empty."
298 elif not isinstance(instance, int):
299 raise InvalidCNF("Instance field of CNF_VAR \"%s\" is not an integer."
303 raise InvalidCNF("Number field of CNF_VAR \"%s\" is empty."
305 elif not isinstance(number, int):
306 raise InvalidCNF("Number field of CNF_VAR \"%s\" is not an integer."
309 raise InvalidCNF("Number field of CNF_VAR \"%s\" must be positive, not %d."
312 other = acc.get(number, None)
313 if other is not None: # already in use
314 raise InvalidCNF("Number field of CNF_VAR \"%s\" already used by variable %s."
316 acc[number] = varname
320 raise InvalidCNF("Parent field of nested CNF_VAR \"%s\" is empty."
322 elif not isinstance(parent, int):
323 raise InvalidCNF("Parent field of CNF_VAR \"%s\" is not an integer."
326 if parent is not None:
327 raise InvalidCNF("Flat CNF_VAR \"%s\" has nonsensical parent field \"%s\"."
334 is_cnf -- Predicate testing "CNF_VAR-ness". Folds the :py:func:`is_valid`
335 predicate over the argument which can be either a well-formed CNF
336 dictionary or a list of CNF_VARs.
338 :type root: cnfvar or cnf list
341 Not that if it returns at all, ``is_cnf()`` returns ``True``. Any non
342 well-formed member of the argument will cause the predicate to bail out
343 with an exception during traversal.
347 raise InvalidCNF(root)
348 return walk_cnf(cnf, False, is_valid, {}) is not None
353 Check whether a dictionary is a valid CNF.
355 :param dict obj: dictionary to check
356 :returns: True if the dictionary has all the mandatory fields and no
357 unknown fields, False otherwise
360 assert isinstance (obj, dict)
362 for f in CNF_FIELD_MANDATORY:
363 if obj.get(f, None) is None:
367 if f not in CNF_FIELD_KNOWN:
373 ###############################################################################
375 ###############################################################################
379 # JSON reader for get_cnf -j (the easy part)
382 def make_varname_lowercase(cnfvar):
384 Custom hook for json decoder: convert variable name to lowercase.
386 Since variable names are case insensitive, :py:func:`read_cnf` converts
387 them all to lower case. Downstream users of :py:func:`read_cnf_json` (e.g.
388 :py:class:`simple_cnf.SimpleCnf`) rely on lowercase variable names.
390 :param dict cnfvar: JSON "object" converted into a dict
391 :returns: same as input but if field `varname` is present, its value is
392 converted to lower case
393 :rtype: dict with str keys
396 cnfvar['varname'] = cnfvar['varname'].lower()
397 except KeyError: # there is no "varname" field
399 except AttributeError: # cnfvar['varname'] is not a string
404 def read_cnf_json(cnfdata):
406 Read json data from cnf data bytes.
408 :param bytes cnfdata: config data
409 :return: the parsed json data
412 .. note:: The JSON module does not decode data for all versions
413 of Python 3 so we handle the decoding ourselves.
415 if isinstance (cnfdata, bytes) is True:
416 cnfdata = from_latin1 (cnfdata)
417 cnf_json = json.loads(cnfdata, object_hook=make_varname_lowercase)
418 if is_cnf(cnf_json) is False:
419 raise TypeError("Invalid CNF_VAR.")
424 # CNF reader (the moderately more complicated part)
426 # Parsing usually starts from the `read_cnf`, which accepts a string containing
427 # the variables to parse in the same structure as returned by `get_cnf`.
429 # In the `prepare` function the string is split into lines, and a 3-element
430 # tuple is built. The first (named `current`) and second (named `next`)
431 # elements of this tuple are respectively the first and second non-empty lines
432 # of the input, while the third is a list of the remaining lines. This tuple is
433 # named `state` in the implementation below, and it is passed around during
434 # parsing. The `get` and `peek` functions are used to easily retrieve the
435 # `current` and `next` items from the "state".
437 # When we "advance" the state, we actually drop the "current" element,
438 # replacing it with the "next", while a new "next" is popped from the list of
439 # remaining lines. Parsing is done this way because we need to look ahead at
440 # the next line -- if it is a child it needs to be appended to the `children`
441 # property of the current line.
443 # Regular expressions are used to extract important information from the CNF
444 # lines. Finally, once parsing is completed, a dictionary is returned. The dict
445 # has the same structure as the serialized JSON output returned by
452 Read cnf data from data bytes.
454 :param bytes data: raw data
455 :return: the parsed cnf data
456 :rtype: {str, {str, str or int}}
458 if isinstance(data, str):
459 data = to_latin1(data)
460 state = prepare(data)
462 raise InvalidCNF("Empty input string.")
464 cnf = parse_cnf_root(state)
465 if is_cnf(cnf) is False:
466 raise TypeError("Invalid CNF_VAR.")
472 Build 3-element iterable from a CNF string dump.
474 :param raw: string content as returned by `get_cnf`
476 :returns: 3-element tuple, where the first two elements are the first two
477 lines of the output and the third is a list containing the rest
478 of the lines in reverse.
479 :rtype: (str * str option * str list) option
481 lines = raw.splitlines()
493 first = first.strip()
495 return advance((first, second, lines))
497 return (first, second, lines)
502 Pop the next line from the stream, advancing the tuple.
504 :param cns: a 3-element tuple containing two CNF lines and a list of the
506 :type cnd: (str, str, [str])
507 :returns: a new tuple with a new item popped from the list of lines
508 :rtype cnd: (str, str, [str])
510 current, next, stream = cns
511 if next is None: # reached end of stream
521 if current == "": # skip blank lines
522 return advance((current, next, stream))
523 return (current, next, stream)
528 Get the current line from the state without advancing it.
530 :param cns: a 3-element tuple containing two CNF lines and a list of the
532 :type cnd: (str, str, [str])
533 :returns: the CNF line stored as `current`
542 Get the next line from the state without advancing it.
544 :param cns: a 3-element tuple containing two CNF lines and a list of the
546 :type cnd: (str, str, [str])
547 :returns: the CNF line stored as `next`
554 def parse_cnf_root(state):
556 Iterate over and parse a list of CNF lines.
558 :param state: a 3-element tuple containing two lines and a list of the
560 :type state: (str, str, [str])
561 :returns: a list of parsed CNF variables
564 The function will parse the first element from the `state` tuple, then read
565 the next line to see if it is a child variable. If it is, it will be
566 appended to the last parsed CNF, otherwise top-level parsing is done
572 cnf_line = read_base_line(current)
573 if cnf_line is not None:
574 lines.append(cnf_line)
575 state = advance(state)
576 if state is None: # -> nothing left to do
579 parent = get_parent(current) # peek at next line
580 if parent is not None: # -> recurse into children
581 (state, children, _parent) = parse_cnf_children(state, parent)
582 cnf_line["children"] = children
587 state = advance(state)
594 def parse_cnf_children(state, parent):
596 Read and parse child CNFs of a given parent until there is none left.
598 :param state: a 3-element tuple containing two lines and a list of the
600 :type state: (str, str, [str])
601 :param parent: id of the parent whose children we are looking for
603 :returns: a 3-element tuple with the current state, a list of children of
604 the given parent and the parent ID
605 :rtype: (tuple, [str], int)
607 The function will recursively parse child lines from the `state` tuple
608 until one of these conditions is satisfied:
610 1. the input is exhausted
612 2.1. is a toplevel line
613 2.2. is a child line whose parent has a lower parent number
615 Conceptually, 2.1 is a very similar to 2.2 but due to the special status of
616 toplevel lines in CNF we need to handle them separately.
618 Note that since nesting of CNF vars is achieved via parent line numbers,
619 lines with different parents could appear out of order. libcnffile will
620 happily parse those and still assign children to the specified parent:
624 1 USER,1337: "l33t_h4x0r"
625 2 (1) USER_GROUP_MEMBER_REF,0: "2"
626 4 USER,1701: "picard"
627 5 (4) USER_GROUP_MEMBER_REF,0: "2"
628 6 (4) USER_PASSWORD,0: "engage"
629 3 (1) USER_PASSWORD,0: "hacktheplanet"
632 1 USER,1337: "l33t_h4x0r"
633 2 (1) USER_GROUP_MEMBER_REF,0: "2"
634 3 (1) USER_PASSWORD,0: "hacktheplanet"
636 1 USER,1701: "picard"
637 2 (1) USER_GROUP_MEMBER_REF,0: "2"
638 3 (1) USER_PASSWORD,0: "engage"
640 It is a limitation of ``cnfvar.py`` that it cannot parse CNF data
641 structured like the above example: child lists are only populated from
642 subsequent CNF vars using the parent number solely to track nesting levels.
643 The parser does not keep track of line numbers while traversing the input
644 so it doesn’t support retroactively assigning a child to anything else but
645 the immediate parent.
650 cnf_line = read_child_line(current)
651 if cnf_line is not None:
652 lines.append(cnf_line)
653 state = advance(state)
657 new_parent = get_parent(current)
658 if new_parent is None:
660 return (state, lines, None)
661 if new_parent > parent:
662 # parent is further down in hierarchy -> new level
663 (state, children, new_parent) = \
664 parse_cnf_children (state, new_parent)
667 cnf_line["children"] = children
669 new_parent = get_parent(current)
670 if new_parent is None:
672 return (state, lines, None)
673 if new_parent < parent:
674 # parent is further up in hierarchy -> pop level
675 return (state, lines, new_parent)
676 # new_parent == parent -> continue parsing on same level
677 return (state, lines, parent)
680 def get_parent(line):
682 Extract the ID of the parent for a given CNF line.
684 :param str line: CNF line
685 :returns: parent ID or None if no parent is found
688 match = re.match(grab_parent_pattern, line)
689 if match is None: # -> no parent
691 return int(match.groups()[0])
694 def read_base_line(line):
696 Turn one top-level CNF line into a dictionary.
698 :param str line: CNF line
701 This performs the necessary decoding on values to obtain proper Python
702 strings from 8-bit encoded CNF data.
704 The function only operates on individual lines. Argument strings that
705 contain data for multiple lines – this includes child lines of the current
706 CNF var! – will trigger a parsing exception.
708 if len(line.strip()) == 0:
709 return None # ignore empty lines
711 return None # ignore comments
713 match = re.match(base_line_pattern, line)
715 raise MalformedCNF("Syntax error in line \"\"\"%s\"\"\"" % line)
716 number, varname, instance, data, comment = match.groups()
718 "number" : marshal_in_number (number),
719 "varname" : marshal_in_varname (varname),
720 "instance" : marshal_in_instance (instance),
721 "data" : marshal_in_data (data),
722 "comment" : marshal_in_comment (comment),
726 def read_child_line(line):
728 Turn one child CNF line into a dictionary.
730 :param str line: CNF line
733 This function only operates on individual lines. If the argument string is
734 syntactically valid but contains input representing multiple CNF vars, a
735 parse error will be thrown.
737 if len(line.strip()) == 0:
738 return None # ignore empty lines
740 return None # ignore comments
742 match = re.match(child_line_pattern, line)
744 raise MalformedCNF("Syntax error in child line \"\"\"%s\"\"\""
745 % from_latin1 (line))
746 number, parent, varname, instance, data, comment = match.groups()
748 "number" : marshal_in_number (number),
749 "parent" : marshal_in_parent (parent),
750 "varname" : marshal_in_varname (varname),
751 "instance" : marshal_in_instance (instance),
752 "data" : marshal_in_data (data),
753 "comment" : marshal_in_comment (comment),
757 ###############################################################################
759 ###############################################################################
762 cnf_line_nest_indent = " "
763 cnf_line_base_fmt = "%d %s,%d: \"%s\""
764 cnf_line_child_fmt = "%d %s(%d) %s,%d: \"%s\""
767 def format_cnf_vars(da, var):
769 Return a list of formatted cnf_line byte strings.
771 :param da: a tuple where the first element is the depth (0 = top-level,
772 >1 = child CNF) and the second is the string being built.
774 :param var: the CNF element to convert to string in the current iteration
776 :returns: a tuple like `da`, where the second element should contain all
780 This function is meant to be passed to the :py:func:`functools.reduce`
783 The variable names are uppercased unconditionally because while ``get_cnf``
784 is case-indifferent for variable names, ``set_cnf`` isn’t.
789 line = cnf_line_child_fmt \
791 cnf_line_nest_indent * depth,
793 var["varname"].upper(),
797 line = cnf_line_base_fmt \
799 var["varname"].upper(),
803 comment = var.get("comment", None)
804 if comment and len(comment) != 0:
805 line = line + (" # %s" % comment)
807 acc.append(to_latin1(line))
809 children = var.get("children", None)
810 if children is not None:
811 (_, acc) = functools.reduce(format_cnf_vars, children, (depth + 1, acc))
818 Extract a list of CNFs from a given structure.
820 :param root: list of CNFs or a CNF dictionary
821 :type root: [dict] or dict
822 :raises: :py:class:`TypeError` if no CNFs can be extracted
823 :returns: list with one or more CNF objects
826 Output varies depending on a few conditions:
827 - If `root` is a list, return it right away
828 - If `root` is a dict corresponding to a valid CNF value, return it wrapped
830 - If `root` is a dict with a `cnf` key containg a list (as the JSON
831 returned by `get_cnf -j`), return the value
832 - Otherwise, raise an error
834 if isinstance(root, list):
836 if not isinstance(root, dict):
838 "Expected dictionary of CNF_VARs, got %s." % type(root))
841 cnf = root.get("cnf", None)
842 if not isinstance(cnf, list):
843 raise TypeError("Expected list of CNF_VARs, got %s." % type(cnf))
847 def normalize_cnf(cnf):
849 Ensure the output conforms to set_cnf()’s expectations.
851 :param cnf: list of CNF objects to normalize
853 :returns: normalized list
856 if isinstance(cnf, list) is False:
857 raise MalformedCNF("expected list of CNF_VARs, got [%s]" % type(cnf))
860 { "number" : var ["number"]
861 , "varname" : var ["varname"].upper()
862 , "instance" : var ["instance"]
863 , "data" : var ["data"]
866 children = var.get("children", None)
867 if children is not None:
868 vvar ["children"] = normalize_cnf(children)
870 parent = var.get("parent", None)
871 if parent is not None:
872 vvar ["parent"] = var["parent"]
874 comment = var.get("comment", None)
875 if comment is not None:
876 vvar ["comment"] = var["comment"]
880 return [norm(var) for var in cnf]
883 ###############################################################################
885 ###############################################################################
888 def walk_cnf(cnf, nested, fun, acc):
890 Depth-first traversal of a CNF tree.
894 :type fun: 'a -> bool -> (cnf stuff) -> 'a
898 Executes ``fun`` recursively for each node in the tree. The function
899 receives the accumulator ``acc`` which can be of an arbitrary type as first
900 argument. The second argument is a flag indicating whether the current
901 CNF var is a child (if ``True``) or a parent var. CNF member fields are
902 passed via named optional arguments.
907 comment=var.get("comment", None),
908 data=var.get("data", None),
909 instance=var.get("instance", None),
910 number=var.get("number", None),
911 parent=var.get("parent", None),
912 varname=var.get("varname", None))
913 children = var.get("children", None)
914 if children is not None:
915 acc = walk_cnf(children, True, fun, acc)
919 def renumber_vars(root, parent=None, toplevel=False):
921 Number cnfvars linearly.
923 If *parent* is specified, numbering will start at this offset. Also, the
924 VAR *root* will be assigned this number as a parent lineno unless
925 *toplevel* is set (the root var in a CNF tree obviously can’t have a
928 The *toplevel* parameter is useful when renumbering an existing variable
929 starting at a given offset without at the same time having that offset
930 assigned as a parent.
932 root = cnf_root (root)
937 if toplevel is False and parent is not None:
938 var["parent"] = parent
939 children = var.get("children", None)
940 if children is not None:
941 i = renumber_vars(children, parent=i, toplevel=False)
945 def count_vars(root):
947 Traverse the cnf structure recursively, counting VAR objects (CNF lines).
951 raise InvalidCNF(root)
952 return walk_cnf(cnf, True, lambda n, _nested, **_kwa: n + 1, 0)
959 def get_vars(cnf, data=None, instance=None):
961 get_vars -- Query a CNF_VAR structure. Skims the *toplevel* of the CNF_VAR
962 structure for entries with a matching `data` or `instance` field.
968 :returns: The structure containing only references to the
969 matching variables. Containing an empty list of
970 variables in case there is no match.
972 Values are compared literally. If both ``instance`` and ``data`` are
973 specified, vars will be compared against both.
977 criterion = lambda _var: False
980 criterion = lambda var: var[
981 "data"] == data and var["instance"] == instance
983 criterion = lambda var: var["data"] == data
985 criterion = lambda var: var["instance"] == instance
987 return {"cnf": [var for var in cnf if criterion(var) is True]}
992 ###############################################################################
994 ###############################################################################
998 # Print/dump raw CNF values
1001 def output_cnf(root, out, renumber=False):
1003 Dump a textual representation of given CNF VAR structure to given stream.
1005 Runs :py:func:`format_cnf_vars` on the input (`root`) and then writes that
1006 to the given file-like object (out).
1008 :param root: a CNF_VAR structure
1009 :type root: dict or list or anything that :py:func:`cnf_root` accepts
1010 :param out: file-like object or something with a `write(str)` function
1011 :param bool renumber: Whether to renumber cnfvars first
1013 Files are converted to the 8-bit format expected by CNF so they can be fed
1014 directly into libcnffile.
1016 cnf = cnf_root(root)
1017 if renumber is True:
1018 _count = renumber_vars(root)
1019 if is_cnf(cnf) is True:
1020 (_, lines) = functools.reduce(format_cnf_vars, cnf, (0, []))
1021 if isinstance(out, (io.RawIOBase, io.BufferedIOBase)):
1022 out.write (b"\n".join (lines))
1024 else: # either subclass of io.TextIOBase or unknown
1025 out.write ("\n".join (map (from_latin1, lines)))
1029 def dump_cnf_bytes (root, renumber=False):
1031 Serialize CNF var structure, returning the result as a byte sequence.
1033 cnf = cnf_root(root)
1035 output_cnf(root, out, renumber=renumber)
1036 res = out.getvalue()
1041 def dump_cnf_string(root, renumber=False):
1043 Serialize CNF var structure, returning a latin1-encode byte string.
1045 .. todo::this is identical to :py:func:`dump_cnf_bytes`!
1047 cnf = cnf_root(root)
1049 output_cnf(root, out, renumber=renumber)
1050 res = out.getvalue()
1055 def print_cnf(root, out=None, renumber=False):
1057 Print given CNF_VAR structure to stdout (or other file-like object).
1059 Note that per default the config is printed to sys.stdout using the shell's
1060 preferred encoding. If the shell cannot handle unicode this might raise
1063 All params forwarded to :py:func:`output_cnf`. See args there.
1065 if root is not None:
1066 output_cnf(root, out or sys.stdout, renumber=renumber)
1069 def write_cnf(*argv, **kw_argv):
1070 """Alias for :py:func:`print_cnf`."""
1071 print_cnf(*argv, **kw_argv)
1074 def print_cnf_raw(root, out=None):
1075 """`if root is not None: out.write(root)`."""
1076 if root is not None:
1080 def write_cnf_raw(*argv, **kw_argv):
1081 """Alias for :py:func:`print_cnf_raw`."""
1082 print_cnf_raw(*argv, **kw_argv)
1086 # Print/dump CNF values in JSON format
1090 def output_json(root, out, renumber=False):
1092 Dump CNF_VAR structure to file-like object in json format.
1094 :param root: CNF_VAR structure
1095 :type root: dict or list or anything that :py:func:`cnf_root` accepts
1096 :param out: file-like object, used as argument to :py:func:`json.dumps` so
1097 probably has to accept `str` (as opposed to `bytes`).
1098 :param bool renumber: whether to renumber variables before dupming.
1100 # if not isinstance(out, file):
1101 #raise TypeError("%s (%s) is not a stream." % (out, type(out)))
1102 if renumber is True:
1103 _count = renumber_vars(root)
1104 if is_cnf(root) is True:
1105 root ["cnf"] = normalize_cnf(cnf_root (root))
1106 data = json.dumps(root)
1109 # TODO: else raise value error?
1112 def dump_json_string(root, renumber=False):
1114 Serialize CNF var structure as JSON, returning the result as a string.
1117 output_json(root, out, renumber=renumber)
1118 res = out.getvalue()
1123 def print_cnf_json(root, out=None, renumber=False):
1125 Print CNF_VAR structure in json format to stdout.
1127 Calls :py:func:`output_json` with `sys.stdout` if `out` is not given or
1130 if root is not None:
1131 output_json(root, out or sys.stdout, renumber=renumber)
1134 def write_cnf_json(*argv, **kw_argv):
1135 """Alias for :py:func:`print_cnf_json`."""
1136 print_cnf_json(*argv, **kw_argv)
1140 ###############################################################################
1141 # ENTRY POINT FOR DEVELOPMENT
1142 ###############################################################################
1146 print("usage: cnfvar.py -" , file=sys.stderr)
1147 print("" , file=sys.stderr)
1148 print(" Read CNF from stdin.", file=sys.stderr)
1149 print("" , file=sys.stderr)
1156 cnf = read_cnf(sys.stdin.buffer.read())
1159 elif first == "test":
1160 cnf = read_cnf(sys.stdin.buffer.read())
1161 cnff = get_vars(cnf, instance=2, data="FAX")
1167 if __name__ == "__main__":
1168 sys.exit(main(sys.argv))