jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/995217?usp=email )

Change subject: [IMPR] Move InteractiveReplace from bot to bot_choice module
......................................................................

[IMPR] Move InteractiveReplace from bot to bot_choice module

InteractiveReplace is a handler for bot_choice.Choice, LinkChoice and
AlwaysChoice. Therefore move it to the bot_choice module.

InteractiveReplace is imported in bot module like other bot_choice
classes; there is no change in importing necessary (yet).

Change-Id: I63ab7e9796a238a53411eab229d1d801eea7121f
---
M pywikibot/bot.py
M pywikibot/bot_choice.py
M pywikibot/specialbots/_unlink.py
3 files changed, 245 insertions(+), 206 deletions(-)

Approvals:
  Matěj Suchánek: Looks good to me, approved
  jenkins-bot: Verified




diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 3121ebc..cc583e2 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -57,7 +57,7 @@
 ``put_current`` is used.
 """
 #
-# (C) Pywikibot team, 2008-2023
+# (C) Pywikibot team, 2008-2024
 #
 # Distributed under the terms of the MIT license.
 #
@@ -113,7 +113,7 @@

 import pywikibot
 from pywikibot import config, daemonize, i18n, version
-from pywikibot.backports import Callable, Dict, Iterable, Mapping, Sequence
+from pywikibot.backports import Callable, Dict, Iterable, Sequence
 from pywikibot.bot_choice import (
     AlwaysChoice,
     Choice,
@@ -121,6 +121,7 @@
     ContextOption,
     HighlightContextOption,
     IntegerOption,
+    InteractiveReplace,
     LinkChoice,
     ListOption,
     MultipleChoiceList,
@@ -172,9 +173,6 @@


 if TYPE_CHECKING:
-    from typing_extensions import Literal
-
-    from pywikibot.page import BaseLink, Link, Page
     from pywikibot.site import BaseSite

     AnswerType = Iterable[tuple[str, str] | Option] | Option
@@ -641,196 +639,6 @@
                                 force=force)


-class InteractiveReplace:
-
-    """
-    A callback class for textlib's replace_links.
-
-    It shows various options which can be switched on and off:
-    * allow_skip_link = True (skip the current link)
-    * allow_unlink = True (unlink)
-    * allow_replace = False (just replace target, keep section and label)
-    * allow_replace_section = False (replace target and section, keep label)
-    * allow_replace_label = False (replace target and label, keep section)
-    * allow_replace_all = False (replace target, section and label)
-    (The boolean values are the default values)
-
-    It has also a ``context`` attribute which must be a non-negative
-    integer. If it is greater 0 it shows that many characters before and
-    after the link in question. The ``context_delta`` attribute can be
-    defined too and adds an option to increase ``context`` by the given
-    amount each time the option is selected.
-
-    Additional choices can be defined using the 'additional_choices' and will
-    be amended to the choices defined by this class. This list is mutable and
-    the Choice instance returned and created by this class are too.
-    """
-
-    def __init__(self,
-                 old_link: Link | Page,
-                 new_link: Link | Page | Literal[False],
-                 default: str | None = None,
-                 automatic_quit: bool = True) -> None:
-        """
-        Initializer.
-
-        :param old_link: The old link which is searched. The label and section
-            are ignored.
-        :param new_link: The new link with which it should be replaced.
-            Depending on the replacement mode it'll use this link's label and
-            section. If False it'll unlink all and the attributes beginning
-            with allow_replace are ignored.
-        :param default: The default answer as the shortcut
-        :param automatic_quit: Add an option to quit and raise a
-            QuitKeyboardException.
-        """
-        if isinstance(old_link, pywikibot.Page):
-            self._old = old_link._link
-        else:
-            self._old = old_link
-        if isinstance(new_link, pywikibot.Page):
-            self._new: BaseLink | Literal[False] = new_link._link
-        else:
-            self._new = new_link
-        self._default = default
-        self._quit = automatic_quit
-
-        self._current_match: tuple[
-            Link | Page,
-            str,
-            Mapping[str, str],
-            tuple[int, int]
-        ] | None = None
-
-        self.context = 30
-        self.context_delta = 0
-        self.allow_skip_link = True
-        self.allow_unlink = True
-        self.allow_replace = False
-        self.allow_replace_section = False
-        self.allow_replace_label = False
-        self.allow_replace_all = False
-        # Use list to preserve order
-        self._own_choices: list[tuple[str, StandardOption]] = [
-            ('skip_link', StaticChoice('Do not change', 'n', None)),
-            ('unlink', StaticChoice('Unlink', 'u', False)),
-        ]
-        if self._new:
-            self._own_choices += [
-                ('replace', LinkChoice('Change link target', 't', self,
-                                       False, False)),
-                ('replace_section', LinkChoice(
-                    'Change link target and section', 's', self, True, False)),
-                ('replace_label', LinkChoice('Change link target and label',
-                                             'l', self, False, True)),
-                ('replace_all', LinkChoice('Change complete link', 'c', self,
-                                           True, True)),
-            ]
-
-        self.additional_choices: list[StandardOption] = []
-
-    def handle_answer(self, choice: str) -> Any:
-        """Return the result for replace_links."""
-        for c in self.choices:
-            if isinstance(c, Choice) and c.shortcut == choice:
-                return c.handle()
-
-        raise ValueError(f'Invalid choice "{choice}"')
-
-    def __call__(self, link: Link | Page,
-                 text: str, groups: Mapping[str, str],
-                 rng: tuple[int, int]) -> Any:
-        """Ask user how the selected link should be replaced."""
-        if self._old == link:
-            self._current_match = (link, text, groups, rng)
-            while True:
-                try:
-                    answer = self.handle_link()
-                except UnhandledAnswer as e:
-                    if e.stop:
-                        raise
-                else:
-                    break
-            self._current_match = None  # don't reset in case of an exception
-            return answer
-        return None
-
-    @property
-    def choices(self) -> tuple[StandardOption, ...]:
-        """Return the tuple of choices."""
-        choices = []
-        for name, choice in self._own_choices:
-            if getattr(self, 'allow_' + name):
-                choices += [choice]
-        if self.context_delta > 0:
-            choices += [HighlightContextOption(
-                'more context', 'm', self.current_text, self.context,
-                self.context_delta, *self.current_range)]
-        choices += self.additional_choices
-        return tuple(choices)
-
-    def handle_link(self) -> Any:
-        """Handle the currently given replacement."""
-        choices = self.choices
-        for c in choices:
-            if isinstance(c, AlwaysChoice) and c.handle_link():
-                return c.answer
-
-        question = 'Should the link '
-        if self.context > 0:
-            rng = self.current_range
-            text = self.current_text
-            # at the beginning of the link, start red color.
-            # at the end of the link, reset the color to default
-            pywikibot.info(text[max(0, rng[0] - self.context): rng[0]]
-                           + f'<<lightred>>{text[rng[0]:rng[1]]}<<default>>'
-                           + text[rng[1]: rng[1] + self.context])
-        else:
-            question += (
-                f'<<lightred>>{self._old.canonical_title()}<<default>> ')
-
-        if self._new is False:
-            question += 'be unlinked?'
-        else:
-            question += 'target to <<lightpurple>>{}<<default>>?'.format(
-                self._new.canonical_title())
-
-        choice = pywikibot.input_choice(question, choices,
-                                        default=self._default,
-                                        automatic_quit=self._quit)
-
-        assert isinstance(choice, str)
-        return self.handle_answer(choice)
-
-    @property
-    def current_link(self) -> Link | Page:
-        """Get the current link when it's handling one currently."""
-        if self._current_match is None:
-            raise ValueError('No current link')
-        return self._current_match[0]
-
-    @property
-    def current_text(self) -> str:
-        """Get the current text when it's handling one currently."""
-        if self._current_match is None:
-            raise ValueError('No current text')
-        return self._current_match[1]
-
-    @property
-    def current_groups(self) -> Mapping[str, str]:
-        """Get the current groups when it's handling one currently."""
-        if self._current_match is None:
-            raise ValueError('No current groups')
-        return self._current_match[2]
-
-    @property
-    def current_range(self) -> tuple[int, int]:
-        """Get the current range when it's handling one currently."""
-        if self._current_match is None:
-            raise ValueError('No current range')
-        return self._current_match[3]
-
-
 # Command line parsing and help
 def calledModuleName() -> str:
     """Return the name of the module calling this function.
diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py
index 5ece69f..035acd8 100644
--- a/pywikibot/bot_choice.py
+++ b/pywikibot/bot_choice.py
@@ -9,10 +9,39 @@
 import re
 from abc import ABC, abstractmethod
 from textwrap import fill
-from typing import Any
+from typing import TYPE_CHECKING, Any

 import pywikibot
-from pywikibot.backports import Iterable, Sequence
+from pywikibot.backports import Iterable, Mapping, Sequence
+
+
+__all__ = (
+    'AlwaysChoice',
+    'Choice',
+    'ChoiceException',
+    'ContextOption',
+    'HighlightContextOption',
+    'IntegerOption',
+    'InteractiveReplace',
+    'LinkChoice',
+    'ListOption',
+    'MultipleChoiceList',
+    'NestedOption',
+    'Option',
+    'OutputProxyOption',
+    'QuitKeyboardInterrupt',
+    'ShowingListOption',
+    'ShowingMultipleChoiceList',
+    'StandardOption',
+    'StaticChoice',
+    'UnhandledAnswer',
+)
+
+
+if TYPE_CHECKING:
+    from typing_extensions import Literal
+
+    from pywikibot.page import BaseLink, Link, Page


 class Option(ABC):
@@ -259,14 +288,14 @@
         self,
         option: str,
         shortcut: str,
-        replacer: pywikibot.bot.InteractiveReplace | None
+        replacer: InteractiveReplace | None
     ) -> None:
         """Initializer."""
         super().__init__(option, shortcut)
         self._replacer = replacer

     @property
-    def replacer(self) -> pywikibot.bot.InteractiveReplace | None:
+    def replacer(self) -> InteractiveReplace | None:
         """The replacer."""
         return self._replacer

@@ -302,7 +331,7 @@
         self,
         option: str,
         shortcut: str,
-        replacer: pywikibot.bot.InteractiveReplace | None,
+        replacer: InteractiveReplace | None,
         replace_section: bool,
         replace_label: bool
     ) -> None:
@@ -345,7 +374,7 @@

     """Add an option to always apply the default."""

-    def __init__(self, replacer: pywikibot.bot.InteractiveReplace | None,
+    def __init__(self, replacer: InteractiveReplace | None,
                  option: str = 'always', shortcut: str = 'a') -> None:
         """Initializer."""
         super().__init__(option, shortcut, replacer)
@@ -601,3 +630,191 @@
     def __init__(self) -> None:
         """Constructor using the 'quit' ('q') in input_choice."""
         super().__init__('quit', 'q')
+
+
+class InteractiveReplace:
+
+    """A callback class for textlib's replace_links.
+
+    It shows various options which can be switched on and off:
+    * allow_skip_link = True (skip the current link)
+    * allow_unlink = True (unlink)
+    * allow_replace = False (just replace target, keep section and label)
+    * allow_replace_section = False (replace target and section, keep label)
+    * allow_replace_label = False (replace target and label, keep section)
+    * allow_replace_all = False (replace target, section and label)
+    (The boolean values are the default values)
+
+    It has also a ``context`` attribute which must be a non-negative
+    integer. If it is greater 0 it shows that many characters before and
+    after the link in question. The ``context_delta`` attribute can be
+    defined too and adds an option to increase ``context`` by the given
+    amount each time the option is selected.
+
+    Additional choices can be defined using the 'additional_choices' and will
+    be amended to the choices defined by this class. This list is mutable and
+    the Choice instance returned and created by this class are too.
+    """
+
+    def __init__(self,
+                 old_link: Link | Page,
+                 new_link: Link | Page | Literal[False],
+                 default: str | None = None,
+                 automatic_quit: bool = True) -> None:
+        """Initializer.
+
+        :param old_link: The old link which is searched. The label and section
+            are ignored.
+        :param new_link: The new link with which it should be replaced.
+            Depending on the replacement mode it'll use this link's label and
+            section. If False it'll unlink all and the attributes beginning
+            with allow_replace are ignored.
+        :param default: The default answer as the shortcut
+        :param automatic_quit: Add an option to quit and raise a
+            QuitKeyboardException.
+        """
+        if isinstance(old_link, pywikibot.Page):
+            self._old = old_link._link
+        else:
+            self._old = old_link
+        if isinstance(new_link, pywikibot.Page):
+            self._new: BaseLink | Literal[False] = new_link._link
+        else:
+            self._new = new_link
+        self._default = default
+        self._quit = automatic_quit
+
+        self._current_match: tuple[
+            Link | Page,
+            str,
+            Mapping[str, str],
+            tuple[int, int]
+        ] | None = None
+
+        self.context = 30
+        self.context_delta = 0
+        self.allow_skip_link = True
+        self.allow_unlink = True
+        self.allow_replace = False
+        self.allow_replace_section = False
+        self.allow_replace_label = False
+        self.allow_replace_all = False
+        # Use list to preserve order
+        self._own_choices: list[tuple[str, StandardOption]] = [
+            ('skip_link', StaticChoice('Do not change', 'n', None)),
+            ('unlink', StaticChoice('Unlink', 'u', False)),
+        ]
+        if self._new:
+            self._own_choices += [
+                ('replace', LinkChoice('Change link target', 't', self,
+                                       False, False)),
+                ('replace_section', LinkChoice(
+                    'Change link target and section', 's', self, True, False)),
+                ('replace_label', LinkChoice('Change link target and label',
+                                             'l', self, False, True)),
+                ('replace_all', LinkChoice('Change complete link', 'c', self,
+                                           True, True)),
+            ]
+
+        self.additional_choices: list[StandardOption] = []
+
+    def handle_answer(self, choice: str) -> Any:
+        """Return the result for replace_links."""
+        for c in self.choices:
+            if isinstance(c, Choice) and c.shortcut == choice:
+                return c.handle()
+
+        raise ValueError(f'Invalid choice "{choice}"')
+
+    def __call__(self, link: Link | Page,
+                 text: str, groups: Mapping[str, str],
+                 rng: tuple[int, int]) -> Any:
+        """Ask user how the selected link should be replaced."""
+        if self._old == link:
+            self._current_match = (link, text, groups, rng)
+            while True:
+                try:
+                    answer = self.handle_link()
+                except UnhandledAnswer as e:
+                    if e.stop:
+                        raise
+                else:
+                    break
+            self._current_match = None  # don't reset in case of an exception
+            return answer
+        return None
+
+    @property
+    def choices(self) -> tuple[StandardOption, ...]:
+        """Return the tuple of choices."""
+        choices = []
+        for name, choice in self._own_choices:
+            if getattr(self, 'allow_' + name):
+                choices += [choice]
+        if self.context_delta > 0:
+            choices += [HighlightContextOption(
+                'more context', 'm', self.current_text, self.context,
+                self.context_delta, *self.current_range)]
+        choices += self.additional_choices
+        return tuple(choices)
+
+    def handle_link(self) -> Any:
+        """Handle the currently given replacement."""
+        choices = self.choices
+        for c in choices:
+            if isinstance(c, AlwaysChoice) and c.handle_link():
+                return c.answer
+
+        question = 'Should the link '
+        if self.context > 0:
+            rng = self.current_range
+            text = self.current_text
+            # at the beginning of the link, start red color.
+            # at the end of the link, reset the color to default
+            pywikibot.info(text[max(0, rng[0] - self.context): rng[0]]
+                           + f'<<lightred>>{text[rng[0]:rng[1]]}<<default>>'
+                           + text[rng[1]: rng[1] + self.context])
+        else:
+            question += (
+                f'<<lightred>>{self._old.canonical_title()}<<default>> ')
+
+        if self._new is False:
+            question += 'be unlinked?'
+        else:
+            question += 'target to <<lightpurple>>{}<<default>>?'.format(
+                self._new.canonical_title())
+
+        choice = pywikibot.input_choice(question, choices,
+                                        default=self._default,
+                                        automatic_quit=self._quit)
+
+        assert isinstance(choice, str)
+        return self.handle_answer(choice)
+
+    @property
+    def current_link(self) -> Link | Page:
+        """Get the current link when it's handling one currently."""
+        if self._current_match is None:
+            raise ValueError('No current link')
+        return self._current_match[0]
+
+    @property
+    def current_text(self) -> str:
+        """Get the current text when it's handling one currently."""
+        if self._current_match is None:
+            raise ValueError('No current text')
+        return self._current_match[1]
+
+    @property
+    def current_groups(self) -> Mapping[str, str]:
+        """Get the current groups when it's handling one currently."""
+        if self._current_match is None:
+            raise ValueError('No current groups')
+        return self._current_match[2]
+
+    @property
+    def current_range(self) -> tuple[int, int]:
+        """Get the current range when it's handling one currently."""
+        if self._current_match is None:
+            raise ValueError('No current range')
+        return self._current_match[3]
diff --git a/pywikibot/specialbots/_unlink.py b/pywikibot/specialbots/_unlink.py
index 96c6566..dc65dfa 100644
--- a/pywikibot/specialbots/_unlink.py
+++ b/pywikibot/specialbots/_unlink.py
@@ -3,20 +3,19 @@
 Do not import classes directly from here but from specialbots.
 """
 #
-# (C) Pywikibot team, 2003-2022
+# (C) Pywikibot team, 2003-2024
 #
 # Distributed under the terms of the MIT license.
 #
 from __future__ import annotations

-from pywikibot.bot import (
+from pywikibot.bot import AutomaticTWSummaryBot, ExistingPageBot
+from pywikibot.bot_choice import (
     AlwaysChoice,
-    AutomaticTWSummaryBot,
     ChoiceException,
-    ExistingPageBot,
     InteractiveReplace,
+    UnhandledAnswer,
 )
-from pywikibot.bot_choice import UnhandledAnswer
 from pywikibot.editor import TextEditor
 from pywikibot.textlib import replace_links


--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/995217?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.wikimedia.org/r/settings

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I63ab7e9796a238a53411eab229d1d801eea7121f
Gerrit-Change-Number: 995217
Gerrit-PatchSet: 6
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Matěj Suchánek <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to