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

Change subject: category: Extract argument handling
......................................................................

category: Extract argument handling

Move category option parsing and generator configuration into focused
helpers backed by a settings object.

Bug: T434526
Change-Id: I42ad6b781d8b28dfc8797020ba3b1bb917caf48c
---
M scripts/category.py
M tests/category_bot_tests.py
2 files changed, 297 insertions(+), 163 deletions(-)

Approvals:
  jenkins-bot: Verified
  Xqt: Looks good to me, approved




diff --git a/scripts/category.py b/scripts/category.py
index a5cc4e0..e7858dc 100755
--- a/scripts/category.py
+++ b/scripts/category.py
@@ -165,6 +165,7 @@
 import re
 from collections.abc import Sequence
 from contextlib import suppress
+from dataclasses import dataclass, field
 from itertools import chain
 from operator import methodcaller
 from textwrap import fill
@@ -1541,6 +1542,132 @@
             grandchild.change_category(self.cat, None, summary)


+_CATEGORY_ACTIONS = {
+    'add', 'clean', 'listify', 'move', 'remove', 'tidy', 'tree'
+}
+_GENERATOR_ACTIONS = {'add', 'listify', 'move', 'remove', 'tidy'}
+_SETTING_FLAGS = {
+    'allowsplit': ('allow_split', True),
+    'append': ('append', True),
+    'batch': ('batch', True),
+    'create': ('create_pages', True),
+    'hist': ('history', True),
+    'inplace': ('inplace', True),
+    'keepsortkey': ('keep_sortkey', True),
+    'mvtogether': ('move_together', True),
+    'nodelete': ('delete_empty_cat', False),
+    'nodelsum': ('use_deletion_summary', False),
+    'nowb': ('wikibase', False),
+    'overwrite': ('overwrite', True),
+    'pagesonly': ('pagesonly', True),
+    'person': ('sort_by_last_name', True),
+    'rebuild': ('rebuild', True),
+    'redirect': ('follow_redirects', True),
+    'showimages': ('showimages', True),
+    'talkpages': ('talkpages', True),
+}
+
+
+@dataclass
+class _CategorySettings:
+
+    """Configuration parsed from the command line."""
+
+    options: dict[str, str | int | bool] = field(default_factory=dict)
+    action: str | None = None
+    batch: bool = False
+    summary: str = ''
+    inplace: bool = False
+    append: bool = False
+    overwrite: bool = False
+    showimages: bool = False
+    talkpages: bool = False
+    title_regex: str | None = None
+    pagesonly: bool = False
+    wikibase: bool = True
+    history: bool = False
+    rebuild: bool = False
+    allow_split: bool = False
+    move_together: bool = False
+    keep_sortkey: bool | None = None
+    depth: int = 5
+    use_deletion_summary: bool = True
+    sort_by_last_name: bool = False
+    create_pages: bool = False
+    follow_redirects: bool = False
+    delete_empty_cat: bool = True
+    unknown: list[str] = field(default_factory=list)
+    generator_args: list[str] = field(default_factory=list)
+
+
+def _handle_option(
+    settings: _CategorySettings,
+    option: str,
+    value: str,
+) -> bool:
+    """Apply a category option and return whether it was recognized."""
+    if option in _SETTING_FLAGS:
+        name, flag = _SETTING_FLAGS[option]
+        setattr(settings, name, flag)
+    elif option in ('from', 'to'):
+        settings.options[option] = value.replace('_', ' ')
+    elif option == 'summary':
+        settings.summary = value
+    elif option == 'match':
+        settings.title_regex = value or pywikibot.input(
+            'Which regular expression should affected objects match?')
+    elif option == 'recurse':
+        settings.options[option] = True if value == '' else int(value)
+    elif option == 'depth':
+        settings.depth = int(value)
+    elif option == 'prefix':
+        settings.options[option] = value
+    elif option == 'always':
+        settings.options[option] = True
+    else:
+        return False
+    return True
+
+
+def _parse_args(args: Sequence[str]) -> _CategorySettings:
+    """Parse command-line arguments for the category script."""
+    settings = _CategorySettings()
+    local_args = pywikibot.handle_args(args)
+
+    for arg in local_args:
+        if arg in _CATEGORY_ACTIONS:
+            settings.action = arg
+            continue
+
+        if arg[0] != '-':
+            settings.unknown.append(arg)
+            continue
+
+        option, _, value = arg[1:].partition(':')
+        if not _handle_option(settings, option, value):
+            settings.generator_args.append(arg)
+
+    return settings
+
+
+def _configure_generator_factory(
+    settings: _CategorySettings,
+) -> pagegenerators.GeneratorFactory | None:
+    """Create a generator factory for actions which support one."""
+    if settings.action not in _GENERATOR_ACTIONS:
+        settings.unknown += settings.generator_args
+        return None
+
+    enabled = (
+        ['namespace'] if settings.action in ('tidy', 'listify') else None
+    )
+    generator_factory = pagegenerators.GeneratorFactory(
+        enabled_options=enabled)
+    settings.unknown += generator_factory.handle_args(
+        settings.generator_args)
+    return generator_factory
+
+
 def main(*args: str) -> None:
     """Process command line arguments and invoke bot.

@@ -1548,200 +1675,107 @@

     :param args: command line arguments.
     """
