def construct_contents_tree(headings_list: List[Heading]) -> List[Heading]: """ Constructs a Tree that represents the Contents from a flat List of :class:`Heading`. :param headings_list: :return: """ if len(headings_list) == 0: return [] # list of root Headings graph = [] lowest_level = headings_list[0].level for i, heading in enumerate(headings_list): if heading.level <= lowest_level: if heading.level < lowest_level: lowest_level = heading.level graph.append(heading) else: subheading = Heading(heading.val, heading.level, heading.start, heading.end) graph[-1].add_subheading(subheading) for heading in graph: subheadings = PrettyMD.construct_contents_tree(heading.subheadings) heading.set_subheadings(subheadings) return graph
def __init__(self, original: str, includes_title: bool = False, link: bool = True, back_to_toc_link: bool = True): """ :param original: Input Markdown text. :param includes_title: Whether a Title is included in the input text. If so, it'll be excluded from the ToC. :param link: Whether to include hyperlinks from the ToC to the relevant headings. :param back_to_toc_link: Whether to include hyperlinks at end of each section that navigates to the ToC. """ self._input = original self._output = "" self._includes_title = includes_title self._link = link self._navlink = back_to_toc_link self.title = None # Root node of Contents self._contents_tree = Heading(val=None, level=-1, start=0, end=0, subheadings=None, height=-1) # table of contents - need to join with \n self._toc_items = ["<a name='nav'></a>\n## Contents 🗺"] # Updates self._contents_tree - adds all Headings as subheadings. Still a mostly-flat structure. self.parse_headings() # if MD includes title, then don't put title in ToC, and put ToC's location to begin after the title. if self._includes_title: all_headings = self._contents_tree.subheadings[1:] self._toc_location = self._contents_tree.subheadings[0].end else: all_headings = self._contents_tree self._toc_location = 0 # Make Contents Tree from list of Headings. Unflattened structure. self._contents_tree.set_subheadings(self.construct_contents_tree(all_headings)) # update self._toc_items self.make_toc() return
def flatten_contents_tree(self, heading: Heading) -> List[Heading]: """ In-order traversal of :instance_attribute:`self._contents_tree` :return: """ flat_contents_tree = [ Heading(val=heading.val, level=heading.level, height=heading.height, start=heading.start, end=heading.end)] if len(heading) == 0: return flat_contents_tree for subheading in heading.subheadings: flat_contents_tree.extend(self.flatten_contents_tree(subheading)) return flat_contents_tree
def parse_headings(self) -> None: """ Finds using RegEx the headings in :instance_attribute:`self._input`. Add all these headings to the root of the contents tree, that is the :instance_attribute:`self._contents_tree`. :return: """ # find all headings by matching regex headings_iterator = re.finditer(self.HEADING_REGEX, self._input) # make Heading objects and put them all in as roots to the Contents Tree. for i, heading_match in enumerate(headings_iterator): level = self.get_heading_level(heading_match.group(1)) text = heading_match.group(2).rstrip() subheading = Heading(val=text, level=level, start=heading_match.start(), end=heading_match.end()) self._contents_tree.add_subheading(subheading) if self._includes_title: self.title = self._contents_tree.subheadings[0] return
def toggleHeading(self): selected = self.web.selectedText() Heading(self, self.parentWindow, selected)
def plan_move(self, readings): # Get the ID of the current square. square_id = self.square_id(self.state.pos) # If it's our first move, mark the square as a node and pick an exit or # rotate. if self.initialising: self.initialising = False self.graph.add_node(square_id) self.last_node = square_id # Get all the exits. exits = np.nonzero(readings)[0] # If no exits, rotate. if len(exits) == 0: return -90, 0 # Pick the first exit. sensor = Sensor(exits[0]) rot = sensor.rotation() rot = Sensor.rotation(sensor) return rot, 1 # Check if we should reset. if self.phase == Phase.PLAN and self.reached_goal: if self.verbose: print(f"[MOUSE] Finished planning.") # Begin execution phase. self.phase = Phase.EXECUTE self.state.reset() self.initialising = True self.graph = Graph() return 'RESET', 'RESET' # Check if we're backtracking. if self.backtrack: self.backtrack = False # Get the direction we're moving in. move_heading = self.state.heading.rotate(Rotation.LEFT) # Load up the edge we'll be travelling on. edge = self.graph.find_edge_by_heading(square_id, move_heading) # What's the largest move we can make down this edge? move = self.edge_move(edge) return Rotation.LEFT, move # If it's not a node, just move forward. if not self.node_sensed(readings): # Get the edge we're currently on. edge = self.graph.find_edge_by_heading(self.last_node, self.state.heading) # If we're on an edge, move further if possible. move = self.edge_move(edge) if edge else 1 return Rotation.NONE, move # At this point we've decided that the square is a node. # Add the node if it hasn't been already. node_already_added = self.graph.node_added(square_id) if not node_already_added: # Add the node. self.graph.add_node(square_id) # We're turning around on the spot, don't need to add an edge. if square_id != self.last_node: # Check if edge already exists. if not self.graph.find_edge_by_nodes(self.last_node, square_id): # Get positions of nodes. node_pos1 = self.square_position(self.last_node) node_pos2 = self.square_position(square_id) # Get distance between nodes. vec = node_pos2 - node_pos1 dist = int(np.linalg.norm(vec)) # Get headings traversing from node 1 to 2, and reverse. head_vect = vec / dist heading = Heading.from_components(head_vect) # Add the new edge. self.graph.add_edge(self.last_node, square_id, dist, heading) else: # Increment the number of traversals for this edge. self.graph.increment_traversal(self.last_node, square_id) # Get a prob for each direction. sensors = np.array([]) weights = np.array([], dtype=np.float32) traversals = np.array([], dtype=np.int8) move_vecs = np.ndarray((0, 2), dtype=np.int8) for i, reading in enumerate(readings): # Don't consider the move if we'll hit a wall. if reading == 0: continue # Create the Sensor. sensor = Sensor(i) # Get the edge we'll be traversing if we take this move. sensor_heading = self.state.heading.rotate(sensor.rotation()) edge = self.graph.find_edge_by_heading(square_id, sensor_heading) # Get number of traversals. 0 if edge isn't recorded. traversal = edge['traversals'] if edge else 0 # Don't take the edge if we've been there twice already. if traversal == 2: continue # If we're on an edge, we can possibly move faster. move = self.edge_move(edge) if edge else 1 # Get the move vector components. move_vec = move * sensor_heading.components() # Add the number of edge traversals. traversals = np.append(traversals, traversal) # Add the move vec and sensor ID. move_vecs = np.vstack((move_vecs, move_vec)) sensors = np.append(sensors, sensor) # How much of this move is towards the centre? weight = np.dot(move_vec, self.unit_centre()) weights = np.append(weights, weight) # If no possible moves, let's turn around. if len(move_vecs) == 0: self.last_node = square_id return Rotation.LEFT, 0 # If we're not turning on the spot, and we've already seen the node. if self.last_node != square_id and node_already_added: # If we only traversed the last edge once, go back that way. We've # reached the end of a branch in our depth-first search algorithm. num_traversals = self.graph.find_edge_by_nodes(self.last_node, square_id)['traversals'] if num_traversals == 1: self.last_node = square_id self.backtrack = True return Rotation.LEFT, 0 # Take the road less travelled, i.e, select those squares that we've visited less. min_idx = np.argwhere(traversals == np.min(traversals)).flatten() # Only keep edges with minimum traversals. sensors = sensors[min_idx] weights = weights[min_idx] move_vecs = move_vecs[min_idx] # Apply the softmax function. probs = self.softmax(weights) # Get a sensor based on the probs. sensor = np.random.choice(sensors, p=probs) idx = np.where(sensors == sensor)[0][0] # Get the rotation and move to perform. rot = sensor.rotation() move_vec = move_vecs[idx] move = abs(move_vec).max() # Update internal state. self.last_node = square_id # The last thing we do is update our state. We don't know anything about # the next square at this point; this info will be handed to us with the # next sensor reading. So it doesn't make any sense to start working on # adding the next node or anything. In fact, we don't know if this step # will bring us to a node or a passage until we get sensor readings. return rot, move
class PrettyMD: HEADING_LEVEL = Literal[1, 2, 3, 4, 5, 6] # Headings start with '#' chars, which can have upto 3 whitespaces before it. # Heading then needs to have some text after it that is not whitespace or #. HEADING_REGEX = re.compile(r'^_{,3}(#{1,6})\s*([^#].+)\s*', re.MULTILInseINE) BULLET_POINT_CHAR = "*" def __init__(self, original: str, includes_title: bool = False, link: bool = True, back_to_toc_link: bool = True): """ :param original: Input Markdown text. :param includes_title: Whether a Title is included in the input text. If so, it'll be excluded from the ToC. :param link: Whether to include hyperlinks from the ToC to the relevant headings. :param back_to_toc_link: Whether to include hyperlinks at end of each section that navigates to the ToC. """ self._input = original self._output = "" self._includes_title = includes_title self._link = link self._navlink = back_to_toc_link self.title = None # Root node of Contents self._contents_tree = Heading(val=None, level=-1, start=0, end=0, subheadings=None, height=-1) # table of contents - need to join with \n self._toc_items = ["<a name='nav'></a>\n## Contents 🗺"] # Updates self._contents_tree - adds all Headings as subheadings. Still a mostly-flat structure. self.parse_headings() # if MD includes title, then don't put title in ToC, and put ToC's location to begin after the title. if self._includes_title: all_headings = self._contents_tree.subheadings[1:] self._toc_location = self._contents_tree.subheadings[0].end else: all_headings = self._contents_tree self._toc_location = 0 # Make Contents Tree from list of Headings. Unflattened structure. self._contents_tree.set_subheadings(self.construct_contents_tree(all_headings)) # update self._toc_items self.make_toc() return def parse_headings(self) -> None: """ Finds using RegEx the headings in :instance_attribute:`self._input`. Add all these headings to the root of the contents tree, that is the :instance_attribute:`self._contents_tree`. :return: """ # find all headings by matching regex headings_iterator = re.finditer(self.HEADING_REGEX, self._input) # make Heading objects and put them all in as roots to the Contents Tree. for i, heading_match in enumerate(headings_iterator): level = self.get_heading_level(heading_match.group(1)) text = heading_match.group(2).rstrip() subheading = Heading(val=text, level=level, start=heading_match.start(), end=heading_match.end()) self._contents_tree.add_subheading(subheading) if self._includes_title: self.title = self._contents_tree.subheadings[0] return def make_toc(self) -> None: """ Updates :instance_attribute:`self._toc_items` with text that represents the Table of Contents. :return: """ # 1st element is the root of the graph, which is always going to have val=None flattened_toc = self.flatten_contents_tree(self._contents_tree)[1:] i = 0 while i < len(flattened_toc): if self._link: toc_line = "[{}](#{})".format(flattened_toc[i].val, flattened_toc[i].anchor_name) else: toc_line = "{}".format(flattened_toc[i].val) self._toc_items.append("\t" * flattened_toc[i].height + "{} {}".format(self.BULLET_POINT_CHAR, toc_line)) i += 1 # if ToC lines to be linked to the Headings, insert the anchors. if self._link: self.insert_anchors(flattened_toc) else: self._output = self._input return def flatten_contents_tree(self, heading: Heading) -> List[Heading]: """ In-order traversal of :instance_attribute:`self._contents_tree` :return: """ flat_contents_tree = [ Heading(val=heading.val, level=heading.level, height=heading.height, start=heading.start, end=heading.end)] if len(heading) == 0: return flat_contents_tree for subheading in heading.subheadings: flat_contents_tree.extend(self.flatten_contents_tree(subheading)) return flat_contents_tree def insert_anchors(self, flattened_toc: List[Heading]) -> None: """ Inserts the anchors before each of the headings. Updates :instance_attribute:`self._output`. :param flattened_toc: :return: """ input_str_ptr = 0 if self._includes_title: input_str_ptr = flattened_toc[0].end for heading in flattened_toc: # get anchor str e.g. <a name='#anchor'></a> anchor = heading.generate_anchor() navlink = "" if self._navlink: navlink = "\n[🗺 Go back to Navigation ↑](#nav)\n\n" # update _output string by inserting anchor before the heading text self._output += "{}\n{}{}\n{}" \ .format(self._input[input_str_ptr:heading.start], navlink, anchor, self._input[heading.start:heading.end]) # update pointer in _input string input_str_ptr = heading.end self._output += self._input[input_str_ptr:] + "\n\n[🗺 Go back to Navigation ↑](#nav)\n" return @staticmethod def get_heading_level(heading: str) -> HEADING_LEVEL: return min(len(heading), 6) @staticmethod def construct_contents_tree(headings_list: List[Heading]) -> List[Heading]: """ Constructs a Tree that represents the Contents from a flat List of :class:`Heading`. :param headings_list: :return: """ if len(headings_list) == 0: return [] # list of root Headings graph = [] lowest_level = headings_list[0].level for i, heading in enumerate(headings_list): if heading.level <= lowest_level: if heading.level < lowest_level: lowest_level = heading.level graph.append(heading) else: subheading = Heading(heading.val, heading.level, heading.start, heading.end) graph[-1].add_subheading(subheading) for heading in graph: subheadings = PrettyMD.construct_contents_tree(heading.subheadings) heading.set_subheadings(subheadings) return graph @property def toc(self): return "\n".join(self._toc_items) @property def output(self): title = "" if self.title is not None: title = self._input[:self.title.end] return "{title}\n{contents}{body}".format(title=title, contents=self.toc, body=self._output)
def _start_heading(self): return Heading(shared_vars['start_heading'].get())
def _desired_heading(self): return Heading(shared_vars['desired_heading'].get())
def _heading(self): return Heading(shared_vars['heading'].get())
def toggle_heading(editor): selected = editor.web.selectedText() Heading(editor, editor.parentWindow, selected)