Coverage for app/venv/lib/python3.14/site-packages/weblate/addons/management/commands/list_addons.py: 0%

65 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 07:15 +0000

1# Copyright © Michal Čihař <michal@weblate.org> 

2# 

3# SPDX-License-Identifier: GPL-3.0-or-later 

4 

5from __future__ import annotations 

6 

7from textwrap import wrap 

8from typing import TYPE_CHECKING 

9 

10from weblate.addons.events import POST_CONFIGURE_EVENTS, AddonEvent 

11from weblate.addons.models import ADDONS, Addon 

12from weblate.trans.models import Component, Project 

13from weblate.utils.management.base import BaseCommand 

14from weblate.utils.rst import format_rst_string, format_table 

15 

16if TYPE_CHECKING: 

17 from collections.abc import Iterable 

18 

19SKIP_FIELDS: tuple[tuple[str, str]] = ( 

20 ("weblate.flags.bulk", "path"), # Used internally only 

21) 

22 

23 

24def event_link(event: AddonEvent) -> str: 

25 return f"addon-event-{event.label.lower().replace(' ', '-')}" 

26 

27 

28def sorted_events(events: Iterable[AddonEvent]) -> Iterable[AddonEvent]: 

29 return sorted(events, key=lambda event: event.label) 

30 

31 

32class Command(BaseCommand): 

33 help = "List installed add-ons" 

34 

35 @staticmethod 

36 def get_help_text(field, name: str) -> str: 

37 result = [] 

38 if field.help_text: 

39 result.append(format_rst_string(field.help_text)) 

40 choices = getattr(field, "choices", None) 

41 if choices and name not in { 

42 "component", 

43 "engines", 

44 "file_format", 

45 "source", 

46 "target", 

47 }: 

48 if result: 

49 result.append("") 

50 

51 result.extend( 

52 ( 

53 ".. list-table:: Available choices:", 

54 " :width: 100%", 

55 "", 

56 ) 

57 ) 

58 for value, description in choices: 

59 result.extend( 

60 ( 

61 f" * - ``{value}``".replace("\\", "\\\\"), 

62 f" - {format_rst_string(description)}".replace( 

63 "\\", "\\\\" 

64 ), 

65 ) 

66 ) 

67 return "\n".join(result) 

68 

69 def handle(self, *args, **options) -> None: 

70 """List installed add-ons.""" 

71 self.stdout.write(""".. 

72 Partly generated using ./manage.py list_addons 

73""") 

74 self.stdout.write(".. _addon-event-install:\n\n") 

75 self.stdout.write("Add-on installation\n") 

76 self.stdout.write("-------------------\n\n") 

77 for event in sorted_events(AddonEvent): 

78 self.stdout.write(f".. _{event_link(event)}:\n\n") 

79 self.stdout.write(f"{event.label}\n") 

80 self.stdout.write("-" * len(event.label)) 

81 self.stdout.write("\n\n") 

82 self.stdout.write("\n") 

83 fake_addon = Addon(component=Component(project=Project(pk=-1), pk=-1)) 

84 for addon_name, obj in sorted(ADDONS.items()): 

85 self.stdout.write(f".. _addon-{obj.name}:") 

86 self.stdout.write("\n") 

87 self.stdout.write(str(obj.verbose)) 

88 self.stdout.write("-" * len(obj.verbose)) 

89 self.stdout.write("\n") 

90 self.stdout.write(f":Add-on ID: ``{obj.name}``") 

91 prefix = ":Configuration: " 

92 if obj.settings_form: 

93 form = obj(fake_addon).get_settings_form(None) 

94 table: list[list[str | list[list[str]]]] = [ 

95 [ 

96 f"``{name}``", 

97 str(field.label), 

98 self.get_help_text(field, name), 

99 ] 

100 for name, field in form.fields.items() 

101 if (addon_name, name) not in SKIP_FIELDS 

102 ] 

103 

104 for table_row in format_table(table, None): 

105 self.stdout.write(f"{prefix}{table_row}") 

106 if not prefix.isspace(): 

107 prefix = " " * len(prefix) 

108 else: 

109 self.stdout.write(f"{prefix}`This add-on has no configuration.`") 

110 events = ", ".join( 

111 f":ref:`{event_link(event)}`" for event in sorted_events(obj.events) 

112 ) 

113 if POST_CONFIGURE_EVENTS & set(obj.events): 

114 events = f":ref:`addon-event-install`, {events}" 

115 self.stdout.write(f":Triggers: {events}") 

116 self.stdout.write("\n") 

117 self.stdout.write("\n".join(wrap(str(obj.description), 79))) 

118 self.stdout.write("\n")