-    options = {}
-    # TODO: use options instead following variables:
-    batch = False
-    summary = ''
-    inplace = False
-    append = False
-    overwrite = False
-    showimages = False
-    talkpages = False
-    title_regex = None
-    pagesonly = False
-    wikibase = True
-    history = False
-    rebuild = False
-    allow_split = False
-    move_together = False
-    keep_sortkey = None
-    depth = 5
+    settings = _parse_args(args)
+    gen_factory = _configure_generator_factory(settings)
+    suggest_help(unknown_parameters=settings.unknown)

-    # Process global args and prepare generator args parser
-    local_args = pywikibot.handle_args(args)
+    cat_db = CategoryDatabase(rebuild=settings.rebuild)

-    # When this is True then the custom edit summary given for removing
-    # categories from articles will also be used as the deletion reason.
-    # Otherwise it will generate deletion specific comments.
-    use_deletion_summary = True
-    action = None
-    sort_by_last_name = False
-    create_pages = False
-    follow_redirects = False
-    delete_empty_cat = True
-    unknown = []
-    pg_options = []
-    for arg in local_args:
-        if arg in ('add', 'remove', 'move', 'tidy', 'tree', 'listify',
-                   'clean'):
-            action = arg
-            continue
-
-        if arg[0] != '-':
-            unknown.append(arg)
-            continue
-
-        option, _, value = arg[1:].partition(':')
-        if option == 'nodelete':
-            delete_empty_cat = False
-        elif option == 'person':
-            sort_by_last_name = True
-        elif option == 'rebuild':
-            rebuild = True
-        elif option in ('from', 'to'):
-            options[option] = value.replace('_', ' ')
-        elif option == 'batch':
-            batch = True
-        elif option == 'inplace':
-            inplace = True
-        elif option == 'nodelsum':
-            use_deletion_summary = False
-        elif option == 'append':
-            append = True
-        elif option == 'overwrite':
-            overwrite = True
-        elif option == 'showimages':
-            showimages = True
-        elif option == 'summary':
-            summary = value
-        elif option == 'match':
-            title_regex = value or pywikibot.input(
-                'Which regular expression should affected objects match?')
-        elif option == 'talkpages':
-            talkpages = True
-        elif option == 'recurse':
-            options[option] = True if value == '' else int(value)
-        elif option == 'pagesonly':
-            pagesonly = True
-        elif option == 'nowb':
-            wikibase = False
-        elif option == 'allowsplit':
-            allow_split = True
-        elif option == 'mvtogether':
-            move_together = True
-        elif option == 'create':
-            create_pages = True
-        elif option == 'redirect':
-            follow_redirects = True
-        elif option == 'hist':
-            history = True
-        elif option == 'depth':
-            depth = int(value)
-        elif option == 'keepsortkey':
-            keep_sortkey = True
-        elif option == 'prefix':
-            options[option] = value
-        elif option == 'always':
-            options[option] = True
-        else:
-            pg_options.append(arg)
-
-    enabled = ['namespace'] if action in ('tidy', 'listify') else None
-    if action in ('add', 'listify', 'move', 'remove', 'tidy'):
-        gen_factory = pagegenerators.GeneratorFactory(enabled_options=enabled)
-        unknown += gen_factory.handle_args(pg_options)
-    else:
-        unknown += pg_options
-    suggest_help(unknown_parameters=unknown)
-
-    cat_db = CategoryDatabase(rebuild=rebuild)
-
-    if action == 'add':
+    if settings.action == 'add':
+        assert gen_factory is not None
         gen = gen_factory.getCombinedGenerator(preload=True)
         if not gen:
             # default for backwards compatibility
             gen_factory.handle_arg('-links')
             gen = gen_factory.getCombinedGenerator(preload=True)
         bot = CategoryAddBot(gen,
-                             newcat=options.get('to'),
-                             sort_by_last_name=sort_by_last_name,
-                             create=create_pages,
-                             comment=summary,
-                             follow_redirects=follow_redirects)
-    elif action == 'remove':
-        if 'from' not in options:
-            options['from'] = \
+                             newcat=settings.options.get('to'),
+                             sort_by_last_name=settings.sort_by_last_name,
+                             create=settings.create_pages,
+                             comment=settings.summary,
+                             follow_redirects=settings.follow_redirects)
+    elif settings.action == 'remove':
+        assert gen_factory is not None
+        if 'from' not in settings.options:
+            settings.options['from'] = \
                 pywikibot.input('Please enter the name of the '
                                 'category that should be removed:')
         gen = gen_factory.getCombinedGenerator()
