A parameterizable object that submits responses to specific patterns. Commonly used to implement password auto-responds for things like ``sudo``. .. versionadded:: 1.0
| 51 | |
| 52 | |
| 53 | class Responder(StreamWatcher): |
| 54 | """ |
| 55 | A parameterizable object that submits responses to specific patterns. |
| 56 | |
| 57 | Commonly used to implement password auto-responds for things like ``sudo``. |
| 58 | |
| 59 | .. versionadded:: 1.0 |
| 60 | """ |
| 61 | |
| 62 | def __init__(self, pattern: str, response: str) -> None: |
| 63 | r""" |
| 64 | Imprint this `Responder` with necessary parameters. |
| 65 | |
| 66 | :param pattern: |
| 67 | A raw string (e.g. ``r"\[sudo\] password for .*:"``) which will be |
| 68 | turned into a regular expression. |
| 69 | |
| 70 | :param response: |
| 71 | The string to submit to the subprocess' stdin when ``pattern`` is |
| 72 | detected. |
| 73 | """ |
| 74 | # TODO: precompile the keys into regex objects |
| 75 | self.pattern = pattern |
| 76 | self.response = response |
| 77 | self.index = 0 |
| 78 | |
| 79 | def pattern_matches( |
| 80 | self, stream: str, pattern: str, index_attr: str |
| 81 | ) -> Iterable[str]: |
| 82 | """ |
| 83 | Generic "search for pattern in stream, using index" behavior. |
| 84 | |
| 85 | Used here and in some subclasses that want to track multiple patterns |
| 86 | concurrently. |
| 87 | |
| 88 | :param str stream: The same data passed to ``submit``. |
| 89 | :param str pattern: The pattern to search for. |
| 90 | :param str index_attr: The name of the index attribute to use. |
| 91 | :returns: An iterable of string matches. |
| 92 | |
| 93 | .. versionadded:: 1.0 |
| 94 | """ |
| 95 | # NOTE: generifies scanning so it can be used to scan for >1 pattern at |
| 96 | # once, e.g. in FailingResponder. |
| 97 | # Only look at stream contents we haven't seen yet, to avoid dupes. |
| 98 | index = getattr(self, index_attr) |
| 99 | new = stream[index:] |
| 100 | # Search, across lines if necessary |
| 101 | matches = re.findall(pattern, new, re.S) |
| 102 | # Update seek index if we've matched |
| 103 | if matches: |
| 104 | setattr(self, index_attr, index + len(new)) |
| 105 | return matches |
| 106 | |
| 107 | def submit(self, stream: str) -> Generator[str, None, None]: |
| 108 | # Iterate over findall() response in case >1 match occurred. |
| 109 | for _ in self.pattern_matches(stream, self.pattern, "index"): |
| 110 | yield self.response |
no outgoing calls
no test coverage detected
searching dependent graphs…