1+ """
2+ This module provides utilities for working with ranges of text in source code.
3+
4+ It includes classes and functions for specifying ranges, finding lines,
5+ and manipulating text within those ranges. The main components are:
6+
7+ - RangeSpec: A class representing a range of lines in a text.
8+ - IdentifierBoundaries: A class representing the boundaries of an identifier in code.
9+ - Various utility functions for working with these classes and text manipulation.
10+ """
11+
112import re
213from collections .abc import Sequence
314from typing import NamedTuple
1223
1324@total_ordering
1425class RangeSpec (NamedTuple ):
26+ """
27+ Represents a range of lines in a text, with start and end indices and indentation.
28+
29+ This class is used to specify a range of lines in a text, typically for
30+ text manipulation operations. It includes methods for comparing ranges,
31+ modifying the range, and performing operations on text using the range.
32+
33+ Attributes:
34+ start (int): The starting line index of the range.
35+ end (int): The ending line index of the range (exclusive).
36+ indent (int): The indentation level of the range.
37+ """
1538 start : int
1639 end : int
1740 indent : int = 0
1841
1942 def __str__ (self ):
43+ """Return a string representation of the RangeSpec."""
2044 return (f'{ self .start } :{ self .end } ' if self .as_index is None else f'%{ self .as_index } ' ) + f'@{ self .indent } '
2145
2246 def __lt__ (self , other ):
47+ """Compare if this range is strictly before another range."""
2348 return self .end < other .start
2449
2550 def __le__ (self , other ):
51+ """Compare if this range is before or adjacent to another range."""
2652 return self .end <= other .start
2753
2854 def __gt__ (self , other ):
55+ """Compare if this range is strictly after another range."""
2956 return self .start > other .end
3057
3158 def __ge__ (self , other ):
59+ """Compare if this range is after or adjacent to another range."""
3260 return self .start >= other .end
3361
3462 @property
3563 def line_count (self ):
64+ """Return the number of lines in the range."""
3665 return self .end - self .start
3766
3867 @property
3968 def as_index (self ) -> int | None :
69+ """Return the start index if the range is empty, otherwise None."""
4070 return None if self .line_count else self .start
4171
4272 @property
4373 def collapsed (self ):
74+ """Return a new RangeSpec with the same start but zero length."""
4475 return self .set_line_count (0 )
4576
4677 def set_line_count (self , range_len : int ):
78+ """Return a new RangeSpec with the specified line count."""
4779 return self ._replace (end = self .start + range_len )
4880
4981 def inc (self , count : int = 1 ):
82+ """Return a new RangeSpec shifted forward by the specified count."""
5083 return self ._replace (start = self .start + count , end = self .end + count )
5184
5285 def dec (self , count : int = 1 ):
86+ """Return a new RangeSpec shifted backward by the specified count."""
5387 return self ._replace (start = self .start - count , end = self .end - count )
5488
5589 def read (self , src : Sequence [str ]) -> Sequence [str ]:
90+ """Read and return the lines from the source sequence specified by this range."""
5691 return src [self .start :self .end ]
5792
5893 def write (self , src : Sequence [str ], target : Sequence [str ]):
94+ """Write the source lines into the target sequence at the position specified by this range."""
5995 target [self .start :self .end ] = src
6096
6197 def delete (self , src : Sequence [str ]) -> Sequence [str ]:
98+ """Delete the lines specified by this range from the source sequence and return the deleted lines."""
6299 result = self .read (src )
63100 del src [self .start :self .end ]
64101 return result
65102
66103 @staticmethod
67104 def normalize_line (line : str ):
105+ """Normalize a line by replacing non-word characters with dots and stripping whitespace."""
68106 return re .sub (r'[^\w]' , '.' , line .strip (), flags = re .UNICODE )
69107
70108 @classmethod
@@ -77,35 +115,31 @@ def from_line_marker(
77115 """
78116 Find the index of a specified line within a list of strings, considering different match types and an offset.
79117
80- This function searches for a given line within a list, considering 4 types of matches in order of priority:
118+ This method searches for a given line within a list, considering 4 types of matches in order of priority:
81119 1. Exact match
82120 2. Stripped match (ignoring leading and trailing whitespace)
83121 3. Normalized match (ignoring non-alphanumeric characters)
84122 4. Partial (Searching for a substring, using `casefold` to ignore upper- and lower-case differences).
85123
86- The function applies the offset across all match types while maintaining the priority order.
124+ The method applies the offset across all match types while maintaining the priority order.
87125
88- : Args:
89- :param lines: The list of strings to search through.
90- :param search_term :
91- search_marker. value: The line to search for.
92- search_marker. offset: The number of matches to skip before returning a result.
126+ Args:
127+ lines (Sequence[str]) : The list of strings to search through.
128+ search_term (Marker): A Marker object containing :
129+ - value: The line to search for.
130+ - offset: The number of matches to skip before returning a result.
93131 0 skips no match and returns the first match, 1 returns the second match, and so on.
94- :param search_range: The index to start the search from and to end the search at (exclusive).
95- Defaults to (0, -1), which means search to the end of the list.
132+ search_range (RangeSpec, optional): The range to search within. Defaults to None, which means search the entire list.
96133
97- :returns:
98- RangeSpec: The index for the desired line in the 'lines' list.
99- Returns None if no match is found or if the offset exceeds the number of matches within each category.
134+ Returns:
135+ RangeSpec: A RangeSpec object representing the found line, or None if no match is found.
100136
101- :Example:
102- >> lines = ["Hello, world!", " Hello, world! ", "Héllo, wörld?", "Another line", "Hello, world!"]
103- >> _find_line_index(lines, "Hello, world!", 1)
104- 4 # Returns the index of the second exact match
137+ Raises:
138+ ValueError: If there are multiple matches and no offset is specified, or if the offset exceeds the number of matches.
105139
106140 Note:
107- - The function prioritizes match types in the order: exact, stripped, normalized, partial.
108- - The offset is considered separately for each type.
141+ - The method prioritizes match types in the order: exact, stripped, normalized, partial.
142+ - The offset is considered separately for each match type.
109143 """
110144 search_start_index , search_end_index , _ = search_range if search_range is not None else (0 , - 1 , 0 )
111145 search_line = search_term .value
@@ -199,26 +233,55 @@ def from_line_marker(
199233
200234
201235class IdentifierBoundaries (NamedTuple ):
236+ """
237+ Represents the boundaries of an identifier in code, including its whole range and body range.
238+
239+ This class is used to specify the range of an entire identifier (whole) and its body,
240+ which is typically the content inside the identifier's definition.
241+
242+ Attributes:
243+ whole (RangeSpec): The RangeSpec representing the entire identifier.
244+ body (RangeSpec): The RangeSpec representing the body of the identifier.
245+ """
246+
202247 whole : RangeSpec
203248 body : RangeSpec
204249
205250 def __str__ (self ):
251+ """Return a string representation of the IdentifierBoundaries."""
206252 return f'IdentifierBoundaries({ self .whole } (BODY: { self .body } ) )'
207253
208254 @property
209255 def start_line (self ) -> int :
256+ """Return the 1-indexed start line of the whole identifier."""
210257 return self .whole .start + 1
211258
212259 @property
213260 def body_start_line (self ) -> int :
261+ """Return the 1-indexed start line of the identifier's body."""
214262 return self .body .start + 1
215263
216264 @property
217265 def end_line (self ) -> int :
266+ """Return the 1-indexed end line of the whole identifier."""
218267 return self .whole .end
219268
220- # See the other bow_to_search_range
221269 def location_to_search_range (self , location : BodyOrWhole | RelativePositionType ) -> RangeSpec :
270+ """
271+ Convert a location specifier to a RangeSpec for searching.
272+
273+ This method interprets various location specifiers and returns the appropriate
274+ RangeSpec for searching within or around the identifier.
275+
276+ Args:
277+ location (BodyOrWhole | RelativePositionType): The location specifier.
278+
279+ Returns:
280+ RangeSpec: The corresponding RangeSpec for the specified location.
281+
282+ Raises:
283+ ValueError: If an invalid location specifier is provided.
284+ """
222285 match location :
223286 case BodyOrWhole .BODY :
224287 return self .body
0 commit comments