-        bot = CategoryMoveRobot(oldcat=options.get('from'),
-                                batch=batch,
-                                comment=summary,
-                                inplace=inplace,
-                                delete_oldcat=delete_empty_cat,
-                                title_regex=title_regex,
-                                history=history,
-                                pagesonly=pagesonly,
-                                deletion_comment=use_deletion_summary,
+        bot = CategoryMoveRobot(oldcat=settings.options.get('from'),
+                                batch=settings.batch,
+                                comment=settings.summary,
+                                inplace=settings.inplace,
+                                delete_oldcat=settings.delete_empty_cat,
+                                title_regex=settings.title_regex,
+                                history=settings.history,
+                                pagesonly=settings.pagesonly,
+                                deletion_comment=(
+                                    settings.use_deletion_summary),
                                 generator=gen)
-    elif action == 'move':
+    elif settings.action == 'move':
+        assert gen_factory is not None
         while True:
-            if 'from' not in options:
-                options['from'] = pywikibot.input(
+            if 'from' not in settings.options:
+                settings.options['from'] = pywikibot.input(
                     'Please enter the old name of the category:')
-                if not options['from']:
+                if not settings.options['from']:
                     return

-            if 'to' not in options:
-                options['to'] = pywikibot.input(
+            if 'to' not in settings.options:
+                settings.options['to'] = pywikibot.input(
                     'Please enter the new name of the category:')

-            if options['from'] != options['to']:
+            if settings.options['from'] != settings.options['to']:
                 break

             pywikibot.error('-from and -to arguments are equal, please retry.')
-            del options['from']
-            del options['to']
+            del settings.options['from']
+            del settings.options['to']

-        if use_deletion_summary:
+        if settings.use_deletion_summary:
             deletion_comment = \
                 CategoryMoveRobot.DELETION_COMMENT_SAME_AS_EDIT_COMMENT
         else:
             deletion_comment = CategoryMoveRobot.DELETION_COMMENT_AUTOMATIC
         gen = gen_factory.getCombinedGenerator()
-        bot = CategoryMoveRobot(oldcat=options.get('from'),
-                                newcat=options.get('to'),
-                                batch=batch,
-                                comment=summary,
-                                inplace=inplace,
-                                delete_oldcat=delete_empty_cat,
-                                title_regex=title_regex,
-                                history=history,
-                                pagesonly=pagesonly,
+        bot = CategoryMoveRobot(oldcat=settings.options.get('from'),
+                                newcat=settings.options.get('to'),
+                                batch=settings.batch,
+                                comment=settings.summary,
+                                inplace=settings.inplace,
+                                delete_oldcat=settings.delete_empty_cat,
+                                title_regex=settings.title_regex,
+                                history=settings.history,
+                                pagesonly=settings.pagesonly,
                                 deletion_comment=deletion_comment,
-                                wikibase=wikibase,
-                                allow_split=allow_split,
-                                move_together=move_together,
-                                keep_sortkey=keep_sortkey,
+                                wikibase=settings.wikibase,
+                                allow_split=settings.allow_split,
+                                move_together=settings.move_together,
+                                keep_sortkey=settings.keep_sortkey,
                                 generator=gen)
-    elif action == 'tidy':
-        bot = CategoryTidyRobot(options.get('from'), cat_db,
-                                gen_factory.namespaces, summary)
-    elif action == 'tree':
-        bot = CategoryTreeRobot(options.get('from'), cat_db,
-                                options.get('to'), max_depth=depth)
-    elif action == 'listify':
-        bot = CategoryListifyRobot(options.get('from'),
-                                   options.get('to'), summary,
-                                   append, overwrite, showimages,
-                                   talk_pages=talkpages,
-                                   recurse=options.get('recurse', False),
+    elif settings.action == 'tidy':
+        assert gen_factory is not None
+        bot = CategoryTidyRobot(settings.options.get('from'), cat_db,
+                                gen_factory.namespaces, settings.summary)
+    elif settings.action == 'tree':
+        bot = CategoryTreeRobot(settings.options.get('from'), cat_db,
+                                settings.options.get('to'),
+                                max_depth=settings.depth)
+    elif settings.action == 'listify':
+        assert gen_factory is not None
+        bot = CategoryListifyRobot(settings.options.get('from'),
+                                   settings.options.get('to'),
+                                   settings.summary, settings.append,
+                                   settings.overwrite, settings.showimages,
+                                   talk_pages=settings.talkpages,
+                                   recurse=settings.options.get(
+                                       'recurse', False),
                                    namespaces=gen_factory.namespaces,
-                                   **options)
-    elif action == 'clean':
-        bot = CleanBot(**options)
+                                   **settings.options)
+    elif settings.action == 'clean':
+        bot = CleanBot(**settings.options)

-    if not suggest_help(missing_action=not action):
+    if not suggest_help(missing_action=not settings.action):
         pywikibot.Site().login()
         try:
             bot.run()
diff --git a/tests/category_bot_tests.py b/tests/category_bot_tests.py
index c73502c..de56235 100755
--- a/tests/category_bot_tests.py
+++ b/tests/category_bot_tests.py
@@ -13,6 +13,7 @@

 import pywikibot
 from pywikibot.site import BaseSite
+from scripts import category
 from scripts.category import CategoryMoveRobot, CategoryPreprocess
 from tests.aspects import DefaultSiteTestCase, TestCase

@@ -107,6 +108,105 @@
         self.assertEqual(bot.includeonly, [])


+class TestCategoryArguments(TestCase):
+
+    """Test category script argument handling."""
+
+    net = False
+
+    @patch.object(category.pywikibot, 'handle_args', side_effect=lambda x: x)
+    def test_parse_args(self, handle_args) -> None:
+        """Test parsing category and generator options."""
+        args = (
+            'move', '-nodelete', '-person', '-rebuild', '-from:Old_cat',
+            '-to:New_cat', '-batch', '-inplace', '-nodelsum', '-append',
+            '-overwrite', '-showimages', '-summary:summary', '-match:title',
+            '-talkpages', '-recurse:2', '-pagesonly', '-nowb', '-allowsplit',
+            '-mvtogether', '-create', '-redirect', '-hist', '-depth:7',
+            '-keepsortkey', '-prefix:#', '-always', '-page:Example',
+            'unknown',
+        )
+
+        settings = category._parse_args(args)
+
+        handle_args.assert_called_once_with(args)
+        self.assertEqual(settings.action, 'move')
+        self.assertEqual(settings.options, {
+            'always': True,
+            'from': 'Old cat',
+            'prefix': '#',
+            'recurse': 2,
+            'to': 'New cat',
+        })
+        expected = {
+            'allow_split': True,
+            'append': True,
+            'batch': True,
+            'create_pages': True,
+            'delete_empty_cat': False,
+            'depth': 7,
+            'follow_redirects': True,
+            'history': True,
+            'inplace': True,
+            'keep_sortkey': True,
+            'move_together': True,
+            'overwrite': True,
+            'pagesonly': True,
+            'rebuild': True,
+            'showimages': True,
+            'sort_by_last_name': True,
+            'summary': 'summary',
+            'talkpages': True,
+            'title_regex': 'title',
+            'use_deletion_summary': False,
+            'wikibase': False,
+        }
+        for name, value in expected.items():
+            with self.subTest(option=name):
+                self.assertEqual(getattr(settings, name), value)
+        self.assertEqual(settings.generator_args, ['-page:Example'])
+        self.assertEqual(settings.unknown, ['unknown'])
+
+        with patch.object(category.pywikibot, 'input', return_value='title'):
+            settings = category._parse_args(('listify', '-match', '-recurse'))
+        self.assertEqual(settings.title_regex, 'title')
+        self.assertIs(settings.options['recurse'], True)
+
+    def test_configure_generator_factory(self) -> None:
+        """Test generator option support for category actions."""
+        for action, enabled in (
+            ('listify', ['namespace']),
+            ('move', None),
+        ):
+            with (
+                self.subTest(action=action),
+                patch.object(category.pagegenerators,
+                             'GeneratorFactory') as factory_class,
+            ):
+                factory = factory_class.return_value
+                factory.handle_args.return_value = ['-unknown']
+                settings = category._CategorySettings(
+                    action=action, generator_args=['-page:Example'])
+
+                result = category._configure_generator_factory(settings)
+
+                self.assertIs(result, factory)
+                factory_class.assert_called_once_with(
+                    enabled_options=enabled)
+                factory.handle_args.assert_called_once_with(
+                    ['-page:Example'])
+                self.assertEqual(settings.unknown, ['-unknown'])
+
+        settings = category._CategorySettings(
+            action='tree', generator_args=['-page:Example'])
+        with patch.object(category.pagegenerators,
+                          'GeneratorFactory') as factory_class:
+            result = category._configure_generator_factory(settings)
+        self.assertIsNone(result)
+        factory_class.assert_not_called()
+        self.assertEqual(settings.unknown, ['-page:Example'])
+
+
 if __name__ == '__main__':
     with suppress(SystemExit):
         unittest.main()

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

Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I42ad6b781d8b28dfc8797020ba3b1bb917caf48c
Gerrit-Change-Number: 1326255
Gerrit-PatchSet: 2
Gerrit-Owner: Mahveotm <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to