1""" Dual ParserNode implementation """ 2from certbot_apache._internal import apacheparser 3from certbot_apache._internal import assertions 4from certbot_apache._internal import augeasparser 5 6 7class DualNodeBase: 8 """ Dual parser interface for in development testing. This is used as the 9 base class for dual parser interface classes. This class handles runtime 10 attribute value assertions.""" 11 12 def save(self, msg): # pragma: no cover 13 """ Call save for both parsers """ 14 self.primary.save(msg) 15 self.secondary.save(msg) 16 17 def __getattr__(self, aname): 18 """ Attribute value assertion """ 19 firstval = getattr(self.primary, aname) 20 secondval = getattr(self.secondary, aname) 21 exclusions = [ 22 # Metadata will inherently be different, as ApacheParserNode does 23 # not have Augeas paths and so on. 24 aname == "metadata", 25 callable(firstval) 26 ] 27 if not any(exclusions): 28 assertions.assertEqualSimple(firstval, secondval) 29 return firstval 30 31 def find_ancestors(self, name): 32 """ Traverses the ancestor tree and returns ancestors matching name """ 33 return self._find_helper(DualBlockNode, "find_ancestors", name) 34 35 def _find_helper(self, nodeclass, findfunc, search, **kwargs): 36 """A helper for find_* functions. The function specific attributes should 37 be passed as keyword arguments. 38 39 :param interfaces.ParserNode nodeclass: The node class for results. 40 :param str findfunc: Name of the find function to call 41 :param str search: The search term 42 """ 43 44 primary_res = getattr(self.primary, findfunc)(search, **kwargs) 45 secondary_res = getattr(self.secondary, findfunc)(search, **kwargs) 46 47 # The order of search results for Augeas implementation cannot be 48 # assured. 49 50 pass_primary = assertions.isPassNodeList(primary_res) 51 pass_secondary = assertions.isPassNodeList(secondary_res) 52 new_nodes = [] 53 54 if pass_primary and pass_secondary: 55 # Both unimplemented 56 new_nodes.append(nodeclass(primary=primary_res[0], 57 secondary=secondary_res[0])) # pragma: no cover 58 elif pass_primary: 59 for c in secondary_res: 60 new_nodes.append(nodeclass(primary=primary_res[0], 61 secondary=c)) 62 elif pass_secondary: 63 for c in primary_res: 64 new_nodes.append(nodeclass(primary=c, 65 secondary=secondary_res[0])) 66 else: 67 assert len(primary_res) == len(secondary_res) 68 matches = self._create_matching_list(primary_res, secondary_res) 69 for p, s in matches: 70 new_nodes.append(nodeclass(primary=p, secondary=s)) 71 72 return new_nodes 73 74 75class DualCommentNode(DualNodeBase): 76 """ Dual parser implementation of CommentNode interface """ 77 78 def __init__(self, **kwargs): 79 """ This initialization implementation allows ordinary initialization 80 of CommentNode objects as well as creating a DualCommentNode object 81 using precreated or fetched CommentNode objects if provided as optional 82 arguments primary and secondary. 83 84 Parameters other than the following are from interfaces.CommentNode: 85 86 :param CommentNode primary: Primary pre-created CommentNode, mainly 87 used when creating new DualParser nodes using add_* methods. 88 :param CommentNode secondary: Secondary pre-created CommentNode 89 """ 90 91 kwargs.setdefault("primary", None) 92 kwargs.setdefault("secondary", None) 93 primary = kwargs.pop("primary") 94 secondary = kwargs.pop("secondary") 95 96 if primary or secondary: 97 assert primary and secondary 98 self.primary = primary 99 self.secondary = secondary 100 else: 101 self.primary = augeasparser.AugeasCommentNode(**kwargs) 102 self.secondary = apacheparser.ApacheCommentNode(**kwargs) 103 104 assertions.assertEqual(self.primary, self.secondary) 105 106 107class DualDirectiveNode(DualNodeBase): 108 """ Dual parser implementation of DirectiveNode interface """ 109 110 def __init__(self, **kwargs): 111 """ This initialization implementation allows ordinary initialization 112 of DirectiveNode objects as well as creating a DualDirectiveNode object 113 using precreated or fetched DirectiveNode objects if provided as optional 114 arguments primary and secondary. 115 116 Parameters other than the following are from interfaces.DirectiveNode: 117 118 :param DirectiveNode primary: Primary pre-created DirectiveNode, mainly 119 used when creating new DualParser nodes using add_* methods. 120 :param DirectiveNode secondary: Secondary pre-created DirectiveNode 121 122 123 """ 124 125 kwargs.setdefault("primary", None) 126 kwargs.setdefault("secondary", None) 127 primary = kwargs.pop("primary") 128 secondary = kwargs.pop("secondary") 129 130 if primary or secondary: 131 assert primary and secondary 132 self.primary = primary 133 self.secondary = secondary 134 else: 135 self.primary = augeasparser.AugeasDirectiveNode(**kwargs) 136 self.secondary = apacheparser.ApacheDirectiveNode(**kwargs) 137 138 assertions.assertEqual(self.primary, self.secondary) 139 140 def set_parameters(self, parameters): 141 """ Sets parameters and asserts that both implementation successfully 142 set the parameter sequence """ 143 144 self.primary.set_parameters(parameters) 145 self.secondary.set_parameters(parameters) 146 assertions.assertEqual(self.primary, self.secondary) 147 148 149class DualBlockNode(DualNodeBase): 150 """ Dual parser implementation of BlockNode interface """ 151 152 def __init__(self, **kwargs): 153 """ This initialization implementation allows ordinary initialization 154 of BlockNode objects as well as creating a DualBlockNode object 155 using precreated or fetched BlockNode objects if provided as optional 156 arguments primary and secondary. 157 158 Parameters other than the following are from interfaces.BlockNode: 159 160 :param BlockNode primary: Primary pre-created BlockNode, mainly 161 used when creating new DualParser nodes using add_* methods. 162 :param BlockNode secondary: Secondary pre-created BlockNode 163 """ 164 165 kwargs.setdefault("primary", None) 166 kwargs.setdefault("secondary", None) 167 primary = kwargs.pop("primary") 168 secondary = kwargs.pop("secondary") 169 170 if primary or secondary: 171 assert primary and secondary 172 self.primary = primary 173 self.secondary = secondary 174 else: 175 self.primary = augeasparser.AugeasBlockNode(**kwargs) 176 self.secondary = apacheparser.ApacheBlockNode(**kwargs) 177 178 assertions.assertEqual(self.primary, self.secondary) 179 180 def add_child_block(self, name, parameters=None, position=None): 181 """ Creates a new child BlockNode, asserts that both implementations 182 did it in a similar way, and returns a newly created DualBlockNode object 183 encapsulating both of the newly created objects """ 184 185 primary_new = self.primary.add_child_block(name, parameters, position) 186 secondary_new = self.secondary.add_child_block(name, parameters, position) 187 assertions.assertEqual(primary_new, secondary_new) 188 return DualBlockNode(primary=primary_new, secondary=secondary_new) 189 190 def add_child_directive(self, name, parameters=None, position=None): 191 """ Creates a new child DirectiveNode, asserts that both implementations 192 did it in a similar way, and returns a newly created DualDirectiveNode 193 object encapsulating both of the newly created objects """ 194 195 primary_new = self.primary.add_child_directive(name, parameters, position) 196 secondary_new = self.secondary.add_child_directive(name, parameters, position) 197 assertions.assertEqual(primary_new, secondary_new) 198 return DualDirectiveNode(primary=primary_new, secondary=secondary_new) 199 200 def add_child_comment(self, comment="", position=None): 201 """ Creates a new child CommentNode, asserts that both implementations 202 did it in a similar way, and returns a newly created DualCommentNode 203 object encapsulating both of the newly created objects """ 204 205 primary_new = self.primary.add_child_comment(comment, position) 206 secondary_new = self.secondary.add_child_comment(comment, position) 207 assertions.assertEqual(primary_new, secondary_new) 208 return DualCommentNode(primary=primary_new, secondary=secondary_new) 209 210 def _create_matching_list(self, primary_list, secondary_list): 211 """ Matches the list of primary_list to a list of secondary_list and 212 returns a list of tuples. This is used to create results for find_ 213 methods. 214 215 This helper function exists, because we cannot ensure that the list of 216 search results returned by primary.find_* and secondary.find_* are ordered 217 in a same way. The function pairs the same search results from both 218 implementations to a list of tuples. 219 """ 220 221 matched = [] 222 for p in primary_list: 223 match = None 224 for s in secondary_list: 225 try: 226 assertions.assertEqual(p, s) 227 match = s 228 break 229 except AssertionError: 230 continue 231 if match: 232 matched.append((p, match)) 233 else: 234 raise AssertionError("Could not find a matching node.") 235 return matched 236 237 def find_blocks(self, name, exclude=True): 238 """ 239 Performs a search for BlockNodes using both implementations and does simple 240 checks for results. This is built upon the assumption that unimplemented 241 find_* methods return a list with a single assertion passing object. 242 After the assertion, it creates a list of newly created DualBlockNode 243 instances that encapsulate the pairs of returned BlockNode objects. 244 """ 245 246 return self._find_helper(DualBlockNode, "find_blocks", name, 247 exclude=exclude) 248 249 def find_directives(self, name, exclude=True): 250 """ 251 Performs a search for DirectiveNodes using both implementations and 252 checks the results. This is built upon the assumption that unimplemented 253 find_* methods return a list with a single assertion passing object. 254 After the assertion, it creates a list of newly created DualDirectiveNode 255 instances that encapsulate the pairs of returned DirectiveNode objects. 256 """ 257 258 return self._find_helper(DualDirectiveNode, "find_directives", name, 259 exclude=exclude) 260 261 def find_comments(self, comment): 262 """ 263 Performs a search for CommentNodes using both implementations and 264 checks the results. This is built upon the assumption that unimplemented 265 find_* methods return a list with a single assertion passing object. 266 After the assertion, it creates a list of newly created DualCommentNode 267 instances that encapsulate the pairs of returned CommentNode objects. 268 """ 269 270 return self._find_helper(DualCommentNode, "find_comments", comment) 271 272 def delete_child(self, child): 273 """Deletes a child from the ParserNode implementations. The actual 274 ParserNode implementations are used here directly in order to be able 275 to match a child to the list of children.""" 276 277 self.primary.delete_child(child.primary) 278 self.secondary.delete_child(child.secondary) 279 280 def unsaved_files(self): 281 """ Fetches the list of unsaved file paths and asserts that the lists 282 match """ 283 primary_files = self.primary.unsaved_files() 284 secondary_files = self.secondary.unsaved_files() 285 assertions.assertEqualSimple(primary_files, secondary_files) 286 287 return primary_files 288 289 def parsed_paths(self): 290 """ 291 Returns a list of file paths that have currently been parsed into the parser 292 tree. The returned list may include paths with wildcard characters, for 293 example: ['/etc/apache2/conf.d/*.load'] 294 295 This is typically called on the root node of the ParserNode tree. 296 297 :returns: list of file paths of files that have been parsed 298 """ 299 300 primary_paths = self.primary.parsed_paths() 301 secondary_paths = self.secondary.parsed_paths() 302 assertions.assertEqualPathsList(primary_paths, secondary_paths) 303 return primary_paths 304